Skip to content

waved: Operate native Ark channels - #1192

Open
sputn1ck wants to merge 5 commits into
kon/ark-channel-lnd-runtimefrom
kon/ark-channel-daemon
Open

waved: Operate native Ark channels#1192
sputn1ck wants to merge 5 commits into
kon/ark-channel-lnd-runtimefrom
kon/ark-channel-daemon

Conversation

@sputn1ck

@sputn1ck sputn1ck commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

  • wire one native Ark channel endpoint into the Wavelength daemon
  • expose promotion, payment, receive, cooperative Ark settlement, and force-close controls over authenticated RPC and CLI
  • register source watchers and recovery callbacks with the daemon-owned unroller
  • extend the harness and run guide for real multi-daemon channel flows

This is layer 4 of 5 in the Ark channels stack. It depends on #1191; the final layer is #1139.

Testing

  • go test ./waved ./waverpc ./cmd/wavecli/waveclicommands ./rpc/restclient ./harness
  • make build
  • make lint-changed-local

Copilot AI lite review requested due to automatic review settings August 24, 2026 14:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Ark channel daemon support and OOR recovery/export surfaces to waved/wavecli, wiring new gRPC + REST endpoints, durable channel lifecycle control, and on-chain recovery/watch infrastructure.

Changes:

  • Extend waverpc.DaemonService with Ark-channel OOR preparation/commit/abort RPCs and an OOR recovery-package export RPC (plus REST gateway + mailbox + REST client wiring).
  • Introduce Ark channel runtime/process wiring in waved (controller startup/shutdown, source archive + watcher, pre-PONR expiry maintenance) with accompanying tests.
  • Add a hidden wavecli channel subtree and update CLI docs and dev-RPC registry.

Reviewed changes

Copilot reviewed 33 out of 35 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
waverpc/daemon.yaml Add HTTP REST mappings for new daemon RPCs
waverpc/daemon.proto Add new daemon RPCs/messages; add identity_key to NewReceiveScriptRequest
waverpc/daemon.pb.gw.go Regenerated gateway handlers for new endpoints
waverpc/daemon_mailboxrpc.pb.go Regenerated mailbox RPC stubs for new methods
waverpc/daemon_grpc.pb.go Regenerated gRPC stubs for new methods
waved/vhtlc_recovery_target.go Persist ConstructionVersion in recovery descriptors
waved/server.go Wire ArkChannelService registration + runtime lifecycle/shutdown hooks
waved/rpc_oor_receive.go Support identity-key-backed receive script registration
waved/rpc_auth.go Add macaroon entity/permissions for channel RPCs
waved/rpc_auth_test.go Extend permissions test coverage for new RPC surface
waved/rpc_ark_channel_recovery.go Add daemon RPC to export local OOR recovery package
waved/rpc_ark_channel_oor.go Add daemon RPCs for preparing/validating/committing/aborting Ark-channel OOR
waved/native_ark_channel_controller_test.go Add tests for channel restore/watch/resume helpers
waved/logging.go Register additional lnd subsystems with log manager
waved/config.go Add process-wiring mailbox client for channel runtime
waved/ark_channel_source.go Implement channel recovery archive + lineage verification + descriptor reconstruction
waved/ark_channel_source_watcher.go Add passive chain-spend watcher that retries durable channel events
waved/ark_channel_source_watcher_test.go Tests for watcher coverage and retry behavior
waved/ark_channel_source_test.go Tests for recovery roots/descriptor/operator-key handling
waved/ark_channel_recovery_runtime.go Add standalone recovery runtime composition (unroll + watches)
waved/ark_channel_process.go Add Ark channel process wiring + ArkChannelService gRPC server
waved/ark_channel_process_test.go Tests for ArkChannelService RPC behavior/unavailable states
waved/ark_channel_oor.go Implement wallet selection + OOR preparation + reconciliation cleanup
waved/ark_channel_expiry.go Add pre-PONR maintenance loop to expire/abort abandoned preparations
waved/ark_channel_expiry_test.go Tests for pre-PONR expiry/abort behavior
rpc/restclient/clients.go Add REST client methods for new daemon endpoints
harness/harness.go Adjust channel setup parameters (explicit Private: false)
docs/daemon_cli_guide.md Document new hidden channel CLI subtree and behavior
cmd/wavecli/waveclicommands/root.go Register channel subtree under advanced commands
cmd/wavecli/waveclicommands/root_groups_test.go Update tests for advanced command visibility/grouping
cmd/wavecli/waveclicommands/devrpc/registry_generated.go Regenerate dev-rpc registry for new daemon methods
cmd/wavecli/waveclicommands/cmd_channel.go Add wavecli channel commands and channel-id parsing
cmd/wavecli/waveclicommands/cmd_channel_test.go Tests for channel-id and amount parsing
Files not reviewed (1)
  • cmd/wavecli/waveclicommands/devrpc/registry_generated.go: Generated file
Suppressed comments (2)

waved/ark_channel_oor.go:211

  • When PrepareChannel reports Existing=true, the selected VTXOs should be unlocked even if the request ctx is already canceled. Using ctx here can prevent the unlock Tell from being delivered, leaving locks behind.
	if prepared.Existing {
		rpcServer.unlockSelectedVTXOsBestEffort(ctx, locked)
	}

waved/rpc_ark_channel_oor.go:62

  • This gRPC handler returns a plain fmt.Errorf for invalid request terms, which maps to codes.Unknown. Prefer returning codes.InvalidArgument via status.Error/status.Errorf for consistent client-visible semantics.
	if terms.Funder != arkchannel.PartyHub ||
		terms.Kind != arkchannel.KindReceiveIntent {
		return nil, fmt.Errorf("daemon channel OOR must fund a " +
			"receive intent")
	}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread waved/ark_channel_oor.go
Comment on lines +87 to +91
fail := func(err error) (arkchannel.VTXOBinding, error) {
rpcServer.unlockSelectedVTXOsBestEffort(ctx, locked)

return arkchannel.VTXOBinding{}, err
}
Comment thread waved/server.go
Comment on lines +1526 to +1535
defer func() {
if runtime := s.getArkChannelMailboxRuntime(); runtime != nil {
shutdownCtx, cancel := context.WithTimeout(
context.Background(), DefaultShutdownTimeout,
)
defer cancel()

_ = runtime.StopAndWait(shutdownCtx)
}
}()
Comment on lines +17 to +19
if r == nil || r.server == nil || r.server.vtxoStore == nil {
return nil, fmt.Errorf("OOR recovery exporter is unavailable")
}
Comment on lines +27 to +31
if terms.Funder != arkchannel.PartyHub ||
terms.Kind != arkchannel.KindReceiveIntent {
return nil, fmt.Errorf("daemon channel OOR must fund a " +
"receive intent")
}
Comment on lines +79 to +81
return nil, fmt.Errorf("unknown channel OOR preparation "+
"status %d", lookup.Status)
}
Comment on lines +153 to +155
if req.GetReason() == "" {
return nil, fmt.Errorf("channel OOR abort reason is required")
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 070cf3dfa4

ℹ️ 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".

Comment on lines +361 to +362
if s.cfg.Swap == nil || s.cfg.Swap.ArkChannelMailbox == nil {
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Publish the channel mailbox before actor startup

initArkChannelProcess always takes this early return in the shipped daemon: a repo-wide search finds no assignment to SwapConfig.ArkChannelMailbox, while swapclientserver.Register only publishes Backend, CreditServer, and CreditDaemon in swapclientserver/service.go:304-315. Consequently no channel controller is constructed and every newly exposed wavecli channel RPC returns Unavailable; publish swapClients.mailbox into the config before wallet-dependent actors start.

Useful? React with 👍 / 👎.

Comment thread waved/ark_channel_oor.go
}
rpcServer := &RPCServer{server: s}
fail := func(err error) (arkchannel.VTXOBinding, error) {
rpcServer.unlockSelectedVTXOsBestEffort(ctx, locked)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a live bounded context for VTXO unlocks

If the request is canceled after wallet selection—such as while BuildTransferInputs, change construction, or OOR preparation is running—the failure path passes that canceled context to Tell, so UnlockVTXOsRequest is rejected and the selected coins remain locked. Detach this cleanup from the request and bound it with a fresh timeout, as the existing submitted-OOR cleanup path does.

AGENTS.md reference: AGENTS.md:L77-L81

Useful? React with 👍 / 👎.

Comment on lines +1275 to +1277
record, err := c.service.GetChannel(ctx, preparation.ChannelID)
if err != nil {
return LightningPaymentResult{}, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cancel the remote preparation when local lookup fails

Once PrepareOutgoingPayment succeeds, the operator owns a durable hold/payment worker, but a transient local GetChannel failure returns here without calling CancelOutgoingPayment; the SCID-mismatch return has the same problem. The only cancellation currently occurs for the later native payment error, so these earlier failures leave the remote preparation active until its own expiry or recovery; arrange cleanup for every failure after preparation.

Useful? React with 👍 / 👎.

Comment on lines +1287 to +1289
cancelErr := c.paymentPeer.CancelOutgoingPayment(
ctx, preparation.PaymentHash, err.Error(),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detach outgoing-payment cancellation from the request

When PayInvoiceResult fails because the RPC deadline expired or its caller disconnected, ctx is already canceled, so this mailbox cancellation fails immediately and cannot release the operator's hold/payment worker. Run this required cleanup with a fresh bounded context that survives request cancellation.

AGENTS.md reference: AGENTS.md:L77-L81

Useful? React with 👍 / 👎.

Comment thread waved/rpc_oor_receive.go
var keyDesc *keychain.KeyDescriptor
var pkScript []byte
if req.GetIdentityKey() {
identityKey := r.server.clientKeyDesc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read the identity descriptor through its lock

For an identity_key request, this accesses clientKeyDesc directly rather than through loadClientKeyDesc, so any overlap with startup publication creates an unsynchronized read/write and may observe an incomplete descriptor. Use the established accessor for this new RPC path.

AGENTS.md reference: waved/AGENTS.md:L284-L290

Useful? React with 👍 / 👎.

@litbot-9000

litbot-9000 commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Doc gardening: scoped drift advisory

This PR's Go changes leave the per-package docs for waved, waverpc,
cmd/wavecli/waveclicommands, and harness behind the code — the
native Ark channel runtime and its startup/shutdown ordering, the six new
DaemonService channel-funding RPCs plus identity_key receive scripts, the
new channel macaroon entity and hidden channel CLI subtree, and the
announced-channel harness change are all undocumented today. Proposed
reconciliation below (each CLAUDE.md plus its byte-identical AGENTS.md
mirror).

cmd/wavecli/waveclicommands/devrpc and rpc/restclient were also in scope
but need no change: both docs describe their surfaces generically, and the new
methods land inside descriptions that are still accurate.

Proposed diff — 354 insertions, 24 deletions across 8 files
diff --git a/cmd/wavecli/waveclicommands/AGENTS.md b/cmd/wavecli/waveclicommands/AGENTS.md
index fa9b16a5..6435fd27 100644
--- a/cmd/wavecli/waveclicommands/AGENTS.md
+++ b/cmd/wavecli/waveclicommands/AGENTS.md
@@ -16,15 +16,16 @@ default `--help` face:
    wavewalletrpc-backed.
 2. **Daemon introspection (group "Introspection")** — getinfo, schema, mcp
    (the built-in `help` command is grouped here too).
-3. **Advanced subtrees (`ark`, `dev`, `recovery`)** — raw
-   waverpc/devrpc commands for power users and operator runbooks.
+3. **Advanced subtrees (`ark`, `channel`, `dev`, `recovery`)** — raw
+   waverpc/arkchannelrpc/devrpc commands for power users and operator
+   runbooks.
    Hidden from the default `--help` via cobra `Hidden` (not a build tag),
    so they stay compiled and fully runnable in the shipped binary;
    `WAVELENGTH_DEV=1` reveals them under an "Advanced" group. The env var only
    changes visibility — it never gates execution. `ark.*` stays on the
    `schema`/MCP surfaces; `dev` is reachable only as the generated (hidden)
-   CLI subtree — it is not registered in `schema`/MCP — and `recovery` is
-   exposed on neither.
+   CLI subtree — it is not registered in `schema`/MCP — and `channel` and
+   `recovery` are exposed on neither.
 
 The `swap.*` verbs were retired: `send`/`recv --offchain` and `activity`
 cover them (the swapruntime daemon runtime that powers those verbs is
@@ -72,6 +73,22 @@ who want direct access.
 | `ark listtransactions` | `ListTransactions` | Raw paginated transaction history |
 | `ark send {inround,oor}` | `SendVTXO` / `SendOOR` | Raw in-round / OOR send; real transfers require interactive approval or `--yes` |
 
+### `channel.*` advanced commands
+
+Development control surface for native Ark-backed Lightning channels,
+served by `arkchannelrpc.ArkChannelService` on the same daemon listener.
+Every verb prints the raw proto JSON response.
+
+| Command | RPC | Description |
+|---------|-----|-------------|
+| `channel create <amount-sat>` | `PromoteVTXO` | Promote wallet value into a channel; capacity is the only input — the daemon owns OOR preparation, backing reserve, and activation |
+| `channel get <channel-id>` | `GetChannel` | One durable channel snapshot |
+| `channel send <channel-id> <amount-sat>` | `SendPayment` | Pay a hub invoice over one active channel |
+| `channel receive <channel-id> <amount-sat>` | `ReceivePayment` | Create and settle a client invoice over one active channel |
+| `channel pay <bolt11>` | `PayLightningInvoice` | Bridge a private HTLC into the operator's public Lightning payment; `--max-fee-sat` (default 100000) caps routing fees |
+| `channel close <channel-id>` | `RequestCooperativeClose` | Start or resume the client-owned cooperative close |
+| `channel force-close <channel-id>` | `MaterializeAndForceClose` | Publish the Ark ancestry, signed backing, and latest commitment |
+
 ### `recovery.*` advanced commands
 
 Manual control of daemon-owned vHTLC recovery rows; normal swap clients
@@ -136,6 +153,8 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/cmd/wave
   - `rpc/wavewalletrpc` (generated stubs for the top-level wallet verbs;
     `WalletService` / `WalletInspectionService` clients).
   - `waverpc` (generated stubs for `ark.*`, `recovery.*`, getinfo).
+  - `rpc/arkchannelrpc` (generated `ArkChannelService` stubs for
+    `channel.*`).
   - `cmd/wavecli/waveclicommands/devrpc` (the generated `dev`
     subtree; its registry references `swapclientrpc` service
     descriptors).
@@ -182,6 +201,18 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/cmd/wave
   which flag to drop and why, since stating the constraint alone invites
   dropping `--onchain-address` and silently getting the more dangerous
   unilateral exit (`swapwallet.forceUnroll` carries the same wording).
+- `channel.*` dials `ArkChannelService` through the same
+  `getDaemonConn` transport as every other verb, via the
+  `getArkChannelClient` seam that command-wiring tests replace.
+  `parseChannelID` accepts a channel ID as either 32-byte hex or the
+  base64 form protobuf JSON prints, so an ID copied straight out of a
+  previous command's output round-trips; anything that does not decode to
+  exactly 32 bytes is rejected. Amounts must parse as strictly positive
+  int64.
+- `channel.*` verbs are NOT confirmation-gated and are NOT two-phase —
+  `create`, `send`, `pay`, `close`, and `force-close` all move funds in a
+  single call. They stay off the `schema`/MCP surfaces for that reason;
+  the gate is that the subtree is development-only.
 - `recovery escalate` refuses to run on non-interactive stdin unless
   `--yes` is passed — it never blocks on a y/N prompt an agent can't
   answer.
diff --git a/cmd/wavecli/waveclicommands/CLAUDE.md b/cmd/wavecli/waveclicommands/CLAUDE.md
index fa9b16a5..6435fd27 100644
--- a/cmd/wavecli/waveclicommands/CLAUDE.md
+++ b/cmd/wavecli/waveclicommands/CLAUDE.md
@@ -16,15 +16,16 @@ default `--help` face:
    wavewalletrpc-backed.
 2. **Daemon introspection (group "Introspection")** — getinfo, schema, mcp
    (the built-in `help` command is grouped here too).
-3. **Advanced subtrees (`ark`, `dev`, `recovery`)** — raw
-   waverpc/devrpc commands for power users and operator runbooks.
+3. **Advanced subtrees (`ark`, `channel`, `dev`, `recovery`)** — raw
+   waverpc/arkchannelrpc/devrpc commands for power users and operator
+   runbooks.
    Hidden from the default `--help` via cobra `Hidden` (not a build tag),
    so they stay compiled and fully runnable in the shipped binary;
    `WAVELENGTH_DEV=1` reveals them under an "Advanced" group. The env var only
    changes visibility — it never gates execution. `ark.*` stays on the
    `schema`/MCP surfaces; `dev` is reachable only as the generated (hidden)
-   CLI subtree — it is not registered in `schema`/MCP — and `recovery` is
-   exposed on neither.
+   CLI subtree — it is not registered in `schema`/MCP — and `channel` and
+   `recovery` are exposed on neither.
 
 The `swap.*` verbs were retired: `send`/`recv --offchain` and `activity`
 cover them (the swapruntime daemon runtime that powers those verbs is
@@ -72,6 +73,22 @@ who want direct access.
 | `ark listtransactions` | `ListTransactions` | Raw paginated transaction history |
 | `ark send {inround,oor}` | `SendVTXO` / `SendOOR` | Raw in-round / OOR send; real transfers require interactive approval or `--yes` |
 
+### `channel.*` advanced commands
+
+Development control surface for native Ark-backed Lightning channels,
+served by `arkchannelrpc.ArkChannelService` on the same daemon listener.
+Every verb prints the raw proto JSON response.
+
+| Command | RPC | Description |
+|---------|-----|-------------|
+| `channel create <amount-sat>` | `PromoteVTXO` | Promote wallet value into a channel; capacity is the only input — the daemon owns OOR preparation, backing reserve, and activation |
+| `channel get <channel-id>` | `GetChannel` | One durable channel snapshot |
+| `channel send <channel-id> <amount-sat>` | `SendPayment` | Pay a hub invoice over one active channel |
+| `channel receive <channel-id> <amount-sat>` | `ReceivePayment` | Create and settle a client invoice over one active channel |
+| `channel pay <bolt11>` | `PayLightningInvoice` | Bridge a private HTLC into the operator's public Lightning payment; `--max-fee-sat` (default 100000) caps routing fees |
+| `channel close <channel-id>` | `RequestCooperativeClose` | Start or resume the client-owned cooperative close |
+| `channel force-close <channel-id>` | `MaterializeAndForceClose` | Publish the Ark ancestry, signed backing, and latest commitment |
+
 ### `recovery.*` advanced commands
 
 Manual control of daemon-owned vHTLC recovery rows; normal swap clients
@@ -136,6 +153,8 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/cmd/wave
   - `rpc/wavewalletrpc` (generated stubs for the top-level wallet verbs;
     `WalletService` / `WalletInspectionService` clients).
   - `waverpc` (generated stubs for `ark.*`, `recovery.*`, getinfo).
+  - `rpc/arkchannelrpc` (generated `ArkChannelService` stubs for
+    `channel.*`).
   - `cmd/wavecli/waveclicommands/devrpc` (the generated `dev`
     subtree; its registry references `swapclientrpc` service
     descriptors).
@@ -182,6 +201,18 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/cmd/wave
   which flag to drop and why, since stating the constraint alone invites
   dropping `--onchain-address` and silently getting the more dangerous
   unilateral exit (`swapwallet.forceUnroll` carries the same wording).
+- `channel.*` dials `ArkChannelService` through the same
+  `getDaemonConn` transport as every other verb, via the
+  `getArkChannelClient` seam that command-wiring tests replace.
+  `parseChannelID` accepts a channel ID as either 32-byte hex or the
+  base64 form protobuf JSON prints, so an ID copied straight out of a
+  previous command's output round-trips; anything that does not decode to
+  exactly 32 bytes is rejected. Amounts must parse as strictly positive
+  int64.
+- `channel.*` verbs are NOT confirmation-gated and are NOT two-phase —
+  `create`, `send`, `pay`, `close`, and `force-close` all move funds in a
+  single call. They stay off the `schema`/MCP surfaces for that reason;
+  the gate is that the subtree is development-only.
 - `recovery escalate` refuses to run on non-interactive stdin unless
   `--yes` is passed — it never blocks on a y/N prompt an agent can't
   answer.
diff --git a/harness/AGENTS.md b/harness/AGENTS.md
index 2ac7ee2f..6be2bd71 100644
--- a/harness/AGENTS.md
+++ b/harness/AGENTS.md
@@ -48,6 +48,11 @@ helpers for end-to-end tests.
   primary LND node resyncs to the new tip.
 - `LNDRequireInterceptor` only applies to the primary LND node; additional
   nodes started via `StartAdditionalLND*` never set it.
+- `SetupChannelBetween` opens **announced** channels (`Private: false`,
+  set explicitly rather than left to the proto zero value). Tests that
+  route through a third node need the channel in the network graph, and
+  relying on the default would silently flip them to private if lnd ever
+  changed it.
 - Container teardown (`Stop`) is guarded by `sync.Once`; a signal handler
   also calls `Stop` as a safety net against orphaned containers.
 
diff --git a/harness/CLAUDE.md b/harness/CLAUDE.md
index 2ac7ee2f..6be2bd71 100644
--- a/harness/CLAUDE.md
+++ b/harness/CLAUDE.md
@@ -48,6 +48,11 @@ helpers for end-to-end tests.
   primary LND node resyncs to the new tip.
 - `LNDRequireInterceptor` only applies to the primary LND node; additional
   nodes started via `StartAdditionalLND*` never set it.
+- `SetupChannelBetween` opens **announced** channels (`Private: false`,
+  set explicitly rather than left to the proto zero value). Tests that
+  route through a third node need the channel in the network graph, and
+  relying on the default would silently flip them to private if lnd ever
+  changed it.
 - Container teardown (`Stop`) is guarded by `sync.Once`; a signal handler
   also calls `Stop` as a safety net against orphaned containers.
 
diff --git a/waved/AGENTS.md b/waved/AGENTS.md
index 8caf29d4..4cbaf9c3 100644
--- a/waved/AGENTS.md
+++ b/waved/AGENTS.md
@@ -14,7 +14,9 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   server, and `ActorSystem`. Caches `localMailboxID` (pubkey-derived),
   `authSigHex` (Schnorr auth), `clientKeyDesc` (the stable daemon identity
   descriptor, behind `clientKeyDescMu`), `mailboxAuthSigs` (per-recipient
-  mailbox auth signature memo, behind `mailboxAuthSigsMu`), and a single
+  mailbox auth signature memo, behind `mailboxAuthSigsMu`), the Ark channel
+  process triple (`arkChannelController`, `arkChannelMailboxRuntime`,
+  `arkChannelPeerIngress`, all behind `arkChannelMu`), and a single
   `clk` (`clock.Clock`) shared by all sub-stores for deterministic time
   injection.
 - `RPCServer` — implements the gRPC `DaemonService`. Most write RPCs
@@ -22,7 +24,12 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   validate input locally then `Ask` the relevant actor; `GetRound` and
   `ListVTXOs` merge live actor state with persisted SQL rows, while
   `GetFeeHistory` and `ListTransactions` are pure SQL reads
-  (`rpc_fees.go`).
+  (`rpc_fees.go`). The channel-funding RPCs (`PrepareArkChannelOOR`,
+  `LookupPreparedArkChannelOOR`, `ValidatePreparedArkChannelOOR`,
+  `CommitPreparedArkChannelOOR`, `AbortPreparedArkChannelOOR` in
+  `rpc_ark_channel_oor.go`) and `ExportOORRecoveryPackage`
+  (`rpc_ark_channel_recovery.go`) drive the `arkchannel/oorbridge` and
+  recovery-archive seams instead.
 - `Config` — daemon configuration: wallet backend selection, mailbox/chain
   backend wiring, `OORConfig`/`OORLimitsConfig` (receive safety caps),
   `UnrollConfig` (unilateral-exit fee-bump cadence and cap), and
@@ -33,6 +40,42 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   post-unlock recovery hook.
 - `UnrollConfig` / `OORConfig` — subsystem tunables; see `Config.Validate()`
   for the invariants each enforces.
+- `ArkChannelController` — the local process boundary published behind
+  `arkchannelrpc.ArkChannelService`. It composes
+  `ArkChannelLifecycleController` (`PromoteVTXO`,
+  `MaterializeAndForceClose`, `RequestCooperativeClose`, `GetChannel`,
+  `PeerMessageHandler`, `Stop`) and `ArkChannelPaymentController`
+  (`SendPayment`, `ReceivePayment`, `PayLightningInvoice`, plus the
+  incoming-payment `Prepare`/`Register`/`Wait` triple the swap subserver
+  calls through `RPCServer.*ArkChannelIncomingPayment`).
+- `NativeArkChannelController` — the concrete implementation, owning one
+  `arkchannel` FSM and one modular lnd endpoint. Built either as a lazy
+  client endpoint (`NewClientArkChannelController`, which loads hub policy
+  on the first lifecycle request) or eagerly as the hub endpoint
+  (`NewHubArkChannelController` over `HubArkChannelControllerConfig`, whose
+  advertised policy comes from `NewHubFundingPeerInfo`).
+  `LightningPaymentResult` is what `PayLightningInvoice` returns.
+- `ArkChannelControllerConfig` — the process-owned dependency set supplied
+  after wallet, database, and authenticated swap-server transport startup:
+  channel store, peer transport, `lwwallet`, chain backend/notifier/fee
+  estimator, the OOR and unroll bridges, identity key, and the funding
+  seams below.
+- `ArkChannelOORPreparer` / `ArkChannelOORLookup` /
+  `ArkChannelReceiveCapitalReserver` — funding callbacks the controller
+  invokes; `Server.prepareArkChannelOOR` and `Server.lookupArkChannelOOR`
+  supply the first two, and only the hub supplies the third.
+- `ArkChannelRecoveryController` — the endpoint-local source archive, spend
+  watcher, and unroll-preparation boundary
+  (`lnruntime.ChannelRecoveryManager` +
+  `arkchannel.ChannelEventSinkBinder` + `unrollbridge.SourcePreparer`),
+  implemented by `arkChannelRecoveryArchive` over
+  `arkChannelSourceWatcher`.
+- `ArkChannelRecoveryRuntime` / `ArkChannelRecoveryRuntimeConfig` — the same
+  unroller, package archive, and channel materializer composed for a host
+  that owns its own database, chain backend, and wallet (swapd's hub
+  endpoint). `ArkChannelUnrollWallet` (built by
+  `NewLNDArkChannelUnrollWallet`) is the shared `txconfirm.Wallet` +
+  `unroll.SweepWallet` surface it needs.
 
 ## Relationships
 
@@ -40,7 +83,9 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   `chainsource`, `lib/actormsg`, `db`, `ledger`, `round`, `txconfirm`,
   `unroll`, `vtxo`, `wallet`, `walletcore`, `oor`, `serverconn`, `indexer`,
   `arkrpc`, `lndbackend`, `fraud`, `gateway`, `rpc/restclient`,
-  `vhtlcrecovery`, `vhtlcrecovery/coordinator`, `vhtlcrecovery/unrollpolicy`.
+  `vhtlcrecovery`, `vhtlcrecovery/coordinator`, `vhtlcrecovery/unrollpolicy`,
+  `arkchannel`, `arkchannel/oorbridge`, `arkchannel/unrollbridge`,
+  `lnruntime`, `rpc/arkchannelrpc`, `chainfees`.
 - **Depended on by**: `cmd/waved`.
 
 ## Invariants
@@ -117,9 +162,68 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   code must not call `clock.NewDefaultClock()` directly, use `s.clk`.
 - Actor startup order in `startWalletDependentActors`: VTXO manager, then
   round actor, then the unroll subsystem (`initUnrollSubsystem`), then the
-  OOR actor (`initOORActor`). The VTXO manager is constructed with a
+  OOR actor (`initOORActor`), then the Ark channel process
+  (`initArkChannelProcess`). The VTXO manager is constructed with a
   `vtxo.LazyChainResolver` placeholder that `initUnrollSubsystem` fills in
   later; anything needing that seam must run after `initUnrollSubsystem`.
+  The channel process is last because it needs the durable channel store,
+  the swap-server mailbox edge, and every wallet-owned native dependency.
+- The Ark channel runtime is **opt-in on transport, not on a user flag**:
+  `initArkChannelProcess` returns early unless `SwapConfig.ArkChannelMailbox`
+  is set, which `swapclientserver` publishes as process wiring
+  (`mapstructure:"-"`) and no config file can supply. When it is set the
+  daemon stands up a dedicated `serverconn.Runtime`
+  (`arkchannel-serverconn-<localMailboxID>`) over that authenticated edge,
+  an `lnruntime.PeerMessageIngress` actor for durable BOLT peer messages,
+  and the controller, then publishes all three together under
+  `arkChannelMu` via `setArkChannelProcess`.
+- Channel shutdown order is peer ingress → controller → mailbox runtime,
+  and it runs **before** the main `serverconn` runtime and
+  `actorSystem.Shutdown()`. The mailbox runtime's deferred stop in
+  `runInner` is registered *after* the swap registrar's cleanup defers so
+  it runs first: the channel transport borrows the registrar's edge and
+  must not outlive it.
+- `arkChannelRPCServer` serves `arkchannelrpc.ArkChannelService` on the same
+  authenticated gRPC listener as `DaemonService`. Every method returns
+  `codes.Unavailable` when `getArkChannelController()` is nil, so a caller
+  that dials before the wallet-dependent actors finish can retry instead of
+  reading a startup race as `Internal`. The swap subserver reaches the same
+  controller through `RPCServer.waitArkChannelController`, which polls every
+  `arkChannelControllerPollInterval` (25 ms) until the caller's context
+  ends, because subservers are constructed before the channel process is
+  published.
+- The `channel` macaroon entity (`rpc_auth.go`) gates both surfaces:
+  `channel:read` covers `LookupPreparedArkChannelOOR` and
+  `ArkChannelService.GetChannel`; `channel:write` covers the
+  prepare/validate/commit/abort quartet and every `ArkChannelService`
+  mutation. `ExportOORRecoveryPackage` is deliberately `oor:read` — it
+  exports OOR lineage, not channel state.
+- `NewReceiveScript` accepts `identity_key` to register the daemon's durable
+  `clientKeyDesc` instead of deriving a fresh receive key, for protocol
+  destinations that must survive an independent process restart. It cannot
+  be combined with `idempotency_key` (`InvalidArgument`): the two are
+  opposite retry models, one keyed on a caller string and one on the
+  daemon's own identity. `ensureConfiguredArkChannelCloseDelivery`
+  registers exactly that script for cooperative-close payouts from
+  `startWalletReadyServices` — after the main mailbox ingress is running,
+  since registration is an indexer RPC — bounded by
+  `operatorTermsRefreshTimeout`.
+- The unroll subsystem's `ExitSpendPolicyResolver` is an
+  `unroll.PolicyResolvers` chain: the vHTLC
+  `unrollpolicy.ExitSpendPolicyResolver` first, then `unrollbridge.Resolver`
+  over the Ark channel store. The channel-policy VTXO is recovery-only — it
+  never appears in wallet balance, coin selection, refresh, or ordinary VTXO
+  actor recovery, so its exit policy has to arrive through this resolver.
+- `startPrePONRReaper` runs one controller-lifetime loop over
+  `context.WithoutCancel`, expiring an abandoned pre-PONR channel
+  reservation after `defaultArkChannelPrePONRTimeout` (10 min, scanned every
+  30 s). Client controllers start it before lnd or the hub is reachable so a
+  locally reserved wallet VTXO is released even if the channel never
+  progresses.
+- `setupLoggers` also installs lnd's own subsystem loggers (`funding`,
+  `CHFD`, `LNWL`, `HSWC`, `INVC`, `routing`, `CNCT`, `BRAR`, `UTXN`, `SWPR`,
+  `CHDB`, `chainio`) into `SubLoggers`, so the embedded native channel stack
+  logs through waved's configuration rather than lnd's package defaults.
 - `initUnrollSubsystem` boot ordering is policy-preserving.
   `recoverySvc.RestoreNonTerminal` (in-flight vHTLC recovery jobs, each
   carrying its durable exit policy) runs **before** the chain resolver is
diff --git a/waved/CLAUDE.md b/waved/CLAUDE.md
index 8caf29d4..4cbaf9c3 100644
--- a/waved/CLAUDE.md
+++ b/waved/CLAUDE.md
@@ -14,7 +14,9 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   server, and `ActorSystem`. Caches `localMailboxID` (pubkey-derived),
   `authSigHex` (Schnorr auth), `clientKeyDesc` (the stable daemon identity
   descriptor, behind `clientKeyDescMu`), `mailboxAuthSigs` (per-recipient
-  mailbox auth signature memo, behind `mailboxAuthSigsMu`), and a single
+  mailbox auth signature memo, behind `mailboxAuthSigsMu`), the Ark channel
+  process triple (`arkChannelController`, `arkChannelMailboxRuntime`,
+  `arkChannelPeerIngress`, all behind `arkChannelMu`), and a single
   `clk` (`clock.Clock`) shared by all sub-stores for deterministic time
   injection.
 - `RPCServer` — implements the gRPC `DaemonService`. Most write RPCs
@@ -22,7 +24,12 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   validate input locally then `Ask` the relevant actor; `GetRound` and
   `ListVTXOs` merge live actor state with persisted SQL rows, while
   `GetFeeHistory` and `ListTransactions` are pure SQL reads
-  (`rpc_fees.go`).
+  (`rpc_fees.go`). The channel-funding RPCs (`PrepareArkChannelOOR`,
+  `LookupPreparedArkChannelOOR`, `ValidatePreparedArkChannelOOR`,
+  `CommitPreparedArkChannelOOR`, `AbortPreparedArkChannelOOR` in
+  `rpc_ark_channel_oor.go`) and `ExportOORRecoveryPackage`
+  (`rpc_ark_channel_recovery.go`) drive the `arkchannel/oorbridge` and
+  recovery-archive seams instead.
 - `Config` — daemon configuration: wallet backend selection, mailbox/chain
   backend wiring, `OORConfig`/`OORLimitsConfig` (receive safety caps),
   `UnrollConfig` (unilateral-exit fee-bump cadence and cap), and
@@ -33,6 +40,42 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   post-unlock recovery hook.
 - `UnrollConfig` / `OORConfig` — subsystem tunables; see `Config.Validate()`
   for the invariants each enforces.
+- `ArkChannelController` — the local process boundary published behind
+  `arkchannelrpc.ArkChannelService`. It composes
+  `ArkChannelLifecycleController` (`PromoteVTXO`,
+  `MaterializeAndForceClose`, `RequestCooperativeClose`, `GetChannel`,
+  `PeerMessageHandler`, `Stop`) and `ArkChannelPaymentController`
+  (`SendPayment`, `ReceivePayment`, `PayLightningInvoice`, plus the
+  incoming-payment `Prepare`/`Register`/`Wait` triple the swap subserver
+  calls through `RPCServer.*ArkChannelIncomingPayment`).
+- `NativeArkChannelController` — the concrete implementation, owning one
+  `arkchannel` FSM and one modular lnd endpoint. Built either as a lazy
+  client endpoint (`NewClientArkChannelController`, which loads hub policy
+  on the first lifecycle request) or eagerly as the hub endpoint
+  (`NewHubArkChannelController` over `HubArkChannelControllerConfig`, whose
+  advertised policy comes from `NewHubFundingPeerInfo`).
+  `LightningPaymentResult` is what `PayLightningInvoice` returns.
+- `ArkChannelControllerConfig` — the process-owned dependency set supplied
+  after wallet, database, and authenticated swap-server transport startup:
+  channel store, peer transport, `lwwallet`, chain backend/notifier/fee
+  estimator, the OOR and unroll bridges, identity key, and the funding
+  seams below.
+- `ArkChannelOORPreparer` / `ArkChannelOORLookup` /
+  `ArkChannelReceiveCapitalReserver` — funding callbacks the controller
+  invokes; `Server.prepareArkChannelOOR` and `Server.lookupArkChannelOOR`
+  supply the first two, and only the hub supplies the third.
+- `ArkChannelRecoveryController` — the endpoint-local source archive, spend
+  watcher, and unroll-preparation boundary
+  (`lnruntime.ChannelRecoveryManager` +
+  `arkchannel.ChannelEventSinkBinder` + `unrollbridge.SourcePreparer`),
+  implemented by `arkChannelRecoveryArchive` over
+  `arkChannelSourceWatcher`.
+- `ArkChannelRecoveryRuntime` / `ArkChannelRecoveryRuntimeConfig` — the same
+  unroller, package archive, and channel materializer composed for a host
+  that owns its own database, chain backend, and wallet (swapd's hub
+  endpoint). `ArkChannelUnrollWallet` (built by
+  `NewLNDArkChannelUnrollWallet`) is the shared `txconfirm.Wallet` +
+  `unroll.SweepWallet` surface it needs.
 
 ## Relationships
 
@@ -40,7 +83,9 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   `chainsource`, `lib/actormsg`, `db`, `ledger`, `round`, `txconfirm`,
   `unroll`, `vtxo`, `wallet`, `walletcore`, `oor`, `serverconn`, `indexer`,
   `arkrpc`, `lndbackend`, `fraud`, `gateway`, `rpc/restclient`,
-  `vhtlcrecovery`, `vhtlcrecovery/coordinator`, `vhtlcrecovery/unrollpolicy`.
+  `vhtlcrecovery`, `vhtlcrecovery/coordinator`, `vhtlcrecovery/unrollpolicy`,
+  `arkchannel`, `arkchannel/oorbridge`, `arkchannel/unrollbridge`,
+  `lnruntime`, `rpc/arkchannelrpc`, `chainfees`.
 - **Depended on by**: `cmd/waved`.
 
 ## Invariants
@@ -117,9 +162,68 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.<S
   code must not call `clock.NewDefaultClock()` directly, use `s.clk`.
 - Actor startup order in `startWalletDependentActors`: VTXO manager, then
   round actor, then the unroll subsystem (`initUnrollSubsystem`), then the
-  OOR actor (`initOORActor`). The VTXO manager is constructed with a
+  OOR actor (`initOORActor`), then the Ark channel process
+  (`initArkChannelProcess`). The VTXO manager is constructed with a
   `vtxo.LazyChainResolver` placeholder that `initUnrollSubsystem` fills in
   later; anything needing that seam must run after `initUnrollSubsystem`.
+  The channel process is last because it needs the durable channel store,
+  the swap-server mailbox edge, and every wallet-owned native dependency.
+- The Ark channel runtime is **opt-in on transport, not on a user flag**:
+  `initArkChannelProcess` returns early unless `SwapConfig.ArkChannelMailbox`
+  is set, which `swapclientserver` publishes as process wiring
+  (`mapstructure:"-"`) and no config file can supply. When it is set the
+  daemon stands up a dedicated `serverconn.Runtime`
+  (`arkchannel-serverconn-<localMailboxID>`) over that authenticated edge,
+  an `lnruntime.PeerMessageIngress` actor for durable BOLT peer messages,
+  and the controller, then publishes all three together under
+  `arkChannelMu` via `setArkChannelProcess`.
+- Channel shutdown order is peer ingress → controller → mailbox runtime,
+  and it runs **before** the main `serverconn` runtime and
+  `actorSystem.Shutdown()`. The mailbox runtime's deferred stop in
+  `runInner` is registered *after* the swap registrar's cleanup defers so
+  it runs first: the channel transport borrows the registrar's edge and
+  must not outlive it.
+- `arkChannelRPCServer` serves `arkchannelrpc.ArkChannelService` on the same
+  authenticated gRPC listener as `DaemonService`. Every method returns
+  `codes.Unavailable` when `getArkChannelController()` is nil, so a caller
+  that dials before the wallet-dependent actors finish can retry instead of
+  reading a startup race as `Internal`. The swap subserver reaches the same
+  controller through `RPCServer.waitArkChannelController`, which polls every
+  `arkChannelControllerPollInterval` (25 ms) until the caller's context
+  ends, because subservers are constructed before the channel process is
+  published.
+- The `channel` macaroon entity (`rpc_auth.go`) gates both surfaces:
+  `channel:read` covers `LookupPreparedArkChannelOOR` and
+  `ArkChannelService.GetChannel`; `channel:write` covers the
+  prepare/validate/commit/abort quartet and every `ArkChannelService`
+  mutation. `ExportOORRecoveryPackage` is deliberately `oor:read` — it
+  exports OOR lineage, not channel state.
+- `NewReceiveScript` accepts `identity_key` to register the daemon's durable
+  `clientKeyDesc` instead of deriving a fresh receive key, for protocol
+  destinations that must survive an independent process restart. It cannot
+  be combined with `idempotency_key` (`InvalidArgument`): the two are
+  opposite retry models, one keyed on a caller string and one on the
+  daemon's own identity. `ensureConfiguredArkChannelCloseDelivery`
+  registers exactly that script for cooperative-close payouts from
+  `startWalletReadyServices` — after the main mailbox ingress is running,
+  since registration is an indexer RPC — bounded by
+  `operatorTermsRefreshTimeout`.
+- The unroll subsystem's `ExitSpendPolicyResolver` is an
+  `unroll.PolicyResolvers` chain: the vHTLC
+  `unrollpolicy.ExitSpendPolicyResolver` first, then `unrollbridge.Resolver`
+  over the Ark channel store. The channel-policy VTXO is recovery-only — it
+  never appears in wallet balance, coin selection, refresh, or ordinary VTXO
+  actor recovery, so its exit policy has to arrive through this resolver.
+- `startPrePONRReaper` runs one controller-lifetime loop over
+  `context.WithoutCancel`, expiring an abandoned pre-PONR channel
+  reservation after `defaultArkChannelPrePONRTimeout` (10 min, scanned every
+  30 s). Client controllers start it before lnd or the hub is reachable so a
+  locally reserved wallet VTXO is released even if the channel never
+  progresses.
+- `setupLoggers` also installs lnd's own subsystem loggers (`funding`,
+  `CHFD`, `LNWL`, `HSWC`, `INVC`, `routing`, `CNCT`, `BRAR`, `UTXN`, `SWPR`,
+  `CHDB`, `chainio`) into `SubLoggers`, so the embedded native channel stack
+  logs through waved's configuration rather than lnd's package defaults.
 - `initUnrollSubsystem` boot ordering is policy-preserving.
   `recoverySvc.RestoreNonTerminal` (in-flight vHTLC recovery jobs, each
   carrying its durable exit policy) runs **before** the chain resolver is
diff --git a/waverpc/AGENTS.md b/waverpc/AGENTS.md
index 9d2761a5..014626fd 100644
--- a/waverpc/AGENTS.md
+++ b/waverpc/AGENTS.md
@@ -2,9 +2,10 @@
 
 ## Purpose
 
-Daemon gRPC API definitions for wallet, boarding, round, OOR, unroll, and
-VHTLC-recovery operations, plus the `Sign*` family through which other
-subsystems borrow the daemon identity key (`SignReceiveAuthMessage[Compact]`,
+Daemon gRPC API definitions for wallet, boarding, round, OOR, unroll,
+VHTLC-recovery, and Ark-channel funding operations, plus the `Sign*` family
+through which other subsystems borrow the daemon identity key
+(`SignReceiveAuthMessage[Compact]`,
 `SignOORCustomInput`, `SignVTXOForfeit`, `SignOutSwapHtlcAck`,
 `SignCreditAccountAuthorization`). Proto source: `waverpc/daemon.proto`.
 Generated gRPC, REST-gateway, and mailbox-RPC stubs plus one hand-written
@@ -22,10 +23,16 @@ helper file (`errors.go`) for structured wallet-lifecycle errors.
 - `IsWalletNotReadyError(err)` / `WalletNotReadyState(err)` — Match and unpack
   the structured error produced above; callers should key off these instead of
   matching on message text.
+- `ArkChannelOORPreparationStatus` — enum returned by
+  `LookupPreparedArkChannelOOR`: `UNSPECIFIED`, `ABSENT`, `PENDING`,
+  `PREPARED`, `ACCEPTED`.
 
 ## Relationships
 
-- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types used by the
+- **Depends on**: `rpc/arkchannelrpc` (`daemon.proto` imports
+  `rpc/arkchannelrpc/ark_channel.proto` for `ChannelTerms`,
+  `ChannelVTXOBinding`, `OORRecoverySource`, and `ChannelRecoveryPackage`),
+  `mailbox/rpc` (mailbox-RPC runtime types used by the
   generated mailbox stubs), `google.golang.org/genproto/googleapis/rpc/errdetails`
   and `google.golang.org/grpc` (structured errors in `errors.go`),
   `grpc-gateway/runtime` (REST gateway in `daemon.pb.gw.go`).
@@ -48,3 +55,21 @@ helper file (`errors.go`) for structured wallet-lifecycle errors.
   identity or they will be handed each other's receive scripts. An empty key
   keeps the legacy allocate-a-fresh-script behavior; repeating a non-empty key
   with a *different* label is rejected rather than silently reallocated.
+- `NewReceiveScriptRequest.identity_key` registers the daemon's durable
+  identity key as the receive destination instead of deriving a fresh one.
+  It is reserved for restart-stable protocol destinations (today, Ark
+  channel cooperative-close payouts) and is **mutually exclusive with
+  `idempotency_key`** — one keys the allocation on a caller string, the
+  other on the daemon's own identity, and combining them is
+  `InvalidArgument`.
+- The Ark channel funding RPCs are a strict five-step protocol:
+  `PrepareArkChannelOOR` reserves daemon liquidity and builds the
+  channel-policy output *without releasing signatures*;
+  `LookupPreparedArkChannelOOR` reconciles the deterministic key without
+  selecting or locking new inputs; `ValidatePreparedArkChannelOOR` re-checks
+  a binding; and exactly one of `CommitPreparedArkChannelOOR` (only after
+  both lnd endpoints persisted the signed backing) or
+  `AbortPreparedArkChannelOOR` (pre-PONR release, `reason` required) ends
+  the reservation. `ExportOORRecoveryPackage` is the separate read-only
+  export of a finalized OOR package and its round ancestry for one exact
+  output.
diff --git a/waverpc/CLAUDE.md b/waverpc/CLAUDE.md
index 9d2761a5..014626fd 100644
--- a/waverpc/CLAUDE.md
+++ b/waverpc/CLAUDE.md
@@ -2,9 +2,10 @@
 
 ## Purpose
 
-Daemon gRPC API definitions for wallet, boarding, round, OOR, unroll, and
-VHTLC-recovery operations, plus the `Sign*` family through which other
-subsystems borrow the daemon identity key (`SignReceiveAuthMessage[Compact]`,
+Daemon gRPC API definitions for wallet, boarding, round, OOR, unroll,
+VHTLC-recovery, and Ark-channel funding operations, plus the `Sign*` family
+through which other subsystems borrow the daemon identity key
+(`SignReceiveAuthMessage[Compact]`,
 `SignOORCustomInput`, `SignVTXOForfeit`, `SignOutSwapHtlcAck`,
 `SignCreditAccountAuthorization`). Proto source: `waverpc/daemon.proto`.
 Generated gRPC, REST-gateway, and mailbox-RPC stubs plus one hand-written
@@ -22,10 +23,16 @@ helper file (`errors.go`) for structured wallet-lifecycle errors.
 - `IsWalletNotReadyError(err)` / `WalletNotReadyState(err)` — Match and unpack
   the structured error produced above; callers should key off these instead of
   matching on message text.
+- `ArkChannelOORPreparationStatus` — enum returned by
+  `LookupPreparedArkChannelOOR`: `UNSPECIFIED`, `ABSENT`, `PENDING`,
+  `PREPARED`, `ACCEPTED`.
 
 ## Relationships
 
-- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types used by the
+- **Depends on**: `rpc/arkchannelrpc` (`daemon.proto` imports
+  `rpc/arkchannelrpc/ark_channel.proto` for `ChannelTerms`,
+  `ChannelVTXOBinding`, `OORRecoverySource`, and `ChannelRecoveryPackage`),
+  `mailbox/rpc` (mailbox-RPC runtime types used by the
   generated mailbox stubs), `google.golang.org/genproto/googleapis/rpc/errdetails`
   and `google.golang.org/grpc` (structured errors in `errors.go`),
   `grpc-gateway/runtime` (REST gateway in `daemon.pb.gw.go`).
@@ -48,3 +55,21 @@ helper file (`errors.go`) for structured wallet-lifecycle errors.
   identity or they will be handed each other's receive scripts. An empty key
   keeps the legacy allocate-a-fresh-script behavior; repeating a non-empty key
   with a *different* label is rejected rather than silently reallocated.
+- `NewReceiveScriptRequest.identity_key` registers the daemon's durable
+  identity key as the receive destination instead of deriving a fresh one.
+  It is reserved for restart-stable protocol destinations (today, Ark
+  channel cooperative-close payouts) and is **mutually exclusive with
+  `idempotency_key`** — one keys the allocation on a caller string, the
+  other on the daemon's own identity, and combining them is
+  `InvalidArgument`.
+- The Ark channel funding RPCs are a strict five-step protocol:
+  `PrepareArkChannelOOR` reserves daemon liquidity and builds the
+  channel-policy output *without releasing signatures*;
+  `LookupPreparedArkChannelOOR` reconciles the deterministic key without
+  selecting or locking new inputs; `ValidatePreparedArkChannelOOR` re-checks
+  a binding; and exactly one of `CommitPreparedArkChannelOOR` (only after
+  both lnd endpoints persisted the signed backing) or
+  `AbortPreparedArkChannelOOR` (pre-PONR release, `reason` required) ends
+  the reservation. `ExportOORRecoveryPackage` is the separate read-only
+  export of a finalized OOR package and its round ancestry for one exact
+  output.

How to apply. Save the diff above and git apply it on this branch, or
run the gardening skill locally (/doc-gardening waved, waverpc,
cmd/wavecli/waveclicommands, harness) — the same pass the nightly sweep
runs — and let it regenerate the CLAUDE.md/AGENTS.md pairs.

This check is advisory only; it never fails the build.

View run

@sputn1ck
sputn1ck force-pushed the kon/ark-channel-daemon branch from 070cf3d to 98740b3 Compare August 24, 2026 15:28
@sputn1ck sputn1ck changed the title kon/ark channel daemon waved: Operate native Ark channels Aug 24, 2026
Provide daemon RPCs for promotion, funded receive intents, payments,
cooperative settlement, and materialization recovery.
Wire the durable channel store, lnd runtime, OOR coordinator, source
watcher, expiry cleanup, and recovery handoff into the daemon.
Add concise commands for channel creation, payments, inspection,
cooperative sends, and explicit close operations.
Let system tests expose a public third-party channel so ordinary
post-creation routing can be exercised.
Record the operator and client commands needed to exercise the native
Ark channel lifecycle locally.
@sputn1ck
sputn1ck force-pushed the kon/ark-channel-daemon branch from 98740b3 to 1232f96 Compare August 24, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants