aboutsummaryrefslogtreecommitdiffstats
path: root/src/ark
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-05 23:11:32 -0700
committerAdam Soltys <[email protected]>2026-07-05 23:11:32 -0700
commiteb89b9e3681d9eea331b6471ecc576f39a0fd5ec (patch)
tree63135d74a974502bf1d64fce9bdeb2696b43cdb1 /src/ark
parentd24ba23df05bbc8072cbd97bec84a2a198951d03 (diff)
downloadcoinosv3-eb89b9e3681d9eea331b6471ecc576f39a0fd5ec.tar.gz
coinosv3-eb89b9e3681d9eea331b6471ecc576f39a0fd5ec.zip
Exit status: real progress, blocks-left countdown, honest failures
submitpackage answers HTTP 200 even when it rejects the package — the verdict hides in package_msg/tx-results — so a mainnet no-fee-coin exit sat at 'publishing on-chain…' forever believing its bare hop had relayed. Rejections now surface (and become the actionable needs-a-fee- coin message on the bumpless path). The status line shows hop progress (step 2 of 9), the timelock wait counts down in blocks with a rough wall-clock estimate, and a stuck exit that never published anything gets a 'Cancel this exit' link that returns the vtxo to spendable. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/ark')
-rw-r--r--src/ark/exit.js16
1 files changed, 15 insertions, 1 deletions
diff --git a/src/ark/exit.js b/src/ark/exit.js
index 9310b65..e2b253f 100644
--- a/src/ark/exit.js
+++ b/src/ark/exit.js
@@ -156,7 +156,21 @@ export function buildExitClaim({ vtxo, keys, serverPub, destScript, feeRate }) {
export async function submitPackage(esploraBase, hexes) {
const r = await fetch(`${esploraBase}/txs/package`, { method: 'POST', body: JSON.stringify(hexes) });
const body = await r.text();
- if (r.ok || /already|duplicate/i.test(body)) return body;
+ if (r.ok) {
+ // submitpackage answers 200 even when it REJECTS the txs — the verdict is
+ // inside package_msg / per-tx errors. Treat anything but success (or
+ // already-known) as a real failure or the exit waits forever in silence.
+ let parsed = null;
+ try { parsed = JSON.parse(body); } catch {}
+ if (parsed && parsed.package_msg && parsed.package_msg !== 'success') {
+ const errs = Object.values(parsed['tx-results'] || {}).map((t) => t.error).filter(Boolean).join('; ');
+ if (!/already/i.test(errs)) {
+ throw new Error(`package rejected: ${(errs || parsed.package_msg).slice(0, 160)}`);
+ }
+ }
+ return body;
+ }
+ if (/already|duplicate/i.test(body)) return body;
if (r.status === 404 || /endpoint does not exist/i.test(body)) {
for (const hx of hexes) {
const br = await fetch(`${esploraBase}/tx`, { method: 'POST', body: hx });