aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark/send.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-05 20:39:02 -0700
committerAdam Soltys <[email protected]>2026-07-05 20:39:02 -0700
commit15e0b236e7ff2cafcb439c891c790deea8ec16a7 (patch)
tree960057559e60b4b2e624a7de9e4ac35b1ecb81ad /src/ark/send.js
parent0408609fc68fa71ff705ef7a241880efefddcc3f (diff)
downloadcoinosv3-15e0b236e7ff2cafcb439c891c790deea8ec16a7.tar.gz
coinosv3-15e0b236e7ff2cafcb439c891c790deea8ec16a7.zip
Unilateral exit: put vtxos on-chain without the server
The trustless backstop behind the Ark card's promise. The vtxo's genesis chain reconstructs to fully-signed txs (keyspend sigs for cosigned/arkoor transitions, preimage script-path for hark leaves); they are zero-fee v3 with P2A anchors, so each hop broadcasts as a [parent, CPFP child] package via esplora POST /txs/package, one confirmed hop at a time (TRUC allows a single unconfirmed parent). After the vtxo tx confirms and the CSV exit delta passes, the claim spends the delayed_sign leaf with only the user key into the wallet's own receive address. Settings gains 'Exit on-chain without the server' with live per-exit status lines; the claim tx is labeled 'Ark exit' in History. Probing uses /tx/:txid (definitive 404), never /tx/:txid/status, which answers confirmed:false for unknown txids. Also: a fresh visitor opening an ark gift link now adopts the gift's network automatically instead of being sent to Settings. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/ark/send.js')
-rw-r--r--src/ark/send.js15
1 files changed, 10 insertions, 5 deletions
diff --git a/src/ark/send.js b/src/ark/send.js
index 39cd1fb..a959b22 100644
--- a/src/ark/send.js
+++ b/src/ark/send.js
@@ -84,14 +84,19 @@ export function taprootOneLeaf(userPub, serverPub, leafScript) {
// lift internal x-only, add tweak*G
const internalPoint = P.fromHex(concatBytes(Uint8Array.of(0x02), internalXOnly));
const outputPoint = internalPoint.add(P.BASE.multiply(BigInt('0x' + hex.encode(tapTweak))));
- const outputXOnly = outputPoint.toRawBytes(true).slice(1);
+ const outputCompressed = outputPoint.toRawBytes(true);
+ const outputXOnly = outputCompressed.slice(1);
const scriptPubKey = concatBytes(hex.decode('5120'), outputXOnly);
- return { sortedKeys, internalXOnly, merkleRoot, tapTweak, outputXOnly, scriptPubKey };
+ const outputParity = outputCompressed[0] === 0x03 ? 1 : 0; // for script-path control blocks
+ return { sortedKeys, internalXOnly, merkleRoot, tapTweak, outputXOnly, outputParity, scriptPubKey };
}
-// PubkeyVtxoPolicy: keyspend musig(user,server); leaf = delayed_sign(exit_delta, user)
-export const pubkeyPolicyTaproot = (userPub, serverPub, exitDelta) =>
- taprootOneLeaf(userPub, serverPub, delayedSignScript(exitDelta, xonly(userPub)));
+// PubkeyVtxoPolicy: keyspend musig(user,server); leaf = delayed_sign(exit_delta, user).
+// leafScript is returned so a unilateral exit can claim through it after the CSV.
+export const pubkeyPolicyTaproot = (userPub, serverPub, exitDelta) => {
+ const leafScript = delayedSignScript(exitDelta, xonly(userPub));
+ return { ...taprootOneLeaf(userPub, serverPub, leafScript), leafScript };
+};
// CheckpointVtxoPolicy: keyspend musig(user,server); leaf = timelock_sign(expiry, server)
export const checkpointPolicyTaproot = (userPub, serverPub, expiryHeight) =>