diff --git a/assets/actions.go b/assets/actions.go new file mode 100644 index 000000000..9ad149482 --- /dev/null +++ b/assets/actions.go @@ -0,0 +1,457 @@ +package assets + +import ( + "context" + "crypto/rand" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntypes" +) + +// InitSwapOutContext is the initial context for the InitSwapOut state. +type InitSwapOutContext struct { + // Amount is the amount of the swap. + Amount uint64 + // AssetId is the id of the asset we are swapping. + AssetId []byte + // BlockheightHint is the hint for the current block height. + BlockHeightHint uint32 +} + +// InitSwapOut is the first state of the swap out FSM. It is responsible for +// creating a new swap out and prepay invoice. +func (o *OutFSM) InitSwapOut(ctx context.Context, + initCtx fsm.EventContext) fsm.EventType { + + // We expect the event context to be of type *InstantOutContext. + req, ok := initCtx.(*InitSwapOutContext) + if !ok { + o.Errorf("expected InstantOutContext, got %T", initCtx) + return o.HandleError(fsm.ErrInvalidContextType) + } + + // Create a new key for the swap. + clientKeyDesc, err := o.cfg.Wallet.DeriveNextKey( + o.runCtx, AssetKeyFamily, + ) + if err != nil { + return o.HandleError(err) + } + + // Request the asset out. + assetOutRes, err := o.cfg.AssetClient.RequestAssetLoopOut( + o.runCtx, &swapserverrpc.RequestAssetLoopOutRequest{ + Amount: req.Amount, + RequestedAsset: req.AssetId, + ReceiverKey: clientKeyDesc.PubKey.SerializeCompressed(), + }, + ) + if err != nil { + return o.HandleError(err) + } + + // Create the swap hash from the response. + swapHash, err := lntypes.MakeHash(assetOutRes.SwapHash) + if err != nil { + return o.HandleError(err) + } + + // Parse the server pubkey. + senderPubkey, err := btcec.ParsePubKey(assetOutRes.SenderPubkey) + if err != nil { + return o.HandleError(err) + } + + // With our params, we'll now create the swap out. + swapOut := NewSwapOut( + swapHash, req.Amount, + req.AssetId, clientKeyDesc, senderPubkey, + uint32(assetOutRes.Expiry), req.BlockHeightHint, + ) + o.SwapOut = swapOut + o.PrepayInvoice = assetOutRes.PrepayInvoice + + err = o.cfg.Store.CreateAssetSwapOut(o.runCtx, o.SwapOut) + if err != nil { + return o.HandleError(err) + } + + return onAssetOutInit +} + +// PayPrepay is the state where we try to pay the prepay invoice. +func (o *OutFSM) PayPrepay(ctx context.Context, + _ fsm.EventContext) fsm.EventType { + + trackChan, errChan, err := o.cfg.Router.SendPayment( + o.runCtx, lndclient.SendPaymentRequest{ + Invoice: o.PrepayInvoice, + Timeout: time.Minute, + MaxFee: btcutil.Amount(1000), + }, + ) + if err != nil { + return o.HandleError(err) + } + + go func() { + for { + select { + case result := <-trackChan: + if result.State == lnrpc.Payment_IN_FLIGHT { + o.Debugf("payment in flight") + } + if result.State == lnrpc.Payment_FAILED { + o.Errorf("payment failed: %v", result.FailureReason) + err = o.SendEvent(ctx, fsm.OnError, nil) + if err != nil { + o.Errorf("unable to send event: %v", err) + } + return + } + if result.State == lnrpc.Payment_SUCCEEDED { + o.Debugf("payment succeeded") + err := o.SendEvent(ctx, onPrepaySettled, nil) + if err != nil { + o.Errorf("unable to send event: %v", err) + } + return + } + + case err := <-errChan: + o.Errorf("payment error: %v", err) + err = o.SendEvent(ctx, fsm.OnError, nil) + if err != nil { + o.Errorf("unable to send event: %v", err) + } + return + + case <-o.runCtx.Done(): + return + } + } + }() + + return fsm.NoOp +} + +// FetchProof is the state where we fetch the proof. +func (o *OutFSM) FetchProof(ctx context.Context, + _ fsm.EventContext) fsm.EventType { + + // Fetch the proof from the server. + proofRes, err := o.cfg.AssetClient.PollAssetLoopOutProof( + o.runCtx, &swapserverrpc.PollAssetLoopOutProofRequest{ + SwapHash: o.SwapOut.SwapHash[:], + }, + ) + // If we have an error, we'll wait for the next block and try again. + if err != nil { + return onWaitForBlock + } + // We'll now import the proof into the asset client. + _, err = o.cfg.TapdClient.ImportProof( + o.runCtx, &tapdevrpc.ImportProofRequest{ + ProofFile: proofRes.RawProofFile, + }, + ) + if err != nil { + return o.HandleError(err) + } + + o.SwapOut.RawHtlcProof = proofRes.RawProofFile + + // We'll now save the proof in the database. + err = o.cfg.Store.UpdateAssetSwapOutProof( + o.runCtx, o.SwapOut.SwapHash, proofRes.RawProofFile, + ) + if err != nil { + return o.HandleError(err) + } + + return onProofReceived +} + +func (o *OutFSM) waitForBlock(ctx context.Context, + _ fsm.EventContext) fsm.EventType { + + blockHeight := o.cfg.BlockHeightSubscriber.GetBlockHeight() + + cb := func() { + err := o.SendEvent(ctx, onBlockReceived, nil) + if err != nil { + log.Errorf("Error sending block event %w", err) + } + } + + subscriberId, err := getRandomHash() + if err != nil { + return o.HandleError(err) + } + + alreadyPassed := o.cfg.BlockHeightSubscriber.SubscribeExpiry( + subscriberId, blockHeight+1, cb, + ) + if alreadyPassed { + return onBlockReceived + } + + return fsm.NoOp +} + +// subscribeToHtlcTxConfirmed is the state where we subscribe to the htlc +// transaction to wait for it to be confirmed. +// +// Todo(sputn1ck): handle rebroadcasting if it doesn't confirm. +func (o *OutFSM) subscribeToHtlcTxConfirmed(ctx context.Context, + _ fsm.EventContext) fsm.EventType { + + // First we'll get the htlc pkscript. + htlcPkScript, err := o.getHtlcPkscript() + if err != nil { + return o.HandleError(err) + } + + o.Debugf("pkscript: %x", htlcPkScript) + + txConfCtx, cancel := context.WithCancel(o.runCtx) + + confCallback := func(conf *chainntnfs.TxConfirmation, err error) { + if err != nil { + o.LastActionError = err + err = o.SendEvent(ctx, fsm.OnError, nil) + if err != nil { + log.Errorf("Error sending block event %w", err) + } + } + cancel() + err = o.SendEvent(ctx, onHtlcTxConfirmed, conf) + if err != nil { + log.Errorf("Error sending block event %w", err) + } + } + + err = o.cfg.TxConfSubscriber.SubscribeTxConfirmation( + txConfCtx, o.SwapOut.SwapHash, nil, + htlcPkScript, defaultHtlcConfRequirement, + int32(o.SwapOut.InitiationHeight), confCallback, + ) + if err != nil { + return o.HandleError(err) + } + + return fsm.NoOp +} + +// sendSwapPayment is the state where we pay the swap invoice. +func (o *OutFSM) sendSwapPayment(ctx context.Context, + event fsm.EventContext) fsm.EventType { + + // If we have an EventContext with a confirmation, we'll save the + // confirmation height. + if event != nil { + if conf, ok := event.(*chainntnfs.TxConfirmation); ok { + outpoint, err := o.findPkScript(conf.Tx) + if err != nil { + return o.HandleError(err) + } + o.SwapOut.HtlcConfirmationHeight = conf.BlockHeight + o.SwapOut.HtlcOutPoint = outpoint + + err = o.cfg.Store.UpdateAssetSwapHtlcOutpoint( + o.runCtx, o.SwapOut.SwapHash, + outpoint, int32(conf.BlockHeight), + ) + if err != nil { + o.Errorf( + "unable to update swap outpoint: %v", + err, + ) + } + } + } + + // Fetch the proof from the server. + buyRes, err := o.cfg.AssetClient.RequestAssetBuy( + o.runCtx, &swapserverrpc.RequestAssetBuyRequest{ + SwapHash: o.SwapOut.SwapHash[:], + }, + ) + if err != nil { + return o.HandleError(err) + } + + // We'll also set the swap invoice. + o.SwapInvoice = buyRes.SwapInvoice + + // If the htlc has been confirmed, we can now pay the swap invoice. + trackChan, errChan, err := o.cfg.Router.SendPayment( + o.runCtx, lndclient.SendPaymentRequest{ + Invoice: o.SwapInvoice, + Timeout: time.Minute, + MaxFee: btcutil.Amount(100000), + }, + ) + if err != nil { + return o.HandleError(err) + } + + go func() { + for { + select { + case result := <-trackChan: + if result.State == lnrpc.Payment_FAILED { + o.Errorf("payment failed: %v", result.FailureReason) + err = o.SendEvent(ctx, fsm.OnError, nil) + if err != nil { + o.Errorf("unable to send event: %v", err) + } + return + } + if result.State == lnrpc.Payment_SUCCEEDED { + o.SwapOut.SwapPreimage = result.Preimage + err = o.cfg.Store.UpdateAssetSwapOutPreimage( + o.runCtx, o.SwapOut.SwapHash, + result.Preimage, + ) + if err != nil { + o.Errorf( + "unable to update swap preimage: %v", + err, + ) + } + err := o.SendEvent(ctx, onSwapPreimageReceived, nil) + if err != nil { + o.Errorf("unable to send event: %v", err) + } + return + } + + case err := <-errChan: + o.Errorf("payment error: %v", err) + err = o.SendEvent(ctx, fsm.OnError, nil) + if err != nil { + o.Errorf("unable to send event: %v", err) + } + return + + case <-o.runCtx.Done(): + return + } + } + }() + + return fsm.NoOp +} + +// publishSweepTx is the state where we publish the timeout transaction. +func (o *OutFSM) publishSweepTx(ctx context.Context, + _ fsm.EventContext) fsm.EventType { + + // Create the sweep address. + rpcSweepAddr, err := o.cfg.TapdClient.NewAddr( + ctx, &taprpc.NewAddrRequest{ + AssetId: o.SwapOut.AssetID, + Amt: o.SwapOut.Amount, + }, + ) + if err != nil { + return o.HandleError(err) + } + + sweepAddr, err := address.DecodeAddress( + rpcSweepAddr.Encoded, o.cfg.AddrParams, + ) + if err != nil { + return o.HandleError(err) + } + + // Publish and log the sweep transaction. + outpoint, pkScript, err := o.publishPreimageSweep(sweepAddr) + if err != nil { + return o.HandleError(err) + } + + o.SwapOut.SweepOutpoint = outpoint + o.SwapOut.SweepPkscript = pkScript + + // We can now save the swap outpoint. + err = o.cfg.Store.UpdateAssetSwapOutSweepTx( + o.runCtx, o.SwapOut.SwapHash, outpoint.Hash, + 0, pkScript, + ) + if err != nil { + return o.HandleError(err) + } + + return onHtlcSuccessSweep +} + +// subscribeSweepConf is the state where we subscribe to the sweep transaction +// confirmation. +func (o *OutFSM) subscribeSweepConf(ctx context.Context, + _ fsm.EventContext) fsm.EventType { + + // We'll now subscribe to the confirmation of the sweep transaction. + txConfCtx, cancel := context.WithCancel(o.runCtx) + + confCallback := func(conf *chainntnfs.TxConfirmation, err error) { + if err != nil { + o.LastActionError = err + err = o.SendEvent(ctx, fsm.OnError, nil) + if err != nil { + o.Errorf("Error sending conf event %w", err) + } + } + cancel() + err = o.SendEvent(ctx, onSweepTxConfirmed, conf) + if err != nil { + o.Errorf("Error sending conf event %w", err) + } + } + + err := o.cfg.TxConfSubscriber.SubscribeTxConfirmation( + txConfCtx, o.SwapOut.SwapHash, + &o.SwapOut.SweepOutpoint.Hash, o.SwapOut.SweepPkscript, + defaultHtlcConfRequirement, int32(o.SwapOut.InitiationHeight), + confCallback, + ) + if err != nil { + return o.HandleError(err) + } + + return fsm.NoOp +} + +// HandleError is a helper function that can be used by actions to handle +// errors. +func (o *OutFSM) HandleError(err error) fsm.EventType { + if o == nil { + log.Errorf("StateMachine error: %s", err) + return fsm.OnError + } + o.Errorf("StateMachine error: %s", err) + o.LastActionError = err + return fsm.OnError +} + +// getRandomHash returns a random hash. +func getRandomHash() (lntypes.Hash, error) { + var preimage lntypes.Preimage + _, err := rand.Read(preimage[:]) + if err != nil { + return lntypes.Hash{}, err + } + + return preimage.Hash(), nil +} diff --git a/assets/client.go b/assets/client.go index ae810e6ec..9b3743e21 100644 --- a/assets/client.go +++ b/assets/client.go @@ -8,19 +8,41 @@ import ( "sync" "time" + "bytes" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + tap "github.com/lightninglabs/taproot-assets" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/rfqmath" "github.com/lightninglabs/taproot-assets/tapcfg" + "github.com/lightninglabs/taproot-assets/tappsbt" "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" + "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightninglabs/taproot-assets/tapsend" + "github.com/lightninglabs/taproot-assets/universe" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/btcwallet" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" "gopkg.in/macaroon.v2" ) @@ -64,7 +86,10 @@ type TapdClient struct { tapchannelrpc.TaprootAssetChannelsClient priceoraclerpc.PriceOracleClient rfqrpc.RfqClient + wrpc.AssetWalletClient + mintrpc.MintClient universerpc.UniverseClient + tapdevrpc.TapDevClient cfg *TapdConfig assetNameCache map[string]string @@ -312,3 +337,455 @@ func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) { return conn, nil } + +// FundAndSignVpacket funds and signs a vpacket. +func (t *TapdClient) FundAndSignVpacket(ctx context.Context, + vpkt *tappsbt.VPacket) (*tappsbt.VPacket, error) { + + // Fund the packet. + var buf bytes.Buffer + err := vpkt.Serialize(&buf) + if err != nil { + return nil, err + } + + fundResp, err := t.FundVirtualPsbt( + ctx, &assetwalletrpc.FundVirtualPsbtRequest{ + Template: &assetwalletrpc.FundVirtualPsbtRequest_Psbt{ + Psbt: buf.Bytes(), + }, + }, + ) + if err != nil { + return nil, err + } + + // Sign the packet. + signResp, err := t.SignVirtualPsbt( + ctx, &assetwalletrpc.SignVirtualPsbtRequest{ + FundedPsbt: fundResp.FundedPsbt, + }, + ) + if err != nil { + return nil, err + } + + return tappsbt.NewFromRawBytes( + bytes.NewReader(signResp.SignedPsbt), false, + ) +} + +// addP2WPKHOutputToPsbt adds a normal bitcoin P2WPKH output to a psbt for the +// given key and amount. +func addP2WPKHOutputToPsbt(packet *psbt.Packet, keyDesc keychain.KeyDescriptor, + amount btcutil.Amount, params *chaincfg.Params) error { + + derivation, _, _ := btcwallet.Bip32DerivationFromKeyDesc( + keyDesc, params.HDCoinType, + ) + + // Convert to Bitcoin address. + pubKeyBytes := keyDesc.PubKey.SerializeCompressed() + pubKeyHash := btcutil.Hash160(pubKeyBytes) + address, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, params) + if err != nil { + return err + } + + // Generate the P2WPKH scriptPubKey. + scriptPubKey, err := txscript.PayToAddrScript(address) + if err != nil { + return err + } + + // Add the output to the packet. + packet.UnsignedTx.AddTxOut( + wire.NewTxOut(int64(amount), scriptPubKey), + ) + + packet.Outputs = append(packet.Outputs, psbt.POutput{ + Bip32Derivation: []*psbt.Bip32Derivation{ + derivation, + }, + }) + + return nil +} + +// PrepareAndCommitVirtualPsbts prepares and commits virtual psbt to a BTC +// template so that the underlying wallet can fund the transaction and add +// the necessary additional input to pay for fees as well as a change output +// if the change keydescriptor is not provided. +func (t *TapdClient) PrepareAndCommitVirtualPsbts(ctx context.Context, + vpkt *tappsbt.VPacket, feeRateSatPerVByte chainfee.SatPerVByte, + changeKeyDesc *keychain.KeyDescriptor, params *chaincfg.Params) ( + *psbt.Packet, []*tappsbt.VPacket, []*tappsbt.VPacket, + *assetwalletrpc.CommitVirtualPsbtsResponse, error) { + + encodedVpkt, err := tappsbt.Encode(vpkt) + if err != nil { + return nil, nil, nil, nil, err + } + + btcPkt, err := tapsend.PrepareAnchoringTemplate( + []*tappsbt.VPacket{vpkt}, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + commitRequest := &assetwalletrpc.CommitVirtualPsbtsRequest{ + Fees: &assetwalletrpc.CommitVirtualPsbtsRequest_SatPerVbyte{ + SatPerVbyte: uint64(feeRateSatPerVByte), + }, + AnchorChangeOutput: &assetwalletrpc.CommitVirtualPsbtsRequest_Add{ //nolint:lll + Add: true, + }, + VirtualPsbts: [][]byte{ + encodedVpkt, + }, + } + if changeKeyDesc != nil { + err = addP2WPKHOutputToPsbt( + btcPkt, *changeKeyDesc, btcutil.Amount(1), params, + ) + if err != nil { + return nil, nil, nil, nil, err + } + commitRequest.AnchorChangeOutput = + &assetwalletrpc.CommitVirtualPsbtsRequest_ExistingOutputIndex{ //nolint:lll + ExistingOutputIndex: 1, + } + } else { + commitRequest.AnchorChangeOutput = + &assetwalletrpc.CommitVirtualPsbtsRequest_Add{ + Add: true, + } + } + var buf bytes.Buffer + err = btcPkt.Serialize(&buf) + if err != nil { + return nil, nil, nil, nil, err + } + + commitRequest.AnchorPsbt = buf.Bytes() + + commitResponse, err := t.AssetWalletClient.CommitVirtualPsbts( + ctx, commitRequest, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + fundedPacket, err := psbt.NewFromRawBytes( + bytes.NewReader(commitResponse.AnchorPsbt), false, + ) + if err != nil { + return nil, nil, nil, nil, err + } + + activePackets := make( + []*tappsbt.VPacket, len(commitResponse.VirtualPsbts), + ) + for idx := range commitResponse.VirtualPsbts { + activePackets[idx], err = tappsbt.Decode( + commitResponse.VirtualPsbts[idx], + ) + if err != nil { + return nil, nil, nil, nil, err + } + } + + passivePackets := make( + []*tappsbt.VPacket, len(commitResponse.PassiveAssetPsbts), + ) + for idx := range commitResponse.PassiveAssetPsbts { + passivePackets[idx], err = tappsbt.Decode( + commitResponse.PassiveAssetPsbts[idx], + ) + if err != nil { + return nil, nil, nil, nil, err + } + } + + return fundedPacket, activePackets, passivePackets, commitResponse, nil +} + +// LogAndPublish logs and publishes a psbt with the given active and passive +// assets. +func (t *TapdClient) LogAndPublish(ctx context.Context, btcPkt *psbt.Packet, + activeAssets []*tappsbt.VPacket, passiveAssets []*tappsbt.VPacket, + commitResp *assetwalletrpc.CommitVirtualPsbtsResponse) ( + *taprpc.SendAssetResponse, error) { + + var buf bytes.Buffer + err := btcPkt.Serialize(&buf) + if err != nil { + return nil, err + } + + request := &assetwalletrpc.PublishAndLogRequest{ + AnchorPsbt: buf.Bytes(), + VirtualPsbts: make([][]byte, len(activeAssets)), + PassiveAssetPsbts: make([][]byte, len(passiveAssets)), + ChangeOutputIndex: commitResp.ChangeOutputIndex, + LndLockedUtxos: commitResp.LndLockedUtxos, + } + + for idx := range activeAssets { + request.VirtualPsbts[idx], err = tappsbt.Encode( + activeAssets[idx], + ) + if err != nil { + return nil, err + } + } + for idx := range passiveAssets { + request.PassiveAssetPsbts[idx], err = tappsbt.Encode( + passiveAssets[idx], + ) + if err != nil { + return nil, err + } + } + + resp, err := t.PublishAndLogTransfer(ctx, request) + if err != nil { + return nil, err + } + + return resp, nil +} + +// ListAvailableAssets returns a list of available assets. +func (t *TapdClient) ListAvailableAssets(ctx context.Context) ( + [][]byte, error) { + + balanceRes, err := t.ListBalances(ctx, &taprpc.ListBalancesRequest{ + GroupBy: &taprpc.ListBalancesRequest_AssetId{ + AssetId: true, + }, + }) + if err != nil { + return nil, err + } + + assets := make([][]byte, 0, len(balanceRes.AssetBalances)) + for assetID := range balanceRes.AssetBalances { + asset, err := hex.DecodeString(assetID) + if err != nil { + return nil, err + } + assets = append(assets, asset) + } + + return assets, nil +} + +// GetAssetBalance checks the balance of an asset by its ID. +func (t *TapdClient) GetAssetBalance(ctx context.Context, assetId []byte) ( + uint64, error) { + + // Check if we have enough funds to do the swap. + balanceResp, err := t.ListBalances( + ctx, &taprpc.ListBalancesRequest{ + GroupBy: &taprpc.ListBalancesRequest_AssetId{ + AssetId: true, + }, + AssetFilter: assetId, + }, + ) + if err != nil { + return 0, err + } + + // Check if we have enough funds to do the swap. + balance, ok := balanceResp.AssetBalances[hex.EncodeToString( + assetId, + )] + if !ok { + return 0, status.Error(codes.Internal, "internal error") + } + + return balance.Balance, nil +} + +// GetUnEncumberedAssetBalance returns the total balance of the given asset for +// which the given client owns the script keys. +func (t *TapdClient) GetUnEncumberedAssetBalance(ctx context.Context, + assetID []byte) (uint64, error) { + + allAssets, err := t.ListAssets(ctx, &taprpc.ListAssetRequest{}) + if err != nil { + return 0, err + } + + var balance uint64 + for _, a := range allAssets.Assets { + // Only count assets from the given asset ID. + if !bytes.Equal(a.AssetGenesis.AssetId, assetID) { + continue + } + + // Non-local means we don't have the internal key to spend the + // asset. + if !a.ScriptKeyIsLocal { + continue + } + + // If the asset is not declared known or has a script path, we + // can't spend it directly. + if !a.ScriptKeyDeclaredKnown || a.ScriptKeyHasScriptPath { + continue + } + + balance += a.Amount + } + + return balance, nil +} + +// DeriveNewKeys derives a new internal and script key. +func (t *TapdClient) DeriveNewKeys(ctx context.Context) (asset.ScriptKey, + keychain.KeyDescriptor, error) { + + scriptKeyDesc, err := t.NextScriptKey( + ctx, &assetwalletrpc.NextScriptKeyRequest{ + KeyFamily: uint32(asset.TaprootAssetsKeyFamily), + }, + ) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + + scriptKey, err := taprpc.UnmarshalScriptKey(scriptKeyDesc.ScriptKey) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + + internalKeyDesc, err := t.NextInternalKey( + ctx, &assetwalletrpc.NextInternalKeyRequest{ + KeyFamily: uint32(asset.TaprootAssetsKeyFamily), + }, + ) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + internalKeyLnd, err := taprpc.UnmarshalKeyDescriptor( + internalKeyDesc.InternalKey, + ) + if err != nil { + return asset.ScriptKey{}, keychain.KeyDescriptor{}, err + } + + return *scriptKey, internalKeyLnd, nil +} + +// ImportProofFile imports the proof file and returns the last proof. +func (t *TapdClient) ImportProofFile(ctx context.Context, rawProofFile []byte) ( + *proof.Proof, error) { + + proofFile, err := proof.DecodeFile(rawProofFile) + if err != nil { + return nil, err + } + + var lastProof *proof.Proof + + for i := 0; i < proofFile.NumProofs(); i++ { + lastProof, err = proofFile.ProofAt(uint32(i)) + if err != nil { + return nil, err + } + + var proofBytes bytes.Buffer + err = lastProof.Encode(&proofBytes) + if err != nil { + return nil, err + } + + asset := lastProof.Asset + + proofType := universe.ProofTypeTransfer + if asset.IsGenesisAsset() { + proofType = universe.ProofTypeIssuance + } + + uniID := universe.Identifier{ + AssetID: asset.ID(), + ProofType: proofType, + } + if asset.GroupKey != nil { + uniID.GroupKey = &asset.GroupKey.GroupPubKey + } + + rpcUniID, err := tap.MarshalUniID(uniID) + if err != nil { + return nil, err + } + + outpoint := &universerpc.Outpoint{ + HashStr: lastProof.AnchorTx.TxHash().String(), + Index: int32(lastProof.InclusionProof.OutputIndex), + } + + scriptKey := lastProof.Asset.ScriptKey.PubKey + leafKey := &universerpc.AssetKey{ + Outpoint: &universerpc.AssetKey_Op{ + Op: outpoint, + }, + ScriptKey: &universerpc.AssetKey_ScriptKeyBytes{ + ScriptKeyBytes: scriptKey.SerializeCompressed(), + }, + } + + _, err = t.InsertProof(ctx, &universerpc.AssetProof{ + Key: &universerpc.UniverseKey{ + Id: rpcUniID, + LeafKey: leafKey, + }, + AssetLeaf: &universerpc.AssetLeaf{ + Proof: proofBytes.Bytes(), + }, + }) + if err != nil { + return nil, err + } + } + + return lastProof, nil +} + +func (t *TapdClient) AddHoldInvoice(ctx context.Context, pHash lntypes.Hash, + assetId []byte, assetAmt uint64, memo string) ( + *tapchannelrpc.AddInvoiceResponse, error) { + + // Now we can create the swap invoice. + invoiceRes, err := t.AddInvoice( + ctx, &tapchannelrpc.AddInvoiceRequest{ + + // Todo(sputn1ck):if we have more than one peer, we'll need to + // specify one. This will likely be changed on the tapd front in + // the future. + PeerPubkey: nil, + AssetId: assetId, + AssetAmount: assetAmt, + InvoiceRequest: &lnrpc.Invoice{ + Memo: memo, + RHash: pHash[:], + // todo fix expiries + CltvExpiry: 144, + Expiry: 60, + Private: true, + }, + HodlInvoice: &tapchannelrpc.HodlInvoice{ + PaymentHash: pHash[:], + }, + }, + ) + if err != nil { + return nil, err + } + + return invoiceRes, nil +} diff --git a/assets/htlc/script.go b/assets/htlc/script.go new file mode 100644 index 000000000..842bbc66d --- /dev/null +++ b/assets/htlc/script.go @@ -0,0 +1,88 @@ +package htlc + +import ( + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/txscript" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// GenSuccessPathScript constructs an HtlcScript for the success payment path. +func GenSuccessPathScript(receiverHtlcKey *btcec.PublicKey, + swapHash lntypes.Hash) ([]byte, error) { + + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(input.Ripemd160H(swapHash[:])) + builder.AddOp(txscript.OP_EQUALVERIFY) + builder.AddInt64(1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + return builder.Script() +} + +// GenTimeoutPathScript constructs an HtlcScript for the timeout payment path. +func GenTimeoutPathScript(senderHtlcKey *btcec.PublicKey, csvExpiry int64) ( + []byte, error) { + + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddInt64(csvExpiry) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + return builder.Script() +} + +// GetOpTrueScript returns a script that always evaluates to true. +func GetOpTrueScript() ([]byte, error) { + return txscript.NewScriptBuilder().AddOp(txscript.OP_TRUE).Script() +} + +// CreateOpTrueLeaf creates a taproot leaf that always evaluates to true. +func CreateOpTrueLeaf() (asset.ScriptKey, txscript.TapLeaf, + *txscript.IndexedTapScriptTree, *txscript.ControlBlock, error) { + + // Create the taproot OP_TRUE script. + tapScript, err := GetOpTrueScript() + if err != nil { + return asset.ScriptKey{}, txscript.TapLeaf{}, nil, nil, err + } + + tapLeaf := txscript.NewBaseTapLeaf(tapScript) + tree := txscript.AssembleTaprootScriptTree(tapLeaf) + rootHash := tree.RootNode.TapHash() + tapKey := txscript.ComputeTaprootOutputKey(asset.NUMSPubKey, rootHash[:]) + + merkleRootHash := tree.RootNode.TapHash() + + controlBlock := &txscript.ControlBlock{ + LeafVersion: txscript.BaseLeafVersion, + InternalKey: asset.NUMSPubKey, + } + tapScriptKey := asset.ScriptKey{ + PubKey: tapKey, + TweakedScriptKey: &asset.TweakedScriptKey{ + RawKey: keychain.KeyDescriptor{ + PubKey: asset.NUMSPubKey, + }, + Tweak: merkleRootHash[:], + }, + } + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + controlBlock.OutputKeyYIsOdd = true + } + + return tapScriptKey, tapLeaf, tree, controlBlock, nil +} diff --git a/assets/htlc/swapkit.go b/assets/htlc/swapkit.go new file mode 100644 index 000000000..8d562b80e --- /dev/null +++ b/assets/htlc/swapkit.go @@ -0,0 +1,457 @@ +package htlc + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/tapscript" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// SwapKit holds information needed to facilitate an on-chain asset to offchain +// bitcoin atomic swap. The keys within the struct are the public keys of the +// sender and receiver that will be used to create the on-chain HTLC. +type SwapKit struct { + // SenderPubKey is the public key of the sender for the joint key + // that will be used to create the HTLC. + SenderPubKey *btcec.PublicKey + + // ReceiverPubKey is the public key of the receiver that will be used to + // create the HTLC. + ReceiverPubKey *btcec.PublicKey + + // AssetID is the identifier of the asset that will be swapped. + AssetID []byte + + // Amount is the amount of the asset that will be swapped. Note that + // we use btcutil.Amount here for simplicity, but the actual amount + // is in the asset's native unit. + Amount uint64 + + // SwapHash is the hash of the preimage in the swap HTLC. + SwapHash lntypes.Hash + + // CsvExpiry is the relative timelock in blocks for the swap. + CsvExpiry uint32 +} + +// GetSuccessScript returns the success path script of the swap HTLC. +func (s *SwapKit) GetSuccessScript() ([]byte, error) { + return GenSuccessPathScript(s.ReceiverPubKey, s.SwapHash) +} + +// GetTimeoutScript returns the timeout path script of the swap HTLC. +func (s *SwapKit) GetTimeoutScript() ([]byte, error) { + return GenTimeoutPathScript(s.SenderPubKey, int64(s.CsvExpiry)) +} + +// GetAggregateKey returns the aggregate MuSig2 key used in the swap HTLC. +func (s *SwapKit) GetAggregateKey() (*btcec.PublicKey, error) { + aggregateKey, err := input.MuSig2CombineKeys( + input.MuSig2Version100RC2, + []*btcec.PublicKey{ + s.SenderPubKey, s.ReceiverPubKey, + }, + true, + &input.MuSig2Tweaks{}, + ) + if err != nil { + return nil, err + } + + return aggregateKey.PreTweakedKey, nil +} + +// GetTimeOutLeaf returns the timeout leaf of the swap. +func (s *SwapKit) GetTimeOutLeaf() (txscript.TapLeaf, error) { + timeoutScript, err := s.GetTimeoutScript() + if err != nil { + return txscript.TapLeaf{}, err + } + + timeoutLeaf := txscript.NewBaseTapLeaf(timeoutScript) + + return timeoutLeaf, nil +} + +// GetSuccessLeaf returns the success leaf of the swap. +func (s *SwapKit) GetSuccessLeaf() (txscript.TapLeaf, error) { + successScript, err := s.GetSuccessScript() + if err != nil { + return txscript.TapLeaf{}, err + } + + successLeaf := txscript.NewBaseTapLeaf(successScript) + + return successLeaf, nil +} + +// GetSiblingPreimage returns the sibling preimage of the HTLC bitcoin top level +// output. +func (s *SwapKit) GetSiblingPreimage() (commitment.TapscriptPreimage, error) { + timeOutLeaf, err := s.GetTimeOutLeaf() + if err != nil { + return commitment.TapscriptPreimage{}, err + } + + successLeaf, err := s.GetSuccessLeaf() + if err != nil { + return commitment.TapscriptPreimage{}, err + } + + branch := txscript.NewTapBranch(timeOutLeaf, successLeaf) + + siblingPreimage := commitment.NewPreimageFromBranch(branch) + + return siblingPreimage, nil +} + +// CreateHtlcVpkt creates the vpacket for the HTLC. +func (s *SwapKit) CreateHtlcVpkt(addressParams *address.ChainParams) ( + *tappsbt.VPacket, error) { + + assetId := asset.ID{} + copy(assetId[:], s.AssetID) + + btcInternalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + siblingPreimage, err := s.GetSiblingPreimage() + if err != nil { + return nil, err + } + + tapScriptKey, _, _, _, err := CreateOpTrueLeaf() + if err != nil { + return nil, err + } + + pkt := &tappsbt.VPacket{ + Inputs: []*tappsbt.VInput{{ + PrevID: asset.PrevID{ + ID: assetId, + }, + }}, + Outputs: make([]*tappsbt.VOutput, 0, 2), + ChainParams: addressParams, + Version: tappsbt.V1, + } + pkt.Outputs = append(pkt.Outputs, &tappsbt.VOutput{ + Amount: 0, + Type: tappsbt.TypeSplitRoot, + AnchorOutputIndex: 0, + ScriptKey: asset.NUMSScriptKey, + }) + pkt.Outputs = append(pkt.Outputs, &tappsbt.VOutput{ + // todo(sputn1ck) assetversion + AssetVersion: asset.Version(1), + Amount: s.Amount, + Interactive: true, + AnchorOutputIndex: 1, + ScriptKey: asset.NewScriptKey( + tapScriptKey.PubKey, + ), + AnchorOutputInternalKey: btcInternalKey, + AnchorOutputTapscriptSibling: &siblingPreimage, + }) + + return pkt, nil +} + +// GenTimeoutBtcControlBlock generates the control block for the timeout path of +// the swap. +func (s *SwapKit) GenTimeoutBtcControlBlock(taprootAssetRoot []byte) ( + *txscript.ControlBlock, error) { + + internalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + successLeaf, err := s.GetSuccessLeaf() + if err != nil { + return nil, err + } + + successLeafHash := successLeaf.TapHash() + + btcControlBlock := &txscript.ControlBlock{ + InternalKey: internalKey, + LeafVersion: txscript.BaseLeafVersion, + InclusionProof: append( + successLeafHash[:], taprootAssetRoot..., + ), + } + + timeoutPathScript, err := s.GetTimeoutScript() + if err != nil { + return nil, err + } + + rootHash := btcControlBlock.RootHash(timeoutPathScript) + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash) + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + btcControlBlock.OutputKeyYIsOdd = true + } + + return btcControlBlock, nil +} + +// GenSuccessBtcControlBlock generates the control block for the timeout path of +// the swap. +func (s *SwapKit) GenSuccessBtcControlBlock(taprootAssetRoot []byte) ( + *txscript.ControlBlock, error) { + + internalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + timeOutLeaf, err := s.GetTimeOutLeaf() + if err != nil { + return nil, err + } + + timeOutLeafHash := timeOutLeaf.TapHash() + + btcControlBlock := &txscript.ControlBlock{ + InternalKey: internalKey, + LeafVersion: txscript.BaseLeafVersion, + InclusionProof: append( + timeOutLeafHash[:], taprootAssetRoot..., + ), + } + + successPathScript, err := s.GetSuccessScript() + if err != nil { + return nil, err + } + + rootHash := btcControlBlock.RootHash(successPathScript) + tapKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash) + if tapKey.SerializeCompressed()[0] == + secp256k1.PubKeyFormatCompressedOdd { + + btcControlBlock.OutputKeyYIsOdd = true + } + + return btcControlBlock, nil +} + +// GenTaprootAssetRootFromProof generates the taproot asset root from the proof +// of the swap. +func GenTaprootAssetRootFromProof(proof *proof.Proof) ([]byte, error) { + assetCopy := proof.Asset.CopySpendTemplate() + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.FromAssets( + &version, assetCopy, + ) + if err != nil { + return nil, err + } + + assetCommitment, err = commitment.TrimSplitWitnesses( + &version, assetCommitment, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot := assetCommitment.TapscriptRoot(nil) + + return taprootAssetRoot[:], nil +} + +// GetPkScriptFromAsset returns the toplevel bitcoin script with the given +// asset. +func (s *SwapKit) GetPkScriptFromAsset(asset *asset.Asset) ([]byte, error) { + assetCopy := asset.CopySpendTemplate() + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.FromAssets( + &version, assetCopy, + ) + if err != nil { + return nil, err + } + + assetCommitment, err = commitment.TrimSplitWitnesses( + &version, assetCommitment, + ) + if err != nil { + return nil, err + } + + siblingPreimage, err := s.GetSiblingPreimage() + if err != nil { + return nil, err + } + + siblingHash, err := siblingPreimage.TapHash() + if err != nil { + return nil, err + } + + btcInternalKey, err := s.GetAggregateKey() + if err != nil { + return nil, err + } + + return tapscript.PayToAddrScript( + *btcInternalKey, siblingHash, *assetCommitment, + ) +} + +// CreatePreimageWitness creates a preimage witness for the swap. +func (s *SwapKit) CreatePreimageWitness(ctx context.Context, + signer lndclient.SignerClient, htlcProof *proof.Proof, + sweepBtcPacket *psbt.Packet, keyLocator keychain.KeyLocator, + preimage lntypes.Preimage) (wire.TxWitness, error) { + + assetTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[0].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[0].WitnessUtxo.Value, + } + feeTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[1].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[1].WitnessUtxo.Value, + } + + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = 1 + + successScript, err := s.GetSuccessScript() + if err != nil { + return nil, err + } + + signDesc := &lndclient.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: keyLocator, + }, + SignMethod: input.TaprootScriptSpendSignMethod, + WitnessScript: successScript, + Output: assetTxOut, + InputIndex: 0, + } + sig, err := signer.SignOutputRaw( + ctx, sweepBtcPacket.UnsignedTx, + []*lndclient.SignDescriptor{ + signDesc, + }, + []*wire.TxOut{ + assetTxOut, feeTxOut, + }, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot, err := GenTaprootAssetRootFromProof(htlcProof) + if err != nil { + return nil, err + } + + successControlBlock, err := s.GenSuccessBtcControlBlock( + taprootAssetRoot, + ) + if err != nil { + return nil, err + } + + controlBlockBytes, err := successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return wire.TxWitness{ + preimage[:], + sig[0], + successScript, + controlBlockBytes, + }, nil +} + +// CreateTimeoutWitness creates a timeout witness for the swap. +func (s *SwapKit) CreateTimeoutWitness(ctx context.Context, + signer lndclient.SignerClient, htlcProof *proof.Proof, + sweepBtcPacket *psbt.Packet, keyLocator keychain.KeyLocator) ( + wire.TxWitness, error) { + + assetTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[0].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[0].WitnessUtxo.Value, + } + feeTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[1].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[1].WitnessUtxo.Value, + } + + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = s.CsvExpiry + + timeoutScript, err := s.GetTimeoutScript() + if err != nil { + return nil, err + } + + signDesc := &lndclient.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: keyLocator, + }, + SignMethod: input.TaprootScriptSpendSignMethod, + WitnessScript: timeoutScript, + Output: assetTxOut, + InputIndex: 0, + } + sig, err := signer.SignOutputRaw( + ctx, sweepBtcPacket.UnsignedTx, + []*lndclient.SignDescriptor{ + signDesc, + }, + []*wire.TxOut{ + assetTxOut, feeTxOut, + }, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot, err := GenTaprootAssetRootFromProof(htlcProof) + if err != nil { + return nil, err + } + + timeoutControlBlock, err := s.GenTimeoutBtcControlBlock( + taprootAssetRoot, + ) + if err != nil { + return nil, err + } + + controlBlockBytes, err := timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return wire.TxWitness{ + sig[0], + timeoutScript, + controlBlockBytes, + }, nil +} diff --git a/assets/interfaces.go b/assets/interfaces.go new file mode 100644 index 000000000..c8a7a5beb --- /dev/null +++ b/assets/interfaces.go @@ -0,0 +1,148 @@ +package assets + +import ( + "context" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" + "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" + "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" + "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" + "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" +) + +const ( + // DefaultSwapCSVExpiry is the default expiry for a swap in blocks. + DefaultSwapCSVExpiry = int32(24) + + defaultHtlcFeeConfTarget = 3 + defaultHtlcConfRequirement = 2 + + AssetKeyFamily = 696969 +) + +// TapdClient is an interface that groups the methods required to interact with +// the taproot-assets server and the wallet. +type AssetClient interface { + taprpc.TaprootAssetsClient + wrpc.AssetWalletClient + mintrpc.MintClient + universerpc.UniverseClient + tapdevrpc.TapDevClient + + // FundAndSignVpacket funds ands signs a vpacket. + FundAndSignVpacket(ctx context.Context, + vpkt *tappsbt.VPacket) (*tappsbt.VPacket, error) + + // PrepareAndCommitVirtualPsbts prepares and commits virtual psbts. + PrepareAndCommitVirtualPsbts(ctx context.Context, + vpkt *tappsbt.VPacket, feeRateSatPerKVByte chainfee.SatPerVByte, + changeKeyDesc *keychain.KeyDescriptor, params *chaincfg.Params) ( + *psbt.Packet, []*tappsbt.VPacket, []*tappsbt.VPacket, + *assetwalletrpc.CommitVirtualPsbtsResponse, error) + + // LogAndPublish logs and publishes the virtual psbts. + LogAndPublish(ctx context.Context, btcPkt *psbt.Packet, + activeAssets []*tappsbt.VPacket, passiveAssets []*tappsbt.VPacket, + commitResp *wrpc.CommitVirtualPsbtsResponse) (*taprpc.SendAssetResponse, + error) + + // GetAssetBalance returns the balance of the given asset. + GetAssetBalance(ctx context.Context, assetId []byte) ( + uint64, error) + + // DeriveNewKeys derives a new internal and script key. + DeriveNewKeys(ctx context.Context) (asset.ScriptKey, + keychain.KeyDescriptor, error) + + // AddHoldInvoice adds a new hold invoice. + AddHoldInvoice(ctx context.Context, pHash lntypes.Hash, + assetId []byte, assetAmt uint64, memo string) ( + *tapchannelrpc.AddInvoiceResponse, error) + + // ImportProofFile imports the proof file and returns the last proof. + ImportProofFile(ctx context.Context, rawProofFile []byte) ( + *proof.Proof, error) +} + +// SwapStore is an interface that groups the methods required to store swap +// information. +type SwapStore interface { + // CreateAssetSwapOut creates a new swap out in the store. + CreateAssetSwapOut(ctx context.Context, swap *SwapOut) error + + // UpdateAssetSwapHtlcOutpoint updates the htlc outpoint of a swap out. + UpdateAssetSwapHtlcOutpoint(ctx context.Context, swapHash lntypes.Hash, + outpoint *wire.OutPoint, confirmationHeight int32) error + + // UpdateAssetSwapOutProof updates the proof of a swap out. + UpdateAssetSwapOutProof(ctx context.Context, swapHash lntypes.Hash, + rawProof []byte) error + + // UpdateAssetSwapOutSweepTx updates the sweep tx of a swap out. + UpdateAssetSwapOutSweepTx(ctx context.Context, + swapHash lntypes.Hash, sweepTxid chainhash.Hash, + confHeight int32, sweepPkscript []byte) error + + // InsertAssetSwapUpdate inserts a new swap update in the store. + InsertAssetSwapUpdate(ctx context.Context, + swapHash lntypes.Hash, state fsm.StateType) error + + UpdateAssetSwapOutPreimage(ctx context.Context, + swapHash lntypes.Hash, preimage lntypes.Preimage) error +} + +// BlockHeightSubscriber is responsible for subscribing to the expiry height +// of a swap, as well as getting the current block height. +type BlockHeightSubscriber interface { + // SubscribeExpiry subscribes to the expiry of a swap. It returns true + // if the expiry is already past. Otherwise, it returns false and calls + // the expiryFunc when the expiry height is reached. + SubscribeExpiry(swapHash [32]byte, + expiryHeight int32, expiryFunc func()) bool + // GetBlockHeight returns the current block height. + GetBlockHeight() int32 +} + +// InvoiceSubscriber is responsible for subscribing to an invoice. +type InvoiceSubscriber interface { + // SubscribeInvoice subscribes to an invoice. The update callback is + // called when the invoice is updated and the error callback is called + // when an error occurs. + SubscribeInvoice(ctx context.Context, invoiceHash lntypes.Hash, + updateCallback func(lndclient.InvoiceUpdate, error)) error +} + +// TxConfirmationSubscriber is responsible for subscribing to the confirmation +// of a transaction. +type TxConfirmationSubscriber interface { + + // SubscribeTxConfirmation subscribes to the confirmation of a + // pkscript on the chain. The callback is called when the pkscript is + // confirmed or when an error occurs. + SubscribeTxConfirmation(ctx context.Context, swapHash lntypes.Hash, + txid *chainhash.Hash, pkscript []byte, numConfs int32, + eightHint int32, cb func(*chainntnfs.TxConfirmation, error)) error +} + +// ExchangeRateProvider is responsible for providing the exchange rate between +// assets. +type ExchangeRateProvider interface { + // GetSatsPerAssetUnit returns the amount of satoshis per asset unit. + GetSatsPerAssetUnit(assetId []byte) (btcutil.Amount, error) +} diff --git a/assets/log.go b/assets/log.go new file mode 100644 index 000000000..70981c586 --- /dev/null +++ b/assets/log.go @@ -0,0 +1,26 @@ +package assets + +import ( + "github.com/btcsuite/btclog/v2" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the sub system name of this package. +const Subsystem = "ASSETS" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/assets/manager.go b/assets/manager.go new file mode 100644 index 000000000..b91855d51 --- /dev/null +++ b/assets/manager.go @@ -0,0 +1,203 @@ +package assets + +import ( + "context" + "sync" + "time" + + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/fsm" + loop_rpc "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/loop/utils" + "github.com/lightninglabs/taproot-assets/taprpc" + "github.com/lightningnetwork/lnd/lntypes" +) + +const ( + ClientKeyFamily = 696969 +) + +type Config struct { + AssetClient *TapdClient + Wallet lndclient.WalletKitClient + // ExchangeRateProvider is the exchange rate provider. + ExchangeRateProvider *FixedExchangeRateProvider + Signer lndclient.SignerClient + ChainNotifier lndclient.ChainNotifierClient + Router lndclient.RouterClient + LndClient lndclient.LightningClient + Store *PostgresStore + ServerClient loop_rpc.AssetsSwapServerClient +} + +type AssetsSwapManager struct { + cfg *Config + + expiryManager *utils.ExpiryManager + txConfManager *utils.TxSubscribeConfirmationManager + + blockHeight int32 + runCtx context.Context + activeSwapOuts map[lntypes.Hash]*OutFSM + + sync.Mutex +} + +func NewAssetSwapServer(config *Config) *AssetsSwapManager { + return &AssetsSwapManager{ + cfg: config, + + activeSwapOuts: make(map[lntypes.Hash]*OutFSM), + } +} + +func (m *AssetsSwapManager) Run(ctx context.Context, blockHeight int32) error { + m.runCtx = ctx + m.blockHeight = blockHeight + + // Get our tapd client info. + tapdInfo, err := m.cfg.AssetClient.GetInfo( + ctx, &taprpc.GetInfoRequest{}, + ) + if err != nil { + return err + } + log.Infof("Tapd info: %v", tapdInfo) + + // Create our subscriptionManagers. + m.expiryManager = utils.NewExpiryManager(m.cfg.ChainNotifier) + m.txConfManager = utils.NewTxSubscribeConfirmationManager( + m.cfg.ChainNotifier, + ) + + // Start the expiry manager. + errChan := make(chan error, 1) + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + err := m.expiryManager.Start(ctx, blockHeight) + if err != nil { + log.Errorf("Expiry manager failed: %v", err) + errChan <- err + log.Errorf("Gude1") + } + }() + + // Recover all the active asset swap outs from the database. + err = m.recoverSwapOuts(ctx) + if err != nil { + return err + } + + for { + select { + case err := <-errChan: + log.Errorf("Gude2") + return err + + case <-ctx.Done(): + log.Errorf("Gude3") + // wg.Wait() + log.Errorf("Gude4") + return nil + } + } +} + +func (m *AssetsSwapManager) NewSwapOut(ctx context.Context, + amt uint64, asset []byte) (*OutFSM, error) { + + // Create a new out fsm. + outFSM := NewOutFSM(m.runCtx, m.getFSMOutConfig()) + + // Send the initial event to the fsm. + err := outFSM.SendEvent( + ctx, OnRequestAssetOut, &InitSwapOutContext{ + Amount: amt, + AssetId: asset, + BlockHeightHint: uint32(m.blockHeight), + }, + ) + if err != nil { + return nil, err + } + // Check if the fsm has an error. + if outFSM.LastActionError != nil { + return nil, outFSM.LastActionError + } + + // Wait for the fsm to be in the state we expect. + err = outFSM.DefaultObserver.WaitForState( + ctx, time.Second*15, PayPrepay, + fsm.WithAbortEarlyOnErrorOption(), + ) + if err != nil { + return nil, err + } + + // Add the swap to the active swap outs. + m.Lock() + m.activeSwapOuts[outFSM.SwapOut.SwapHash] = outFSM + m.Unlock() + + return outFSM, nil +} + +// recoverSwapOuts recovers all the active asset swap outs from the database. +func (m *AssetsSwapManager) recoverSwapOuts(ctx context.Context) error { + // Fetch all the active asset swap outs from the database. + activeSwapOuts, err := m.cfg.Store.GetActiveAssetOuts(ctx) + if err != nil { + return err + } + + for _, swapOut := range activeSwapOuts { + log.Debugf("Recovering asset out %v with state %v", + swapOut.SwapHash, swapOut.State) + + swapOutFSM := NewOutFSMFromSwap( + ctx, m.getFSMOutConfig(), swapOut, + ) + + m.Lock() + m.activeSwapOuts[swapOut.SwapHash] = swapOutFSM + m.Unlock() + + // As SendEvent can block, we'll start a goroutine to process + // the event. + go func() { + err := swapOutFSM.SendEvent(ctx, OnRecover, nil) + if err != nil { + log.Errorf("FSM %v Error sending recover "+ + "event %v, state: %v", + swapOutFSM.SwapOut.SwapHash, + err, swapOutFSM.SwapOut.State) + } + }() + } + + return nil +} + +// getFSMOutConfig returns a fsmconfig from the manager. +func (m *AssetsSwapManager) getFSMOutConfig() *FSMConfig { + return &FSMConfig{ + TapdClient: m.cfg.AssetClient, + AssetClient: m.cfg.ServerClient, + BlockHeightSubscriber: m.expiryManager, + TxConfSubscriber: m.txConfManager, + ExchangeRateProvider: m.cfg.ExchangeRateProvider, + Wallet: m.cfg.Wallet, + Router: m.cfg.Router, + + Store: m.cfg.Store, + Signer: m.cfg.Signer, + } +} + +func (m *AssetsSwapManager) ListSwapOutoutputs(ctx context.Context) ([]*SwapOut, + error) { + + return m.cfg.Store.GetAllAssetOuts(ctx) +} diff --git a/assets/out_fsm.go b/assets/out_fsm.go new file mode 100644 index 000000000..2be75147f --- /dev/null +++ b/assets/out_fsm.go @@ -0,0 +1,614 @@ +package assets + +import ( + "bytes" + "context" + "errors" + + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tapscript" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" +) + +const ( + // Limit the observers transition observation stack to 15 entries. + defaultObserverSize = 15 +) + +// States. +const ( + // Init is the initial state of the swap. + Init fsm.StateType = "Init" + + // PayPrepay is the state where we are waiting for the + // prepay invoice to be accepted. + PayPrepay fsm.StateType = "PayPrepay" + + // FetchProof is the state where the prepay invoice has been + // accepted. + FetchProof fsm.StateType = "FetchProof" + + // WaitForBlock is the state where we are waiting for the next block + // to be mined. + WaitForBlock fsm.StateType = "WaitForBlock" + + // WaitForHtlcConfirmed is the state where the htlc transaction + // has been broadcast. + WaitForHtlcConfirmed fsm.StateType = "WaitForHtlcConfirmed" + + // HtlcTxConfirmed is the state where the htlc transaction + // has been confirmed. + HtlcTxConfirmed fsm.StateType = "HtlcTxConfirmed" + + // SweepHtlc is the state where we are creating the swap + // invoice. + SweepHtlc fsm.StateType = "SweepHtlc" + + // WaitForSweepConfirmed is the state where we are waiting for the swap + // payment to be made. This is after we have given the receiver the + // taproot assets proof. + WaitForSweepConfirmed fsm.StateType = "WaitForSweepConfirmed" + + // Finished is the state where the swap has finished. + Finished fsm.StateType = "Finished" + + // FinishedTimeout is the state where the swap has finished due to + // a timeout. + FinishedTimeout fsm.StateType = "FinishedTimeout" + + // Failed is the state where the swap has failed. + Failed fsm.StateType = "Failed" +) + +var ( + finishedStates = []fsm.StateType{ + Finished, FinishedTimeout, Failed, + } +) + +// Events. +var ( + // OnRequestAssetOut is the event where the server receives a swap + // request from the client. + OnRequestAssetOut = fsm.EventType("OnRequestAssetOut") + + // onAssetOutInit is the event where the server has initialized the + // swap. + onAssetOutInit = fsm.EventType("OnAssetOutInit") + + // onPrepaySettled is the event where the prepay invoice has been + // accepted. + onPrepaySettled = fsm.EventType("onPrepaySettled") + + onWaitForBlock = fsm.EventType("onWaitForBlock") + + onBlockReceived = fsm.EventType("onBlockReceived") + + onProofReceived = fsm.EventType("OnProofReceived") + + onHtlcTxConfirmed = fsm.EventType("onHtlcTxConfirmed") + + onSwapPreimageReceived = fsm.EventType("OnSwapPreimageReceived") + + // onHtlcSuccessSweep is the event where the htlc has timed out and we + // are trying to sweep the htlc output. + onHtlcSuccessSweep = fsm.EventType("onHtlcSuccessSweep") + + // onSweepTxConfirmed is the event where the sweep transaction has been + // confirmed. + onSweepTxConfirmed = fsm.EventType("OnSweepTxConfirmed") + + // OnRecover is the event where the swap is being recovered. + OnRecover = fsm.EventType("OnRecover") +) + +// FSMConfig contains the configuration for the FSM. +type FSMConfig struct { + // TapdClient is the client to interact with the taproot asset daemon. + TapdClient AssetClient + + // AssetClient is the client to interact with the asset swap server. + AssetClient swapserverrpc.AssetsSwapServerClient + + // BlockHeightSubscriber is the subscriber to the block height. + BlockHeightSubscriber BlockHeightSubscriber + + // TxConfSubscriber is the subscriber to the transaction confirmation. + TxConfSubscriber TxConfirmationSubscriber + + // ExchangeRateProvider is the provider for the exchange rate. + ExchangeRateProvider ExchangeRateProvider + + // Wallet is the wallet client. + Wallet lndclient.WalletKitClient + + // Router is the lnd router client. + Router lndclient.RouterClient + + // Signer is the signer client. + Signer lndclient.SignerClient + + // Store is the swap store. + Store SwapStore + + // AddrParams are the chain parameters for addresses. + AddrParams *address.ChainParams +} + +type OutFSM struct { + *fsm.StateMachine + + runCtx context.Context + + cfg *FSMConfig + + // SwapOut contains all the information about the swap. + SwapOut *SwapOut + + // PrepayInvoice is the prepay invoice that we are paying to initiate + // the swap. + PrepayInvoice string + + // SwapInvoice is the swap invoice that we are sending to the receiver. + SwapInvoice string + + // HtlcProof is the htlc proof that we use to sweep the htlc output. + HtlcProof *proof.Proof +} + +// NewOutFSM creates a new OutFSM. +func NewOutFSM(ctx context.Context, cfg *FSMConfig) *OutFSM { + out := &SwapOut{ + State: fsm.EmptyState, + } + + return NewOutFSMFromSwap(ctx, cfg, out) +} + +// NewOutFSMFromSwap creates a new OutFSM from a existing swap. +func NewOutFSMFromSwap(ctx context.Context, cfg *FSMConfig, swap *SwapOut, +) *OutFSM { + + outFSM := &OutFSM{ + runCtx: ctx, + cfg: cfg, + SwapOut: swap, + } + + outFSM.StateMachine = fsm.NewStateMachineWithState( + outFSM.GetStates(), outFSM.SwapOut.State, defaultObserverSize, + ) + outFSM.ActionEntryFunc = outFSM.updateSwap + + return outFSM +} + +// GetStates returns the swap out state machine. +func (o *OutFSM) GetStates() fsm.States { + return fsm.States{ + fsm.EmptyState: fsm.State{ + Transitions: fsm.Transitions{ + OnRequestAssetOut: Init, + }, + Action: nil, + }, + Init: fsm.State{ + Transitions: fsm.Transitions{ + onAssetOutInit: PayPrepay, + // Before the htlc has been signed we can always + // fail the swap. + OnRecover: Failed, + fsm.OnError: Failed, + }, + Action: o.InitSwapOut, + }, + PayPrepay: fsm.State{ + Transitions: fsm.Transitions{ + onPrepaySettled: FetchProof, + fsm.OnError: Failed, + OnRecover: Failed, + }, + Action: o.PayPrepay, + }, + FetchProof: fsm.State{ + Transitions: fsm.Transitions{ + onWaitForBlock: WaitForBlock, + onProofReceived: WaitForHtlcConfirmed, + fsm.OnError: Failed, + OnRecover: FetchProof, + }, + Action: o.FetchProof, + }, + WaitForBlock: fsm.State{ + Transitions: fsm.Transitions{ + onBlockReceived: FetchProof, + OnRecover: FetchProof, + }, + Action: o.waitForBlock, + }, + WaitForHtlcConfirmed: fsm.State{ + Transitions: fsm.Transitions{ + onHtlcTxConfirmed: HtlcTxConfirmed, + fsm.OnError: Failed, + OnRecover: WaitForHtlcConfirmed, + }, + Action: o.subscribeToHtlcTxConfirmed, + }, + HtlcTxConfirmed: fsm.State{ + Transitions: fsm.Transitions{ + onSwapPreimageReceived: SweepHtlc, + // Todo(sputn1ck) change to wait for expiry state. + fsm.OnError: Failed, + OnRecover: HtlcTxConfirmed, + }, + Action: o.sendSwapPayment, + }, + SweepHtlc: fsm.State{ + Transitions: fsm.Transitions{ + onHtlcSuccessSweep: WaitForSweepConfirmed, + fsm.OnError: SweepHtlc, + OnRecover: SweepHtlc, + }, + Action: o.publishSweepTx, + }, + WaitForSweepConfirmed: fsm.State{ + Transitions: fsm.Transitions{ + onSweepTxConfirmed: Finished, + fsm.OnError: WaitForSweepConfirmed, + OnRecover: WaitForSweepConfirmed, + }, + Action: o.subscribeSweepConf, + }, + Finished: fsm.State{ + Action: fsm.NoOpAction, + }, + Failed: fsm.State{ + Action: fsm.NoOpAction, + }, + } +} + +// // getSwapCopy returns a copy of the swap that is safe to be used from the +// // caller. +// func (o *OutFSM) getSwapCopy() *SwapOut { + +// updateSwap is called after every action and updates the swap in the db. +func (o *OutFSM) updateSwap(ctx context.Context, + notification fsm.Notification) { + + o.Infof("Current: %v", notification.NextState) + + // Skip the update if the swap is not yet initialized. + if o.SwapOut == nil { + return + } + + o.SwapOut.State = notification.NextState + + // If we're in the early stages we don't have created the swap in the + // store yet and won't need to update it. + if o.SwapOut.State == Init || (notification.PreviousState == Init && + notification.NextState == Failed) { + + return + } + + err := o.cfg.Store.InsertAssetSwapUpdate( + ctx, o.SwapOut.SwapHash, o.SwapOut.State, + ) + if err != nil { + log.Errorf("Error updating swap : %v", err) + return + } +} + +// getHtlcPkscript returns the pkscript of the htlc output. +func (o *OutFSM) getHtlcPkscript() ([]byte, error) { + // First fetch the proof. + proof, err := o.getHtlcProof() + if err != nil { + return nil, err + } + + // // Verify that the asset script matches the one predicted. + // assetScriptkey, _, _, _, err := createOpTrueLeaf() + // if err != nil { + // return nil, err + // } + + // o.Debugf("Asset script key: %x", assetScriptkey.PubKey.SerializeCompressed()) + // o.Debugf("Proof script key: %x", proof.Asset.ScriptKey.PubKey.SerializeCompressed()) + // if !bytes.Equal( + // proof.Asset.ScriptKey.PubKey.SerializeCompressed(), + // assetScriptkey.PubKey.SerializeCompressed(), + // ) { + // return nil, fmt.Errorf("asset script key mismatch") + // } + + assetCpy := proof.Asset.Copy() + assetCpy.PrevWitnesses[0].SplitCommitment = nil + sendCommitment, err := commitment.NewAssetCommitment( + assetCpy, + ) + if err != nil { + return nil, err + } + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.NewTapCommitment( + &version, sendCommitment, + ) + if err != nil { + return nil, err + } + + siblingPreimage, err := o.SwapOut.GetSiblingPreimage() + if err != nil { + return nil, err + } + + siblingHash, err := siblingPreimage.TapHash() + if err != nil { + return nil, err + } + + btcInternalKey, err := o.SwapOut.GetAggregateKey() + if err != nil { + return nil, err + } + + anchorPkScript, err := tapscript.PayToAddrScript( + *btcInternalKey, siblingHash, *assetCommitment, + ) + if err != nil { + return nil, err + } + + return anchorPkScript, nil +} + +// publishPreimageSweep publishes and logs the preimage sweep transaction. +func (o *OutFSM) publishPreimageSweep(addr *address.Tap) (*wire.OutPoint, []byte, error) { + ctx := o.runCtx + + // Check if we have the proof in memory. + htlcProof, err := o.getHtlcProof() + if err != nil { + return nil, nil, err + } + + sweepVpkt, err := CreateOpTrueSweepVpkt( + ctx, []*proof.Proof{htlcProof}, addr, o.cfg.AddrParams, + ) + if err != nil { + return nil, nil, err + } + + feeRate, err := o.cfg.Wallet.EstimateFeeRate( + ctx, defaultHtlcFeeConfTarget, + ) + if err != nil { + return nil, nil, err + } + + // We'll now commit the vpkt in the btcpacket. + sweepBtcPacket, activeAssets, passiveAssets, commitResp, err := + o.cfg.TapdClient.PrepareAndCommitVirtualPsbts( + // todo check change desc and params. + ctx, sweepVpkt, feeRate.FeePerVByte(), nil, nil, + ) + if err != nil { + return nil, nil, err + } + + witness, err := o.createPreimageWitness( + ctx, sweepBtcPacket, htlcProof, + ) + if err != nil { + return nil, nil, err + } + + var buf bytes.Buffer + err = psbt.WriteTxWitness(&buf, witness) + if err != nil { + return nil, nil, err + } + sweepBtcPacket.Inputs[0].SighashType = txscript.SigHashDefault + sweepBtcPacket.Inputs[0].FinalScriptWitness = buf.Bytes() + + signedBtcPacket, err := o.cfg.Wallet.SignPsbt(ctx, sweepBtcPacket) + if err != nil { + return nil, nil, err + } + + finalizedBtcPacket, _, err := o.cfg.Wallet.FinalizePsbt( + ctx, signedBtcPacket, "", + ) + if err != nil { + return nil, nil, err + } + + pkScript := finalizedBtcPacket.UnsignedTx.TxOut[0].PkScript + + // Now we'll publish and log the transfer. + sendResp, err := o.cfg.TapdClient.LogAndPublish( + ctx, finalizedBtcPacket, activeAssets, passiveAssets, + commitResp, + ) + if err != nil { + return nil, nil, err + } + + sweepAnchor := sendResp.Transfer.Outputs[0].Anchor + + outPoint, err := wire.NewOutPointFromString(sweepAnchor.Outpoint) + if err != nil { + return nil, nil, err + } + + return outPoint, pkScript, nil +} + +// getHtlcProof returns the htlc proof for the swap. If the proof is not +// in memory, we will recreate it from the stored proof file. +func (o *OutFSM) getHtlcProof() (*proof.Proof, error) { + // Check if we have the proof in memory. + if o.HtlcProof != nil { + return o.HtlcProof, nil + } + + // Parse the proof. + htlcProofFile, err := proof.DecodeFile(o.SwapOut.RawHtlcProof) + if err != nil { + return nil, err + } + + // Get the proofs. + htlcProof, err := htlcProofFile.LastProof() + if err != nil { + return nil, err + } + + return htlcProof, nil +} + +// createPreimageWitness creates a preimage witness for the swap. +func (o *OutFSM) createPreimageWitness(ctx context.Context, + sweepBtcPacket *psbt.Packet, htlcProof *proof.Proof) (wire.TxWitness, + error) { + + assetTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[0].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[0].WitnessUtxo.Value, + } + feeTxOut := &wire.TxOut{ + PkScript: sweepBtcPacket.Inputs[1].WitnessUtxo.PkScript, + Value: sweepBtcPacket.Inputs[1].WitnessUtxo.Value, + } + + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = 1 + + successScript, err := o.SwapOut.GetSuccessScript() + if err != nil { + return nil, err + } + + signDesc := &lndclient.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: o.SwapOut.ClientKeyLocator, + }, + SignMethod: input.TaprootScriptSpendSignMethod, + WitnessScript: successScript, + Output: assetTxOut, + InputIndex: 0, + } + sig, err := o.cfg.Signer.SignOutputRaw( + ctx, sweepBtcPacket.UnsignedTx, []*lndclient.SignDescriptor{signDesc}, + []*wire.TxOut{assetTxOut, feeTxOut}, + ) + if err != nil { + return nil, err + } + taprootAssetRoot, err := o.SwapOut.genTaprootAssetRootFromProof( + htlcProof, + ) + if err != nil { + return nil, err + } + successControlBlock, err := o.SwapOut.GenSuccessBtcControlBlock( + taprootAssetRoot, + ) + if err != nil { + return nil, err + } + controlBlockBytes, err := successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return wire.TxWitness{ + o.SwapOut.SwapPreimage[:], + sig[0], + successScript, + controlBlockBytes, + }, nil +} + +// Infof logs an info message with the swap hash as prefix. +func (o *OutFSM) Infof(format string, args ...interface{}) { + log.Infof( + "Swap %v: "+format, + append( + []interface{}{o.SwapOut.SwapHash}, + args..., + )..., + ) +} + +// Debugf logs a debug message with the swap hash as prefix. +func (o *OutFSM) Debugf(format string, args ...interface{}) { + log.Debugf( + "Swap %v: "+format, + append( + []interface{}{o.SwapOut.SwapHash}, + args..., + )..., + ) +} + +// Errorf logs an error message with the swap hash as prefix. +func (o *OutFSM) Errorf(format string, args ...interface{}) { + log.Errorf( + "Swap %v: "+format, + append( + []interface{}{o.SwapOut.SwapHash}, + args..., + )..., + ) +} +func (o *OutFSM) findPkScript(tx *wire.MsgTx) (*wire.OutPoint, + error) { + + pkScript, err := o.getHtlcPkscript() + if err != nil { + return nil, err + } + + for i, out := range tx.TxOut { + if bytes.Equal(out.PkScript, pkScript) { + txHash := tx.TxHash() + return wire.NewOutPoint(&txHash, uint32(i)), nil + } + } + return nil, errors.New("pkscript not found") +} + +// IsFinishedState returns true if the passed state is a finished state. +func IsFinishedState(state fsm.StateType) bool { + for _, s := range finishedStates { + if s == state { + return true + } + } + + return false +} + +// FinishedStates returns a string slice of all finished states. +func FinishedStates() []string { + states := make([]string, 0, len(finishedStates)) + for _, s := range finishedStates { + states = append(states, string(s)) + } + + return states +} diff --git a/assets/rateprovider.go b/assets/rateprovider.go new file mode 100644 index 000000000..58a48b943 --- /dev/null +++ b/assets/rateprovider.go @@ -0,0 +1,23 @@ +package assets + +import "github.com/btcsuite/btcd/btcutil" + +const ( + fixedPrice = 100 +) + +// FixedExchangeRateProvider is a fixed exchange rate provider. +type FixedExchangeRateProvider struct { +} + +// NewFixedExchangeRateProvider creates a new fixed exchange rate provider. +func NewFixedExchangeRateProvider() *FixedExchangeRateProvider { + return &FixedExchangeRateProvider{} +} + +// GetSatsPerAssetUnit returns the fixed price in sats per asset unit. +func (e *FixedExchangeRateProvider) GetSatsPerAssetUnit(assetId []byte) ( + btcutil.Amount, error) { + + return btcutil.Amount(fixedPrice), nil +} diff --git a/assets/server.go b/assets/server.go new file mode 100644 index 000000000..66bddccb9 --- /dev/null +++ b/assets/server.go @@ -0,0 +1,126 @@ +package assets + +import ( + "context" + + clientrpc "github.com/lightninglabs/loop/looprpc" + "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightninglabs/taproot-assets/taprpc/universerpc" +) + +type AssetsClientServer struct { + manager *AssetsSwapManager + + clientrpc.UnimplementedAssetsClientServer +} + +func NewAssetsServer(manager *AssetsSwapManager) *AssetsClientServer { + return &AssetsClientServer{ + manager: manager, + } +} + +func (a *AssetsClientServer) SwapOut(ctx context.Context, + req *clientrpc.SwapOutRequest) (*clientrpc.SwapOutResponse, error) { + + swap, err := a.manager.NewSwapOut( + ctx, req.Amt, req.Asset, + ) + if err != nil { + return nil, err + } + return &clientrpc.SwapOutResponse{ + SwapStatus: &clientrpc.AssetSwapStatus{ + SwapHash: swap.SwapOut.SwapHash[:], + SwapStatus: string(swap.SwapOut.State), + }, + }, nil +} + +func (a *AssetsClientServer) ListAssetSwaps(ctx context.Context, + _ *clientrpc.ListAssetSwapsRequest) (*clientrpc.ListAssetSwapsResponse, + error) { + + swaps, err := a.manager.ListSwapOutoutputs(ctx) + if err != nil { + return nil, err + } + + rpcSwaps := make([]*clientrpc.AssetSwapStatus, 0, len(swaps)) + for _, swap := range swaps { + rpcSwaps = append(rpcSwaps, &clientrpc.AssetSwapStatus{ + SwapHash: swap.SwapHash[:], + SwapStatus: string(swap.State), + }) + } + + return &clientrpc.ListAssetSwapsResponse{ + SwapStatus: rpcSwaps, + }, nil +} + +func (a *AssetsClientServer) ClientListAvailableAssets(ctx context.Context, + req *clientrpc.ClientListAvailableAssetsRequest, +) (*clientrpc.ClientListAvailableAssetsResponse, error) { + + assets, err := a.manager.cfg.ServerClient.ListAvailableAssets( + ctx, &swapserverrpc.ListAvailableAssetsRequest{}, + ) + if err != nil { + return nil, err + } + + availableAssets := make([]*clientrpc.Asset, 0, len(assets.Assets)) + + for _, asset := range assets.Assets { + clientAsset := &clientrpc.Asset{ + AssetId: asset.AssetId, + SatsPerUnit: asset.CurrentSatsPerAssetUnit, + Name: "Asset unknown in known universes", + } + universeRes, err := a.manager.cfg.AssetClient.QueryAssetRoots( + ctx, &universerpc.AssetRootQuery{ + Id: &universerpc.ID{ + Id: &universerpc.ID_AssetId{ + AssetId: asset.AssetId, + }, + ProofType: universerpc.ProofType_PROOF_TYPE_ISSUANCE, + }, + }, + ) + if err != nil { + return nil, err + } + + if universeRes.IssuanceRoot != nil { + clientAsset.Name = universeRes.IssuanceRoot.AssetName + } + + availableAssets = append(availableAssets, clientAsset) + } + + return &clientrpc.ClientListAvailableAssetsResponse{ + AvailableAssets: availableAssets, + }, nil +} +func (a *AssetsClientServer) ClientGetAssetSwapOutQuote(ctx context.Context, + req *clientrpc.ClientGetAssetSwapOutQuoteRequest, +) (*clientrpc.ClientGetAssetSwapOutQuoteResponse, error) { + + // Get the quote from the server. + quoteRes, err := a.manager.cfg.ServerClient.QuoteAssetLoopOut( + ctx, &swapserverrpc.QuoteAssetLoopOutRequest{ + Amount: req.Amt, + Asset: req.Asset, + }, + ) + if err != nil { + return nil, err + } + + return &clientrpc.ClientGetAssetSwapOutQuoteResponse{ + SwapFee: quoteRes.SwapFeeRate, + PrepayAmt: quoteRes.FixedPrepayAmt, + SatsPerUnit: quoteRes.CurrentSatsPerAssetUnit, + }, nil +} diff --git a/assets/store.go b/assets/store.go new file mode 100644 index 000000000..cee264daa --- /dev/null +++ b/assets/store.go @@ -0,0 +1,289 @@ +package assets + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightninglabs/loop/loopdb/sqlc" + "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// BaseDB is the interface that contains all the queries generated +// by sqlc for the instantout table. +type BaseDB interface { + // ExecTx allows for executing a function in the context of a database + // transaction. + ExecTx(ctx context.Context, txOptions loopdb.TxOptions, + txBody func(*sqlc.Queries) error) error + + CreateAssetSwap(ctx context.Context, arg sqlc.CreateAssetSwapParams) error + CreateAssetOutSwap(ctx context.Context, swapHash []byte) error + GetAllAssetOutSwaps(ctx context.Context) ([]sqlc.GetAllAssetOutSwapsRow, error) + GetAssetOutSwap(ctx context.Context, swapHash []byte) (sqlc.GetAssetOutSwapRow, error) + InsertAssetSwapUpdate(ctx context.Context, arg sqlc.InsertAssetSwapUpdateParams) error + UpdateAssetSwapHtlcTx(ctx context.Context, arg sqlc.UpdateAssetSwapHtlcTxParams) error + UpdateAssetSwapOutPreimage(ctx context.Context, arg sqlc.UpdateAssetSwapOutPreimageParams) error + UpdateAssetSwapOutProof(ctx context.Context, arg sqlc.UpdateAssetSwapOutProofParams) error + UpdateAssetSwapSweepTx(ctx context.Context, arg sqlc.UpdateAssetSwapSweepTxParams) error +} + +// PostgresStore is the backing store for the instant out manager. +type PostgresStore struct { + queries BaseDB + clock clock.Clock +} + +// NewPostgresStore creates a new PostgresStore. +func NewPostgresStore(queries BaseDB) *PostgresStore { + return &PostgresStore{ + queries: queries, + clock: clock.NewDefaultClock(), + } +} + +// CreateAssetSwapOut creates a new asset swap out in the database. +func (p *PostgresStore) CreateAssetSwapOut(ctx context.Context, + swap *SwapOut) error { + + params := sqlc.CreateAssetSwapParams{ + SwapHash: swap.SwapHash[:], + AssetID: swap.AssetID, + Amt: int64(swap.Amount), + SenderPubkey: swap.SenderPubKey.SerializeCompressed(), + ReceiverPubkey: swap.ReceiverPubKey.SerializeCompressed(), + CsvExpiry: int32(swap.CsvExpiry), + InitiationHeight: int32(swap.InitiationHeight), + CreatedTime: p.clock.Now(), + ServerKeyFamily: int64(swap.ClientKeyLocator.Family), + ServerKeyIndex: int64(swap.ClientKeyLocator.Index), + } + + return p.queries.ExecTx( + ctx, &loopdb.SqliteTxOptions{}, func(q *sqlc.Queries) error { + err := q.CreateAssetSwap(ctx, params) + if err != nil { + return err + } + + return q.CreateAssetOutSwap(ctx, swap.SwapHash[:]) + }, + ) +} + +// UpdateAssetSwapHtlcOutpoint updates the htlc outpoint of the swap out in the +// database. +func (p *PostgresStore) UpdateAssetSwapHtlcOutpoint(ctx context.Context, + swapHash lntypes.Hash, outpoint *wire.OutPoint, confirmationHeight int32) error { + + return p.queries.ExecTx( + ctx, &loopdb.SqliteTxOptions{}, func(q *sqlc.Queries) error { + return q.UpdateAssetSwapHtlcTx( + ctx, sqlc.UpdateAssetSwapHtlcTxParams{ + SwapHash: swapHash[:], + HtlcTxid: outpoint.Hash[:], + HtlcVout: int32(outpoint.Index), + HtlcConfirmationHeight: confirmationHeight, + }) + }, + ) +} + +// UpdateAssetSwapOutProof updates the raw proof of the swap out in the +// database. +func (p *PostgresStore) UpdateAssetSwapOutProof(ctx context.Context, + swapHash lntypes.Hash, rawProof []byte) error { + + return p.queries.ExecTx( + ctx, &loopdb.SqliteTxOptions{}, func(q *sqlc.Queries) error { + return q.UpdateAssetSwapOutProof( + ctx, sqlc.UpdateAssetSwapOutProofParams{ + SwapHash: swapHash[:], + RawProofFile: rawProof, + }) + }, + ) +} + +// UpdateAssetSwapOutPreimage updates the preimage of the swap out in the +// database. +func (p *PostgresStore) UpdateAssetSwapOutPreimage(ctx context.Context, + swapHash lntypes.Hash, preimage lntypes.Preimage) error { + + return p.queries.ExecTx( + ctx, &loopdb.SqliteTxOptions{}, func(q *sqlc.Queries) error { + return q.UpdateAssetSwapOutPreimage( + ctx, sqlc.UpdateAssetSwapOutPreimageParams{ + SwapHash: swapHash[:], + SwapPreimage: preimage[:], + }) + }, + ) +} + +// UpdateAssetSwapOutSweepTx updates the sweep tx of the swap out in the +// database. +func (p *PostgresStore) UpdateAssetSwapOutSweepTx(ctx context.Context, + swapHash lntypes.Hash, sweepTxid chainhash.Hash, confHeight int32, + sweepPkscript []byte) error { + + return p.queries.ExecTx( + ctx, &loopdb.SqliteTxOptions{}, func(q *sqlc.Queries) error { + return q.UpdateAssetSwapSweepTx( + ctx, sqlc.UpdateAssetSwapSweepTxParams{ + SwapHash: swapHash[:], + SweepTxid: sweepTxid[:], + SweepConfirmationHeight: confHeight, + SweepPkscript: sweepPkscript, + }) + }, + ) +} + +// InsertAssetSwapUpdate inserts a new swap update in the database. +func (p *PostgresStore) InsertAssetSwapUpdate(ctx context.Context, + swapHash lntypes.Hash, state fsm.StateType) error { + + return p.queries.ExecTx( + ctx, &loopdb.SqliteTxOptions{}, func(q *sqlc.Queries) error { + return q.InsertAssetSwapUpdate( + ctx, sqlc.InsertAssetSwapUpdateParams{ + SwapHash: swapHash[:], + UpdateState: string(state), + UpdateTimestamp: p.clock.Now(), + }) + }, + ) +} + +// GetAllAssetOuts returns all the asset outs from the database. +func (p *PostgresStore) GetAllAssetOuts(ctx context.Context) ([]*SwapOut, error) { + dbAssetOuts, err := p.queries.GetAllAssetOutSwaps(ctx) + if err != nil { + return nil, err + } + + assetOuts := make([]*SwapOut, 0, len(dbAssetOuts)) + for _, dbAssetOut := range dbAssetOuts { + assetOut, err := newSwapOutFromDB( + dbAssetOut.AssetSwap, dbAssetOut.AssetOutSwap, + dbAssetOut.UpdateState, + ) + if err != nil { + return nil, err + } + assetOuts = append(assetOuts, assetOut) + } + return assetOuts, nil +} + +// GetActiveAssetOuts returns all the active asset outs from the database. +func (p *PostgresStore) GetActiveAssetOuts(ctx context.Context) ([]*SwapOut, + error) { + + dbAssetOuts, err := p.queries.GetAllAssetOutSwaps(ctx) + if err != nil { + return nil, err + } + + assetOuts := make([]*SwapOut, 0) + for _, dbAssetOut := range dbAssetOuts { + if IsFinishedState(fsm.StateType(dbAssetOut.UpdateState)) { + continue + } + + assetOut, err := newSwapOutFromDB( + dbAssetOut.AssetSwap, dbAssetOut.AssetOutSwap, + dbAssetOut.UpdateState, + ) + if err != nil { + return nil, err + } + assetOuts = append(assetOuts, assetOut) + } + + return assetOuts, nil +} + +// newSwapOutFromDB creates a new SwapOut from the databse rows. +func newSwapOutFromDB(assetSwap sqlc.AssetSwap, + assetOutSwap sqlc.AssetOutSwap, state string) ( + *SwapOut, error) { + + swapHash, err := lntypes.MakeHash(assetSwap.SwapHash) + if err != nil { + return nil, err + } + + var swapPreimage lntypes.Preimage + if assetSwap.SwapPreimage != nil { + swapPreimage, err = lntypes.MakePreimage(assetSwap.SwapPreimage) + if err != nil { + return nil, err + } + } + + senderPubkey, err := btcec.ParsePubKey(assetSwap.SenderPubkey) + if err != nil { + return nil, err + } + + receiverPubkey, err := btcec.ParsePubKey(assetSwap.ReceiverPubkey) + if err != nil { + return nil, err + } + + var htlcOutpoint *wire.OutPoint + if assetSwap.HtlcTxid != nil { + htlcHash, err := chainhash.NewHash(assetSwap.HtlcTxid) + if err != nil { + return nil, err + } + htlcOutpoint = wire.NewOutPoint( + htlcHash, uint32(assetSwap.HtlcVout), + ) + } + + var sweepOutpoint *wire.OutPoint + if assetSwap.SweepTxid != nil { + sweepHash, err := chainhash.NewHash(assetSwap.SweepTxid) + if err != nil { + return nil, err + } + sweepOutpoint = wire.NewOutPoint( + sweepHash, 0, + ) + } + + return &SwapOut{ + SwapKit: &htlc.SwapKit{ + SwapHash: swapHash, + Amount: uint64(assetSwap.Amt), + SenderPubKey: senderPubkey, + ReceiverPubKey: receiverPubkey, + CsvExpiry: uint32(assetSwap.CsvExpiry), + AssetID: assetSwap.AssetID, + }, + SwapPreimage: swapPreimage, + State: fsm.StateType(state), + InitiationHeight: uint32(assetSwap.InitiationHeight), + ClientKeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily( + assetSwap.ServerKeyFamily, + ), + Index: uint32(assetSwap.ServerKeyIndex), + }, + HtlcOutPoint: htlcOutpoint, + HtlcConfirmationHeight: uint32(assetSwap.HtlcConfirmationHeight), + SweepOutpoint: sweepOutpoint, + SweepConfirmationHeight: uint32(assetSwap.SweepConfirmationHeight), + SweepPkscript: assetSwap.SweepPkscript, + RawHtlcProof: assetOutSwap.RawProofFile, + }, nil +} diff --git a/assets/store_test.go b/assets/store_test.go new file mode 100644 index 000000000..efa62e1fc --- /dev/null +++ b/assets/store_test.go @@ -0,0 +1,112 @@ +package assets + +import ( + "context" + "crypto/rand" + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/loop/loopdb" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/stretchr/testify/require" +) + +var ( + defaultClientPubkeyBytes, _ = hex.DecodeString("021c97a90a411ff2b10dc2a8e32de2f29d2fa49d41bfbb52bd416e460db0747d0d") + defaultClientPubkey, _ = btcec.ParsePubKey(defaultClientPubkeyBytes) + + defaultOutpoint = &wire.OutPoint{ + Hash: chainhash.Hash{0x01}, + Index: 1, + } +) + +// TestSqlStore tests the asset swap store. +func TestSqlStore(t *testing.T) { + ctxb := context.Background() + testDb := loopdb.NewTestDB(t) + defer testDb.Close() + + store := NewPostgresStore(testDb) + + swapPreimage := getRandomPreimage() + SwapHash := swapPreimage.Hash() + + // Create a new SwapOut. + swapOut := &SwapOut{ + SwapKit: &htlc.SwapKit{ + SwapHash: SwapHash, + Amount: 100, + SenderPubKey: defaultClientPubkey, + ReceiverPubKey: defaultClientPubkey, + CsvExpiry: 100, + AssetID: []byte("assetid"), + }, + SwapPreimage: swapPreimage, + State: fsm.StateType("init"), + InitiationHeight: 1, + ClientKeyLocator: keychain.KeyLocator{ + Family: 1, + Index: 1, + }, + } + + // Save the swap out in the db. + err := store.CreateAssetSwapOut(ctxb, swapOut) + require.NoError(t, err) + + // Insert a new swap out update. + err = store.InsertAssetSwapUpdate( + ctxb, SwapHash, fsm.StateType("state2"), + ) + require.NoError(t, err) + + // Try to fetch all swap outs. + swapOuts, err := store.GetAllAssetOuts(ctxb) + require.NoError(t, err) + require.Len(t, swapOuts, 1) + + // Update the htlc outpoint. + err = store.UpdateAssetSwapHtlcOutpoint( + ctxb, SwapHash, defaultOutpoint, 100, + ) + require.NoError(t, err) + + // Update the offchain payment amount. + err = store.UpdateAssetSwapOutProof( + ctxb, SwapHash, []byte("proof"), + ) + require.NoError(t, err) + + // Try to fetch all active swap outs. + activeSwapOuts, err := store.GetActiveAssetOuts(ctxb) + require.NoError(t, err) + require.Len(t, activeSwapOuts, 1) + + // Update the swap out state to a finished state. + err = store.InsertAssetSwapUpdate( + ctxb, SwapHash, fsm.StateType(FinishedStates()[0]), + ) + require.NoError(t, err) + + // Try to fetch all active swap outs. + activeSwapOuts, err = store.GetActiveAssetOuts(ctxb) + require.NoError(t, err) + require.Len(t, activeSwapOuts, 0) +} + +// getRandomPreimage generates a random reservation ID. +func getRandomPreimage() lntypes.Preimage { + var id lntypes.Preimage + _, err := rand.Read(id[:]) + if err != nil { + panic(err) + } + return id +} diff --git a/assets/swap_out.go b/assets/swap_out.go new file mode 100644 index 000000000..540199719 --- /dev/null +++ b/assets/swap_out.go @@ -0,0 +1,106 @@ +package assets + +import ( + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/loop/fsm" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntypes" +) + +// SwapOut is a struct that represents a swap out. It contains all the +// information needed to perform a swap out. +type SwapOut struct { + // We embed swapkit for all script related helpers. + *htlc.SwapKit + + // SwapPreimage is the preimage of the swap, that enables spending + // the success path, it's hash is the main identifier of the swap. + SwapPreimage lntypes.Preimage + + // State is the current state of the swap. + State fsm.StateType + + // InitiationHeight is the height at which the swap was initiated. + InitiationHeight uint32 + + // ClientKeyLocator is the key locator of the clients key. + ClientKeyLocator keychain.KeyLocator + + // HtlcOutPoint is the outpoint of the htlc that was created to + // perform the swap. + HtlcOutPoint *wire.OutPoint + + // HtlcConfirmationHeight is the height at which the htlc was + // confirmed. + HtlcConfirmationHeight uint32 + + // SweepOutpoint is the outpoint of the htlc that was swept. + SweepOutpoint *wire.OutPoint + + // SweepConfirmationHeight is the height at which the sweep was + // confirmed. + SweepConfirmationHeight uint32 + + // SweepPkscript is the pkscript of the sweep transaction. + SweepPkscript []byte + + // RawHtlcProof is the raw htlc proof that we need to send to the + // receiver. We only keep this in the OutFSM struct as we don't want + // to save it in the store. + RawHtlcProof []byte +} + +// NewSwapOut creates a new swap out. +func NewSwapOut(swapHash lntypes.Hash, amt uint64, + assetId []byte, clientKeyDesc *keychain.KeyDescriptor, + senderPubkey *btcec.PublicKey, csvExpiry, initiationHeight uint32, +) *SwapOut { + + return &SwapOut{ + SwapKit: &htlc.SwapKit{ + SwapHash: swapHash, + Amount: amt, + SenderPubKey: senderPubkey, + ReceiverPubKey: clientKeyDesc.PubKey, + CsvExpiry: csvExpiry, + AssetID: assetId, + }, + State: Init, + InitiationHeight: initiationHeight, + ClientKeyLocator: clientKeyDesc.KeyLocator, + } +} + +// genTaprootAssetRootFromProof generates the taproot asset root from the proof +// of the swap. +func (s *SwapOut) genTaprootAssetRootFromProof(proof *proof.Proof) ([]byte, + error) { + + assetCpy := proof.Asset.Copy() + assetCpy.PrevWitnesses[0].SplitCommitment = nil + sendCommitment, err := commitment.NewAssetCommitment( + assetCpy, + ) + if err != nil { + return nil, err + } + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.NewTapCommitment( + &version, sendCommitment, + ) + if err != nil { + return nil, err + } + taprootAssetRoot := txscript.AssembleTaprootScriptTree( + assetCommitment.TapLeaf(), + ).RootNode.TapHash() + + return taprootAssetRoot[:], nil +} diff --git a/assets/tapkit.go b/assets/tapkit.go new file mode 100644 index 000000000..8cb1b6944 --- /dev/null +++ b/assets/tapkit.go @@ -0,0 +1,130 @@ +package assets + +import ( + "context" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/loop/assets/htlc" + "github.com/lightninglabs/taproot-assets/address" + "github.com/lightninglabs/taproot-assets/asset" + "github.com/lightninglabs/taproot-assets/commitment" + "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/tappsbt" + "github.com/lightninglabs/taproot-assets/tapsend" +) + +// GenTaprootAssetRootFromProof generates the taproot asset root from the proof +// of the swap. +func GenTaprootAssetRootFromProof(proof *proof.Proof) ([]byte, error) { + assetCopy := proof.Asset.CopySpendTemplate() + + version := commitment.TapCommitmentV2 + assetCommitment, err := commitment.FromAssets(&version, assetCopy) + if err != nil { + return nil, err + } + + assetCommitment, err = commitment.TrimSplitWitnesses( + &version, assetCommitment, + ) + if err != nil { + return nil, err + } + + taprootAssetRoot := assetCommitment.TapscriptRoot(nil) + + return taprootAssetRoot[:], nil +} + +// CreateOpTrueSweepVpkt creates a VPacket that sweeps the outputs associated +// with the passed in proofs, given that their TAP script is a simple OP_TRUE. +func CreateOpTrueSweepVpkt(ctx context.Context, proofs []*proof.Proof, + addr *address.Tap, chainParams *address.ChainParams) ( + *tappsbt.VPacket, error) { + + sweepVpkt, err := tappsbt.FromProofs(proofs, chainParams, tappsbt.V1) + if err != nil { + return nil, err + } + + total := uint64(0) + for i, proof := range proofs { + inputKey := proof.InclusionProof.InternalKey + + sweepVpkt.Inputs[i].Anchor.Bip32Derivation = + []*psbt.Bip32Derivation{ + { + PubKey: inputKey.SerializeCompressed(), + }, + } + sweepVpkt.Inputs[i].Anchor.TrBip32Derivation = + []*psbt.TaprootBip32Derivation{ + { + XOnlyPubKey: schnorr.SerializePubKey( + inputKey, + ), + }, + } + + total += proof.Asset.Amount + } + + // Sanity check that the amount that we're attempting to sweep matches + // the address amount. + if total != addr.Amount { + return nil, fmt.Errorf("total amount of proofs does not " + + "match the amount of the address") + } + + sweepVpkt.Outputs = append(sweepVpkt.Outputs, &tappsbt.VOutput{ + AssetVersion: addr.AssetVersion, + Amount: addr.Amount, + Interactive: true, + AnchorOutputIndex: 0, + ScriptKey: asset.NewScriptKey( + &addr.ScriptKey, + ), + AnchorOutputInternalKey: &addr.InternalKey, + AnchorOutputTapscriptSibling: addr.TapscriptSibling, + ProofDeliveryAddress: &addr.ProofCourierAddr, + }) + + err = tapsend.PrepareOutputAssets(ctx, sweepVpkt) + if err != nil { + return nil, err + } + + _, _, _, controlBlock, err := htlc.CreateOpTrueLeaf() + if err != nil { + return nil, err + } + + controlBlockBytes, err := controlBlock.ToBytes() + if err != nil { + return nil, err + } + + opTrueScript, err := htlc.GetOpTrueScript() + if err != nil { + return nil, err + } + + witness := wire.TxWitness{ + opTrueScript, + controlBlockBytes, + } + + firstPrevWitness := &sweepVpkt.Outputs[0].Asset.PrevWitnesses[0] + + if sweepVpkt.Outputs[0].Asset.HasSplitCommitmentWitness() { + rootAsset := firstPrevWitness.SplitCommitment.RootAsset + firstPrevWitness = &rootAsset.PrevWitnesses[0] + } + + firstPrevWitness.TxWitness = witness + + return sweepVpkt, nil +} diff --git a/cmd/loop/assets.go b/cmd/loop/assets.go new file mode 100644 index 000000000..e2bb2e96c --- /dev/null +++ b/cmd/loop/assets.go @@ -0,0 +1,229 @@ +package main + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/loop/looprpc" + "github.com/urfave/cli" +) + +var assetsCommands = cli.Command{ + + Name: "assets", + ShortName: "a", + Usage: "manage asset swaps", + Description: ` + `, + Subcommands: []cli.Command{ + assetsOutCommand, + listOutCommand, + listAvailableAssetsComand, + }, +} +var ( + assetsOutCommand = cli.Command{ + Name: "out", + ShortName: "o", + Usage: "swap asset out", + ArgsUsage: "", + Description: ` + List all reservations. + `, + Flags: []cli.Flag{ + cli.Uint64Flag{ + Name: "amt", + Usage: "the amount in satoshis to loop out.", + }, + cli.StringFlag{ + Name: "asset_id", + Usage: "asset_id", + }, + }, + Action: assetSwapOut, + } + listAvailableAssetsComand = cli.Command{ + Name: "available", + ShortName: "a", + Usage: "list available assets", + ArgsUsage: "", + Description: ` + List available assets from the loop server + `, + + Action: listAvailable, + } + listOutCommand = cli.Command{ + Name: "list", + ShortName: "l", + Usage: "list asset swaps", + ArgsUsage: "", + Description: ` + List all reservations. + `, + Action: listOut, + } +) + +func assetSwapOut(ctx *cli.Context) error { + // First set up the swap client itself. + client, cleanup, err := getAssetsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + args := ctx.Args() + + var amtStr string + switch { + case ctx.IsSet("amt"): + amtStr = ctx.String("amt") + case ctx.NArg() > 0: + amtStr = args[0] + args = args.Tail() //nolint: wastedassign + default: + // Show command help if no arguments and flags were provided. + return cli.ShowCommandHelp(ctx, "out") + } + + amt, err := parseAmt(amtStr) + if err != nil { + return err + } + if amt <= 0 { + return fmt.Errorf("amount must be greater than zero") + } + + assetId, err := hex.DecodeString(ctx.String("asset_id")) + if err != nil { + return err + } + + if len(assetId) != 32 { + return fmt.Errorf("invalid asset id") + } + + // First we'll list the available assets. + assets, err := client.ClientListAvailableAssets( + context.Background(), + &looprpc.ClientListAvailableAssetsRequest{}, + ) + if err != nil { + return err + } + + // We now extract the asset name from the list of available assets. + var assetName string + for _, asset := range assets.AvailableAssets { + if bytes.Equal(asset.AssetId, assetId) { + assetName = asset.Name + break + } + } + if assetName == "" { + return fmt.Errorf("asset not found") + } + + // First we'll quote the swap out to get the current fee and rate. + quote, err := client.ClientGetAssetSwapOutQuote( + context.Background(), + &looprpc.ClientGetAssetSwapOutQuoteRequest{ + Amt: uint64(amt), + Asset: assetId, + }, + ) + if err != nil { + return err + } + + totalSats := (amt * btcutil.Amount(quote.SatsPerUnit)).MulF64(float64(1) + quote.SwapFee) + + fmt.Printf(satAmtFmt, "Fixed prepay cost:", quote.PrepayAmt) + fmt.Printf(bpsFmt, "Swap fee:", int64(quote.SwapFee*10000)) + fmt.Printf(satAmtFmt, "Sats per unit:", quote.SatsPerUnit) + fmt.Printf(satAmtFmt, "Swap Offchain payment:", totalSats) + fmt.Printf(satAmtFmt, "Total Send off-chain:", totalSats+btcutil.Amount(quote.PrepayAmt)) + fmt.Printf(assetFmt, "Receive assets on-chain:", int64(amt), assetName) + + fmt.Println("CONTINUE SWAP? (y/n): ") + + var answer string + fmt.Scanln(&answer) + if answer != "y" { + return errors.New("swap canceled") + } + + res, err := client.SwapOut( + context.Background(), + &looprpc.SwapOutRequest{ + Amt: uint64(amt), + Asset: assetId, + }, + ) + if err != nil { + return err + } + + printRespJSON(res) + return nil +} + +func listAvailable(ctx *cli.Context) error { + // First set up the swap client itself. + client, cleanup, err := getAssetsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + res, err := client.ClientListAvailableAssets( + context.Background(), + &looprpc.ClientListAvailableAssetsRequest{}, + ) + if err != nil { + return err + } + + printRespJSON(res) + return nil +} +func listOut(ctx *cli.Context) error { + // First set up the swap client itself. + client, cleanup, err := getAssetsClient(ctx) + if err != nil { + return err + } + defer cleanup() + + res, err := client.ListAssetSwaps( + context.Background(), + &looprpc.ListAssetSwapsRequest{}, + ) + if err != nil { + return err + } + + printRespJSON(res) + return nil +} + +func getAssetsClient(ctx *cli.Context) (looprpc.AssetsClientClient, func(), error) { + rpcServer := ctx.GlobalString("rpcserver") + tlsCertPath, macaroonPath, err := extractPathArgs(ctx) + if err != nil { + return nil, nil, err + } + conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + if err != nil { + return nil, nil, err + } + cleanup := func() { conn.Close() } + + loopClient := looprpc.NewAssetsClientClient(conn) + return loopClient, cleanup, nil +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 4544dfac4..247a09bbb 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -87,7 +87,7 @@ var ( listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, getInfoCommand, abandonSwapCommand, reservationsCommands, - instantOutCommand, listInstantOutsCommand, + instantOutCommand, listInstantOutsCommand, assetsCommands, } ) @@ -114,6 +114,20 @@ const ( // Exchange rate: 0.0002 USD/SAT rateFmt = "%-36s %12.4f %s/SAT\n" + // bpsFmt formats a basis point value into a one line string, intended to + // prettify the terminal output. For Instance, + // fmt.Printf(f, "Service fee:", fee) + // prints out as, + // Service fee: 20 bps + bpsFmt = "%-36s %12d bps\n" + + // assetFmt formats an asset into a one line string, intended to + // prettify the terminal output. For Instance, + // fmt.Printf(f, "Receive asset onchain:", assetName, assetAmt) + // prints out as, + // Receive asset onchain: 0.0001 USD + assetFmt = "%-36s %12d %s\n" + // blkFmt formats the number of blocks into a one line string, intended // to prettify the terminal output. For Instance, // fmt.Printf(f, "Conf target", target) diff --git a/loopd/config.go b/loopd/config.go index 3a0a78e3e..4355d96b4 100644 --- a/loopd/config.go +++ b/loopd/config.go @@ -201,6 +201,8 @@ type Config struct { Tapd *assets.TapdConfig `group:"tapd" namespace:"tapd"` View viewParameters `command:"view" alias:"v" description:"View all swaps in the database. This command can only be executed when loopd is not running."` + + TapdConfig *assets.TapdConfig `group:"tapd" namespace:"tapd"` } const ( diff --git a/loopd/daemon.go b/loopd/daemon.go index 339da0c60..8638cf2dd 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -248,6 +248,8 @@ func (d *Daemon) startWebServers() error { ) loop_looprpc.RegisterSwapClientServer(d.grpcServer, d) + loop_looprpc.RegisterAssetsClientServer(d.grpcServer, d.assetsServer) + // Register our debug server if it is compiled in. d.registerDebugServer() @@ -494,6 +496,11 @@ func (d *Daemon) initialize(withMacaroonService bool) error { swapClient.Conn, ) + // Create a assets server client. + assetsClient := loop_swaprpc.NewAssetsSwapServerClient( + swapClient.Conn, + ) + // Both the client RPC server and the swap server client should stop // on main context cancel. So we create it early and pass it down. d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background()) @@ -643,6 +650,8 @@ func (d *Daemon) initialize(withMacaroonService bool) error { var ( reservationManager *reservation.Manager instantOutManager *instantout.Manager + assetManager *assets.AssetsSwapManager + assetClientServer *assets.AssetsClientServer ) // Create the reservation and instantout managers. @@ -683,6 +692,27 @@ func (d *Daemon) initialize(withMacaroonService bool) error { instantOutManager = instantout.NewInstantOutManager( instantOutConfig, int32(blockHeight), ) + + tapdClient, err := assets.NewTapdClient( + d.cfg.TapdConfig, + ) + if err != nil { + return err + } + assetsStore := assets.NewPostgresStore(baseDb) + assetsConfig := &assets.Config{ + ServerClient: assetsClient, + Store: assetsStore, + AssetClient: tapdClient, + LndClient: d.lnd.Client, + Router: d.lnd.Router, + ChainNotifier: d.lnd.ChainNotifier, + Signer: d.lnd.Signer, + Wallet: d.lnd.WalletKit, + ExchangeRateProvider: assets.NewFixedExchangeRateProvider(), + } + assetManager = assets.NewAssetSwapServer(assetsConfig) + assetClientServer = assets.NewAssetsServer(assetManager) } // Now finally fully initialize the swap client RPC server instance. @@ -703,6 +733,8 @@ func (d *Daemon) initialize(withMacaroonService bool) error { withdrawalManager: withdrawalManager, staticLoopInManager: staticLoopInManager, assetClient: d.assetClient, + assetManager: assetManager, + assetsServer: assetClientServer, } // Retrieve all currently existing swaps from the database. @@ -897,6 +929,20 @@ func (d *Daemon) initialize(withMacaroonService bool) error { staticLoopInManager.WaitInitComplete() } + // Start the asset manager. + if d.assetManager != nil { + d.wg.Add(1) + go func() { + defer d.wg.Done() + infof("Starting asset manager") + defer infof("Asset manager stopped") + err := d.assetManager.Run(d.mainCtx, int32(getInfo.BlockHeight)) + if err != nil && !errors.Is(err, context.Canceled) { + d.internalErrChan <- err + } + }() + } + // Last, start our internal error handler. This will return exactly one // error or nil on the main error channel to inform the caller that // something went wrong or that shutdown is complete. We don't add to @@ -942,6 +988,9 @@ func (d *Daemon) Stop() { // stop does the actual shutdown and blocks until all goroutines have exit. func (d *Daemon) stop() { + // Sleep a second in order to fix a blocking issue when having a + // startup error. + <-time.After(time.Second) // First of all, we can cancel the main context that all event handlers // are using. This should stop all swap activity and all event handlers // should exit. @@ -959,6 +1008,7 @@ func (d *Daemon) stop() { if d.restServer != nil { // Don't return the error here, we first want to give everything // else a chance to shut down cleanly. + err := d.restServer.Close() if err != nil { errorf("Error stopping REST server: %v", err) diff --git a/loopd/log.go b/loopd/log.go index 0b29d4b32..1a2773720 100644 --- a/loopd/log.go +++ b/loopd/log.go @@ -7,6 +7,7 @@ import ( "github.com/lightninglabs/aperture/l402" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" + "github.com/lightninglabs/loop/assets" "github.com/lightninglabs/loop/fsm" "github.com/lightninglabs/loop/instantout" "github.com/lightninglabs/loop/instantout/reservation" @@ -16,6 +17,7 @@ import ( "github.com/lightninglabs/loop/staticaddr" "github.com/lightninglabs/loop/sweep" "github.com/lightninglabs/loop/sweepbatcher" + "github.com/lightninglabs/loop/utils" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/signal" @@ -92,6 +94,13 @@ func SetupLoggers(root *build.SubLoggerManager, intercept signal.Interceptor) { lnd.AddSubLogger( root, sweep.Subsystem, intercept, sweep.UseLogger, ) + + lnd.AddSubLogger( + root, assets.Subsystem, intercept, assets.UseLogger, + ) + lnd.AddSubLogger( + root, utils.Subsystem, intercept, utils.UseLogger, + ) } // genSubLogger creates a logger for a subsystem. We provide an instance of diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 8015ef11b..4751a29a9 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -98,6 +98,8 @@ type swapClientServer struct { withdrawalManager *withdraw.Manager staticLoopInManager *loopin.Manager assetClient *assets.TapdClient + assetManager *assets.AssetsSwapManager + assetsServer *assets.AssetsClientServer swaps map[lntypes.Hash]loop.SwapInfo subscribers map[int]chan<- interface{} statusChan chan loop.SwapInfo diff --git a/loopdb/sqlc/asset_swaps.sql.go b/loopdb/sqlc/asset_swaps.sql.go new file mode 100644 index 000000000..29ce0b4c9 --- /dev/null +++ b/loopdb/sqlc/asset_swaps.sql.go @@ -0,0 +1,314 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 +// source: asset_swaps.sql + +package sqlc + +import ( + "context" + "time" +) + +const createAssetOutSwap = `-- name: CreateAssetOutSwap :exec +INSERT INTO asset_out_swaps ( + swap_hash +) VALUES ( + $1 +) +` + +func (q *Queries) CreateAssetOutSwap(ctx context.Context, swapHash []byte) error { + _, err := q.db.ExecContext(ctx, createAssetOutSwap, swapHash) + return err +} + +const createAssetSwap = `-- name: CreateAssetSwap :exec + INSERT INTO asset_swaps( + swap_hash, + swap_preimage, + asset_id, + amt, + sender_pubkey, + receiver_pubkey, + csv_expiry, + initiation_height, + created_time, + server_key_family, + server_key_index + ) + VALUES + ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 + ) +` + +type CreateAssetSwapParams struct { + SwapHash []byte + SwapPreimage []byte + AssetID []byte + Amt int64 + SenderPubkey []byte + ReceiverPubkey []byte + CsvExpiry int32 + InitiationHeight int32 + CreatedTime time.Time + ServerKeyFamily int64 + ServerKeyIndex int64 +} + +func (q *Queries) CreateAssetSwap(ctx context.Context, arg CreateAssetSwapParams) error { + _, err := q.db.ExecContext(ctx, createAssetSwap, + arg.SwapHash, + arg.SwapPreimage, + arg.AssetID, + arg.Amt, + arg.SenderPubkey, + arg.ReceiverPubkey, + arg.CsvExpiry, + arg.InitiationHeight, + arg.CreatedTime, + arg.ServerKeyFamily, + arg.ServerKeyIndex, + ) + return err +} + +const getAllAssetOutSwaps = `-- name: GetAllAssetOutSwaps :many +SELECT DISTINCT + asw.id, asw.swap_hash, asw.swap_preimage, asw.asset_id, asw.amt, asw.sender_pubkey, asw.receiver_pubkey, asw.csv_expiry, asw.server_key_family, asw.server_key_index, asw.initiation_height, asw.created_time, asw.htlc_confirmation_height, asw.htlc_txid, asw.htlc_vout, asw.sweep_txid, asw.sweep_confirmation_height, asw.sweep_pkscript, + aos.swap_hash, aos.raw_proof_file, + asu.update_state +FROM + asset_swaps asw +INNER JOIN ( + SELECT + swap_hash, + update_state, + ROW_NUMBER() OVER(PARTITION BY swap_hash ORDER BY id DESC) as rn + FROM + asset_swaps_updates +) asu ON asw.swap_hash = asu.swap_hash AND asu.rn = 1 +INNER JOIN asset_out_swaps aos ON asw.swap_hash = aos.swap_hash +ORDER BY + asw.id +` + +type GetAllAssetOutSwapsRow struct { + AssetSwap AssetSwap + AssetOutSwap AssetOutSwap + UpdateState string +} + +func (q *Queries) GetAllAssetOutSwaps(ctx context.Context) ([]GetAllAssetOutSwapsRow, error) { + rows, err := q.db.QueryContext(ctx, getAllAssetOutSwaps) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllAssetOutSwapsRow + for rows.Next() { + var i GetAllAssetOutSwapsRow + if err := rows.Scan( + &i.AssetSwap.ID, + &i.AssetSwap.SwapHash, + &i.AssetSwap.SwapPreimage, + &i.AssetSwap.AssetID, + &i.AssetSwap.Amt, + &i.AssetSwap.SenderPubkey, + &i.AssetSwap.ReceiverPubkey, + &i.AssetSwap.CsvExpiry, + &i.AssetSwap.ServerKeyFamily, + &i.AssetSwap.ServerKeyIndex, + &i.AssetSwap.InitiationHeight, + &i.AssetSwap.CreatedTime, + &i.AssetSwap.HtlcConfirmationHeight, + &i.AssetSwap.HtlcTxid, + &i.AssetSwap.HtlcVout, + &i.AssetSwap.SweepTxid, + &i.AssetSwap.SweepConfirmationHeight, + &i.AssetSwap.SweepPkscript, + &i.AssetOutSwap.SwapHash, + &i.AssetOutSwap.RawProofFile, + &i.UpdateState, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getAssetOutSwap = `-- name: GetAssetOutSwap :one +SELECT DISTINCT + asw.id, asw.swap_hash, asw.swap_preimage, asw.asset_id, asw.amt, asw.sender_pubkey, asw.receiver_pubkey, asw.csv_expiry, asw.server_key_family, asw.server_key_index, asw.initiation_height, asw.created_time, asw.htlc_confirmation_height, asw.htlc_txid, asw.htlc_vout, asw.sweep_txid, asw.sweep_confirmation_height, asw.sweep_pkscript, + aos.swap_hash, aos.raw_proof_file, + asu.update_state +FROM + asset_swaps asw +INNER JOIN ( + SELECT + swap_hash, + update_state, + ROW_NUMBER() OVER(PARTITION BY swap_hash ORDER BY id DESC) as rn + FROM + asset_swaps_updates +) asu ON asw.swap_hash = asu.swap_hash AND asu.rn = 1 +INNER JOIN asset_out_swaps aos ON asw.swap_hash = aos.swap_hash +WHERE + asw.swap_hash = $1 +` + +type GetAssetOutSwapRow struct { + AssetSwap AssetSwap + AssetOutSwap AssetOutSwap + UpdateState string +} + +func (q *Queries) GetAssetOutSwap(ctx context.Context, swapHash []byte) (GetAssetOutSwapRow, error) { + row := q.db.QueryRowContext(ctx, getAssetOutSwap, swapHash) + var i GetAssetOutSwapRow + err := row.Scan( + &i.AssetSwap.ID, + &i.AssetSwap.SwapHash, + &i.AssetSwap.SwapPreimage, + &i.AssetSwap.AssetID, + &i.AssetSwap.Amt, + &i.AssetSwap.SenderPubkey, + &i.AssetSwap.ReceiverPubkey, + &i.AssetSwap.CsvExpiry, + &i.AssetSwap.ServerKeyFamily, + &i.AssetSwap.ServerKeyIndex, + &i.AssetSwap.InitiationHeight, + &i.AssetSwap.CreatedTime, + &i.AssetSwap.HtlcConfirmationHeight, + &i.AssetSwap.HtlcTxid, + &i.AssetSwap.HtlcVout, + &i.AssetSwap.SweepTxid, + &i.AssetSwap.SweepConfirmationHeight, + &i.AssetSwap.SweepPkscript, + &i.AssetOutSwap.SwapHash, + &i.AssetOutSwap.RawProofFile, + &i.UpdateState, + ) + return i, err +} + +const insertAssetSwapUpdate = `-- name: InsertAssetSwapUpdate :exec +INSERT INTO asset_swaps_updates ( + swap_hash, + update_state, + update_timestamp +) VALUES ( + $1, + $2, + $3 +) +` + +type InsertAssetSwapUpdateParams struct { + SwapHash []byte + UpdateState string + UpdateTimestamp time.Time +} + +func (q *Queries) InsertAssetSwapUpdate(ctx context.Context, arg InsertAssetSwapUpdateParams) error { + _, err := q.db.ExecContext(ctx, insertAssetSwapUpdate, arg.SwapHash, arg.UpdateState, arg.UpdateTimestamp) + return err +} + +const updateAssetSwapHtlcTx = `-- name: UpdateAssetSwapHtlcTx :exec +UPDATE asset_swaps +SET + htlc_confirmation_height = $2, + htlc_txid = $3, + htlc_vout = $4 +WHERE + asset_swaps.swap_hash = $1 +` + +type UpdateAssetSwapHtlcTxParams struct { + SwapHash []byte + HtlcConfirmationHeight int32 + HtlcTxid []byte + HtlcVout int32 +} + +func (q *Queries) UpdateAssetSwapHtlcTx(ctx context.Context, arg UpdateAssetSwapHtlcTxParams) error { + _, err := q.db.ExecContext(ctx, updateAssetSwapHtlcTx, + arg.SwapHash, + arg.HtlcConfirmationHeight, + arg.HtlcTxid, + arg.HtlcVout, + ) + return err +} + +const updateAssetSwapOutPreimage = `-- name: UpdateAssetSwapOutPreimage :exec +UPDATE asset_swaps +SET + swap_preimage = $2 +WHERE + asset_swaps.swap_hash = $1 +` + +type UpdateAssetSwapOutPreimageParams struct { + SwapHash []byte + SwapPreimage []byte +} + +func (q *Queries) UpdateAssetSwapOutPreimage(ctx context.Context, arg UpdateAssetSwapOutPreimageParams) error { + _, err := q.db.ExecContext(ctx, updateAssetSwapOutPreimage, arg.SwapHash, arg.SwapPreimage) + return err +} + +const updateAssetSwapOutProof = `-- name: UpdateAssetSwapOutProof :exec +UPDATE asset_out_swaps +SET + raw_proof_file = $2 +WHERE + asset_out_swaps.swap_hash = $1 +` + +type UpdateAssetSwapOutProofParams struct { + SwapHash []byte + RawProofFile []byte +} + +func (q *Queries) UpdateAssetSwapOutProof(ctx context.Context, arg UpdateAssetSwapOutProofParams) error { + _, err := q.db.ExecContext(ctx, updateAssetSwapOutProof, arg.SwapHash, arg.RawProofFile) + return err +} + +const updateAssetSwapSweepTx = `-- name: UpdateAssetSwapSweepTx :exec +UPDATE asset_swaps +SET + sweep_confirmation_height = $2, + sweep_txid = $3, + sweep_pkscript = $4 +WHERE + asset_swaps.swap_hash = $1 +` + +type UpdateAssetSwapSweepTxParams struct { + SwapHash []byte + SweepConfirmationHeight int32 + SweepTxid []byte + SweepPkscript []byte +} + +func (q *Queries) UpdateAssetSwapSweepTx(ctx context.Context, arg UpdateAssetSwapSweepTxParams) error { + _, err := q.db.ExecContext(ctx, updateAssetSwapSweepTx, + arg.SwapHash, + arg.SweepConfirmationHeight, + arg.SweepTxid, + arg.SweepPkscript, + ) + return err +} diff --git a/loopdb/sqlc/migrations/000014_asset_swaps.down.sql b/loopdb/sqlc/migrations/000014_asset_swaps.down.sql new file mode 100644 index 000000000..93b3502c2 --- /dev/null +++ b/loopdb/sqlc/migrations/000014_asset_swaps.down.sql @@ -0,0 +1,5 @@ +DROP INDEX IF EXISTS asset_out_swaps_swap_hash_idx; +DROP TABLE IF EXISTS asset_out_swaps; +DROP INDEX IF EXISTS asset_swaps_updates_swap_hash_idx; +DROP TABLE IF EXISTS asset_swaps; +DROP TABLE IF EXISTS asset_swaps; \ No newline at end of file diff --git a/loopdb/sqlc/migrations/000014_asset_swaps.up.sql b/loopdb/sqlc/migrations/000014_asset_swaps.up.sql new file mode 100644 index 000000000..3f48e9d16 --- /dev/null +++ b/loopdb/sqlc/migrations/000014_asset_swaps.up.sql @@ -0,0 +1,85 @@ +CREATE TABLE IF NOT EXISTS asset_swaps ( + --- id is the autoincrementing primary key. + id INTEGER PRIMARY KEY, + + -- swap_hash is the randomly generated hash of the swap, which is used + -- as the swap identifier for the clients. + swap_hash BLOB NOT NULL UNIQUE, + + -- swap_preimage is the preimage of the swap. + swap_preimage BLOB, + + -- asset_id is the identifier of the asset being swapped. + asset_id BLOB NOT NULL, + + -- amt is the requested amount to be swapped. + amt BIGINT NOT NULL, + + -- sender_pubkey is the pubkey of the sender. + sender_pubkey BLOB NOT NULL, + + -- receiver_pubkey is the pubkey of the receiver. + receiver_pubkey BLOB NOT NULL, + + -- csv_expiry is the expiry of the swap. + csv_expiry INTEGER NOT NULL, + + -- server_key_family is the family of key being identified. + server_key_family BIGINT NOT NULL, + + -- server_key_index is the precise index of the key being identified. + server_key_index BIGINT NOT NULL, + + -- initiation_height is the height at which the swap was initiated. + initiation_height INTEGER NOT NULL, + + -- created_time is the time at which the swap was created. + created_time TIMESTAMP NOT NULL, + + -- htlc_confirmation_height is the height at which the swap was confirmed. + htlc_confirmation_height INTEGER NOT NULL DEFAULT(0), + + -- htlc_txid is the txid of the confirmation transaction. + htlc_txid BLOB, + + -- htlc_vout is the vout of the confirmation transaction. + htlc_vout INTEGER NOT NULL DEFAULT (0), + + -- sweep_txid is the txid of the sweep transaction. + sweep_txid BLOB, + + -- sweep_confirmation_height is the height at which the swap was swept. + sweep_confirmation_height INTEGER NOT NULL DEFAULT(0), + + sweep_pkscript BLOB +); + + +CREATE TABLE IF NOT EXISTS asset_swaps_updates ( + -- id is auto incremented for each update. + id INTEGER PRIMARY KEY, + + -- swap_hash is the hash of the swap that this update is for. + swap_hash BLOB NOT NULL REFERENCES asset_swaps(swap_hash), + + -- update_state is the state of the swap at the time of the update. + update_state TEXT NOT NULL, + + -- update_timestamp is the time at which the update was created. + update_timestamp TIMESTAMP NOT NULL +); + + +CREATE INDEX IF NOT EXISTS asset_swaps_updates_swap_hash_idx ON asset_swaps_updates(swap_hash); + + +CREATE TABLE IF NOT EXISTS asset_out_swaps ( + -- swap_hash is the identifier of the swap. + swap_hash BLOB PRIMARY KEY REFERENCES asset_swaps(swap_hash), + + -- raw_proof_file is the file containing the raw proof. + raw_proof_file BLOB +); + +CREATE INDEX IF NOT EXISTS asset_out_swaps_swap_hash_idx ON asset_out_swaps(swap_hash); + diff --git a/loopdb/sqlc/models.go b/loopdb/sqlc/models.go index 0f2a166b7..44754d1b4 100644 --- a/loopdb/sqlc/models.go +++ b/loopdb/sqlc/models.go @@ -9,6 +9,39 @@ import ( "time" ) +type AssetOutSwap struct { + SwapHash []byte + RawProofFile []byte +} + +type AssetSwap struct { + ID int32 + SwapHash []byte + SwapPreimage []byte + AssetID []byte + Amt int64 + SenderPubkey []byte + ReceiverPubkey []byte + CsvExpiry int32 + ServerKeyFamily int64 + ServerKeyIndex int64 + InitiationHeight int32 + CreatedTime time.Time + HtlcConfirmationHeight int32 + HtlcTxid []byte + HtlcVout int32 + SweepTxid []byte + SweepConfirmationHeight int32 + SweepPkscript []byte +} + +type AssetSwapsUpdate struct { + ID int32 + SwapHash []byte + UpdateState string + UpdateTimestamp time.Time +} + type Deposit struct { ID int32 DepositID []byte diff --git a/loopdb/sqlc/querier.go b/loopdb/sqlc/querier.go index d5283b868..d55c2622c 100644 --- a/loopdb/sqlc/querier.go +++ b/loopdb/sqlc/querier.go @@ -13,11 +13,15 @@ type Querier interface { AllDeposits(ctx context.Context) ([]Deposit, error) AllStaticAddresses(ctx context.Context) ([]StaticAddress, error) ConfirmBatch(ctx context.Context, id int32) error + CreateAssetOutSwap(ctx context.Context, swapHash []byte) error + CreateAssetSwap(ctx context.Context, arg CreateAssetSwapParams) error CreateDeposit(ctx context.Context, arg CreateDepositParams) error CreateReservation(ctx context.Context, arg CreateReservationParams) error CreateStaticAddress(ctx context.Context, arg CreateStaticAddressParams) error DropBatch(ctx context.Context, id int32) error FetchLiquidityParams(ctx context.Context) ([]byte, error) + GetAllAssetOutSwaps(ctx context.Context) ([]GetAllAssetOutSwapsRow, error) + GetAssetOutSwap(ctx context.Context, swapHash []byte) (GetAssetOutSwapRow, error) GetBatchSweeps(ctx context.Context, batchID int32) ([]Sweep, error) GetBatchSweptAmount(ctx context.Context, batchID int32) (int64, error) GetDeposit(ctx context.Context, depositID []byte) (Deposit, error) @@ -42,6 +46,7 @@ type Querier interface { GetSwapUpdates(ctx context.Context, swapHash []byte) ([]SwapUpdate, error) GetSweepStatus(ctx context.Context, outpoint string) (bool, error) GetUnconfirmedBatches(ctx context.Context) ([]SweepBatch, error) + InsertAssetSwapUpdate(ctx context.Context, arg InsertAssetSwapUpdateParams) error InsertBatch(ctx context.Context, arg InsertBatchParams) (int32, error) InsertDepositUpdate(ctx context.Context, arg InsertDepositUpdateParams) error InsertHtlcKeys(ctx context.Context, arg InsertHtlcKeysParams) error @@ -58,6 +63,10 @@ type Querier interface { InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error IsStored(ctx context.Context, swapHash []byte) (bool, error) OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error + UpdateAssetSwapHtlcTx(ctx context.Context, arg UpdateAssetSwapHtlcTxParams) error + UpdateAssetSwapOutPreimage(ctx context.Context, arg UpdateAssetSwapOutPreimageParams) error + UpdateAssetSwapOutProof(ctx context.Context, arg UpdateAssetSwapOutProofParams) error + UpdateAssetSwapSweepTx(ctx context.Context, arg UpdateAssetSwapSweepTxParams) error UpdateBatch(ctx context.Context, arg UpdateBatchParams) error UpdateDeposit(ctx context.Context, arg UpdateDepositParams) error UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error diff --git a/loopdb/sqlc/queries/asset_swaps.sql b/loopdb/sqlc/queries/asset_swaps.sql new file mode 100644 index 000000000..98201c4b4 --- /dev/null +++ b/loopdb/sqlc/queries/asset_swaps.sql @@ -0,0 +1,108 @@ +-- name: CreateAssetSwap :exec + INSERT INTO asset_swaps( + swap_hash, + swap_preimage, + asset_id, + amt, + sender_pubkey, + receiver_pubkey, + csv_expiry, + initiation_height, + created_time, + server_key_family, + server_key_index + ) + VALUES + ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 + ); + +-- name: CreateAssetOutSwap :exec +INSERT INTO asset_out_swaps ( + swap_hash +) VALUES ( + $1 +); + +-- name: UpdateAssetSwapHtlcTx :exec +UPDATE asset_swaps +SET + htlc_confirmation_height = $2, + htlc_txid = $3, + htlc_vout = $4 +WHERE + asset_swaps.swap_hash = $1; + +-- name: UpdateAssetSwapOutProof :exec +UPDATE asset_out_swaps +SET + raw_proof_file = $2 +WHERE + asset_out_swaps.swap_hash = $1; + +-- name: UpdateAssetSwapOutPreimage :exec +UPDATE asset_swaps +SET + swap_preimage = $2 +WHERE + asset_swaps.swap_hash = $1; + +-- name: UpdateAssetSwapSweepTx :exec +UPDATE asset_swaps +SET + sweep_confirmation_height = $2, + sweep_txid = $3, + sweep_pkscript = $4 +WHERE + asset_swaps.swap_hash = $1; + +-- name: InsertAssetSwapUpdate :exec +INSERT INTO asset_swaps_updates ( + swap_hash, + update_state, + update_timestamp +) VALUES ( + $1, + $2, + $3 +); + + +-- name: GetAssetOutSwap :one +SELECT DISTINCT + sqlc.embed(asw), + sqlc.embed(aos), + asu.update_state +FROM + asset_swaps asw +INNER JOIN ( + SELECT + swap_hash, + update_state, + ROW_NUMBER() OVER(PARTITION BY swap_hash ORDER BY id DESC) as rn + FROM + asset_swaps_updates +) asu ON asw.swap_hash = asu.swap_hash AND asu.rn = 1 +INNER JOIN asset_out_swaps aos ON asw.swap_hash = aos.swap_hash +WHERE + asw.swap_hash = $1; + +-- name: GetAllAssetOutSwaps :many +SELECT DISTINCT + sqlc.embed(asw), + sqlc.embed(aos), + asu.update_state +FROM + asset_swaps asw +INNER JOIN ( + SELECT + swap_hash, + update_state, + ROW_NUMBER() OVER(PARTITION BY swap_hash ORDER BY id DESC) as rn + FROM + asset_swaps_updates +) asu ON asw.swap_hash = asu.swap_hash AND asu.rn = 1 +INNER JOIN asset_out_swaps aos ON asw.swap_hash = aos.swap_hash +ORDER BY + asw.id; + diff --git a/looprpc/clientassets.pb.go b/looprpc/clientassets.pb.go new file mode 100644 index 000000000..61bf089f1 --- /dev/null +++ b/looprpc/clientassets.pb.go @@ -0,0 +1,805 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.21.12 +// source: clientassets.proto + +package looprpc + +import ( + _ "github.com/lightninglabs/loop/swapserverrpc" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SwapOutRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amt uint64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"` + Asset []byte `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` +} + +func (x *SwapOutRequest) Reset() { + *x = SwapOutRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SwapOutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SwapOutRequest) ProtoMessage() {} + +func (x *SwapOutRequest) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SwapOutRequest.ProtoReflect.Descriptor instead. +func (*SwapOutRequest) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{0} +} + +func (x *SwapOutRequest) GetAmt() uint64 { + if x != nil { + return x.Amt + } + return 0 +} + +func (x *SwapOutRequest) GetAsset() []byte { + if x != nil { + return x.Asset + } + return nil +} + +type SwapOutResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SwapStatus *AssetSwapStatus `protobuf:"bytes,1,opt,name=swap_status,json=swapStatus,proto3" json:"swap_status,omitempty"` +} + +func (x *SwapOutResponse) Reset() { + *x = SwapOutResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SwapOutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SwapOutResponse) ProtoMessage() {} + +func (x *SwapOutResponse) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SwapOutResponse.ProtoReflect.Descriptor instead. +func (*SwapOutResponse) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{1} +} + +func (x *SwapOutResponse) GetSwapStatus() *AssetSwapStatus { + if x != nil { + return x.SwapStatus + } + return nil +} + +type ListAssetSwapsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListAssetSwapsRequest) Reset() { + *x = ListAssetSwapsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAssetSwapsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAssetSwapsRequest) ProtoMessage() {} + +func (x *ListAssetSwapsRequest) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAssetSwapsRequest.ProtoReflect.Descriptor instead. +func (*ListAssetSwapsRequest) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{2} +} + +type ListAssetSwapsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SwapStatus []*AssetSwapStatus `protobuf:"bytes,1,rep,name=swap_status,json=swapStatus,proto3" json:"swap_status,omitempty"` +} + +func (x *ListAssetSwapsResponse) Reset() { + *x = ListAssetSwapsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAssetSwapsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAssetSwapsResponse) ProtoMessage() {} + +func (x *ListAssetSwapsResponse) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAssetSwapsResponse.ProtoReflect.Descriptor instead. +func (*ListAssetSwapsResponse) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{3} +} + +func (x *ListAssetSwapsResponse) GetSwapStatus() []*AssetSwapStatus { + if x != nil { + return x.SwapStatus + } + return nil +} + +type AssetSwapStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SwapHash []byte `protobuf:"bytes,1,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` + SwapStatus string `protobuf:"bytes,2,opt,name=swap_status,json=swapStatus,proto3" json:"swap_status,omitempty"` +} + +func (x *AssetSwapStatus) Reset() { + *x = AssetSwapStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AssetSwapStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetSwapStatus) ProtoMessage() {} + +func (x *AssetSwapStatus) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetSwapStatus.ProtoReflect.Descriptor instead. +func (*AssetSwapStatus) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{4} +} + +func (x *AssetSwapStatus) GetSwapHash() []byte { + if x != nil { + return x.SwapHash + } + return nil +} + +func (x *AssetSwapStatus) GetSwapStatus() string { + if x != nil { + return x.SwapStatus + } + return "" +} + +type ClientListAvailableAssetsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ClientListAvailableAssetsRequest) Reset() { + *x = ClientListAvailableAssetsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientListAvailableAssetsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientListAvailableAssetsRequest) ProtoMessage() {} + +func (x *ClientListAvailableAssetsRequest) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientListAvailableAssetsRequest.ProtoReflect.Descriptor instead. +func (*ClientListAvailableAssetsRequest) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{5} +} + +type ClientListAvailableAssetsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AvailableAssets []*Asset `protobuf:"bytes,1,rep,name=available_assets,json=availableAssets,proto3" json:"available_assets,omitempty"` +} + +func (x *ClientListAvailableAssetsResponse) Reset() { + *x = ClientListAvailableAssetsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientListAvailableAssetsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientListAvailableAssetsResponse) ProtoMessage() {} + +func (x *ClientListAvailableAssetsResponse) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientListAvailableAssetsResponse.ProtoReflect.Descriptor instead. +func (*ClientListAvailableAssetsResponse) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{6} +} + +func (x *ClientListAvailableAssetsResponse) GetAvailableAssets() []*Asset { + if x != nil { + return x.AvailableAssets + } + return nil +} + +type Asset struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AssetId []byte `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + SatsPerUnit uint64 `protobuf:"varint,3,opt,name=sats_per_unit,json=satsPerUnit,proto3" json:"sats_per_unit,omitempty"` +} + +func (x *Asset) Reset() { + *x = Asset{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Asset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Asset) ProtoMessage() {} + +func (x *Asset) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Asset.ProtoReflect.Descriptor instead. +func (*Asset) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{7} +} + +func (x *Asset) GetAssetId() []byte { + if x != nil { + return x.AssetId + } + return nil +} + +func (x *Asset) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Asset) GetSatsPerUnit() uint64 { + if x != nil { + return x.SatsPerUnit + } + return 0 +} + +type ClientGetAssetSwapOutQuoteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amt uint64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"` + Asset []byte `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` +} + +func (x *ClientGetAssetSwapOutQuoteRequest) Reset() { + *x = ClientGetAssetSwapOutQuoteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientGetAssetSwapOutQuoteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientGetAssetSwapOutQuoteRequest) ProtoMessage() {} + +func (x *ClientGetAssetSwapOutQuoteRequest) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientGetAssetSwapOutQuoteRequest.ProtoReflect.Descriptor instead. +func (*ClientGetAssetSwapOutQuoteRequest) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{8} +} + +func (x *ClientGetAssetSwapOutQuoteRequest) GetAmt() uint64 { + if x != nil { + return x.Amt + } + return 0 +} + +func (x *ClientGetAssetSwapOutQuoteRequest) GetAsset() []byte { + if x != nil { + return x.Asset + } + return nil +} + +type ClientGetAssetSwapOutQuoteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SwapFee float64 `protobuf:"fixed64,1,opt,name=swap_fee,json=swapFee,proto3" json:"swap_fee,omitempty"` + PrepayAmt uint64 `protobuf:"varint,2,opt,name=prepay_amt,json=prepayAmt,proto3" json:"prepay_amt,omitempty"` + SatsPerUnit uint64 `protobuf:"varint,3,opt,name=sats_per_unit,json=satsPerUnit,proto3" json:"sats_per_unit,omitempty"` +} + +func (x *ClientGetAssetSwapOutQuoteResponse) Reset() { + *x = ClientGetAssetSwapOutQuoteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_clientassets_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientGetAssetSwapOutQuoteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientGetAssetSwapOutQuoteResponse) ProtoMessage() {} + +func (x *ClientGetAssetSwapOutQuoteResponse) ProtoReflect() protoreflect.Message { + mi := &file_clientassets_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientGetAssetSwapOutQuoteResponse.ProtoReflect.Descriptor instead. +func (*ClientGetAssetSwapOutQuoteResponse) Descriptor() ([]byte, []int) { + return file_clientassets_proto_rawDescGZIP(), []int{9} +} + +func (x *ClientGetAssetSwapOutQuoteResponse) GetSwapFee() float64 { + if x != nil { + return x.SwapFee + } + return 0 +} + +func (x *ClientGetAssetSwapOutQuoteResponse) GetPrepayAmt() uint64 { + if x != nil { + return x.PrepayAmt + } + return 0 +} + +func (x *ClientGetAssetSwapOutQuoteResponse) GetSatsPerUnit() uint64 { + if x != nil { + return x.SatsPerUnit + } + return 0 +} + +var File_clientassets_proto protoreflect.FileDescriptor + +var file_clientassets_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x1a, 0x1a, 0x73, + 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x38, 0x0a, 0x0e, 0x53, 0x77, 0x61, + 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, + 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x22, 0x4c, 0x0a, 0x0f, 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x22, 0x17, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, + 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x53, 0x0a, 0x16, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, + 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x4f, 0x0a, 0x0f, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x22, 0x0a, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x5e, 0x0a, 0x21, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x10, 0x61, 0x76, 0x61, + 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x52, 0x0f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x73, 0x22, 0x5a, 0x0a, 0x05, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, + 0x73, 0x61, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x6e, 0x69, 0x74, + 0x22, 0x4b, 0x0a, 0x21, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x03, 0x61, 0x6d, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, 0x82, 0x01, + 0x0a, 0x22, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x66, 0x65, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x73, 0x77, 0x61, 0x70, 0x46, 0x65, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x65, 0x70, 0x61, 0x79, 0x5f, 0x61, 0x6d, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x70, 0x72, 0x65, 0x70, 0x61, 0x79, 0x41, 0x6d, 0x74, 0x12, 0x22, + 0x0a, 0x0d, 0x73, 0x61, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x74, 0x73, 0x50, 0x65, 0x72, 0x55, 0x6e, + 0x69, 0x74, 0x32, 0x8a, 0x03, 0x0a, 0x0c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x07, 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x17, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x51, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, + 0x61, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x72, 0x0a, 0x19, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x73, 0x12, 0x29, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x1a, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x4f, 0x75, + 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x12, 0x2a, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, + 0x77, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x47, 0x65, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x53, 0x77, 0x61, 0x70, 0x4f, + 0x75, 0x74, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, + 0x2f, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_clientassets_proto_rawDescOnce sync.Once + file_clientassets_proto_rawDescData = file_clientassets_proto_rawDesc +) + +func file_clientassets_proto_rawDescGZIP() []byte { + file_clientassets_proto_rawDescOnce.Do(func() { + file_clientassets_proto_rawDescData = protoimpl.X.CompressGZIP(file_clientassets_proto_rawDescData) + }) + return file_clientassets_proto_rawDescData +} + +var file_clientassets_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_clientassets_proto_goTypes = []any{ + (*SwapOutRequest)(nil), // 0: looprpc.SwapOutRequest + (*SwapOutResponse)(nil), // 1: looprpc.SwapOutResponse + (*ListAssetSwapsRequest)(nil), // 2: looprpc.ListAssetSwapsRequest + (*ListAssetSwapsResponse)(nil), // 3: looprpc.ListAssetSwapsResponse + (*AssetSwapStatus)(nil), // 4: looprpc.AssetSwapStatus + (*ClientListAvailableAssetsRequest)(nil), // 5: looprpc.ClientListAvailableAssetsRequest + (*ClientListAvailableAssetsResponse)(nil), // 6: looprpc.ClientListAvailableAssetsResponse + (*Asset)(nil), // 7: looprpc.Asset + (*ClientGetAssetSwapOutQuoteRequest)(nil), // 8: looprpc.ClientGetAssetSwapOutQuoteRequest + (*ClientGetAssetSwapOutQuoteResponse)(nil), // 9: looprpc.ClientGetAssetSwapOutQuoteResponse +} +var file_clientassets_proto_depIdxs = []int32{ + 4, // 0: looprpc.SwapOutResponse.swap_status:type_name -> looprpc.AssetSwapStatus + 4, // 1: looprpc.ListAssetSwapsResponse.swap_status:type_name -> looprpc.AssetSwapStatus + 7, // 2: looprpc.ClientListAvailableAssetsResponse.available_assets:type_name -> looprpc.Asset + 0, // 3: looprpc.AssetsClient.SwapOut:input_type -> looprpc.SwapOutRequest + 2, // 4: looprpc.AssetsClient.ListAssetSwaps:input_type -> looprpc.ListAssetSwapsRequest + 5, // 5: looprpc.AssetsClient.ClientListAvailableAssets:input_type -> looprpc.ClientListAvailableAssetsRequest + 8, // 6: looprpc.AssetsClient.ClientGetAssetSwapOutQuote:input_type -> looprpc.ClientGetAssetSwapOutQuoteRequest + 1, // 7: looprpc.AssetsClient.SwapOut:output_type -> looprpc.SwapOutResponse + 3, // 8: looprpc.AssetsClient.ListAssetSwaps:output_type -> looprpc.ListAssetSwapsResponse + 6, // 9: looprpc.AssetsClient.ClientListAvailableAssets:output_type -> looprpc.ClientListAvailableAssetsResponse + 9, // 10: looprpc.AssetsClient.ClientGetAssetSwapOutQuote:output_type -> looprpc.ClientGetAssetSwapOutQuoteResponse + 7, // [7:11] is the sub-list for method output_type + 3, // [3:7] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_clientassets_proto_init() } +func file_clientassets_proto_init() { + if File_clientassets_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_clientassets_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*SwapOutRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*SwapOutResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*ListAssetSwapsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ListAssetSwapsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*AssetSwapStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*ClientListAvailableAssetsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*ClientListAvailableAssetsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*Asset); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*ClientGetAssetSwapOutQuoteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_clientassets_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*ClientGetAssetSwapOutQuoteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_clientassets_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_clientassets_proto_goTypes, + DependencyIndexes: file_clientassets_proto_depIdxs, + MessageInfos: file_clientassets_proto_msgTypes, + }.Build() + File_clientassets_proto = out.File + file_clientassets_proto_rawDesc = nil + file_clientassets_proto_goTypes = nil + file_clientassets_proto_depIdxs = nil +} diff --git a/looprpc/clientassets.proto b/looprpc/clientassets.proto new file mode 100644 index 000000000..25ca74717 --- /dev/null +++ b/looprpc/clientassets.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +import "swapserverrpc/common.proto"; + +package looprpc; + +option go_package = "github.com/lightninglabs/loop/looprpc"; + +service AssetsClient { + rpc SwapOut (SwapOutRequest) returns (SwapOutResponse); + rpc ListAssetSwaps (ListAssetSwapsRequest) returns (ListAssetSwapsResponse); + rpc ClientListAvailableAssets (ClientListAvailableAssetsRequest) + returns (ClientListAvailableAssetsResponse); + rpc ClientGetAssetSwapOutQuote (ClientGetAssetSwapOutQuoteRequest) + returns (ClientGetAssetSwapOutQuoteResponse); +} + +message SwapOutRequest { + uint64 amt = 1; + bytes asset = 2; +} + +message SwapOutResponse { + AssetSwapStatus swap_status = 1; +} + +message ListAssetSwapsRequest { +} + +message ListAssetSwapsResponse { + repeated AssetSwapStatus swap_status = 1; +} + +message AssetSwapStatus { + bytes swap_hash = 1; + string swap_status = 2; +} + +message ClientListAvailableAssetsRequest { +} + +message ClientListAvailableAssetsResponse { + repeated Asset available_assets = 1; +} + +message Asset { + bytes asset_id = 1; + string name = 2; + uint64 sats_per_unit = 3; +} + +message ClientGetAssetSwapOutQuoteRequest { + uint64 amt = 1; + bytes asset = 2; +} + +message ClientGetAssetSwapOutQuoteResponse { + double swap_fee = 1; + uint64 prepay_amt = 2; + uint64 sats_per_unit = 3; +} \ No newline at end of file diff --git a/looprpc/clientassets_grpc.pb.go b/looprpc/clientassets_grpc.pb.go new file mode 100644 index 000000000..129381600 --- /dev/null +++ b/looprpc/clientassets_grpc.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package looprpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AssetsClientClient is the client API for AssetsClient service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AssetsClientClient interface { + SwapOut(ctx context.Context, in *SwapOutRequest, opts ...grpc.CallOption) (*SwapOutResponse, error) + ListAssetSwaps(ctx context.Context, in *ListAssetSwapsRequest, opts ...grpc.CallOption) (*ListAssetSwapsResponse, error) + ClientListAvailableAssets(ctx context.Context, in *ClientListAvailableAssetsRequest, opts ...grpc.CallOption) (*ClientListAvailableAssetsResponse, error) + ClientGetAssetSwapOutQuote(ctx context.Context, in *ClientGetAssetSwapOutQuoteRequest, opts ...grpc.CallOption) (*ClientGetAssetSwapOutQuoteResponse, error) +} + +type assetsClientClient struct { + cc grpc.ClientConnInterface +} + +func NewAssetsClientClient(cc grpc.ClientConnInterface) AssetsClientClient { + return &assetsClientClient{cc} +} + +func (c *assetsClientClient) SwapOut(ctx context.Context, in *SwapOutRequest, opts ...grpc.CallOption) (*SwapOutResponse, error) { + out := new(SwapOutResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsClient/SwapOut", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsClientClient) ListAssetSwaps(ctx context.Context, in *ListAssetSwapsRequest, opts ...grpc.CallOption) (*ListAssetSwapsResponse, error) { + out := new(ListAssetSwapsResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsClient/ListAssetSwaps", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsClientClient) ClientListAvailableAssets(ctx context.Context, in *ClientListAvailableAssetsRequest, opts ...grpc.CallOption) (*ClientListAvailableAssetsResponse, error) { + out := new(ClientListAvailableAssetsResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsClient/ClientListAvailableAssets", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsClientClient) ClientGetAssetSwapOutQuote(ctx context.Context, in *ClientGetAssetSwapOutQuoteRequest, opts ...grpc.CallOption) (*ClientGetAssetSwapOutQuoteResponse, error) { + out := new(ClientGetAssetSwapOutQuoteResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsClient/ClientGetAssetSwapOutQuote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AssetsClientServer is the server API for AssetsClient service. +// All implementations must embed UnimplementedAssetsClientServer +// for forward compatibility +type AssetsClientServer interface { + SwapOut(context.Context, *SwapOutRequest) (*SwapOutResponse, error) + ListAssetSwaps(context.Context, *ListAssetSwapsRequest) (*ListAssetSwapsResponse, error) + ClientListAvailableAssets(context.Context, *ClientListAvailableAssetsRequest) (*ClientListAvailableAssetsResponse, error) + ClientGetAssetSwapOutQuote(context.Context, *ClientGetAssetSwapOutQuoteRequest) (*ClientGetAssetSwapOutQuoteResponse, error) + mustEmbedUnimplementedAssetsClientServer() +} + +// UnimplementedAssetsClientServer must be embedded to have forward compatible implementations. +type UnimplementedAssetsClientServer struct { +} + +func (UnimplementedAssetsClientServer) SwapOut(context.Context, *SwapOutRequest) (*SwapOutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SwapOut not implemented") +} +func (UnimplementedAssetsClientServer) ListAssetSwaps(context.Context, *ListAssetSwapsRequest) (*ListAssetSwapsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAssetSwaps not implemented") +} +func (UnimplementedAssetsClientServer) ClientListAvailableAssets(context.Context, *ClientListAvailableAssetsRequest) (*ClientListAvailableAssetsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientListAvailableAssets not implemented") +} +func (UnimplementedAssetsClientServer) ClientGetAssetSwapOutQuote(context.Context, *ClientGetAssetSwapOutQuoteRequest) (*ClientGetAssetSwapOutQuoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientGetAssetSwapOutQuote not implemented") +} +func (UnimplementedAssetsClientServer) mustEmbedUnimplementedAssetsClientServer() {} + +// UnsafeAssetsClientServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AssetsClientServer will +// result in compilation errors. +type UnsafeAssetsClientServer interface { + mustEmbedUnimplementedAssetsClientServer() +} + +func RegisterAssetsClientServer(s grpc.ServiceRegistrar, srv AssetsClientServer) { + s.RegisterService(&AssetsClient_ServiceDesc, srv) +} + +func _AssetsClient_SwapOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SwapOutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsClientServer).SwapOut(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsClient/SwapOut", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsClientServer).SwapOut(ctx, req.(*SwapOutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsClient_ListAssetSwaps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAssetSwapsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsClientServer).ListAssetSwaps(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsClient/ListAssetSwaps", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsClientServer).ListAssetSwaps(ctx, req.(*ListAssetSwapsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsClient_ClientListAvailableAssets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientListAvailableAssetsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsClientServer).ClientListAvailableAssets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsClient/ClientListAvailableAssets", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsClientServer).ClientListAvailableAssets(ctx, req.(*ClientListAvailableAssetsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsClient_ClientGetAssetSwapOutQuote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClientGetAssetSwapOutQuoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsClientServer).ClientGetAssetSwapOutQuote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsClient/ClientGetAssetSwapOutQuote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsClientServer).ClientGetAssetSwapOutQuote(ctx, req.(*ClientGetAssetSwapOutQuoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AssetsClient_ServiceDesc is the grpc.ServiceDesc for AssetsClient service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AssetsClient_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "looprpc.AssetsClient", + HandlerType: (*AssetsClientServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SwapOut", + Handler: _AssetsClient_SwapOut_Handler, + }, + { + MethodName: "ListAssetSwaps", + Handler: _AssetsClient_ListAssetSwaps_Handler, + }, + { + MethodName: "ClientListAvailableAssets", + Handler: _AssetsClient_ClientListAvailableAssets_Handler, + }, + { + MethodName: "ClientGetAssetSwapOutQuote", + Handler: _AssetsClient_ClientGetAssetSwapOutQuote_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "clientassets.proto", +} diff --git a/looprpc/perms.go b/looprpc/perms.go index 1082cbb73..10ba1e97a 100644 --- a/looprpc/perms.go +++ b/looprpc/perms.go @@ -169,4 +169,20 @@ var RequiredPermissions = map[string][]bakery.Op{ Entity: "swap", Action: "read", }}, + "/looprpc.AssetsClient/SwapOut": {{ + Entity: "swap", + Action: "execute", + }}, + "/looprpc.AssetsClient/ListAssetSwaps": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.AssetsClient/ClientListAvailableAssets": {{ + Entity: "swap", + Action: "read", + }}, + "/looprpc.AssetsClient/ClientGetAssetSwapOutQuote": {{ + Entity: "swap", + Action: "read", + }}, } diff --git a/swapserverrpc/assets.pb.go b/swapserverrpc/assets.pb.go new file mode 100644 index 000000000..53daa7a5b --- /dev/null +++ b/swapserverrpc/assets.pb.go @@ -0,0 +1,1114 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.21.12 +// source: assets.proto + +// We can't change this to swapserverrpc, it would be a breaking change because +// the package name is also contained in the HTTP URIs and old clients would +// call the wrong endpoints. Luckily with the go_package option we can have +// different golang and RPC package names to fix protobuf namespace conflicts. + +package swapserverrpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type QuoteAssetLoopOutRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // amount is the amount of the asset to loop out. + Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + // asset is the asset to loop out. + Asset []byte `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` +} + +func (x *QuoteAssetLoopOutRequest) Reset() { + *x = QuoteAssetLoopOutRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QuoteAssetLoopOutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QuoteAssetLoopOutRequest) ProtoMessage() {} + +func (x *QuoteAssetLoopOutRequest) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QuoteAssetLoopOutRequest.ProtoReflect.Descriptor instead. +func (*QuoteAssetLoopOutRequest) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{0} +} + +func (x *QuoteAssetLoopOutRequest) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *QuoteAssetLoopOutRequest) GetAsset() []byte { + if x != nil { + return x.Asset + } + return nil +} + +type QuoteAssetLoopOutResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // fixed_prepay_amt is the fixed prepay amount of the swap. + FixedPrepayAmt uint64 `protobuf:"varint,1,opt,name=fixed_prepay_amt,json=fixedPrepayAmt,proto3" json:"fixed_prepay_amt,omitempty"` + // swap_fee_rate is the fee rate that is added to the swap invoice. + SwapFeeRate float64 `protobuf:"fixed64,2,opt,name=swap_fee_rate,json=swapFeeRate,proto3" json:"swap_fee_rate,omitempty"` + // current_sats_per_asset_unit is the sats per asset unit of the swap. + CurrentSatsPerAssetUnit uint64 `protobuf:"varint,3,opt,name=current_sats_per_asset_unit,json=currentSatsPerAssetUnit,proto3" json:"current_sats_per_asset_unit,omitempty"` +} + +func (x *QuoteAssetLoopOutResponse) Reset() { + *x = QuoteAssetLoopOutResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QuoteAssetLoopOutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QuoteAssetLoopOutResponse) ProtoMessage() {} + +func (x *QuoteAssetLoopOutResponse) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QuoteAssetLoopOutResponse.ProtoReflect.Descriptor instead. +func (*QuoteAssetLoopOutResponse) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{1} +} + +func (x *QuoteAssetLoopOutResponse) GetFixedPrepayAmt() uint64 { + if x != nil { + return x.FixedPrepayAmt + } + return 0 +} + +func (x *QuoteAssetLoopOutResponse) GetSwapFeeRate() float64 { + if x != nil { + return x.SwapFeeRate + } + return 0 +} + +func (x *QuoteAssetLoopOutResponse) GetCurrentSatsPerAssetUnit() uint64 { + if x != nil { + return x.CurrentSatsPerAssetUnit + } + return 0 +} + +type ListAvailableAssetsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListAvailableAssetsRequest) Reset() { + *x = ListAvailableAssetsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAvailableAssetsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAvailableAssetsRequest) ProtoMessage() {} + +func (x *ListAvailableAssetsRequest) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAvailableAssetsRequest.ProtoReflect.Descriptor instead. +func (*ListAvailableAssetsRequest) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{2} +} + +type ListAvailableAssetsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // assets is the list of assets that the server supports. + Assets []*AssetInfo `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"` +} + +func (x *ListAvailableAssetsResponse) Reset() { + *x = ListAvailableAssetsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListAvailableAssetsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAvailableAssetsResponse) ProtoMessage() {} + +func (x *ListAvailableAssetsResponse) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAvailableAssetsResponse.ProtoReflect.Descriptor instead. +func (*ListAvailableAssetsResponse) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{3} +} + +func (x *ListAvailableAssetsResponse) GetAssets() []*AssetInfo { + if x != nil { + return x.Assets + } + return nil +} + +type AssetInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AssetId []byte `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` + CurrentSatsPerAssetUnit uint64 `protobuf:"varint,2,opt,name=current_sats_per_asset_unit,json=currentSatsPerAssetUnit,proto3" json:"current_sats_per_asset_unit,omitempty"` +} + +func (x *AssetInfo) Reset() { + *x = AssetInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AssetInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetInfo) ProtoMessage() {} + +func (x *AssetInfo) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetInfo.ProtoReflect.Descriptor instead. +func (*AssetInfo) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{4} +} + +func (x *AssetInfo) GetAssetId() []byte { + if x != nil { + return x.AssetId + } + return nil +} + +func (x *AssetInfo) GetCurrentSatsPerAssetUnit() uint64 { + if x != nil { + return x.CurrentSatsPerAssetUnit + } + return 0 +} + +type RequestAssetLoopOutRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // amount is the amount of the asset to loop out. + Amount uint64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + // receiver_key is the public key of the receiver. + ReceiverKey []byte `protobuf:"bytes,2,opt,name=receiver_key,json=receiverKey,proto3" json:"receiver_key,omitempty"` + // requested_asset is the asset to loop out. + RequestedAsset []byte `protobuf:"bytes,3,opt,name=requested_asset,json=requestedAsset,proto3" json:"requested_asset,omitempty"` +} + +func (x *RequestAssetLoopOutRequest) Reset() { + *x = RequestAssetLoopOutRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestAssetLoopOutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestAssetLoopOutRequest) ProtoMessage() {} + +func (x *RequestAssetLoopOutRequest) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestAssetLoopOutRequest.ProtoReflect.Descriptor instead. +func (*RequestAssetLoopOutRequest) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{5} +} + +func (x *RequestAssetLoopOutRequest) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *RequestAssetLoopOutRequest) GetReceiverKey() []byte { + if x != nil { + return x.ReceiverKey + } + return nil +} + +func (x *RequestAssetLoopOutRequest) GetRequestedAsset() []byte { + if x != nil { + return x.RequestedAsset + } + return nil +} + +type RequestAssetLoopOutResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // swap_hash is the main identifier of the swap. + SwapHash []byte `protobuf:"bytes,1,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` + // prepay_invoice is the invoice to pay to start the swap. On accepted + // the server will publish the htlc output. + PrepayInvoice string `protobuf:"bytes,2,opt,name=prepay_invoice,json=prepayInvoice,proto3" json:"prepay_invoice,omitempty"` + // expiry is the expiry of the htlc output. + Expiry int64 `protobuf:"varint,3,opt,name=expiry,proto3" json:"expiry,omitempty"` + // sender_pubkey is the public key of the sender. + SenderPubkey []byte `protobuf:"bytes,4,opt,name=sender_pubkey,json=senderPubkey,proto3" json:"sender_pubkey,omitempty"` +} + +func (x *RequestAssetLoopOutResponse) Reset() { + *x = RequestAssetLoopOutResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestAssetLoopOutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestAssetLoopOutResponse) ProtoMessage() {} + +func (x *RequestAssetLoopOutResponse) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestAssetLoopOutResponse.ProtoReflect.Descriptor instead. +func (*RequestAssetLoopOutResponse) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{6} +} + +func (x *RequestAssetLoopOutResponse) GetSwapHash() []byte { + if x != nil { + return x.SwapHash + } + return nil +} + +func (x *RequestAssetLoopOutResponse) GetPrepayInvoice() string { + if x != nil { + return x.PrepayInvoice + } + return "" +} + +func (x *RequestAssetLoopOutResponse) GetExpiry() int64 { + if x != nil { + return x.Expiry + } + return 0 +} + +func (x *RequestAssetLoopOutResponse) GetSenderPubkey() []byte { + if x != nil { + return x.SenderPubkey + } + return nil +} + +type PollAssetLoopOutProofRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // swap_hash is the main identifier of the swap. + SwapHash []byte `protobuf:"bytes,1,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` +} + +func (x *PollAssetLoopOutProofRequest) Reset() { + *x = PollAssetLoopOutProofRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PollAssetLoopOutProofRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PollAssetLoopOutProofRequest) ProtoMessage() {} + +func (x *PollAssetLoopOutProofRequest) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PollAssetLoopOutProofRequest.ProtoReflect.Descriptor instead. +func (*PollAssetLoopOutProofRequest) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{7} +} + +func (x *PollAssetLoopOutProofRequest) GetSwapHash() []byte { + if x != nil { + return x.SwapHash + } + return nil +} + +type PollAssetLoopOutProofResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // raw_proof_file is the raw proof file of the swap. + RawProofFile []byte `protobuf:"bytes,1,opt,name=raw_proof_file,json=rawProofFile,proto3" json:"raw_proof_file,omitempty"` +} + +func (x *PollAssetLoopOutProofResponse) Reset() { + *x = PollAssetLoopOutProofResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PollAssetLoopOutProofResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PollAssetLoopOutProofResponse) ProtoMessage() {} + +func (x *PollAssetLoopOutProofResponse) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PollAssetLoopOutProofResponse.ProtoReflect.Descriptor instead. +func (*PollAssetLoopOutProofResponse) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{8} +} + +func (x *PollAssetLoopOutProofResponse) GetRawProofFile() []byte { + if x != nil { + return x.RawProofFile + } + return nil +} + +type RequestAssetBuyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // swap_hash is the main identifier of the swap. + SwapHash []byte `protobuf:"bytes,1,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` +} + +func (x *RequestAssetBuyRequest) Reset() { + *x = RequestAssetBuyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestAssetBuyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestAssetBuyRequest) ProtoMessage() {} + +func (x *RequestAssetBuyRequest) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestAssetBuyRequest.ProtoReflect.Descriptor instead. +func (*RequestAssetBuyRequest) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{9} +} + +func (x *RequestAssetBuyRequest) GetSwapHash() []byte { + if x != nil { + return x.SwapHash + } + return nil +} + +type RequestAssetBuyResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // swap_invoice is the invoice to pay to receive the preimage to claim the + // asset. + SwapInvoice string `protobuf:"bytes,1,opt,name=swap_invoice,json=swapInvoice,proto3" json:"swap_invoice,omitempty"` +} + +func (x *RequestAssetBuyResponse) Reset() { + *x = RequestAssetBuyResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestAssetBuyResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestAssetBuyResponse) ProtoMessage() {} + +func (x *RequestAssetBuyResponse) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestAssetBuyResponse.ProtoReflect.Descriptor instead. +func (*RequestAssetBuyResponse) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{10} +} + +func (x *RequestAssetBuyResponse) GetSwapInvoice() string { + if x != nil { + return x.SwapInvoice + } + return "" +} + +type RequestMusig2SweepRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // swap_hash is the main identifier of the swap. + SwapHash []byte `protobuf:"bytes,1,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` + // digest is that the client wants the server to sign. + Digest []byte `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"` + // receiver_nonce is the nonce of the receiver. + ReceiverNonce []byte `protobuf:"bytes,3,opt,name=receiver_nonce,json=receiverNonce,proto3" json:"receiver_nonce,omitempty"` + // roothash is the roothash we tweak the musig2 pubkey with. + Roothash []byte `protobuf:"bytes,4,opt,name=roothash,proto3" json:"roothash,omitempty"` +} + +func (x *RequestMusig2SweepRequest) Reset() { + *x = RequestMusig2SweepRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestMusig2SweepRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestMusig2SweepRequest) ProtoMessage() {} + +func (x *RequestMusig2SweepRequest) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestMusig2SweepRequest.ProtoReflect.Descriptor instead. +func (*RequestMusig2SweepRequest) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{11} +} + +func (x *RequestMusig2SweepRequest) GetSwapHash() []byte { + if x != nil { + return x.SwapHash + } + return nil +} + +func (x *RequestMusig2SweepRequest) GetDigest() []byte { + if x != nil { + return x.Digest + } + return nil +} + +func (x *RequestMusig2SweepRequest) GetReceiverNonce() []byte { + if x != nil { + return x.ReceiverNonce + } + return nil +} + +func (x *RequestMusig2SweepRequest) GetRoothash() []byte { + if x != nil { + return x.Roothash + } + return nil +} + +type RequestMusig2SweepResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // sender_nonce is the nonce of the sender. + SenderNonce []byte `protobuf:"bytes,1,opt,name=sender_nonce,json=senderNonce,proto3" json:"sender_nonce,omitempty"` + // sender_sig is the signature of the sender. + SenderSig []byte `protobuf:"bytes,2,opt,name=sender_sig,json=senderSig,proto3" json:"sender_sig,omitempty"` +} + +func (x *RequestMusig2SweepResponse) Reset() { + *x = RequestMusig2SweepResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_assets_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestMusig2SweepResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestMusig2SweepResponse) ProtoMessage() {} + +func (x *RequestMusig2SweepResponse) ProtoReflect() protoreflect.Message { + mi := &file_assets_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestMusig2SweepResponse.ProtoReflect.Descriptor instead. +func (*RequestMusig2SweepResponse) Descriptor() ([]byte, []int) { + return file_assets_proto_rawDescGZIP(), []int{12} +} + +func (x *RequestMusig2SweepResponse) GetSenderNonce() []byte { + if x != nil { + return x.SenderNonce + } + return nil +} + +func (x *RequestMusig2SweepResponse) GetSenderSig() []byte { + if x != nil { + return x.SenderSig + } + return nil +} + +var File_assets_proto protoreflect.FileDescriptor + +var file_assets_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, + 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x22, 0x48, 0x0a, 0x18, 0x51, 0x75, 0x6f, 0x74, 0x65, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, + 0x73, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x22, 0xa7, 0x01, 0x0a, 0x19, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x28, 0x0a, 0x10, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x65, 0x70, 0x61, 0x79, 0x5f, + 0x61, 0x6d, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x66, 0x69, 0x78, 0x65, 0x64, + 0x50, 0x72, 0x65, 0x70, 0x61, 0x79, 0x41, 0x6d, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x73, 0x77, 0x61, + 0x70, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x0b, 0x73, 0x77, 0x61, 0x70, 0x46, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x3c, 0x0a, + 0x1b, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x5f, 0x70, 0x65, + 0x72, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x17, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x73, 0x50, + 0x65, 0x72, 0x41, 0x73, 0x73, 0x65, 0x74, 0x55, 0x6e, 0x69, 0x74, 0x22, 0x1c, 0x0a, 0x1a, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x1b, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x73, 0x73, 0x65, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, + 0x70, 0x63, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x73, 0x22, 0x64, 0x0a, 0x09, 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x73, 0x73, 0x65, 0x74, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1b, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x61, 0x74, 0x73, 0x5f, 0x70, 0x65, 0x72, + 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x6e, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x17, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x61, 0x74, 0x73, 0x50, 0x65, + 0x72, 0x41, 0x73, 0x73, 0x65, 0x74, 0x55, 0x6e, 0x69, 0x74, 0x22, 0x80, 0x01, 0x0a, 0x1a, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, + 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x73, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x22, 0x9e, 0x01, + 0x0a, 0x1b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, + 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, + 0x65, 0x70, 0x61, 0x79, 0x5f, 0x69, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x65, 0x70, 0x61, 0x79, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x3b, + 0x0a, 0x1c, 0x50, 0x6f, 0x6c, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, + 0x75, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, + 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x22, 0x45, 0x0a, 0x1d, 0x50, + 0x6f, 0x6c, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x0e, + 0x72, 0x61, 0x77, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72, 0x61, 0x77, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x46, 0x69, + 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x42, 0x75, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, 0x73, 0x68, 0x22, 0x3c, 0x0a, 0x17, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x69, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x77, 0x61, 0x70, + 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x77, 0x61, 0x70, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x77, 0x61, 0x70, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, + 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x68, 0x61, 0x73, 0x68, 0x22, 0x5e, 0x0a, + 0x1a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x53, 0x77, + 0x65, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x53, 0x69, 0x67, 0x32, 0xcf, 0x04, + 0x0a, 0x10, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x53, 0x77, 0x61, 0x70, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x12, 0x5a, 0x0a, 0x11, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, + 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, + 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x60, + 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x60, 0x0a, 0x13, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, + 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x12, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, + 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, + 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x66, 0x0a, 0x15, 0x50, 0x6f, 0x6c, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, + 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x25, 0x2e, 0x6c, 0x6f, + 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, + 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, + 0x6c, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4c, 0x6f, 0x6f, 0x70, 0x4f, 0x75, 0x74, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x12, 0x1f, 0x2e, + 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, + 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x42, 0x75, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x5d, 0x0a, 0x12, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x67, + 0x32, 0x53, 0x77, 0x65, 0x65, 0x70, 0x12, 0x22, 0x2e, 0x6c, 0x6f, 0x6f, 0x70, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x75, 0x73, 0x69, 0x67, 0x32, 0x53, 0x77, + 0x65, 0x65, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6c, 0x6f, 0x6f, + 0x70, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x75, 0x73, 0x69, + 0x67, 0x32, 0x53, 0x77, 0x65, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x6f, 0x6f, 0x70, + 0x2f, 0x73, 0x77, 0x61, 0x70, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x72, 0x70, 0x63, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_assets_proto_rawDescOnce sync.Once + file_assets_proto_rawDescData = file_assets_proto_rawDesc +) + +func file_assets_proto_rawDescGZIP() []byte { + file_assets_proto_rawDescOnce.Do(func() { + file_assets_proto_rawDescData = protoimpl.X.CompressGZIP(file_assets_proto_rawDescData) + }) + return file_assets_proto_rawDescData +} + +var file_assets_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_assets_proto_goTypes = []interface{}{ + (*QuoteAssetLoopOutRequest)(nil), // 0: looprpc.QuoteAssetLoopOutRequest + (*QuoteAssetLoopOutResponse)(nil), // 1: looprpc.QuoteAssetLoopOutResponse + (*ListAvailableAssetsRequest)(nil), // 2: looprpc.ListAvailableAssetsRequest + (*ListAvailableAssetsResponse)(nil), // 3: looprpc.ListAvailableAssetsResponse + (*AssetInfo)(nil), // 4: looprpc.AssetInfo + (*RequestAssetLoopOutRequest)(nil), // 5: looprpc.RequestAssetLoopOutRequest + (*RequestAssetLoopOutResponse)(nil), // 6: looprpc.RequestAssetLoopOutResponse + (*PollAssetLoopOutProofRequest)(nil), // 7: looprpc.PollAssetLoopOutProofRequest + (*PollAssetLoopOutProofResponse)(nil), // 8: looprpc.PollAssetLoopOutProofResponse + (*RequestAssetBuyRequest)(nil), // 9: looprpc.RequestAssetBuyRequest + (*RequestAssetBuyResponse)(nil), // 10: looprpc.RequestAssetBuyResponse + (*RequestMusig2SweepRequest)(nil), // 11: looprpc.RequestMusig2SweepRequest + (*RequestMusig2SweepResponse)(nil), // 12: looprpc.RequestMusig2SweepResponse +} +var file_assets_proto_depIdxs = []int32{ + 4, // 0: looprpc.ListAvailableAssetsResponse.assets:type_name -> looprpc.AssetInfo + 0, // 1: looprpc.AssetsSwapServer.QuoteAssetLoopOut:input_type -> looprpc.QuoteAssetLoopOutRequest + 2, // 2: looprpc.AssetsSwapServer.ListAvailableAssets:input_type -> looprpc.ListAvailableAssetsRequest + 5, // 3: looprpc.AssetsSwapServer.RequestAssetLoopOut:input_type -> looprpc.RequestAssetLoopOutRequest + 7, // 4: looprpc.AssetsSwapServer.PollAssetLoopOutProof:input_type -> looprpc.PollAssetLoopOutProofRequest + 9, // 5: looprpc.AssetsSwapServer.RequestAssetBuy:input_type -> looprpc.RequestAssetBuyRequest + 11, // 6: looprpc.AssetsSwapServer.RequestMusig2Sweep:input_type -> looprpc.RequestMusig2SweepRequest + 1, // 7: looprpc.AssetsSwapServer.QuoteAssetLoopOut:output_type -> looprpc.QuoteAssetLoopOutResponse + 3, // 8: looprpc.AssetsSwapServer.ListAvailableAssets:output_type -> looprpc.ListAvailableAssetsResponse + 6, // 9: looprpc.AssetsSwapServer.RequestAssetLoopOut:output_type -> looprpc.RequestAssetLoopOutResponse + 8, // 10: looprpc.AssetsSwapServer.PollAssetLoopOutProof:output_type -> looprpc.PollAssetLoopOutProofResponse + 10, // 11: looprpc.AssetsSwapServer.RequestAssetBuy:output_type -> looprpc.RequestAssetBuyResponse + 12, // 12: looprpc.AssetsSwapServer.RequestMusig2Sweep:output_type -> looprpc.RequestMusig2SweepResponse + 7, // [7:13] is the sub-list for method output_type + 1, // [1:7] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_assets_proto_init() } +func file_assets_proto_init() { + if File_assets_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_assets_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QuoteAssetLoopOutRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QuoteAssetLoopOutResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAvailableAssetsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAvailableAssetsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AssetInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestAssetLoopOutRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestAssetLoopOutResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PollAssetLoopOutProofRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PollAssetLoopOutProofResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestAssetBuyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestAssetBuyResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestMusig2SweepRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_assets_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestMusig2SweepResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_assets_proto_rawDesc, + NumEnums: 0, + NumMessages: 13, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_assets_proto_goTypes, + DependencyIndexes: file_assets_proto_depIdxs, + MessageInfos: file_assets_proto_msgTypes, + }.Build() + File_assets_proto = out.File + file_assets_proto_rawDesc = nil + file_assets_proto_goTypes = nil + file_assets_proto_depIdxs = nil +} diff --git a/swapserverrpc/assets.proto b/swapserverrpc/assets.proto new file mode 100644 index 000000000..7233f100c --- /dev/null +++ b/swapserverrpc/assets.proto @@ -0,0 +1,135 @@ +syntax = "proto3"; + +// We can't change this to swapserverrpc, it would be a breaking change because +// the package name is also contained in the HTTP URIs and old clients would +// call the wrong endpoints. Luckily with the go_package option we can have +// different golang and RPC package names to fix protobuf namespace conflicts. +package looprpc; + +option go_package = "github.com/lightninglabs/loop/swapserverrpc"; + +service AssetsSwapServer { + // QuoteAssetLoopOut requests a quote for an asset loop out swap from the + // server. + rpc QuoteAssetLoopOut (QuoteAssetLoopOutRequest) + returns (QuoteAssetLoopOutResponse); + + // ListAvailableAssets returns the list of assets that the server supports. + rpc ListAvailableAssets (ListAvailableAssetsRequest) + returns (ListAvailableAssetsResponse); + + // RequestAssetLoopOut requests an asset loop out swap from the server. + rpc RequestAssetLoopOut (RequestAssetLoopOutRequest) + returns (RequestAssetLoopOutResponse); + + // PollAssetLoopOutProof requests the server to poll for the proof of the + // asset loop out swap. + rpc PollAssetLoopOutProof (PollAssetLoopOutProofRequest) + returns (PollAssetLoopOutProofResponse); + + // RequestAssetBuy requests an asset buy swap from the server. This + // requires an already confirmed asset output on chain. + rpc RequestAssetBuy (RequestAssetBuyRequest) + returns (RequestAssetBuyResponse); + + // RequestMusig2Sweep requests a musig2 sweep from the server. + rpc RequestMusig2Sweep (RequestMusig2SweepRequest) + returns (RequestMusig2SweepResponse); +} + +message QuoteAssetLoopOutRequest { + // amount is the amount of the asset to loop out. + uint64 amount = 1; + + // asset is the asset to loop out. + bytes asset = 2; +} + +message QuoteAssetLoopOutResponse { + // fixed_prepay_amt is the fixed prepay amount of the swap. + uint64 fixed_prepay_amt = 1; + + // swap_fee_rate is the fee rate that is added to the swap invoice. + double swap_fee_rate = 2; + + // current_sats_per_asset_unit is the sats per asset unit of the swap. + uint64 current_sats_per_asset_unit = 3; +} + +message ListAvailableAssetsRequest { +} + +message ListAvailableAssetsResponse { + // assets is the list of assets that the server supports. + repeated AssetInfo assets = 1; +} + +message AssetInfo { + bytes asset_id = 1; + uint64 current_sats_per_asset_unit = 2; +} + +message RequestAssetLoopOutRequest { + // amount is the amount of the asset to loop out. + uint64 amount = 1; + + // receiver_key is the public key of the receiver. + bytes receiver_key = 2; + + // requested_asset is the asset to loop out. + bytes requested_asset = 3; +} + +message RequestAssetLoopOutResponse { + // swap_hash is the main identifier of the swap. + bytes swap_hash = 1; + + // prepay_invoice is the invoice to pay to start the swap. On accepted + // the server will publish the htlc output. + string prepay_invoice = 2; + + // expiry is the expiry of the htlc output. + int64 expiry = 3; + + // sender_pubkey is the public key of the sender. + bytes sender_pubkey = 4; +} + +message PollAssetLoopOutProofRequest { + // swap_hash is the main identifier of the swap. + bytes swap_hash = 1; +} + +message PollAssetLoopOutProofResponse { + // raw_proof_file is the raw proof file of the swap. + bytes raw_proof_file = 1; +} + +message RequestAssetBuyRequest { + // swap_hash is the main identifier of the swap. + bytes swap_hash = 1; +} + +message RequestAssetBuyResponse { + // swap_invoice is the invoice to pay to receive the preimage to claim the + // asset. + string swap_invoice = 1; +} + +message RequestMusig2SweepRequest { + // swap_hash is the main identifier of the swap. + bytes swap_hash = 1; + // digest is that the client wants the server to sign. + bytes digest = 2; + // receiver_nonce is the nonce of the receiver. + bytes receiver_nonce = 3; + // roothash is the roothash we tweak the musig2 pubkey with. + bytes roothash = 4; +} + +message RequestMusig2SweepResponse { + // sender_nonce is the nonce of the sender. + bytes sender_nonce = 1; + // sender_sig is the signature of the sender. + bytes sender_sig = 2; +} diff --git a/swapserverrpc/assets_grpc.pb.go b/swapserverrpc/assets_grpc.pb.go new file mode 100644 index 000000000..957bd57f7 --- /dev/null +++ b/swapserverrpc/assets_grpc.pb.go @@ -0,0 +1,299 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package swapserverrpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// AssetsSwapServerClient is the client API for AssetsSwapServer service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AssetsSwapServerClient interface { + // QuoteAssetLoopOut requests a quote for an asset loop out swap from the + // server. + QuoteAssetLoopOut(ctx context.Context, in *QuoteAssetLoopOutRequest, opts ...grpc.CallOption) (*QuoteAssetLoopOutResponse, error) + // ListAvailableAssets returns the list of assets that the server supports. + ListAvailableAssets(ctx context.Context, in *ListAvailableAssetsRequest, opts ...grpc.CallOption) (*ListAvailableAssetsResponse, error) + // RequestAssetLoopOut requests an asset loop out swap from the server. + RequestAssetLoopOut(ctx context.Context, in *RequestAssetLoopOutRequest, opts ...grpc.CallOption) (*RequestAssetLoopOutResponse, error) + // PollAssetLoopOutProof requests the server to poll for the proof of the + // asset loop out swap. + PollAssetLoopOutProof(ctx context.Context, in *PollAssetLoopOutProofRequest, opts ...grpc.CallOption) (*PollAssetLoopOutProofResponse, error) + // RequestAssetBuy requests an asset buy swap from the server. This + // requires an already confirmed asset output on chain. + RequestAssetBuy(ctx context.Context, in *RequestAssetBuyRequest, opts ...grpc.CallOption) (*RequestAssetBuyResponse, error) + // RequestMusig2Sweep requests a musig2 sweep from the server. + RequestMusig2Sweep(ctx context.Context, in *RequestMusig2SweepRequest, opts ...grpc.CallOption) (*RequestMusig2SweepResponse, error) +} + +type assetsSwapServerClient struct { + cc grpc.ClientConnInterface +} + +func NewAssetsSwapServerClient(cc grpc.ClientConnInterface) AssetsSwapServerClient { + return &assetsSwapServerClient{cc} +} + +func (c *assetsSwapServerClient) QuoteAssetLoopOut(ctx context.Context, in *QuoteAssetLoopOutRequest, opts ...grpc.CallOption) (*QuoteAssetLoopOutResponse, error) { + out := new(QuoteAssetLoopOutResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsSwapServer/QuoteAssetLoopOut", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsSwapServerClient) ListAvailableAssets(ctx context.Context, in *ListAvailableAssetsRequest, opts ...grpc.CallOption) (*ListAvailableAssetsResponse, error) { + out := new(ListAvailableAssetsResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsSwapServer/ListAvailableAssets", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsSwapServerClient) RequestAssetLoopOut(ctx context.Context, in *RequestAssetLoopOutRequest, opts ...grpc.CallOption) (*RequestAssetLoopOutResponse, error) { + out := new(RequestAssetLoopOutResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsSwapServer/RequestAssetLoopOut", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsSwapServerClient) PollAssetLoopOutProof(ctx context.Context, in *PollAssetLoopOutProofRequest, opts ...grpc.CallOption) (*PollAssetLoopOutProofResponse, error) { + out := new(PollAssetLoopOutProofResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsSwapServer/PollAssetLoopOutProof", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsSwapServerClient) RequestAssetBuy(ctx context.Context, in *RequestAssetBuyRequest, opts ...grpc.CallOption) (*RequestAssetBuyResponse, error) { + out := new(RequestAssetBuyResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsSwapServer/RequestAssetBuy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *assetsSwapServerClient) RequestMusig2Sweep(ctx context.Context, in *RequestMusig2SweepRequest, opts ...grpc.CallOption) (*RequestMusig2SweepResponse, error) { + out := new(RequestMusig2SweepResponse) + err := c.cc.Invoke(ctx, "/looprpc.AssetsSwapServer/RequestMusig2Sweep", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AssetsSwapServerServer is the server API for AssetsSwapServer service. +// All implementations must embed UnimplementedAssetsSwapServerServer +// for forward compatibility +type AssetsSwapServerServer interface { + // QuoteAssetLoopOut requests a quote for an asset loop out swap from the + // server. + QuoteAssetLoopOut(context.Context, *QuoteAssetLoopOutRequest) (*QuoteAssetLoopOutResponse, error) + // ListAvailableAssets returns the list of assets that the server supports. + ListAvailableAssets(context.Context, *ListAvailableAssetsRequest) (*ListAvailableAssetsResponse, error) + // RequestAssetLoopOut requests an asset loop out swap from the server. + RequestAssetLoopOut(context.Context, *RequestAssetLoopOutRequest) (*RequestAssetLoopOutResponse, error) + // PollAssetLoopOutProof requests the server to poll for the proof of the + // asset loop out swap. + PollAssetLoopOutProof(context.Context, *PollAssetLoopOutProofRequest) (*PollAssetLoopOutProofResponse, error) + // RequestAssetBuy requests an asset buy swap from the server. This + // requires an already confirmed asset output on chain. + RequestAssetBuy(context.Context, *RequestAssetBuyRequest) (*RequestAssetBuyResponse, error) + // RequestMusig2Sweep requests a musig2 sweep from the server. + RequestMusig2Sweep(context.Context, *RequestMusig2SweepRequest) (*RequestMusig2SweepResponse, error) + mustEmbedUnimplementedAssetsSwapServerServer() +} + +// UnimplementedAssetsSwapServerServer must be embedded to have forward compatible implementations. +type UnimplementedAssetsSwapServerServer struct { +} + +func (UnimplementedAssetsSwapServerServer) QuoteAssetLoopOut(context.Context, *QuoteAssetLoopOutRequest) (*QuoteAssetLoopOutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QuoteAssetLoopOut not implemented") +} +func (UnimplementedAssetsSwapServerServer) ListAvailableAssets(context.Context, *ListAvailableAssetsRequest) (*ListAvailableAssetsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAvailableAssets not implemented") +} +func (UnimplementedAssetsSwapServerServer) RequestAssetLoopOut(context.Context, *RequestAssetLoopOutRequest) (*RequestAssetLoopOutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RequestAssetLoopOut not implemented") +} +func (UnimplementedAssetsSwapServerServer) PollAssetLoopOutProof(context.Context, *PollAssetLoopOutProofRequest) (*PollAssetLoopOutProofResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PollAssetLoopOutProof not implemented") +} +func (UnimplementedAssetsSwapServerServer) RequestAssetBuy(context.Context, *RequestAssetBuyRequest) (*RequestAssetBuyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RequestAssetBuy not implemented") +} +func (UnimplementedAssetsSwapServerServer) RequestMusig2Sweep(context.Context, *RequestMusig2SweepRequest) (*RequestMusig2SweepResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RequestMusig2Sweep not implemented") +} +func (UnimplementedAssetsSwapServerServer) mustEmbedUnimplementedAssetsSwapServerServer() {} + +// UnsafeAssetsSwapServerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AssetsSwapServerServer will +// result in compilation errors. +type UnsafeAssetsSwapServerServer interface { + mustEmbedUnimplementedAssetsSwapServerServer() +} + +func RegisterAssetsSwapServerServer(s grpc.ServiceRegistrar, srv AssetsSwapServerServer) { + s.RegisterService(&AssetsSwapServer_ServiceDesc, srv) +} + +func _AssetsSwapServer_QuoteAssetLoopOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QuoteAssetLoopOutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsSwapServerServer).QuoteAssetLoopOut(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsSwapServer/QuoteAssetLoopOut", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsSwapServerServer).QuoteAssetLoopOut(ctx, req.(*QuoteAssetLoopOutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsSwapServer_ListAvailableAssets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAvailableAssetsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsSwapServerServer).ListAvailableAssets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsSwapServer/ListAvailableAssets", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsSwapServerServer).ListAvailableAssets(ctx, req.(*ListAvailableAssetsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsSwapServer_RequestAssetLoopOut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RequestAssetLoopOutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsSwapServerServer).RequestAssetLoopOut(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsSwapServer/RequestAssetLoopOut", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsSwapServerServer).RequestAssetLoopOut(ctx, req.(*RequestAssetLoopOutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsSwapServer_PollAssetLoopOutProof_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PollAssetLoopOutProofRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsSwapServerServer).PollAssetLoopOutProof(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsSwapServer/PollAssetLoopOutProof", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsSwapServerServer).PollAssetLoopOutProof(ctx, req.(*PollAssetLoopOutProofRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsSwapServer_RequestAssetBuy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RequestAssetBuyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsSwapServerServer).RequestAssetBuy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsSwapServer/RequestAssetBuy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsSwapServerServer).RequestAssetBuy(ctx, req.(*RequestAssetBuyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AssetsSwapServer_RequestMusig2Sweep_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RequestMusig2SweepRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AssetsSwapServerServer).RequestMusig2Sweep(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.AssetsSwapServer/RequestMusig2Sweep", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AssetsSwapServerServer).RequestMusig2Sweep(ctx, req.(*RequestMusig2SweepRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AssetsSwapServer_ServiceDesc is the grpc.ServiceDesc for AssetsSwapServer service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AssetsSwapServer_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "looprpc.AssetsSwapServer", + HandlerType: (*AssetsSwapServerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "QuoteAssetLoopOut", + Handler: _AssetsSwapServer_QuoteAssetLoopOut_Handler, + }, + { + MethodName: "ListAvailableAssets", + Handler: _AssetsSwapServer_ListAvailableAssets_Handler, + }, + { + MethodName: "RequestAssetLoopOut", + Handler: _AssetsSwapServer_RequestAssetLoopOut_Handler, + }, + { + MethodName: "PollAssetLoopOutProof", + Handler: _AssetsSwapServer_PollAssetLoopOutProof_Handler, + }, + { + MethodName: "RequestAssetBuy", + Handler: _AssetsSwapServer_RequestAssetBuy_Handler, + }, + { + MethodName: "RequestMusig2Sweep", + Handler: _AssetsSwapServer_RequestMusig2Sweep_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "assets.proto", +} diff --git a/utils/listeners.go b/utils/listeners.go new file mode 100644 index 000000000..54716d8d3 --- /dev/null +++ b/utils/listeners.go @@ -0,0 +1,257 @@ +package utils + +import ( + "context" + "sync" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lntypes" +) + +// ExpiryManager is a manager for block height expiry events. +type ExpiryManager struct { + chainNotifier lndclient.ChainNotifierClient + + expiryHeightMap map[[32]byte]int32 + expiryFuncMap map[[32]byte]func() + + currentBlockHeight int32 + + sync.Mutex +} + +// NewExpiryManager creates a new expiry manager. +func NewExpiryManager( + chainNotifier lndclient.ChainNotifierClient) *ExpiryManager { + + return &ExpiryManager{ + chainNotifier: chainNotifier, + expiryHeightMap: make(map[[32]byte]int32), + expiryFuncMap: make(map[[32]byte]func()), + } +} + +// Start starts the expiry manager and listens for block height notifications. +func (e *ExpiryManager) Start(ctx context.Context, startingBlockHeight int32, +) error { + + e.Lock() + e.currentBlockHeight = startingBlockHeight + e.Unlock() + + log.Debugf("Starting expiry manager at height %d", startingBlockHeight) + defer log.Debugf("Expiry manager stopped") + + blockHeightChan, errChan, err := e.chainNotifier.RegisterBlockEpochNtfn( + ctx, + ) + if err != nil { + return err + } + + for { + select { + case blockHeight := <-blockHeightChan: + + log.Debugf("Received block height %d", blockHeight) + + e.Lock() + e.currentBlockHeight = blockHeight + e.Unlock() + + e.checkExpiry(blockHeight) + + case err := <-errChan: + log.Debugf("Expiry manager error") + return err + + case <-ctx.Done(): + log.Debugf("Expiry manager stopped") + return nil + } + } +} + +// GetBlockHeight returns the current block height. +func (e *ExpiryManager) GetBlockHeight() int32 { + e.Lock() + defer e.Unlock() + + return e.currentBlockHeight +} + +// checkExpiry checks if any swaps have expired and calls the expiry function if +// they have. +func (e *ExpiryManager) checkExpiry(blockHeight int32) { + e.Lock() + defer e.Unlock() + + for swapHash, expiryHeight := range e.expiryHeightMap { + if blockHeight >= expiryHeight { + expiryFunc := e.expiryFuncMap[swapHash] + go expiryFunc() + + delete(e.expiryHeightMap, swapHash) + delete(e.expiryFuncMap, swapHash) + } + } +} + +// SubscribeExpiry subscribes to an expiry event for a swap. If the expiry height +// has already been reached, the expiryFunc is not called and the function +// returns true. Otherwise, the expiryFunc is called when the expiry height is +// reached and the function returns false. +func (e *ExpiryManager) SubscribeExpiry(swapHash [32]byte, + expiryHeight int32, expiryFunc func()) bool { + + e.Lock() + defer e.Unlock() + + if e.currentBlockHeight >= expiryHeight { + return true + } + + log.Debugf("Subscribing to expiry for swap %x at height %d", + swapHash, expiryHeight) + + e.expiryHeightMap[swapHash] = expiryHeight + e.expiryFuncMap[swapHash] = expiryFunc + + return false +} + +// SubscribeInvoiceManager is a manager for invoice subscription events. +type SubscribeInvoiceManager struct { + invoicesClient lndclient.InvoicesClient + + subscribers map[[32]byte]struct{} + + sync.Mutex +} + +// NewSubscribeInvoiceManager creates a new subscribe invoice manager. +func NewSubscribeInvoiceManager( + invoicesClient lndclient.InvoicesClient) *SubscribeInvoiceManager { + + return &SubscribeInvoiceManager{ + invoicesClient: invoicesClient, + subscribers: make(map[[32]byte]struct{}), + } +} + +// SubscribeInvoice subscribes to invoice events for a swap hash. The update +// callback is called when the invoice is updated and the error callback is +// called when an error occurs. +func (s *SubscribeInvoiceManager) SubscribeInvoice(ctx context.Context, + invoiceHash lntypes.Hash, callback func(lndclient.InvoiceUpdate, error), +) error { + + s.Lock() + defer s.Unlock() + // If we already have a subscriber for this swap hash, return early. + if _, ok := s.subscribers[invoiceHash]; ok { + return nil + } + + log.Debugf("Subscribing to invoice %v", invoiceHash) + + updateChan, errChan, err := s.invoicesClient.SubscribeSingleInvoice( + ctx, invoiceHash, + ) + if err != nil { + return err + } + + s.subscribers[invoiceHash] = struct{}{} + + go func() { + for { + select { + case update := <-updateChan: + callback(update, nil) + + case err := <-errChan: + callback(lndclient.InvoiceUpdate{}, err) + delete(s.subscribers, invoiceHash) + return + + case <-ctx.Done(): + delete(s.subscribers, invoiceHash) + return + } + } + }() + + return nil +} + +// TxSubscribeConfirmationManager is a manager for transaction confirmation +// subscription events. +type TxSubscribeConfirmationManager struct { + chainNotifier lndclient.ChainNotifierClient + + subscribers map[[32]byte]struct{} + + sync.Mutex +} + +// NewTxSubscribeConfirmationManager creates a new transaction confirmation +// subscription manager. +func NewTxSubscribeConfirmationManager(chainNtfn lndclient.ChainNotifierClient, +) *TxSubscribeConfirmationManager { + + return &TxSubscribeConfirmationManager{ + chainNotifier: chainNtfn, + subscribers: make(map[[32]byte]struct{}), + } +} + +// SubscribeTxConfirmation subscribes to transaction confirmation events for a +// swap hash. The callback is called when the transaction is confirmed or an +// error occurs. +func (t *TxSubscribeConfirmationManager) SubscribeTxConfirmation( + ctx context.Context, swapHash lntypes.Hash, txid *chainhash.Hash, + pkscript []byte, numConfs int32, heightHint int32, + cb func(*chainntnfs.TxConfirmation, error)) error { + + t.Lock() + defer t.Unlock() + + // If we already have a subscriber for this swap hash, return early. + if _, ok := t.subscribers[swapHash]; ok { + return nil + } + + log.Debugf("Subscribing to tx confirmation for swap %v", swapHash) + + confChan, errChan, err := t.chainNotifier.RegisterConfirmationsNtfn( + ctx, txid, pkscript, numConfs, heightHint, + ) + if err != nil { + return err + } + + t.subscribers[swapHash] = struct{}{} + + go func() { + for { + select { + case conf := <-confChan: + cb(conf, nil) + + case err := <-errChan: + cb(nil, err) + delete(t.subscribers, swapHash) + return + + case <-ctx.Done(): + delete(t.subscribers, swapHash) + return + } + } + }() + + return nil +} diff --git a/utils/log.go b/utils/log.go new file mode 100644 index 000000000..fcefd75c4 --- /dev/null +++ b/utils/log.go @@ -0,0 +1,26 @@ +package utils + +import ( + "github.com/btcsuite/btclog/v2" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the sub system name of this package. +const Subsystem = "UTILS" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +}