Skip to content

Commit b2c1a94

Browse files
committed
assets: add basic asset client
1 parent 8062952 commit b2c1a94

File tree

3 files changed

+392
-143
lines changed

3 files changed

+392
-143
lines changed

assets/client.go

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

0 commit comments

Comments
 (0)