From bff3692ed92dbce4aa1e71f1ba6ec491645eb5db Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 2 Aug 2026 17:07:26 -0700 Subject: Ark: partial moves to Savings via an exact-amount split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Into Savings now has an amount field with a Max button. Max (or the full balance) keeps the existing whole-balance cooperative offboard. A partial amount first mints a vtxo of exactly that amount with a self-send — the mailbox delivers self-sends, verified on regtest — then offboards just that vtxo, so the number typed is the number that leaves Spending (fees still come off what arrives, shown on the success screen). startOffboard accepts a vtxo subset for this; an exact-match vtxo skips the split. The unilateral exit page remains reachable from a link under the field. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi --- src/ark/manager.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src/ark') diff --git a/src/ark/manager.js b/src/ark/manager.js index 3b54582..5617cf1 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -363,13 +363,19 @@ export class ArkManager { return action; } - // ---- offboard (collaborative exit: all spendable vtxos -> one on-chain output) ---- - async startOffboard(spk, address) { + // ---- offboard (collaborative exit: spendable vtxos -> one on-chain output) ---- + // Whole vtxos only — there is no change on this path. Default is everything + // spendable; vtxoIds selects a subset (the feature splits an exact-amount + // vtxo first via a self-send when the user asks for a partial move). + async startOffboard(spk, address, vtxoIds) { if (this.pendingActions().some((a) => a.type === 'offboard')) { throw new Error('an offboard is already in progress'); } - const inputs = this.state.vtxos.filter((v) => v.state === 'spendable'); + const inputs = vtxoIds + ? vtxoIds.map((id) => this._vtxo(id)) + : this.state.vtxos.filter((v) => v.state === 'spendable'); if (!inputs.length) throw new Error('no spendable ark balance'); + if (inputs.some((v) => !v || v.state !== 'spendable')) throw new Error('vtxo not spendable'); if (this.info.maxOffboardInputs && inputs.length > this.info.maxOffboardInputs) { throw new Error(`too many coins for one offboard (${inputs.length}) — refresh/consolidate first`); } -- cgit v1.2.3