@@ -5,22 +5,18 @@ import (
55 "context"
66 "crypto/rand"
77 "encoding/hex"
8- "encoding/json"
98 "errors"
109 "fmt"
1110 "strconv"
12- "time"
1311
1412 "github.com/lightninglabs/taproot-assets/asset"
15- "github.com/lightninglabs/taproot-assets/rfqmsg "
13+ "github.com/lightninglabs/taproot-assets/rfq "
1614 "github.com/lightninglabs/taproot-assets/taprpc"
17- "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
1815 tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
1916 "github.com/lightningnetwork/lnd/cmd/commands"
2017 "github.com/lightningnetwork/lnd/lnrpc"
2118 "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
2219 "github.com/lightningnetwork/lnd/lntypes"
23- "github.com/lightningnetwork/lnd/lnwire"
2420 "github.com/lightningnetwork/lnd/record"
2521 "github.com/urfave/cli"
2622 "google.golang.org/grpc"
@@ -160,64 +156,6 @@ func fundChannel(c *cli.Context) error {
160156 return nil
161157}
162158
163- type assetBalance struct {
164- AssetID string
165- Name string
166- LocalBalance uint64
167- RemoteBalance uint64
168- Channel * lnrpc.Channel
169- }
170-
171- type channelBalResp struct {
172- Assets map [string ]* assetBalance `json:"assets"`
173- }
174-
175- func computeAssetBalances (lnd lnrpc.LightningClient ) (* channelBalResp , error ) {
176- ctxb := context .Background ()
177- openChans , err := lnd .ListChannels (
178- ctxb , & lnrpc.ListChannelsRequest {},
179- )
180- if err != nil {
181- return nil , fmt .Errorf ("unable to fetch channels: %w" , err )
182- }
183-
184- balanceResp := & channelBalResp {
185- Assets : make (map [string ]* assetBalance ),
186- }
187- for _ , openChan := range openChans .Channels {
188- if len (openChan .CustomChannelData ) == 0 {
189- continue
190- }
191-
192- var assetData rfqmsg.JsonAssetChannel
193- err = json .Unmarshal (openChan .CustomChannelData , & assetData )
194- if err != nil {
195- return nil , fmt .Errorf ("unable to unmarshal asset " +
196- "data: %w" , err )
197- }
198-
199- for _ , assetOutput := range assetData .Assets {
200- assetID := assetOutput .AssetInfo .AssetGenesis .AssetID
201- assetName := assetOutput .AssetInfo .AssetGenesis .Name
202-
203- balance , ok := balanceResp .Assets [assetID ]
204- if ! ok {
205- balance = & assetBalance {
206- AssetID : assetID ,
207- Name : assetName ,
208- Channel : openChan ,
209- }
210- balanceResp .Assets [assetID ] = balance
211- }
212-
213- balance .LocalBalance += assetOutput .LocalBalance
214- balance .RemoteBalance += assetOutput .RemoteBalance
215- }
216- }
217-
218- return balanceResp , nil
219- }
220-
221159var (
222160 assetIDFlag = cli.StringFlag {
223161 Name : "asset_id" ,
@@ -517,7 +455,8 @@ func payInvoice(ctx *cli.Context) error {
517455
518456 stream , err := tchrpcClient .SendPayment (
519457 ctx , & tchrpc.SendPaymentRequest {
520- AssetId : assetIDBytes ,
458+ AssetId : assetIDBytes ,
459+ PaymentRequest : req ,
521460 },
522461 )
523462 if err != nil {
@@ -551,6 +490,14 @@ var addInvoiceCommand = cli.Command{
551490 Name : "asset_amount" ,
552491 Usage : "the amount of assets to receive" ,
553492 },
493+ cli.StringFlag {
494+ Name : "rfq_peer_pubkey" ,
495+ Usage : "(optional) the public key of the peer to ask " +
496+ "for a quote when converting from assets to " +
497+ "sats for the invoice; must be set if there " +
498+ "are multiple channels with the same " +
499+ "asset ID present" ,
500+ },
554501 ),
555502 Action : addInvoice ,
556503}
@@ -572,6 +519,8 @@ func addInvoice(ctx *cli.Context) error {
572519
573520 var (
574521 assetAmount uint64
522+ preimage []byte
523+ descHash []byte
575524 err error
576525 )
577526 switch {
@@ -587,167 +536,63 @@ func addInvoice(ctx *cli.Context) error {
587536 return fmt .Errorf ("asset_amount argument missing" )
588537 }
589538
590- expiry := time .Now ().Add (300 * time .Second )
591- if ctx .IsSet ("expiry" ) {
592- expirySeconds := ctx .Uint64 ("expiry" )
593- expiry = time .Now ().Add (
594- time .Duration (expirySeconds ) * time .Second ,
595- )
539+ if ctx .IsSet ("preimage" ) {
540+ preimage , err = hex .DecodeString (ctx .String ("preimage" ))
541+ if err != nil {
542+ return fmt .Errorf ("unable to parse preimage: %w" , err )
543+ }
596544 }
597545
598- lndConn , cleanup , err := connectClient (ctx , false )
546+ descHash , err = hex . DecodeString (ctx . String ( "description_hash" ) )
599547 if err != nil {
600- return fmt .Errorf ("unable to make rpc con : %w" , err )
548+ return fmt .Errorf ("unable to parse description_hash : %w" , err )
601549 }
602550
603- defer cleanup ()
604-
605- lndClient := lnrpc .NewLightningClient (lndConn )
551+ expirySeconds := int64 (rfq .DefaultInvoiceExpiry .Seconds ())
552+ if ctx .IsSet ("expiry" ) {
553+ expirySeconds = ctx .Int64 ("expiry" )
554+ }
606555
607556 assetIDBytes , err := hex .DecodeString (assetIDStr )
608557 if err != nil {
609558 return fmt .Errorf ("unable to decode assetID: %v" , err )
610559 }
611560
612- // First, based on the asset ID and amount, we'll make sure that this
613- // channel even has enough funds to send.
614- assetBalances , err := computeAssetBalances (lndClient )
615- if err != nil {
616- return fmt .Errorf ("unable to compute asset balances: %w" , err )
617- }
618-
619- balance , ok := assetBalances .Assets [assetIDStr ]
620- if ! ok {
621- return fmt .Errorf ("unable to send asset_id=%v, not in " +
622- "channel" , assetIDStr )
623- }
624-
625- if balance .RemoteBalance == 0 {
626- return fmt .Errorf ("no remote asset balance available for " +
627- "receiving asset_id=%v" , assetIDStr )
628- }
629-
630561 var assetID asset.ID
631562 copy (assetID [:], assetIDBytes )
632563
633- tapdConn , cleanup , err := connectTapdClient (ctx )
634- if err != nil {
635- return fmt .Errorf ("error creating tapd connection: %w" , err )
636- }
637-
638- defer cleanup ()
639-
640- peerPubKey , err := hex .DecodeString (balance .Channel .RemotePubkey )
641- if err != nil {
642- return fmt .Errorf ("unable to decode peer pubkey: %w" , err )
643- }
644-
645- rfqClient := rfqrpc .NewRfqClient (tapdConn )
646-
647- timeoutSeconds := uint32 (60 )
648- fmt .Printf ("Asking peer %x for quote to buy assets to receive for " +
649- "invoice over %d units; waiting up to %ds\n " , peerPubKey ,
650- assetAmount , timeoutSeconds )
651-
652- resp , err := rfqClient .AddAssetBuyOrder (
653- ctxb , & rfqrpc.AddAssetBuyOrderRequest {
654- AssetSpecifier : & rfqrpc.AssetSpecifier {
655- Id : & rfqrpc.AssetSpecifier_AssetIdStr {
656- AssetIdStr : assetIDStr ,
657- },
658- },
659- MinAssetAmount : assetAmount ,
660- Expiry : uint64 (expiry .Unix ()),
661- PeerPubKey : peerPubKey ,
662- TimeoutSeconds : timeoutSeconds ,
663- },
664- )
564+ rfqPeerKey , err := hex .DecodeString (ctx .String (rfqPeerPubKeyFlag .Name ))
665565 if err != nil {
666- return fmt .Errorf ("error adding sell order: %w" , err )
667- }
668-
669- var acceptedQuote * rfqrpc.PeerAcceptedBuyQuote
670- switch r := resp .Response .(type ) {
671- case * rfqrpc.AddAssetBuyOrderResponse_AcceptedQuote :
672- acceptedQuote = r .AcceptedQuote
673-
674- case * rfqrpc.AddAssetBuyOrderResponse_InvalidQuote :
675- return fmt .Errorf ("peer %v sent back an invalid quote, " +
676- "status: %v" , r .InvalidQuote .Peer ,
677- r .InvalidQuote .Status .String ())
678-
679- case * rfqrpc.AddAssetBuyOrderResponse_RejectedQuote :
680- return fmt .Errorf ("peer %v rejected the quote, code: %v, " +
681- "error message: %v" , r .RejectedQuote .Peer ,
682- r .RejectedQuote .ErrorCode , r .RejectedQuote .ErrorMessage )
683-
684- default :
685- return fmt .Errorf ("unexpected response type: %T" , r )
566+ return fmt .Errorf ("unable to decode RFQ peer public key: " +
567+ "%w" , err )
686568 }
687569
688- msatPerUnit := acceptedQuote .AskPrice
689- numMSats := lnwire .MilliSatoshi (assetAmount * msatPerUnit )
690-
691- descHash , err := hex .DecodeString (ctx .String ("description_hash" ))
570+ tapdConn , cleanup , err := connectTapdClient (ctx )
692571 if err != nil {
693- return fmt .Errorf ("unable to parse description_hash : %w" , err )
572+ return fmt .Errorf ("error creating tapd connection : %w" , err )
694573 }
574+ defer cleanup ()
695575
696- ourPolicy , err := getOurPolicy (
697- lndClient , balance .Channel .ChanId , balance .Channel .RemotePubkey ,
698- )
699- if err != nil {
700- return fmt .Errorf ("unable to get our policy: %w" , err )
701- }
702-
703- hopHint := & lnrpc.HopHint {
704- NodeId : balance .Channel .RemotePubkey ,
705- ChanId : acceptedQuote .Scid ,
706- FeeBaseMsat : uint32 (ourPolicy .FeeBaseMsat ),
707- FeeProportionalMillionths : uint32 (ourPolicy .FeeRateMilliMsat ),
708- CltvExpiryDelta : ourPolicy .TimeLockDelta ,
709- }
710-
711- invoice := & lnrpc.Invoice {
712- Memo : ctx .String ("memo" ),
713- ValueMsat : int64 (numMSats ),
714- DescriptionHash : descHash ,
715- FallbackAddr : ctx .String ("fallback_addr" ),
716- Expiry : int64 (ctx .Uint64 ("expiry" )),
717- Private : ctx .Bool ("private" ),
718- IsAmp : ctx .Bool ("amp" ),
719- RouteHints : []* lnrpc.RouteHint {
720- {
721- HopHints : []* lnrpc.HopHint {hopHint },
722- },
576+ channelsClient := tchrpc .NewTaprootAssetChannelsClient (tapdConn )
577+ resp , err := channelsClient .AddInvoice (ctxb , & tchrpc.AddInvoiceRequest {
578+ AssetId : assetIDBytes ,
579+ AssetAmount : assetAmount ,
580+ PeerPubkey : rfqPeerKey ,
581+ InvoiceRequest : & lnrpc.Invoice {
582+ Memo : ctx .String ("memo" ),
583+ RPreimage : preimage ,
584+ DescriptionHash : descHash ,
585+ FallbackAddr : ctx .String ("fallback_addr" ),
586+ Expiry : expirySeconds ,
587+ Private : ctx .Bool ("private" ),
588+ IsAmp : ctx .Bool ("amp" ),
723589 },
724- }
725-
726- invoiceResp , err := lndClient .AddInvoice (ctxb , invoice )
727- if err != nil {
728- return err
729- }
730-
731- printRespJSON (invoiceResp )
732-
733- return nil
734- }
735-
736- func getOurPolicy (lndClient lnrpc.LightningClient , chanID uint64 ,
737- remotePubKey string ) (* lnrpc.RoutingPolicy , error ) {
738-
739- ctxb := context .Background ()
740- edge , err := lndClient .GetChanInfo (ctxb , & lnrpc.ChanInfoRequest {
741- ChanId : chanID ,
742590 })
743591 if err != nil {
744- return nil , fmt .Errorf ("unable to fetch channel : %w" , err )
592+ return fmt .Errorf ("error adding invoice : %w" , err )
745593 }
746594
747- policy := edge .Node1Policy
748- if edge .Node1Pub == remotePubKey {
749- policy = edge .Node2Policy
750- }
595+ printRespJSON (resp )
751596
752- return policy , nil
597+ return nil
753598}
0 commit comments