Skip to content

Arkade payout: accept on-chain + LN destinations (auto-route by type) #65

@Kukks

Description

@Kukks

Context

Today ArkPayoutHandler.ParseClaimDestination (BTCPayServer.Plugins.ArkPayServer/Payouts/Ark/ArkPayoutHandler.cs:96-117) accepts exactly two destination shapes:

  • A bare ArkAddress (tark1...)
  • A bitcoin: URI (wrapped as ArkUriClaimDestination)

Anything else — a bare on-chain address (bc1…, bcrt1…, etc.), a BOLT11 invoice (lnbc…), or an LNURL — is rejected with "A valid address was not provided". Operators that want to pay those destinations have to fall back to the BTC-CHAIN or BTC-LN payout methods explicitly, which fragments the "Arkade balance pays things" mental model.

The plugin already has the underlying primitives wired up for the Send wizard:

  • VTXO → on-chain: batch-settle to an on-chain output, or chain swap via Boltz
  • VTXO → Lightning: submarine swap via Boltz

So the gap is at the payout-handler layer, not the protocol layer.

Proposal

Let the Arkade payout method auto-detect the destination type:

Destination Route
tark1… (Ark address) VTXO → VTXO (current behaviour)
bitcoin: URI with ark= VTXO → VTXO (current behaviour)
bitcoin: URI without ark= VTXO → on-chain (chain swap or batch on-chain output)
Bare on-chain address VTXO → on-chain (same as above)
lnbc… (BOLT11) VTXO → LN (Boltz submarine swap)
LNURL-pay resolve to BOLT11, then route as LN

Single payout method, dispatch happens inside InitiatePayment based on the parsed claim destination.

Tradeoffs / open questions

  1. Failure-mode opacity. Each route has very different latency and failure characteristics:

    • VTXO → VTXO: instant, near-bulletproof
    • VTXO → on-chain: ~10s batch wait, or minutes via Boltz chain swap (depends on Boltz limits + arkd batch session)
    • VTXO → LN: depends on Boltz availability, swap limits, and the LN route

    A payout that "just hangs" because we silently fell back across tiers will be hard to debug. We probably need explicit status messages per route (e.g. "Awaiting batch settle", "Boltz swap pending", "LN settling").

  2. Min/max amount constraints differ per route. Boltz swaps have configured min/max; arkd batch settle has its own dust threshold; LN has its own. GetMinimumPayoutAmount currently returns the arkd dust value — would need to be route-aware (or just return the loosest minimum and validate later).

  3. Fees differ per route. Today the payout proof is ArkPayoutProof (TransactionId). A chain-swap or LN payout would have a different "proof" — swap id, preimage, or final on-chain txid. The proof model probably needs to be a discriminated union, or a base type with route-specific subclasses.

  4. Refund semantics for failed swaps. If a Boltz submarine swap fails after our VTXO is locked into the HTLC, the cooperative-refund path needs to be triggered automatically (or surfaced to the operator). The plugin already has SwapsManagementService.RequestRefundCooperatively — needs to be tied into payout cancellation/reject.

  5. Alternative scope: keep payout strict (Ark address only) and instead document that operators should pick BTC-LN / BTC-CHAIN explicitly for non-Ark destinations. Less elegant UX but the dispatch + failure-mapping problem disappears.

Acceptance criteria (sketch)

  • ArkPayoutHandler.ParseClaimDestination recognises bare on-chain addresses + BOLT11 invoices (and optionally LNURL).
  • InitiatePayment dispatches each destination type to the appropriate Send/Swap flow rather than always redirecting to the offchain Send wizard.
  • ArkPayoutProof is extended (or split) so the proof reflects the route actually taken (offchain txid / on-chain txid / swap id + preimage).
  • GetMinimumPayoutAmount is route-aware.
  • Failed swaps trigger cooperative refund automatically.
  • UI surfaces the route + current status per payout (not a generic "Awaiting payment").
  • Existing Ark-address-only payouts keep working with the same proof shape (no migration of historical payout rows).

Out of scope

  • Cross-asset payouts (Arkade asset balance → BTC destination). Same hatch but different problem.
  • Boarding-input-only payouts (paying into a boarding address from an external wallet — that's the inverse of what we're doing here).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions