Snapshot commit rolling up ~255 files of coherent-but-multi-domain in-flight
work that had accumulated on this branch. Split by area:
Contracts + covenants
- New: AsmFxSwap, DeviantOpenMintNBA, DeviantTieredMintOracleFT,
TermCouponVault, UniformReserve, UniformReserveAggregate.
- Verify-artifact refresh (src/verify/artifacts/*.json + canonical.ts).
Wallet + shell (shells/app + src/wallet)
- App.tsx, MainScreen, ArtemisionScreen, AssetDetail, AgoraScreen,
SettingsScreen, Unlock, TokenImportModal, UniformFiat/DepositPanel,
KatalogosPanel (renamed from PinaxPanel), shell.css.
- src/wallet: wallet.ts, runtime.ts, index.ts. Core registry/chipnetFile.
- Deleted stale hook: useAllDevBalances.ts.
Cross-chain + correspondent (services/correspondent + scripts)
- Correspondent server/bch/config updates for PYUSD Solana, EURC Sepolia,
aggregate reserve flows.
- New scripts: PYUSD roundtrip, EURC onboarding, GMX game loop /
genesis / mint sanity, tiered mint fee, aggregate reserve, product2
shares dividends, product3 t-bond, uniform fiat cell tests.
Telegram order-book (P2PExchange bot)
- db.ts, index.ts, en/ru locales — updates upstream from the extracted
standalone repo at D:/Dev/P2PExchange.
Docs — Parameters (specs), wiki, snapshots, design mockups
- Parameters: FIATm/MBA_Shares/Potidaea/Anthemus refinements +
ASM_CrossChain/DebitCredit + UniformFiat + Anthemus DEX MVP prompt +
Artemision + Athenae Politeia + Philosophy + SSHkeyServer runbook +
new dApp/{Artemision,Katalogos,Pinax,Pari_escrow,SURFACES}.
- wiki: cross-chain, games-tote, glossary, governance/voting-systems,
infra, wallet, wallet-ui-design, decisions (ADR log).
- snapshots: catch-up entries and INDEX regeneration.
- design: mockups refreshed (agora, asset-detail, assets-tab, potidaea)
+ Potidaea assets + tote deploy notes.
Housekeeping
- .gitignore: exclude the new root test/session state files
(.sol-vault-keys.json, .football-tote-state.json,
.tote-platform-state.json, .redeem-roundtrip-state.json,
.eurc-onboard-state.json — some contain oracle privkeys / vault
secretKeyBase58; NEVER commit) + the .usdm-historical-chart.html
chart output + MS Office `~$*` lock files + scripts/bundle-surfaces
`build/` output. Also add scripts/bundle-surfaces.mjs.
- CLAUDE.md, WALLET-COORDINATION.md, snapshots/INDEX.md, design/README.md
refreshed.
- package.json / package-lock.json bumps.
Verified: sensitive files at repo root confirmed ignored (git check-ignore
each). No `.env`, no raw WIF/xprv/mnemonic/secretKey/oraclePriv strings in
the staged diff. MS Word autosave file (`Parameters/Philosophy/~$*.docx`)
excluded and gitignored.
The WizardConnect responder work landed separately in the previous commit
(6987591); this is everything else that had accumulated alongside it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
4.3 KiB
JavaScript
76 lines
4.3 KiB
JavaScript
/**
|
|
* chipnet-safe.mjs — safe chipnet UTXO selection + broadcast, hardened against a real, diagnosed bug.
|
|
*
|
|
* THE BUG (root-caused 2026-07-21). The cashscript `ElectrumNetworkProvider`'s default chipnet
|
|
* electrum server returns a STALE UTXO index: it lists already-SPENT outpoints as unspent. Measured
|
|
* on the operator wallet, the cashscript provider reported 94 UTXOs while mainnet-js (a fresher,
|
|
* independent electrum server) reported 81 — a 14-outpoint difference, every one a CHANGE output from
|
|
* an earlier genesis tx that had long since been spent (all confirmed-long-ago, vout 1). A driver that
|
|
* builds a tx from that stale view can select a spent outpoint as an input; the node then rejects the
|
|
* broadcast with **"Missing inputs" (bad-txns-inputs-missingorspent)**. Fragmentation raises the odds
|
|
* of hitting a phantom, so the wallet looks permanently "stuck".
|
|
*
|
|
* THE FIX. Source wallet UTXO selection from **mainnet-js**, which returned the ACCURATE set (it had
|
|
* already dropped the 14 phantoms). Convert to the cashscript UTXO shape the TransactionBuilder wants.
|
|
* The cashscript provider is still fine for BROADCAST and for reading the covenant/token UTXOs (a
|
|
* freshly-created controller is never a phantom); we just stop trusting its wallet-UTXO *listing*.
|
|
* Belt-and-suspenders: `sendWithRetry` re-fetches and rebuilds if a broadcast still hits a stale-input
|
|
* reject (e.g. mainnet-js momentarily lagging). Prefer confirmed inputs, but ACCEPT unconfirmed when
|
|
* necessary (a fresh split output is real at 0-conf — 0-conf chaining is normal and safe here).
|
|
*/
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const STALE_INPUT = /Missing inputs|missingorspent|bad-txns-inputs|txn-mempool-conflict|txn-already-known/i;
|
|
const toCs = (x) => ({ txid: x.txid, vout: x.vout, satoshis: BigInt(x.satoshis), token: x.token });
|
|
const bySatsDesc = (a, b) => Number(BigInt(b.satoshis) - BigInt(a.satoshis));
|
|
|
|
/** The ACCURATE spendable UTXO set (from mainnet-js), in cashscript shape. `provider` is unused but
|
|
* kept in the signature so call sites read symmetrically and can be swapped later. */
|
|
export async function safeUtxos(_provider, wallet) {
|
|
const mj = await wallet.getUtxos();
|
|
return mj.map(toCs);
|
|
}
|
|
|
|
/** Largest-first BCH selection covering `need` sats from a pre-fetched set (non-token only). */
|
|
export function pickBch(utxos, need) {
|
|
const p = []; let t = 0n;
|
|
for (const x of utxos.filter((x) => !x.token).sort(bySatsDesc)) { p.push(x); t += BigInt(x.satoshis); if (t >= need) break; }
|
|
if (t < need) throw new Error(`insufficient BCH: have ${t}, need ${need}`);
|
|
return { p, t };
|
|
}
|
|
|
|
/**
|
|
* A non-token UTXO at outpoint index 0 — required to genesis a CashToken whose category == that
|
|
* outpoint's txid (CashTokens genesis rule: an input must spend a vout-0 output). Prefer a CONFIRMED
|
|
* one; else use any; else split one into existence (send-to-self) and use the fresh (0-conf) output.
|
|
*/
|
|
export async function eligibleVout0(_provider, wallet, { splitSats = 3000n, waitMs = 14000 } = {}) {
|
|
const pick0 = (arr) => arr.filter((x) => x.vout === 0 && !x.token).sort(bySatsDesc)[0];
|
|
let mj = await wallet.getUtxos();
|
|
let e = pick0(mj.filter((x) => Number(x.height ?? 0) > 0)) || pick0(mj); // confirmed first, else any
|
|
if (e) return toCs(e);
|
|
await wallet.send([{ cashaddr: wallet.cashaddr, value: splitSats, unit: 'sat' }]); // create a vout-0 output
|
|
await sleep(waitMs);
|
|
mj = await wallet.getUtxos();
|
|
e = pick0(mj); // accept the fresh 0-conf output
|
|
if (!e) throw new Error('could not obtain a vout-0 UTXO after split');
|
|
return toCs(e);
|
|
}
|
|
|
|
/**
|
|
* Run `buildAndSend` (an async thunk that RE-FETCHES its inputs, builds, and broadcasts), retrying on
|
|
* the stale-input reject. The thunk must re-fetch each call so the retry uses fresh state.
|
|
*/
|
|
export async function sendWithRetry(buildAndSend, { retries = 3, backoffMs = 8000, onRetry } = {}) {
|
|
for (let i = 0; ; i++) {
|
|
try { return await buildAndSend(); }
|
|
catch (e) {
|
|
const msg = e?.message ?? String(e);
|
|
if (i < retries && STALE_INPUT.test(msg)) { onRetry?.(i + 1, msg); await sleep(backoffMs); continue; }
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Consolidate a wallet's UTXOs into one output (maintenance; reduces fragmentation). */
|
|
export async function consolidate(wallet) { return wallet.sendMax(wallet.cashaddr); }
|