Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e72353a
assets: add asset HTLC helpers
bhandras Jul 14, 2025
6a54be2
assets: add no-csv option to the asset HTLC to support package relay
bhandras Jul 14, 2025
eeb929c
assets: extend the tapd client and add high-level TAP helpers
bhandras Jul 14, 2025
5bf08b2
swapserverrpc: add proto definitions for server asset deposit RPCs
bhandras Jul 14, 2025
b033f71
assets: add deposit kit encapsulating TAP deposit interactions
bhandras Jul 15, 2025
423bb98
build[tmp]: add replaces needed to support SubmitPackage
bhandras Aug 17, 2025
8ed1697
looprpc: add client side asset deposit service proto stubs
bhandras Jul 15, 2025
2d26575
loop: add stubs and CLI commands for the asset deposit subserver
bhandras Jul 15, 2025
8322425
loopd: add profiler to the Loop daemon
bhandras Jul 15, 2025
61483e3
assets: add asset deposit sweeper
bhandras Jul 15, 2025
29833e3
loopdb: add basic schema for asset deposits
bhandras Jul 15, 2025
6c6a3a4
assets+loopd: add scaffolding for the asset deposit manager
bhandras Jul 15, 2025
685d50c
assets+loopdb: implement new asset deposit functionality
bhandras Jul 15, 2025
b954e59
assets+loopdb: implement functionality to list asset deposits
bhandras Jul 15, 2025
183d542
assets+loopdb: handle asset deposit expiry and timeout sweep
bhandras Jul 15, 2025
f0ed1b6
assets+loopdb: recover active deposits on startup
bhandras Jul 15, 2025
17ba66f
assets+loopdb: implement asset deposit withdrawal
bhandras Jul 15, 2025
00469c8
assets+loopdb: publish cooperative deposit withdrawal transaction
bhandras Aug 17, 2025
ed52492
assets: implement asset deposit key reveal
bhandras Jul 15, 2025
6a02fe0
assets: add support for co-signing a zero-fee deposit HTLC spend
bhandras Jul 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
651 changes: 622 additions & 29 deletions assets/client.go

Large diffs are not rendered by default.

248 changes: 248 additions & 0 deletions assets/deposit/deposit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package deposit

import (
"context"
"encoding/hex"
"fmt"
"time"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightninglabs/loop/assets"
"github.com/lightninglabs/taproot-assets/proof"
)

// State is the enum used for deposit states.
type State uint8

const (
// StateInitiated indicates that the deposit has been initiated by the
// client.
StateInitiated State = 0

// StatePending indicates that the deposit is pending confirmation on
// the blockchain.
StatePending State = 1

// StateConfirmed indicates that the deposit has been confirmed on the
// blockchain.
StateConfirmed State = 2

// StateExpired indicates that the deposit has expired.
StateExpired State = 3

// StateTimeoutSweepPublished indicates that the timeout sweep has been
// published.
StateTimeoutSweepPublished State = 4

// StateWithdrawn indicates that the deposit has been withdrawn.
StateWithdrawn State = 5

// StateCooperativeSweepPublished indicates that the cooperative sweep
// withdrawing the deposit has been published.
StateCooperativeSweepPublished State = 6

// StateKeyRevealed indicates that the client has revealed a valid key
// for the deposit which is now ready to be swept.
StateKeyRevealed State = 7

// StateSpent indicates that the deposit has been spent.
StateSpent State = 8

// StateSwept indicates that the deposit has been swept, either by a
// timeout sweep or a cooperative (ie withdrawal) sweep.
StateSwept State = 9
)

// String coverts a deposit state to human readable string.
func (s State) String() string {
switch s {
case StateInitiated:
return "Initiated"

case StatePending:
return "Pending"

case StateConfirmed:
return "Confirmed"

case StateExpired:
return "Expired"

case StateTimeoutSweepPublished:
return "TimeoutSweepPublished"

case StateWithdrawn:
return "Withdrawn"

case StateCooperativeSweepPublished:
return "CooperativeSweepPublished"

case StateKeyRevealed:
return "KeyRevealed"

case StateSpent:
return "Spent"

case StateSwept:
return "Swept"

default:
return "Unknown"
}
}

// IsFinal returns true if the deposit state is final, meaning that no further
// actions can be taken on the deposit.
func (s State) IsFinal() bool {
return s == StateSpent || s == StateSwept
}

// Info holds publicly available information about an asset deposit.
// It is used to communicate deposit details to clients of the deposit Manager.
type Info struct {
// ID is the unique identifier for this deposit which will also be used
// to store the deposit in both the server and client databases.
ID string

// Version is the protocol version of the deposit.
Version AssetDepositProtocolVersion

// CreatedAt is the time when the deposit was created (on the client).
CreatedAt time.Time

// Amount is the amount of asset to be deposited.
Amount uint64

// Addr is the TAP deposit address where the asset will be sent.
Addr string

// State is the deposit state.
State State

// ConfirmationHeight is the block height at which the deposit was
// confirmed.
ConfirmationHeight uint32

// Outpoint is the anchor outpoint of the deposit. It is only set if the
// deposit has been confirmed.
Outpoint *wire.OutPoint

// Expiry is the block height at which the deposit will expire. It is
// only set if the deposit has been confirmed.
Expiry uint32

// SweepScriptKey is the script key of the swept asset.
SweepScriptKey *btcec.PublicKey

// SweepInternalKey is the internal key of output of the swept asset.
SweepInternalKey *btcec.PublicKey
}

// Copy creates a copy of the Info struct.
func (d *Info) Copy() *Info {
info := &Info{
ID: d.ID,
Version: d.Version,
CreatedAt: d.CreatedAt,
Amount: d.Amount,
Addr: d.Addr,
State: d.State,
ConfirmationHeight: d.ConfirmationHeight,
Expiry: d.Expiry,
}

if d.Outpoint != nil {
info.Outpoint = &wire.OutPoint{
Hash: d.Outpoint.Hash,
Index: d.Outpoint.Index,
}
}

if d.SweepScriptKey != nil {
pubKey := *d.SweepScriptKey
info.SweepScriptKey = &pubKey
}

if d.SweepInternalKey != nil {
pubKey := *d.SweepInternalKey
info.SweepInternalKey = &pubKey
}

return info
}

// Deposit is the struct that holds all the information about an asset deposit.
type Deposit struct {
*Kit

*Info

// PkScript is the pkscript of the deposit anchor output.
PkScript []byte

// Proof is the proof of the deposit transfer.
Proof *proof.Proof

// AnchorRootHash is the root hash of the deposit anchor output.
AnchorRootHash []byte
}

// fundingLabel returns a string label that we can use for marking a transfer
// funding the deposit. This is useful if we need to filter deposits.
func (d *Deposit) fundingLabel() string {
return fmt.Sprintf("deposit funding %v", d.ID)
}

// timeoutSweepLabel is a string label that we can use for marking a timeout
// sweep transfer. This is useful if we need to filter deposits.
func (d *Deposit) timeoutSweepLabel() string {
return fmt.Sprintf("deposit timeout sweep %v", d.ID)
}

// withdrawLabel is a string label that we can use for marking a withdrawal
// sweep transfer. This is useful if we need to filter deposits.
func (d *Deposit) withdrawLabel() string {
return fmt.Sprintf("deposit withdraw sweep %v", d.ID)
}

// lockID converts a deposit ID to a lock ID. The lock ID is used to lock inputs
// used for the deposit sweep transaction. Note that we assume that the deposit
// ID is a hex-encoded string of the same length as the lock ID.
func (d *Deposit) lockID() (wtxmgr.LockID, error) {
var lockID wtxmgr.LockID
depositIDBytes, err := hex.DecodeString(d.ID)
if err != nil {
return wtxmgr.LockID{}, err
}

if len(depositIDBytes) != len(lockID) {
return wtxmgr.LockID{}, fmt.Errorf("invalid deposit ID "+
"length: %d", len(depositIDBytes))
}

copy(lockID[:], depositIDBytes)

return lockID, nil
}

// GenerateSweepKeys generates the sweep script key and internal key for the
// deposit sweep.
func (d *Deposit) GenerateSweepKeys(ctx context.Context,
client *assets.TapdClient) error {

if d.SweepScriptKey != nil {
return nil
}

scriptKey, internalKeyDesc, err := client.DeriveNewKeys(ctx)
if err != nil {
return err
}

d.SweepScriptKey = scriptKey.PubKey
d.SweepInternalKey = internalKeyDesc.PubKey

return nil
}
Loading