aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ark/exit.js17
-rw-r--r--src/features/ark.js14
-rw-r--r--src/i18n.js11
3 files changed, 32 insertions, 10 deletions
diff --git a/src/ark/exit.js b/src/ark/exit.js
index 2770284..9310b65 100644
--- a/src/ark/exit.js
+++ b/src/ark/exit.js
@@ -150,11 +150,22 @@ export function buildExitClaim({ vtxo, keys, serverPub, destScript, feeRate }) {
// Submit a [parent, child] (or single-tx) package via esplora -> bitcoind
// submitpackage. Treats already-known txs as success so retries are safe.
+// Esploras without the endpoint (regtest electrs) fall back to sequential
+// /tx posts — which relays only if the node accepts zero-fee v3 parents
+// (regtest with -minrelaytxfee=0); otherwise the node's error surfaces.
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)) {
- throw new Error(`package submit failed: ${body.slice(0, 160)}`);
+ if (r.ok || /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 });
+ const bb = await br.text();
+ if (!br.ok && !/already|duplicate/i.test(bb)) {
+ throw new Error(`broadcast failed: ${bb.slice(0, 160)}`);
+ }
+ }
+ return 'sequential';
}
- return body;
+ throw new Error(`package submit failed: ${body.slice(0, 160)}`);
}
diff --git a/src/features/ark.js b/src/features/ark.js
index 9a180f7..57a8f82 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -278,9 +278,15 @@ export function arkFeature(ctx) {
{ class: 'card col', style: 'gap:10px' },
...head,
spendables.length ? row(t('arkVtxos'), String(spendables.length)) : null,
- pendingActions.length
- ? h('div', { class: 'small muted' }, t('arkPendingActions', { n: pendingActions.length }) + ' — ' + pendingActions.map((a) => `${a.type}:${a.step}`).join(', '))
- : null,
+ // in-flight operations, summarized in plain words (exits get their own
+ // per-exit status lines below)
+ (() => {
+ const counts = {};
+ for (const a of pendingActions) { if (a.type !== 'exit') counts[a.type] = (counts[a.type] || 0) + 1; }
+ const LABEL = { board: 'arkOpsBoard', send: 'arkOpsSend', refresh: 'arkOpsRefresh', offboard: 'arkOpsOffboard' };
+ const parts = Object.entries(counts).map(([type, n]) => t(LABEL[type] || 'arkPendingActions', { n }));
+ return parts.length ? h('div', { class: 'small muted' }, parts.join(' · ')) : null;
+ })(),
spendables.length >= 1
? h('button', { class: 'btn-ghost btn-block', disabled: !!ui.arkBusy, onClick: doArkRefresh },
ui.arkBusy === 'refresh' ? h('span', { class: 'spinner sm' }) : t('arkRefreshBtn', { n: spendables.length }))
@@ -295,7 +301,7 @@ export function arkFeature(ctx) {
a.step === 'chain' ? t('arkExitChainStatus', { n: fmtAmount(a.amountSat) })
: a.step === 'timelock' ? t('arkExitTimelockStatus', { n: fmtAmount(a.amountSat), blocks: String(a.claimableAt) })
: t('arkExitClaimingStatus', { n: fmtAmount(a.amountSat) }),
- a.lastError ? h('div', { class: 'small err' }, a.lastError) : null))
+ a.lastError ? h('div', { class: 'small faint' }, t('arkExitRetrying')) : null))
);
}
diff --git a/src/i18n.js b/src/i18n.js
index 47e93f4..6c40a74 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -391,9 +391,14 @@ const STR = {
arkExitBtn: 'Exit on-chain without the server ({n} coin(s))',
arkExitStarted: 'Unilateral exit started for {n} coin(s) — broadcasting the exit chain.',
arkExitNoFeeCoin: 'Exiting needs an on-chain coin to pay the exit-chain fees.',
- arkExitChainStatus: 'Exit {n}: broadcasting the exit chain…',
- arkExitTimelockStatus: 'Exit {n}: waiting for the timelock (claimable at block {blocks})',
- arkExitClaimingStatus: 'Exit {n}: claim broadcast, awaiting confirmation',
+ arkExitChainStatus: 'Exiting {n} sats — publishing on-chain…',
+ arkExitTimelockStatus: 'Exiting {n} sats — waiting out the timelock (block {blocks})',
+ arkExitClaimingStatus: 'Exiting {n} sats — almost done, awaiting confirmation',
+ arkExitRetrying: 'hit a snag — retrying automatically',
+ arkOpsBoard: '{n} deposit(s) confirming',
+ arkOpsSend: '{n} send(s) finishing up',
+ arkOpsRefresh: '{n} refresh(es) waiting for the next round',
+ arkOpsOffboard: '{n} withdrawal(s) confirming',
arkExited: 'Exited on-chain',
arkExitHistory: 'Ark exit',
arkRefreshStarted: 'Refresh submitted — completes with the next round.',