swapwallet: Settle Lightning receives over Ark channels - #1139
Conversation
|
/gateway review |
|
Cannot run |
|
/gateway review |
|
❌ gateway review failed: could not fetch PR context: {"level":"info","script":"fetch-pr-context.sh","event":"fetch","outcome":"attempt","step":"pr","path":"repos/lightninglabs/wavelength/pulls/1139"} {"level":"info","script":"fetch-pr-context.sh","event":"fetch","outcome":"attempt","step":"diff","path":"repos/lightninglabs/wavelength/pulls/1139"} {"level":"error","script":"fetch-pr-context.sh","event":"fetch_diff","outcome":"failure","message":"fetch diff failed: gh: Sorry, the diff exceeded the maximum number of lines (20000) (HTTP 406) "} See workflow logs for details. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3c0bffeffb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if record.Snapshot.Phase != arkchannel.PhaseActive || | ||
| record.Snapshot.Terms.Kind != | ||
| arkchannel.KindReceiveIntent { | ||
| return arkchannel.ID{}, nil |
There was a problem hiding this comment.
Wait for channel activation before reporting settlement
When the private invoice settles while the receive channel is still PhaseActivating—after lnd activates the link but before the coordinator records ChannelActive—this branch returns a zero channel ID with no error. waitForChannelOrVHTLC consequently persists the swap as successfully completed with SettlementTypeArkChannel and an empty channel ID, losing the correlation needed to inspect or recover the manifested channel. Continue waiting for syncReceiveIntent to establish PhaseActive, or return an error instead of treating this intermediate state as success.
Useful? React with 👍 / 👎.
| case err := <-syncResult: | ||
| if err != nil && c.cfg.Log != nil { | ||
| c.cfg.Log.WarnS( | ||
| ctx, | ||
| "Receive channel intent stopped", | ||
| err, | ||
| btclog.Hex("payment_hash", hash[:]), | ||
| ) | ||
| } | ||
| syncResult = nil |
There was a problem hiding this comment.
Keep synchronizing after transient receive-intent errors
If syncReceiveIntent encounters any transient mailbox or store error before activation, this case only logs it and permanently disables the synchronization branch by setting syncResult to nil. The receive then waits for an invoice that cannot settle because the client no longer binds or advances the prepared channel, typically consuming the entire invoice deadline before the swap worker can retry. Retry the durable synchronization operation or propagate the error promptly so the outer worker can resume it.
Useful? React with 👍 / 👎.
2b9973c to
bbded8e
Compare
bbded8e to
bec163d
Compare
Carry the reserved SCID and manifested channel identity through swap and wallet activity responses without reviving the removed fee field.
Race the ordinary funded vHTLC rail against a registered native channel while persisting enough state to resume either outcome.
Connect daemon-owned invoices and channel settlement to the existing swap receive lifecycle and keep pending workers restartable.
Expose channel identities in wallet activity without changing the ordinary wallet receive interface.
bec163d to
95edaaf
Compare
Doc advisoryThis PR's Go changes left the per-package docs for Proposed doc changes (advisory only — nothing is committed)diff --git a/rpc/swapclientrpc/AGENTS.md b/rpc/swapclientrpc/AGENTS.md
index 33aa666c..fe82cfcc 100644
--- a/rpc/swapclientrpc/AGENTS.md
+++ b/rpc/swapclientrpc/AGENTS.md
@@ -14,6 +14,13 @@ credit funding/redemption/listing). Registered only in swapruntime builds.
- Request/response messages (`QuotePayRequest`, `StartPayRequest`,
`CreateCreditRequest`, `ListCreditsRequest`, etc.) and enums
(`SwapState`, `SwapDirection`, `CreditOperationState`, ...).
+- `SwapSettlementType` — how a swap is backed: `LIGHTNING`, `IN_ARK`,
+ `CREDIT`, `MIXED`, and `ARK_CHANNEL` (the receive settled directly into an
+ Ark-backed native Lightning channel).
+- `SwapSummary` — flat durable view of one swap. Beyond the amount/state
+ fields it carries `channel_id` (the Ark channel manifested for a receive,
+ empty when an existing channel was reused) and `reserved_scid` (the future
+ virtual SCID advertised in the invoice).
## Relationships
@@ -25,6 +32,9 @@ credit funding/redemption/listing). Registered only in swapruntime builds.
- Generated from `swap_client.proto` via `make rpc`; do not hand-edit any
`.pb.go` / `.pb.gw.go` file.
+- `SwapSummary` field 29 is `reserved` under the retired name
+ `channel_backing_fee_sat`. Reserved numbers and names are a wire contract:
+ never reuse them, and reserve rather than delete when a field is dropped.
## Deep Docs
diff --git a/rpc/swapclientrpc/CLAUDE.md b/rpc/swapclientrpc/CLAUDE.md
index 33aa666c..fe82cfcc 100644
--- a/rpc/swapclientrpc/CLAUDE.md
+++ b/rpc/swapclientrpc/CLAUDE.md
@@ -14,6 +14,13 @@ credit funding/redemption/listing). Registered only in swapruntime builds.
- Request/response messages (`QuotePayRequest`, `StartPayRequest`,
`CreateCreditRequest`, `ListCreditsRequest`, etc.) and enums
(`SwapState`, `SwapDirection`, `CreditOperationState`, ...).
+- `SwapSettlementType` — how a swap is backed: `LIGHTNING`, `IN_ARK`,
+ `CREDIT`, `MIXED`, and `ARK_CHANNEL` (the receive settled directly into an
+ Ark-backed native Lightning channel).
+- `SwapSummary` — flat durable view of one swap. Beyond the amount/state
+ fields it carries `channel_id` (the Ark channel manifested for a receive,
+ empty when an existing channel was reused) and `reserved_scid` (the future
+ virtual SCID advertised in the invoice).
## Relationships
@@ -25,6 +32,9 @@ credit funding/redemption/listing). Registered only in swapruntime builds.
- Generated from `swap_client.proto` via `make rpc`; do not hand-edit any
`.pb.go` / `.pb.gw.go` file.
+- `SwapSummary` field 29 is `reserved` under the retired name
+ `channel_backing_fee_sat`. Reserved numbers and names are a wire contract:
+ never reuse them, and reserve rather than delete when a field is dropped.
## Deep Docs
diff --git a/rpc/wavewalletrpc/AGENTS.md b/rpc/wavewalletrpc/AGENTS.md
index 00d5fbdf..c1425954 100644
--- a/rpc/wavewalletrpc/AGENTS.md
+++ b/rpc/wavewalletrpc/AGENTS.md
@@ -67,6 +67,12 @@ id; unlike `List` it may leak internal correlators, so it is kept out of
- `ExitMode` — which of the two exits an `Exit` call actually ran
(`COOPERATIVE` / `UNILATERAL`). `EXIT_MODE_UNSPECIFIED` is what an older
daemon sends; clients must treat it as "unknown", not as a default.
+- `ActivitySwapTrace` — `InspectActivity`'s per-swap drill-down. Carries the
+ internal correlators `List` deliberately drops, including `channel_id` (the
+ Ark channel manifested by a receive fallback, empty when existing inbound
+ was used) and `reserved_scid` (the virtual SCID advertised in the receive
+ invoice and later installed as the channel's local alias). Field 23 is
+ `reserved` under the retired name `channel_backing_fee_sat`.
- `FailureDomain` / `Reason*` constants (`failure_reasons.go`) — the
`google.rpc.ErrorInfo` domain/reason wire contract for failed wallet
RPCs; existing reason values MUST NOT be renamed.
diff --git a/rpc/wavewalletrpc/CLAUDE.md b/rpc/wavewalletrpc/CLAUDE.md
index 00d5fbdf..c1425954 100644
--- a/rpc/wavewalletrpc/CLAUDE.md
+++ b/rpc/wavewalletrpc/CLAUDE.md
@@ -67,6 +67,12 @@ id; unlike `List` it may leak internal correlators, so it is kept out of
- `ExitMode` — which of the two exits an `Exit` call actually ran
(`COOPERATIVE` / `UNILATERAL`). `EXIT_MODE_UNSPECIFIED` is what an older
daemon sends; clients must treat it as "unknown", not as a default.
+- `ActivitySwapTrace` — `InspectActivity`'s per-swap drill-down. Carries the
+ internal correlators `List` deliberately drops, including `channel_id` (the
+ Ark channel manifested by a receive fallback, empty when existing inbound
+ was used) and `reserved_scid` (the virtual SCID advertised in the receive
+ invoice and later installed as the channel's local alias). Field 23 is
+ `reserved` under the retired name `channel_backing_fee_sat`.
- `FailureDomain` / `Reason*` constants (`failure_reasons.go`) — the
`google.rpc.ErrorInfo` domain/reason wire contract for failed wallet
RPCs; existing reason values MUST NOT be renamed.
diff --git a/sdk/swaps/AGENTS.md b/sdk/swaps/AGENTS.md
index a981539e..ec779ada 100644
--- a/sdk/swaps/AGENTS.md
+++ b/sdk/swaps/AGENTS.md
@@ -18,7 +18,9 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
- `SwapClient` — top-level entry point. Constructed via `NewSwapClient`
(no persistence) or `NewSwapClientWithStore` (SQLite-backed). Holds
an `OutSwapEventReceiver` overridable via
- `SetOutSwapEventReceiver`.
+ `SetOutSwapEventReceiver`, and an optional
+ `ArkChannelPaymentBridge` installed via
+ `SetArkChannelPaymentBridge`.
- `PaySession` — Ark-to-Lightning pay FSM:
`Created → SwapCreated → FundingInitiated → VHTLCFunded →
WaitingForClaim → Completed` (or `Expired` / `RefundInitiated →
@@ -28,7 +30,10 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
ClaimInitiated → Completed` (or `Expired` / `NeedsIntervention` /
`Failed`). `HTLCEventAccepted` is a durable checkpoint persisted
after the server mailbox event is validated so funding detection
- resumes without re-driving mailbox delivery.
+ resumes without re-driving mailbox delivery. When an
+ `ArkChannelPaymentBridge` is installed, `InvoiceCreated` can also
+ advance straight to `Completed`: the payment settled inside an
+ Ark-backed native Lightning channel, so there is no vHTLC to claim.
- `MailboxOutSwapEventReceiver` — mailbox-backed receiver. Pulls
out-swap HTLC events from a `mailbox/pb` edge keyed by a per-session
mailbox ID derived from the client identity key and payment hash.
@@ -55,9 +60,18 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
- `IncomingVHTLCEventReceiver` — interface for receivers that
handle both Lightning-backed and same-Ark vHTLC events; implemented
by `MailboxOutSwapEventReceiver`.
-- `SettlementType` — `SettlementTypeLightning`, `SettlementTypeInArk`
- (returned in `InSwapConfig` identifying how the server bridges
- payment).
+- `SettlementType` — how a swap is backed: `SettlementTypeLightning`,
+ `SettlementTypeInArk`, `SettlementTypeCredit`, `SettlementTypeMixed`
+ (vHTLC + reserved credit), and `SettlementTypeArkChannel` (the
+ receive settled directly into an active Ark-backed native Lightning
+ channel). Returned in `InSwapConfig` for pay quotes and recorded on
+ `SwapSummary` / `ReceiveResult` for receives.
+- `ArkChannelPaymentBridge` — narrow daemon-owned channel boundary
+ (`PrepareIncomingPayment`, `RegisterIncomingPayment`,
+ `WaitIncomingPayment`) that lets a receive settle over a native
+ Ark channel without the SDK owning invoices or channel state. lnd
+ stays authoritative; the production implementation is
+ `swapclientserver.arkChannelPaymentBridge` over `waved.RPCServer`.
- `Store` — isolated SQLite persistence. Runs its own migration table
(`swap_client_schema_migrations`) separate from the main daemon DB.
- `SwapServerConn` / `GRPCSwapServerConn` — remote swap-server gRPC
@@ -83,7 +97,13 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
- `PayState` / `ReceiveState` — typed FSM enums with `IsTerminal()` /
`String()`. `ReceiveState` includes `ReceiveStateHTLCEventAccepted`.
- `VHTLCConfig`, `InSwapConfig`, `RouteHint` — server-negotiation
- DTOs. `SwapSummary` — flat list view for persisted sessions.
+ DTOs. `SwapSummary` — flat list view for persisted sessions; it
+ also carries `ChannelID` (the Ark channel manifested for a receive,
+ zero when an existing channel was reused) and `ReservedSCID` (the
+ virtual SCID advertised in the invoice route hint).
+- `ReceiveResult` — returned by `ReceiveSession.Wait` / `Claim`;
+ reports `ChannelID` and `SettlementType` alongside the claimed VTXO
+ outpoint so callers can tell a channel settlement from a vHTLC claim.
- `RecoveryPolicy` / `DefaultRecoveryPolicy` — governs auto-escalation
from cooperative vHTLC retry to daemon-owned on-chain recovery
(arm/escalate/cancel via `DaemonConn`'s VHTLC recovery RPCs).
@@ -110,7 +130,8 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
## Relationships
- **Depends on**: `lib/arkscript` (vHTLC policy + claim/refund
- tapscript paths), `sdk/ark` (type aliases), `swaprpc` (gRPC stubs),
+ tapscript paths), `arkchannel` (`arkchannel.ID` returned by the
+ channel bridge), `sdk/ark` (type aliases), `swaprpc` (gRPC stubs),
`vtxo` (forfeit sign-request conversion), `mailbox/pb` (edge
pull/ack), `serverconn` + `serverconn/mailboxpull` (`CompoundMailboxID`,
`PubKeyMailboxID`, mailbox pull backoff), `db` + `db/migrate` +
@@ -134,6 +155,13 @@ same-Ark vHTLC event: if `outEvents` implements
otherwise the flow falls back to `WaitOutSwapHtlc` and converts the
result into an `IncomingVHTLCNotification`.
+When an `ArkChannelPaymentBridge` is installed, `prepareInvoice`
+additionally calls `PrepareIncomingPayment` (before the route quote,
+to install the known-preimage native invoice) and
+`RegisterIncomingPayment` (after it, to bind the quoted final-hop
+SCID at the hub). `waitForChannelOrVHTLC` then races
+`WaitIncomingPayment` against the mailbox vHTLC rail.
+
## Invariants
- `mutateAndPersist` is the only way to change session state — it
@@ -168,6 +196,20 @@ result into an `IncomingVHTLCNotification`.
and durably persisted the event. `AckCursor` is `eventSeq + 1`.
- `ReceiveAuthKey` signing/ECDH is always delegated to the daemon;
the SDK never holds the raw private key for receive-auth.
+- **Exactly one receive rail may reveal the shared preimage.**
+ `waitForChannelOrVHTLC` cancels only its own child context, so a
+ losing local subscription is torn down without failing the swap. A
+ channel-bridge error while the wait context is still live drops that
+ rail and keeps waiting on the vHTLC rail; a vHTLC error is returned.
+- `SettlementTypeArkChannel` is **receive-only**. Both
+ `validateInSwapPreview` and `validateInSwapQuote` reject it for pay
+ quotes rather than trying to price a channel settlement.
+- Receive rows persist `reserved_scid` (8 bytes big-endian, empty when
+ unset) and `channel_id` (32 bytes, empty when unset).
+ `receiveSummaryFromRow` / `receiveSessionFromRow` reject any other
+ width and a negative `channel_backing_fee_sat` rather than silently
+ truncating. `LatestMigrationVersion` is `2`; bump it with every new
+ file in `migrations/`.
- Error sentinels (`ErrSwapExpired`, `ErrSwapRefunded`,
`ErrSwapSummaryNotFound`) are exported; callers use `errors.Is`.
- The credit ledger is server-authoritative; local state only records
diff --git a/sdk/swaps/CLAUDE.md b/sdk/swaps/CLAUDE.md
index a981539e..ec779ada 100644
--- a/sdk/swaps/CLAUDE.md
+++ b/sdk/swaps/CLAUDE.md
@@ -18,7 +18,9 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
- `SwapClient` — top-level entry point. Constructed via `NewSwapClient`
(no persistence) or `NewSwapClientWithStore` (SQLite-backed). Holds
an `OutSwapEventReceiver` overridable via
- `SetOutSwapEventReceiver`.
+ `SetOutSwapEventReceiver`, and an optional
+ `ArkChannelPaymentBridge` installed via
+ `SetArkChannelPaymentBridge`.
- `PaySession` — Ark-to-Lightning pay FSM:
`Created → SwapCreated → FundingInitiated → VHTLCFunded →
WaitingForClaim → Completed` (or `Expired` / `RefundInitiated →
@@ -28,7 +30,10 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
ClaimInitiated → Completed` (or `Expired` / `NeedsIntervention` /
`Failed`). `HTLCEventAccepted` is a durable checkpoint persisted
after the server mailbox event is validated so funding detection
- resumes without re-driving mailbox delivery.
+ resumes without re-driving mailbox delivery. When an
+ `ArkChannelPaymentBridge` is installed, `InvoiceCreated` can also
+ advance straight to `Completed`: the payment settled inside an
+ Ark-backed native Lightning channel, so there is no vHTLC to claim.
- `MailboxOutSwapEventReceiver` — mailbox-backed receiver. Pulls
out-swap HTLC events from a `mailbox/pb` edge keyed by a per-session
mailbox ID derived from the client identity key and payment hash.
@@ -55,9 +60,18 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
- `IncomingVHTLCEventReceiver` — interface for receivers that
handle both Lightning-backed and same-Ark vHTLC events; implemented
by `MailboxOutSwapEventReceiver`.
-- `SettlementType` — `SettlementTypeLightning`, `SettlementTypeInArk`
- (returned in `InSwapConfig` identifying how the server bridges
- payment).
+- `SettlementType` — how a swap is backed: `SettlementTypeLightning`,
+ `SettlementTypeInArk`, `SettlementTypeCredit`, `SettlementTypeMixed`
+ (vHTLC + reserved credit), and `SettlementTypeArkChannel` (the
+ receive settled directly into an active Ark-backed native Lightning
+ channel). Returned in `InSwapConfig` for pay quotes and recorded on
+ `SwapSummary` / `ReceiveResult` for receives.
+- `ArkChannelPaymentBridge` — narrow daemon-owned channel boundary
+ (`PrepareIncomingPayment`, `RegisterIncomingPayment`,
+ `WaitIncomingPayment`) that lets a receive settle over a native
+ Ark channel without the SDK owning invoices or channel state. lnd
+ stays authoritative; the production implementation is
+ `swapclientserver.arkChannelPaymentBridge` over `waved.RPCServer`.
- `Store` — isolated SQLite persistence. Runs its own migration table
(`swap_client_schema_migrations`) separate from the main daemon DB.
- `SwapServerConn` / `GRPCSwapServerConn` — remote swap-server gRPC
@@ -83,7 +97,13 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
- `PayState` / `ReceiveState` — typed FSM enums with `IsTerminal()` /
`String()`. `ReceiveState` includes `ReceiveStateHTLCEventAccepted`.
- `VHTLCConfig`, `InSwapConfig`, `RouteHint` — server-negotiation
- DTOs. `SwapSummary` — flat list view for persisted sessions.
+ DTOs. `SwapSummary` — flat list view for persisted sessions; it
+ also carries `ChannelID` (the Ark channel manifested for a receive,
+ zero when an existing channel was reused) and `ReservedSCID` (the
+ virtual SCID advertised in the invoice route hint).
+- `ReceiveResult` — returned by `ReceiveSession.Wait` / `Claim`;
+ reports `ChannelID` and `SettlementType` alongside the claimed VTXO
+ outpoint so callers can tell a channel settlement from a vHTLC claim.
- `RecoveryPolicy` / `DefaultRecoveryPolicy` — governs auto-escalation
from cooperative vHTLC retry to daemon-owned on-chain recovery
(arm/escalate/cancel via `DaemonConn`'s VHTLC recovery RPCs).
@@ -110,7 +130,8 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/sdk/swap
## Relationships
- **Depends on**: `lib/arkscript` (vHTLC policy + claim/refund
- tapscript paths), `sdk/ark` (type aliases), `swaprpc` (gRPC stubs),
+ tapscript paths), `arkchannel` (`arkchannel.ID` returned by the
+ channel bridge), `sdk/ark` (type aliases), `swaprpc` (gRPC stubs),
`vtxo` (forfeit sign-request conversion), `mailbox/pb` (edge
pull/ack), `serverconn` + `serverconn/mailboxpull` (`CompoundMailboxID`,
`PubKeyMailboxID`, mailbox pull backoff), `db` + `db/migrate` +
@@ -134,6 +155,13 @@ same-Ark vHTLC event: if `outEvents` implements
otherwise the flow falls back to `WaitOutSwapHtlc` and converts the
result into an `IncomingVHTLCNotification`.
+When an `ArkChannelPaymentBridge` is installed, `prepareInvoice`
+additionally calls `PrepareIncomingPayment` (before the route quote,
+to install the known-preimage native invoice) and
+`RegisterIncomingPayment` (after it, to bind the quoted final-hop
+SCID at the hub). `waitForChannelOrVHTLC` then races
+`WaitIncomingPayment` against the mailbox vHTLC rail.
+
## Invariants
- `mutateAndPersist` is the only way to change session state — it
@@ -168,6 +196,20 @@ result into an `IncomingVHTLCNotification`.
and durably persisted the event. `AckCursor` is `eventSeq + 1`.
- `ReceiveAuthKey` signing/ECDH is always delegated to the daemon;
the SDK never holds the raw private key for receive-auth.
+- **Exactly one receive rail may reveal the shared preimage.**
+ `waitForChannelOrVHTLC` cancels only its own child context, so a
+ losing local subscription is torn down without failing the swap. A
+ channel-bridge error while the wait context is still live drops that
+ rail and keeps waiting on the vHTLC rail; a vHTLC error is returned.
+- `SettlementTypeArkChannel` is **receive-only**. Both
+ `validateInSwapPreview` and `validateInSwapQuote` reject it for pay
+ quotes rather than trying to price a channel settlement.
+- Receive rows persist `reserved_scid` (8 bytes big-endian, empty when
+ unset) and `channel_id` (32 bytes, empty when unset).
+ `receiveSummaryFromRow` / `receiveSessionFromRow` reject any other
+ width and a negative `channel_backing_fee_sat` rather than silently
+ truncating. `LatestMigrationVersion` is `2`; bump it with every new
+ file in `migrations/`.
- Error sentinels (`ErrSwapExpired`, `ErrSwapRefunded`,
`ErrSwapSummaryNotFound`) are exported; callers use `errors.Is`.
- The credit ledger is server-authoritative; local state only records
diff --git a/swapclientserver/AGENTS.md b/swapclientserver/AGENTS.md
index 69571f77..19e3f69d 100644
--- a/swapclientserver/AGENTS.md
+++ b/swapclientserver/AGENTS.md
@@ -32,6 +32,11 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
- `receiveSessionAdapter` — Adds method accessors over
`sdk/swaps.ReceiveSession` so both production code and tests share the same
interface without exposing struct fields.
+- `arkChannelPaymentBridge` — Adapts `sdk/swaps.ArkChannelPaymentBridge` to
+ `waved.RPCServer`'s process-owned native lnd channel controller
+ (`PrepareArkChannelIncomingPayment`, `RegisterArkChannelIncomingPayment`,
+ `WaitArkChannelIncomingPayment`) without an in-process gRPC loop. Installed
+ with `SwapClient.SetArkChannelPaymentBridge` during service construction.
- `creditServerBridge` / `creditDaemonBridge` — Adapt the subserver and the
in-process Ark/daemon facade to the `credit` package's `CreditServer` /
`CreditDaemon` interfaces, so the credit durable-actor subsystem reuses this
@@ -46,6 +51,9 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
`MailboxOutSwapEventReceiver` (empty mailbox ID — receiver derives the
per-swap mailbox from client identity + payment hash) on the
`SwapClient` so out-swap HTLC events flow over the mailbox transport,
+ installs the `arkChannelPaymentBridge`, publishes the authenticated
+ swapdk-server mailbox edge as `cfg.Swap.ArkChannelMailbox` (cleared again
+ on cleanup) so `waved`'s Ark-channel process can reuse the same transport,
publishes `cfg.Swap.Backend`/`CreditServer`/`CreditDaemon` bridges,
registers the gRPC subserver, calls `resumePending` (unless
`cfg.Swap.SuppressResume`), and returns a cleanup function.
@@ -69,6 +77,8 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
- **Depends on**: `sdk/swaps` (swap FSM, `SwapClient`, `Store`, session,
credit types), `sdk/ark` (`WrapDaemonServer`, in-process Ark facade),
+ `arkchannel` (`arkchannel.ID` returned by the channel bridge),
+ `mailbox/pb` (mailbox method names for the wait-for-ready interceptor),
`waved` (`RPCServer`, `Config`, `SwapConfig`, `SwapSubsystem`), `credit`
(`CreditServer`/`CreditDaemon` interfaces bridged for the credit actor
subsystem), `rpc/swapclientrpc` (generated gRPC stubs + proto types).
@@ -88,6 +98,13 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
- Worker ownership is process-local and mutex-guarded: at most one goroutine
drives a given payment hash at any time. `markActive` is the admission gate;
`markInactive` releases it on goroutine exit.
+- A process-local resume or `Wait` error is not itself terminal. The worker
+ reads the durable summary and retries while the FSM remains pending; it
+ releases ownership only after terminal state, successful completion, missing
+ durable state, or daemon shutdown. `runSwapWorker` is the shared loop for
+ both directions and paces retries with `swapClientService.workerRetryDelay`
+ (`defaultWorkerRetryDelay`, 1s), kept on the service so tests can shorten it
+ without moving retry policy into the swap FSM.
- The daemon uses `rootCtx` (not the individual RPC contexts) for all
`ResumePayViaLightning` / `ResumeReceiveViaLightning` calls. A CLI
disconnect does not cancel an admitted swap.
@@ -128,6 +145,15 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
"no filesystem here". It mirrors `waved.ensureDataDir`; keep the two in
step, since a Node host given an unwritable path should fail there rather
than at the first database open.
+- `swapServerOperationWaitsForReady` gates gRPC `WaitForReady` on methods that
+ create or advance protocol state. Mailbox `Send` is in that set — a receive
+ ack must survive a swapdk-server reconnect — while mailbox `Pull`, quotes,
+ and credit snapshots stay fail-fast so a poll loop does not block on a dead
+ connection. Add new state-advancing swap or mailbox RPCs to the switch.
+- `SetArkChannelPaymentBridge`, like `SetOutSwapEventReceiver`, must be
+ installed before `resumePending`: the bridge decides whether a receive races
+ the native channel rail against the mailbox vHTLC rail, so a late install
+ would leave revived sessions on the mailbox-only path.
- `SetOutSwapEventReceiver` must run before any receive worker is started:
`SwapClient` captures the receiver into the per-swap worker at start time,
so a late install would leave already-running workers using whatever
diff --git a/swapclientserver/CLAUDE.md b/swapclientserver/CLAUDE.md
index 3277b4db..19e3f69d 100644
--- a/swapclientserver/CLAUDE.md
+++ b/swapclientserver/CLAUDE.md
@@ -32,6 +32,11 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
- `receiveSessionAdapter` — Adds method accessors over
`sdk/swaps.ReceiveSession` so both production code and tests share the same
interface without exposing struct fields.
+- `arkChannelPaymentBridge` — Adapts `sdk/swaps.ArkChannelPaymentBridge` to
+ `waved.RPCServer`'s process-owned native lnd channel controller
+ (`PrepareArkChannelIncomingPayment`, `RegisterArkChannelIncomingPayment`,
+ `WaitArkChannelIncomingPayment`) without an in-process gRPC loop. Installed
+ with `SwapClient.SetArkChannelPaymentBridge` during service construction.
- `creditServerBridge` / `creditDaemonBridge` — Adapt the subserver and the
in-process Ark/daemon facade to the `credit` package's `CreditServer` /
`CreditDaemon` interfaces, so the credit durable-actor subsystem reuses this
@@ -46,6 +51,9 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
`MailboxOutSwapEventReceiver` (empty mailbox ID — receiver derives the
per-swap mailbox from client identity + payment hash) on the
`SwapClient` so out-swap HTLC events flow over the mailbox transport,
+ installs the `arkChannelPaymentBridge`, publishes the authenticated
+ swapdk-server mailbox edge as `cfg.Swap.ArkChannelMailbox` (cleared again
+ on cleanup) so `waved`'s Ark-channel process can reuse the same transport,
publishes `cfg.Swap.Backend`/`CreditServer`/`CreditDaemon` bridges,
registers the gRPC subserver, calls `resumePending` (unless
`cfg.Swap.SuppressResume`), and returns a cleanup function.
@@ -69,6 +77,8 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
- **Depends on**: `sdk/swaps` (swap FSM, `SwapClient`, `Store`, session,
credit types), `sdk/ark` (`WrapDaemonServer`, in-process Ark facade),
+ `arkchannel` (`arkchannel.ID` returned by the channel bridge),
+ `mailbox/pb` (mailbox method names for the wait-for-ready interceptor),
`waved` (`RPCServer`, `Config`, `SwapConfig`, `SwapSubsystem`), `credit`
(`CreditServer`/`CreditDaemon` interfaces bridged for the credit actor
subsystem), `rpc/swapclientrpc` (generated gRPC stubs + proto types).
@@ -91,7 +101,10 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
- A process-local resume or `Wait` error is not itself terminal. The worker
reads the durable summary and retries while the FSM remains pending; it
releases ownership only after terminal state, successful completion, missing
- durable state, or daemon shutdown.
+ durable state, or daemon shutdown. `runSwapWorker` is the shared loop for
+ both directions and paces retries with `swapClientService.workerRetryDelay`
+ (`defaultWorkerRetryDelay`, 1s), kept on the service so tests can shorten it
+ without moving retry policy into the swap FSM.
- The daemon uses `rootCtx` (not the individual RPC contexts) for all
`ResumePayViaLightning` / `ResumeReceiveViaLightning` calls. A CLI
disconnect does not cancel an admitted swap.
@@ -132,6 +145,15 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`.
"no filesystem here". It mirrors `waved.ensureDataDir`; keep the two in
step, since a Node host given an unwritable path should fail there rather
than at the first database open.
+- `swapServerOperationWaitsForReady` gates gRPC `WaitForReady` on methods that
+ create or advance protocol state. Mailbox `Send` is in that set — a receive
+ ack must survive a swapdk-server reconnect — while mailbox `Pull`, quotes,
+ and credit snapshots stay fail-fast so a poll loop does not block on a dead
+ connection. Add new state-advancing swap or mailbox RPCs to the switch.
+- `SetArkChannelPaymentBridge`, like `SetOutSwapEventReceiver`, must be
+ installed before `resumePending`: the bridge decides whether a receive races
+ the native channel rail against the mailbox vHTLC rail, so a late install
+ would leave revived sessions on the mailbox-only path.
- `SetOutSwapEventReceiver` must run before any receive worker is started:
`SwapClient` captures the receiver into the per-swap worker at start time,
so a late install would leave already-running workers using whatever
diff --git a/swaprpc/AGENTS.md b/swaprpc/AGENTS.md
index c6c377ca..e8710cd5 100644
--- a/swaprpc/AGENTS.md
+++ b/swaprpc/AGENTS.md
@@ -88,6 +88,10 @@ because they are part of the wire contract, not of either side's logic.
- `SettlementType.SETTLEMENT_TYPE_UNSPECIFIED` (0) is treated as Lightning
for backward compatibility with older server responses; do not repurpose
the zero value.
+- `RequestChannelIdRequest` field 7 and `RequestChannelIdResponse` field 10
+ are `reserved` under the retired name `channel_backing_fee_sat` — receives
+ no longer negotiate a legacy channel fee reserve on the wire. Reserved
+ numbers and names are part of the wire contract; never reuse them.
- `SwapMailboxEvent` is a proto oneof: read the populated variant, don't
assume `OutSwapHtlcEvent` is the only case as new event kinds are added.
diff --git a/swaprpc/CLAUDE.md b/swaprpc/CLAUDE.md
index c6c377ca..e8710cd5 100644
--- a/swaprpc/CLAUDE.md
+++ b/swaprpc/CLAUDE.md
@@ -88,6 +88,10 @@ because they are part of the wire contract, not of either side's logic.
- `SettlementType.SETTLEMENT_TYPE_UNSPECIFIED` (0) is treated as Lightning
for backward compatibility with older server responses; do not repurpose
the zero value.
+- `RequestChannelIdRequest` field 7 and `RequestChannelIdResponse` field 10
+ are `reserved` under the retired name `channel_backing_fee_sat` — receives
+ no longer negotiate a legacy channel fee reserve on the wire. Reserved
+ numbers and names are part of the wire contract; never reuse them.
- `SwapMailboxEvent` is a proto oneof: read the populated variant, don't
assume `OutSwapHtlcEvent` is the only case as new event kinds are added.What drifted
No changes proposed for How to applySave the diff above to a file and Either way, finish with This check is advisory and never fails the build. |
Summary
This is layer 5 of 5 in the Ark channels stack and depends on #1192. The swapdk-server operator stack consumes this tip.
LND dependency
The series pins sputn1ck/lnd:kon/modular-channel-runtime as a Go module. No nested lnd submodule or lnd PR is required.
Testing
go test ./oor ./arkchannel/... ./lnruntime ./waved ./dbmake buildmake lint-changed-local