Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 32 additions & 6 deletions rpc/swapclientrpc/swap_client.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions rpc/swapclientrpc/swap_client.proto
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum SwapSettlementType {
SWAP_SETTLEMENT_TYPE_IN_ARK = 2;
SWAP_SETTLEMENT_TYPE_CREDIT = 3;
SWAP_SETTLEMENT_TYPE_MIXED = 4;
SWAP_SETTLEMENT_TYPE_ARK_CHANNEL = 5;
}

enum CreditFundingSource {
Expand Down Expand Up @@ -429,4 +430,14 @@ message SwapSummary {
// available_credit_sat is the balance considered when the receive route was
// planned.
uint64 available_credit_sat = 26;

// channel_id identifies the manifested Ark-backed Lightning channel when
// this receive completed through channel settlement.
bytes channel_id = 27;

// reserved_scid is the future virtual SCID advertised in the invoice.
uint64 reserved_scid = 28;

reserved 29;
reserved "channel_backing_fee_sat";
}
29 changes: 26 additions & 3 deletions rpc/wavewalletrpc/wallet.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions rpc/wavewalletrpc/wallet.proto
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,17 @@ message ActivitySwapTrace {
// revealed it. For a completed pay swap this is the proof of payment for
// the paid invoice; it is empty until the preimage is durably known.
string preimage = 20;

// channel_id identifies the Ark-backed Lightning channel manifested by a
// receive fallback. It is empty when the payment used existing inbound.
bytes channel_id = 21;

// reserved_scid is the virtual short channel ID advertised in the receive
// invoice and later installed as the manifested channel's local alias.
uint64 reserved_scid = 22;

reserved 23;
reserved "channel_backing_fee_sat";
}

// ActivityVTXOTrace describes one VTXO movement correlated to a wallet activity
Expand Down
57 changes: 51 additions & 6 deletions sdk/swaps/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/btcsuite/btcd/chaincfg/v2"
"github.com/btcsuite/btclog/v2"
"github.com/lightninglabs/wavelength/arkchannel"
sdkark "github.com/lightninglabs/wavelength/sdk/ark"
"github.com/lightninglabs/wavelength/waverpc"
"github.com/lightningnetwork/lnd/invoices"
Expand Down Expand Up @@ -51,6 +52,10 @@ const (
// SettlementTypeMixed means the invoice is funded by both a vHTLC and
// a reserved credit balance.
SettlementTypeMixed SettlementType = "mixed"

// SettlementTypeArkChannel means the receive settled directly into an
// active Ark-backed native Lightning channel.
SettlementTypeArkChannel SettlementType = "ark_channel"
)

// CreditQuote describes how a pay quote uses wallet credits.
Expand Down Expand Up @@ -223,6 +228,15 @@ type SwapSummary struct {
// or as a same-Ark payment when that detail is durably known.
SettlementType SettlementType

// ChannelID identifies a new Ark-backed Lightning channel manifested
// for this receive. It is zero when settlement reused an existing
// channel.
ChannelID [32]byte

// ReservedSCID is the virtual route advertised before direct settlement
// or fallback channel manifestation.
ReservedSCID uint64

// CreditQuote records the credit component of a pay quote when the
// server selected a credit or mixed rail.
CreditQuote *CreditQuote
Expand Down Expand Up @@ -283,6 +297,15 @@ type ReceiveResult struct {

// AmountSat is the value of the claimed VTXO in satoshis.
AmountSat int64

// ChannelID identifies a new Ark channel manifested for this receive.
// It is zero when the payment reused an existing channel or settled
// through the ordinary vHTLC rail.
ChannelID [32]byte

// SettlementType reports whether the result used a channel or ordinary
// vHTLC claim.
SettlementType SettlementType
}

// ReceiveVHTLCInfo holds the script details for one prepared
Expand Down Expand Up @@ -738,6 +761,19 @@ type SwapServerConn interface {
Close() error
}

// ArkChannelPaymentBridge is the narrow daemon-owned channel boundary used by
// the swap SDK. lnd remains authoritative for invoices and payments.
type ArkChannelPaymentBridge interface {
PrepareIncomingPayment(context.Context, lntypes.Preimage,
btcutil.Amount) error

RegisterIncomingPayment(context.Context, lntypes.Hash, btcutil.Amount,
uint64) error

WaitIncomingPayment(context.Context,
lntypes.Hash) (arkchannel.ID, error)
}

// DaemonConn abstracts the connection to the client's own daemon for wallet
// operations such as OOR sends and indexed VTXO lookups.
//
Expand Down Expand Up @@ -891,12 +927,13 @@ type InSwapRefundAuthorization struct {
// SwapClient is the high-level client API for Lightning<->Ark
// swaps.
type SwapClient struct {
server SwapServerConn
daemon DaemonConn
invoiceGen InvoiceCreator
outEvents OutSwapEventReceiver
store *Store
log btclog.Logger
server SwapServerConn
daemon DaemonConn
invoiceGen InvoiceCreator
outEvents OutSwapEventReceiver
store *Store
log btclog.Logger
channelBridge ArkChannelPaymentBridge

waitPollInterval time.Duration
overdueReceivePollWindow time.Duration
Expand All @@ -912,6 +949,14 @@ type SwapClient struct {
now func() time.Time
}

// SetArkChannelPaymentBridge enables direct channel settlement and vHTLC
// promotion for receive sessions. Callers must set it before resuming swaps.
func (c *SwapClient) SetArkChannelPaymentBridge(
bridge ArkChannelPaymentBridge) {

c.channelBridge = bridge
}

// SetOutSwapEventReceiver sets the mailbox event receiver used by
// ReceiveViaLightning. Callers should configure this before starting receives.
func (c *SwapClient) SetOutSwapEventReceiver(receiver OutSwapEventReceiver) {
Expand Down
8 changes: 8 additions & 0 deletions sdk/swaps/in_swap_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ func validateInSwapPreview(invoice string, quote *InSwapQuote,
expectedAmountSat := expectedInvoiceSat + quote.FeeSat
switch quote.SettlementType {
case "", SettlementTypeLightning, SettlementTypeInArk:
case SettlementTypeArkChannel:
return fmt.Errorf("ark channel settlement is not valid for " +
"pay quotes")

case SettlementTypeCredit:
expectedAmountSat = 0

Expand Down Expand Up @@ -232,6 +236,10 @@ func validateInSwapQuote(invoice string, maxFeeSat uint64, cfg *InSwapConfig,
expectedAmountSat := amountSat + cfg.FeeSat
switch cfg.SettlementType {
case "", SettlementTypeLightning, SettlementTypeInArk:
case SettlementTypeArkChannel:
return fmt.Errorf("ark channel settlement is not valid for " +
"pay quotes")

case SettlementTypeCredit:
if cfg.Preimage == nil {
return fmt.Errorf("credit in-swap config missing " +
Expand Down
35 changes: 35 additions & 0 deletions sdk/swaps/in_swap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,41 @@ func TestValidateInSwapPreviewAllowsCreditOnlyZeroAmount(t *testing.T) {
require.NoError(t, err)
}

// TestInSwapValidationRejectsArkChannelSettlement verifies the receive-only
// settlement result cannot be supplied as a pay-side quote.
func TestInSwapValidationRejectsArkChannelSettlement(t *testing.T) {
t.Parallel()

serverPriv, err := btcec.NewPrivateKey()
require.NoError(t, err)

preimage, err := NewPreimage()
require.NoError(t, err)
invoice := testValidPayInvoice(t, preimage)

quote := &InSwapQuote{
PaymentHash: preimage.Hash(),
InvoiceAmountSat: testInSwapInvoiceSat,
AmountSat: testInSwapAmountSat,
FeeSat: testInSwapFeeSat,
Expiry: time.Now().Add(time.Minute),
SettlementType: SettlementTypeArkChannel,
}
err = validateInSwapPreview(
invoice, quote, &chaincfg.RegressionNetParams,
)
require.ErrorContains(t, err, "not valid for pay quotes")

cfg := testInSwapConfig(
serverPriv.PubKey(), preimage, time.Now().Add(time.Minute),
)
cfg.SettlementType = SettlementTypeArkChannel
err = validateInSwapQuote(
invoice, testInSwapFeeSat, cfg, &chaincfg.RegressionNetParams,
)
require.ErrorContains(t, err, "not valid for pay quotes")
}

// TestValidateInSwapQuoteRejectsServerMismatches verifies the client treats
// the swap server response as a quote that must match the caller's invoice.
func TestValidateInSwapQuoteRejectsServerMismatches(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions sdk/swaps/migrations/000002_ark_channel_receive.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE receive_swaps DROP COLUMN channel_id;
ALTER TABLE receive_swaps DROP COLUMN channel_backing_fee_sat;
ALTER TABLE receive_swaps DROP COLUMN reserved_scid;
10 changes: 10 additions & 0 deletions sdk/swaps/migrations/000002_ark_channel_receive.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Persist the future-channel identity and legacy fee reserve needed to resume
-- direct private settlement after restart.
ALTER TABLE receive_swaps
ADD COLUMN reserved_scid BLOB;

ALTER TABLE receive_swaps
ADD COLUMN channel_backing_fee_sat BIGINT NOT NULL DEFAULT 0;

ALTER TABLE receive_swaps
ADD COLUMN channel_id BLOB;
Loading
Loading