Skip to content

Commit 684db85

Browse files
authored
Merge pull request #872 from sputn1ck/asset_simple_loopout
Simple Asset Loop out
2 parents 8062952 + 51bdf04 commit 684db85

34 files changed

+2786
-1154
lines changed

assets/client.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package assets
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"fmt"
7+
"os"
8+
"sync"
9+
"time"
10+
11+
"github.com/btcsuite/btcd/btcutil"
12+
"github.com/lightninglabs/taproot-assets/tapcfg"
13+
"github.com/lightninglabs/taproot-assets/taprpc"
14+
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
15+
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
16+
"github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
17+
"github.com/lightninglabs/taproot-assets/taprpc/universerpc"
18+
"github.com/lightningnetwork/lnd/lnrpc"
19+
"github.com/lightningnetwork/lnd/macaroons"
20+
"google.golang.org/grpc"
21+
"google.golang.org/grpc/credentials"
22+
"gopkg.in/macaroon.v2"
23+
)
24+
25+
var (
26+
27+
// maxMsgRecvSize is the largest message our client will receive. We
28+
// set this to 200MiB atm.
29+
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200)
30+
31+
// defaultRfqTimeout is the default timeout we wait for tapd peer to
32+
// accept RFQ.
33+
defaultRfqTimeout = time.Second * 60
34+
)
35+
36+
// TapdConfig is a struct that holds the configuration options to connect to a
37+
// taproot assets daemon.
38+
type TapdConfig struct {
39+
Activate bool `long:"activate" description:"Activate the Tap daemon"`
40+
Host string `long:"host" description:"The host of the Tap daemon, in the format of host:port"`
41+
MacaroonPath string `long:"macaroonpath" description:"Path to the admin macaroon"`
42+
TLSPath string `long:"tlspath" description:"Path to the TLS certificate"`
43+
RFQtimeout time.Duration `long:"rfqtimeout" description:"The timeout we wait for tapd peer to accept RFQ"`
44+
}
45+
46+
// DefaultTapdConfig returns a default configuration to connect to a taproot
47+
// assets daemon.
48+
func DefaultTapdConfig() *TapdConfig {
49+
defaultConf := tapcfg.DefaultConfig()
50+
return &TapdConfig{
51+
Activate: false,
52+
Host: "localhost:10029",
53+
MacaroonPath: defaultConf.RpcConf.MacaroonPath,
54+
TLSPath: defaultConf.RpcConf.TLSCertPath,
55+
RFQtimeout: defaultRfqTimeout,
56+
}
57+
}
58+
59+
// TapdClient is a client for the Tap daemon.
60+
type TapdClient struct {
61+
taprpc.TaprootAssetsClient
62+
tapchannelrpc.TaprootAssetChannelsClient
63+
priceoraclerpc.PriceOracleClient
64+
rfqrpc.RfqClient
65+
universerpc.UniverseClient
66+
67+
cfg *TapdConfig
68+
assetNameCache map[string]string
69+
assetNameMutex sync.Mutex
70+
cc *grpc.ClientConn
71+
}
72+
73+
// NewTapdClient returns a new taproot assets client.
74+
func NewTapdClient(config *TapdConfig) (*TapdClient, error) {
75+
// Create the client connection to the server.
76+
conn, err := getClientConn(config)
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
// Create the TapdClient.
82+
client := &TapdClient{
83+
assetNameCache: make(map[string]string),
84+
cc: conn,
85+
cfg: config,
86+
TaprootAssetsClient: taprpc.NewTaprootAssetsClient(conn),
87+
TaprootAssetChannelsClient: tapchannelrpc.NewTaprootAssetChannelsClient(conn),
88+
PriceOracleClient: priceoraclerpc.NewPriceOracleClient(conn),
89+
RfqClient: rfqrpc.NewRfqClient(conn),
90+
UniverseClient: universerpc.NewUniverseClient(conn),
91+
}
92+
93+
return client, nil
94+
}
95+
96+
// Close closes the client connection to the server.
97+
func (c *TapdClient) Close() {
98+
c.cc.Close()
99+
}
100+
101+
// GetRfqForAsset returns a RFQ for the given asset with the given amount and
102+
// to the given peer.
103+
func (c *TapdClient) GetRfqForAsset(ctx context.Context,
104+
satAmount btcutil.Amount, assetId, peerPubkey []byte,
105+
expiry int64, feeLimitMultiplier float64) (
106+
*rfqrpc.PeerAcceptedSellQuote, error) {
107+
108+
feeLimit, err := lnrpc.UnmarshallAmt(
109+
int64(satAmount)+int64(satAmount.MulF64(feeLimitMultiplier)), 0,
110+
)
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
rfq, err := c.RfqClient.AddAssetSellOrder(
116+
ctx, &rfqrpc.AddAssetSellOrderRequest{
117+
AssetSpecifier: &rfqrpc.AssetSpecifier{
118+
Id: &rfqrpc.AssetSpecifier_AssetId{
119+
AssetId: assetId,
120+
},
121+
},
122+
PeerPubKey: peerPubkey,
123+
PaymentMaxAmt: uint64(feeLimit),
124+
Expiry: uint64(expiry),
125+
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
126+
})
127+
if err != nil {
128+
return nil, err
129+
}
130+
if rfq.GetInvalidQuote() != nil {
131+
return nil, fmt.Errorf("invalid RFQ: %v", rfq.GetInvalidQuote())
132+
}
133+
if rfq.GetRejectedQuote() != nil {
134+
return nil, fmt.Errorf("rejected RFQ: %v",
135+
rfq.GetRejectedQuote())
136+
}
137+
138+
if rfq.GetAcceptedQuote() != nil {
139+
return rfq.GetAcceptedQuote(), nil
140+
}
141+
142+
return nil, fmt.Errorf("no accepted quote")
143+
}
144+
145+
// GetAssetName returns the human-readable name of the asset.
146+
func (c *TapdClient) GetAssetName(ctx context.Context,
147+
assetId []byte) (string, error) {
148+
149+
c.assetNameMutex.Lock()
150+
defer c.assetNameMutex.Unlock()
151+
assetIdStr := hex.EncodeToString(assetId)
152+
if name, ok := c.assetNameCache[assetIdStr]; ok {
153+
return name, nil
154+
}
155+
156+
assetStats, err := c.UniverseClient.QueryAssetStats(
157+
ctx, &universerpc.AssetStatsQuery{
158+
AssetIdFilter: assetId,
159+
},
160+
)
161+
if err != nil {
162+
return "", err
163+
}
164+
165+
if len(assetStats.AssetStats) == 0 {
166+
return "", fmt.Errorf("asset not found")
167+
}
168+
169+
var assetName string
170+
171+
// If the asset belongs to a group, return the group name.
172+
if assetStats.AssetStats[0].GroupAnchor != nil {
173+
assetName = assetStats.AssetStats[0].GroupAnchor.AssetName
174+
} else {
175+
assetName = assetStats.AssetStats[0].Asset.AssetName
176+
}
177+
178+
c.assetNameCache[assetIdStr] = assetName
179+
180+
return assetName, nil
181+
}
182+
183+
func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) {
184+
// Load the specified TLS certificate and build transport credentials.
185+
creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "")
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
// Load the specified macaroon file.
191+
macBytes, err := os.ReadFile(config.MacaroonPath)
192+
if err != nil {
193+
return nil, err
194+
}
195+
mac := &macaroon.Macaroon{}
196+
if err := mac.UnmarshalBinary(macBytes); err != nil {
197+
return nil, err
198+
}
199+
200+
macaroon, err := macaroons.NewMacaroonCredential(mac)
201+
if err != nil {
202+
return nil, err
203+
}
204+
// Create the DialOptions with the macaroon credentials.
205+
opts := []grpc.DialOption{
206+
grpc.WithTransportCredentials(creds),
207+
grpc.WithPerRPCCredentials(macaroon),
208+
grpc.WithDefaultCallOptions(maxMsgRecvSize),
209+
}
210+
211+
// Dial the gRPC server.
212+
conn, err := grpc.Dial(config.Host, opts...)
213+
if err != nil {
214+
return nil, err
215+
}
216+
217+
return conn, nil
218+
}

0 commit comments

Comments
 (0)