deviant/scripts/correspondent-e2e-aggregate.mjs
Local Dev debd6a6fbb chore: sweep in-flight work across all domains
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>
2026-07-29 01:14:33 +02:00

166 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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); });