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>
166 lines
12 KiB
JavaScript
166 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* correspondent-e2e-aggregate.mjs — end-to-end validation of the AGGREGATE model in the
|
||
* correspondent SERVICE: the `EURC (Ethereum)` config row (carrying `currency: 'EUR'`) routes
|
||
* through bch-aggregate.mjs onto the PRODUCTION UniformReserveAggregate covenant (ADR-0053).
|
||
*
|
||
* Flow (real Sepolia + chipnet coins, via the REAL service handlers):
|
||
* quote → Alice locks 2 EURC in the gateway with her own signer → confirm (mint 2 unified
|
||
* EUR on BCH, crediting the EURC.Ethereum CELL in the persisted off-chain ledger) →
|
||
* redeem quote (cell-coverage check + OP_RETURN marker) → Alice burns via the aggregate
|
||
* covenant's pro-rata withdraw + marker → redeem confirm (marker binding + on-chain
|
||
* AGGREGATE drop verified, ledger debited largest-first) → vault.release on Sepolia.
|
||
*
|
||
* Run: node --import tsx scripts/correspondent-e2e-aggregate.mjs --yes
|
||
*/
|
||
import { readFileSync } from 'node:fs';
|
||
import { join } from 'node:path';
|
||
import { TestNetWallet } from 'mainnet-js';
|
||
import { Contract as CashContract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript';
|
||
import { JsonRpcProvider, Wallet, Contract } from 'ethers';
|
||
import { handleQuote, handleConfirm, handleRedeemQuote, handleRedeemConfirm, handleReconcile } from '../services/correspondent/server.mjs';
|
||
import { VAULT_ABI, ERC20_ABI } from '../services/correspondent/evm.mjs';
|
||
import { safeUtxos, pickBch as pickSafe, sendWithRetry } from './lib/chipnet-safe.mjs';
|
||
|
||
const ROOT = join(import.meta.dirname, '..');
|
||
const ART = JSON.parse(readFileSync(join(ROOT, 'contracts/dist/UniformReserveAggregate.json'), 'utf8'));
|
||
const LEDGER_KEY = 'EURC (Ethereum)';
|
||
const RPC = process.env.SEPOLIA_RPC || 'https://ethereum-sepolia-rpc.publicnode.com';
|
||
const AMOUNT = '2';
|
||
const ATOMIC = 2_000_000n;
|
||
const SUPPLY = 1_000_000_000n, FX_SCALE = 1_000_000n, FX_BAND = 0n;
|
||
const DUST = 1000n, MF = 12_000n;
|
||
const OP_WALLET = process.env.CORR_BCH_WALLET || 'd';
|
||
|
||
const hexToBin = (h) => { const o = new Uint8Array(h.length / 2); for (let i = 0; i < o.length; i++) o[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16); return o; };
|
||
const binToHex = (b) => [...b].map((x) => x.toString(16).padStart(2, '0')).join('');
|
||
const txidLE = (h) => { const d = hexToBin(h); const o = new Uint8Array(d.length); for (let i = 0; i < d.length; i++) o[i] = d[d.length - 1 - i]; return o; };
|
||
const u64le = (n) => { const b = new Uint8Array(8); new DataView(b.buffer).setBigUint64(0, BigInt(n), true); return b; };
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||
const div = (c = '=') => console.log(c.repeat(84));
|
||
const step = (t) => { console.log(''); div(); console.log(t); div(); };
|
||
const txidOf = (s) => (typeof s === 'string' ? s : s?.txid ?? s?.txId);
|
||
const fmt = (n) => (Number(n) / 1e6).toFixed(2);
|
||
|
||
async function main() {
|
||
if (!process.argv.includes('--yes')) { console.log('Re-run with --yes to broadcast on Sepolia + chipnet.'); return; }
|
||
|
||
const ethKey = JSON.parse(readFileSync(join(ROOT, '.sepolia-keys.json'), 'utf8'));
|
||
const alice = new Wallet(ethKey.privateKey, new JsonRpcProvider(RPC));
|
||
|
||
const ALICE = await TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, '.chipnet-keys-b.json'), 'utf8')).wif);
|
||
const OP = await TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, `.chipnet-keys-${OP_WALLET}.json`), 'utf8')).wif);
|
||
const [E, F, G] = await Promise.all(['e', 'f', 'g'].map(async (k) => TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, `.chipnet-keys-${k}.json`), 'utf8')).wif)));
|
||
const provider = new ElectrumNetworkProvider('chipnet');
|
||
const sigAlice = new SignatureTemplate(ALICE.privateKey);
|
||
const sigOp = new SignatureTemplate(OP.privateKey);
|
||
const ftBal = async (cat) => (await provider.getUtxos(ALICE.cashaddr)).filter((x) => x.token?.category === cat && !x.token?.nft).reduce((s, x) => s + BigInt(x.token.amount), 0n);
|
||
|
||
console.log(`Sepolia Alice ${alice.address} · BCH Alice ${ALICE.cashaddr} · operator wallet ${OP_WALLET}`);
|
||
|
||
// ── 1. QUOTE (service → aggregate controller genesis-once, LIVE price disclosure) ──
|
||
step(`1. POST /quote — ${AMOUNT} ${LEDGER_KEY} → unified EUR on BCH (AGGREGATE model)`);
|
||
const quote = await handleQuote({ ledger: LEDGER_KEY, amount: AMOUNT, bchAddress: ALICE.cashaddr });
|
||
console.log(` gateway ${quote.vault}\n disclosure: ${quote.priceInfo?.disclosure ?? 'none'}`);
|
||
|
||
// ── 2. USER LOCK (Alice's own signer, real Sepolia tx) ──
|
||
step('2. USER LOCK — Alice deposits EURC into the gateway (her own wallet)');
|
||
const eurc = new Contract(quote.token, ERC20_ABI, alice);
|
||
const eurcStart = BigInt(await eurc.balanceOf(alice.address));
|
||
await (await eurc.approve(quote.vault, ATOMIC)).wait();
|
||
const gateway = new Contract(quote.vault, VAULT_ABI, alice);
|
||
const lockTx = await (await gateway.depositToBitcoinCash(ATOMIC, quote.bchRecipient)).wait();
|
||
console.log(` ✅ locked ${AMOUNT} EURC · Sepolia tx ${lockTx.hash}`);
|
||
|
||
// ── 3. CONFIRM (service verifies + quorum-attests + mints unified EUR) ──
|
||
step('3. POST /confirm — the service mints unified EUR, crediting the EURC.Ethereum CELL');
|
||
let conf = null;
|
||
for (let i = 0; i < 12 && !conf; i++) {
|
||
try { conf = await handleConfirm({ quoteId: quote.quoteId, lockTxHash: lockTx.hash }); }
|
||
catch (e) {
|
||
if (!/insufficient confirmations|controller UTXO missing/.test(e?.message ?? '')) throw e;
|
||
console.log(` … waiting (${(e.message || '').slice(0, 48)})`); await sleep(6000);
|
||
}
|
||
}
|
||
if (!conf) throw new Error('confirm did not succeed after retries');
|
||
const MINTED = BigInt(conf.mintedAmount); // market-priced, ≤ locked
|
||
console.log(` ✅ minted ${fmt(MINTED)} unified ${conf.currency} for ${fmt(BigInt(conf.lockedAmount))} EURC · ${conf.bchTxHash}`);
|
||
console.log(` pricing ${conf.priceInfo?.pricing} @ ${conf.priceInfo?.price?.toFixed?.(6) ?? 'par'} (${(conf.priceInfo?.sources ?? []).map((s) => s.source).join('+') || 'none'})`);
|
||
console.log(` cell ${conf.cell}=${fmt(BigInt(conf.cellValue))} · aggregate ${fmt(BigInt(conf.aggregate))} · circulating ${fmt(BigInt(conf.circulating))}`);
|
||
await sleep(9000);
|
||
const bchBal = await ftBal(conf.category);
|
||
|
||
// ── 4. RECONCILE (aggregate view — over-collateralized by the market discount) ──
|
||
const rec = await handleReconcile('EURC');
|
||
console.log(` reconcile[${rec.currency}]: circulating ${rec.bchCirculating} · source locked ${rec.totalSourceLocked} · backed ${rec.backed} (+${rec.overCollateralizedBy}) · on-chain==ledger ${rec.consistent}`);
|
||
|
||
// ── 5. REDEEM QUOTE (cell-coverage + marker) — burn the FULL priced balance ──
|
||
const redeemDec = `${MINTED / 1_000_000n}.${(MINTED % 1_000_000n).toString().padStart(6, '0')}`;
|
||
step(`5. POST /redeem/quote — burn ${redeemDec} EUR → release EURC on Ethereum`);
|
||
const rq = await handleRedeemQuote({ ledger: LEDGER_KEY, amount: redeemDec, sourceRecipient: alice.address });
|
||
console.log(` redeemId ${rq.redeemId} · cell ${fmt(BigInt(rq.cellValue))} covers · marker ${rq.opReturnMarker}`);
|
||
|
||
// ── 6. USER BURN (aggregate covenant pro-rata withdraw + the marker) ──
|
||
step('6. USER BURN — UniformReserveAggregate.withdraw(amount) + OP_RETURN marker');
|
||
const contract = new CashContract(ART, [txidLE(rq.category), E.publicKey, F.publicKey, G.publicKey, SUPPLY, FX_SCALE, FX_BAND], { provider });
|
||
let burnTxid;
|
||
await sendWithRetry(async () => {
|
||
const ctrl = (await provider.getUtxos(contract.tokenAddress)).find((x) => x.token?.category === rq.category && x.token?.nft);
|
||
const rootHex = ctrl.token.nft.commitment.slice(16); // root stays STALE on withdraw (ADR-0053 §4)
|
||
const aggOnchain = BigInt('0x' + [...hexToBin(ctrl.token.nft.commitment.slice(0, 16))].reverse().map((x) => x.toString(16).padStart(2, '0')).join(''));
|
||
const fts = (await provider.getUtxos(ALICE.cashaddr)).filter((x) => x.token?.category === rq.category && !x.token?.nft);
|
||
const held = fts.reduce((s, x) => s + BigInt(x.token.amount), 0n);
|
||
if (held < MINTED) throw new Error(`Alice holds ${held} < ${MINTED}`);
|
||
const { p, t } = pickSafe(await safeUtxos(provider, OP), MF + 1000n);
|
||
const tb = new TransactionBuilder({ provider });
|
||
tb.addInput(ctrl, contract.unlock.withdraw(MINTED));
|
||
for (const x of fts) tb.addInput(x, sigAlice.unlockP2PKH());
|
||
for (const x of p) tb.addInput(x, sigOp.unlockP2PKH());
|
||
tb.addOutput({ to: contract.tokenAddress, amount: BigInt(ctrl.satoshis), token: { amount: BigInt(ctrl.token.amount) + MINTED, category: rq.category, nft: { capability: 'mutable', commitment: binToHex(u64le(aggOnchain - MINTED)) + rootHex } } });
|
||
tb.addOpReturnOutput([rq.opReturnMarker]);
|
||
if (held > MINTED) tb.addOutput({ to: ALICE.tokenaddr, amount: DUST, token: { amount: held - MINTED, category: rq.category } });
|
||
const aliceSats = fts.reduce((s, x) => s + BigInt(x.satoshis), 0n);
|
||
tb.addOutput({ to: OP.cashaddr, amount: t + aliceSats - MF - (held > MINTED ? DUST : 0n) });
|
||
burnTxid = txidOf(await tb.send());
|
||
console.log(` ✅ burned ${redeemDec} unified EUR · ${burnTxid}`);
|
||
}, { onRetry: (n, m) => console.log(` ↻ burn retry ${n} (${m.slice(0, 32)}…)`) });
|
||
|
||
// ── 7. REDEEM CONFIRM (marker + aggregate drop + largest-first debit + release) ──
|
||
step('7. POST /redeem/confirm — marker + on-chain aggregate drop verified, EURC released');
|
||
const rc = await handleRedeemConfirm({ redeemId: rq.redeemId, bchTxHash: burnTxid });
|
||
console.log(` ✅ released ${fmt(BigInt(rc.releasedAmount))} EURC on Sepolia · tx ${rc.sourceTx}`);
|
||
console.log(` cell debits: ${JSON.stringify(rc.cellDebits)} · aggregate after ${fmt(BigInt(rc.aggregate))}`);
|
||
|
||
// ── VERDICT ──
|
||
step('VERDICT — the SERVICE now speaks the PRODUCTION aggregate covenant');
|
||
const eurcEnd = BigInt(await eurc.balanceOf(alice.address));
|
||
const bchEnd = await ftBal(conf.category);
|
||
const rec2 = await handleReconcile('EURC');
|
||
const discount = ATOMIC - MINTED; // the market discount, stays in the vault as backing
|
||
// band rule (report-16): par-in-band / par-premium-capped ⇒ minted == locked;
|
||
// oracle-median (beyond the band) ⇒ minted == locked × price.
|
||
const pr = conf.priceInfo?.pricing;
|
||
const checks = [
|
||
['deposit: the band rule applied (par within 3%, market beyond, premium capped)',
|
||
pr === 'oracle-median'
|
||
? MINTED === ATOMIC * BigInt(conf.priceInfo.priceScaled) / 1_000_000n
|
||
: MINTED === ATOMIC && ['par-in-band', 'par-premium-capped', 'par-fallback'].includes(pr)],
|
||
['deposit: LIVE price present + sane (0.5–2.0) from ≥2 independent sources (unless degraded)',
|
||
pr === 'par-fallback' || (conf.priceInfo.price > 0.5 && conf.priceInfo.price < 2.0 && conf.priceInfo.sources.length >= 2)],
|
||
['deposit: the EURC.Ethereum CELL credited the PRICED value', conf.cell === 'EURC.Ethereum' && BigInt(conf.cellValue) === MINTED],
|
||
['redeem: marker binding + aggregate drop verified, released on Sepolia', BigInt(rc.releasedAmount) === MINTED],
|
||
['redeem: ledger debited largest-first', rc.cellDebits.some((d) => d.cell === 'EURC.Ethereum' && BigInt(d.debited) === MINTED)],
|
||
['Alice EURC round-trip differs ONLY by the market discount (vault keeps it as backing)', eurcStart - eurcEnd === discount],
|
||
['BCH unified EUR fully burned (Alice holds 0)', bchEnd === 0n],
|
||
['reconcile: on-chain aggregate == off-chain ledger', rec2.consistent === true],
|
||
];
|
||
for (const [l, ok] of checks) console.log(` ${ok ? '✅' : '❌'} ${l}`);
|
||
console.log(`\n Sepolia Alice EURC ${fmt(eurcStart)} → ${fmt(eurcEnd)} (discount ${discount} µEURC retained by the vault) · BCH Alice ${fmt(bchEnd)} EUR`);
|
||
const ok = checks.every(([, c]) => c);
|
||
console.log(ok
|
||
? '\nTHE SERVICE SPEAKS THE PRODUCTION COVENANT — quote/confirm/redeem run on UniformReserveAggregate\n(per-currency controller, quorum attestation, persisted cell ledger, marker-bound burns), end to end real.'
|
||
: '\nSOME CHECKS FAILED — inspect the steps above.');
|
||
process.exit(ok ? 0 : 1);
|
||
}
|
||
|
||
main().catch((e) => { console.error('FAILED:', e?.shortMessage ?? e?.message ?? e); process.exit(1); });
|