Snapshot of accumulated work-in-progress on top of the baseline commit. This session — retire Tier B from the wallet shell (single-tier model, glossary "Tiers & trust"): - SendFlow.tsx: BCH-only; removed the non-functional Tier-B send path, the D-4/D-5 raw-BCH co-display, and the ADR-0013 friction ladder. - ArtemisionScreen.tsx: Create wizard collapsed to a single 5-step on-chain genesis flow; tier-selection step + Tier-B branch (prepareTierBImport) deleted. - QuickActions.tsx / SettingsScreen.tsx: receive note reworded; "Send friction thresholds" + "Tier explainer" rows removed; registry-trust note reworded. - Verified: tsc clean, vitest 74/74, full preview walkthrough, no console errors. Pre-existing uncommitted work also folded in: - USDm -> FIATm family rename/reorg (Parameters/FIATm/, design records) - ~130 chipnet + sim-fiatm scripts (catalytic-converter, hyperinflation backtests) - snapshots 0153-0168; wiki updates (glossary, decisions, index); ADR-0044/0045 - mockups + additional shell screens (Agora, Apps, MainScreen, AssetDetail, ...) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 lines
7.6 KiB
JavaScript
145 lines
7.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* chipnet-zero-zero-swap-test.mjs — can TWO zero-value (unbacked) tokens
|
|
* swap into each other on the ASM?
|
|
*
|
|
* Distinct from snapshot 0028 (which swapped an unbacked token into a
|
|
* *backed* fixed-rate target). Here BOTH sides are zero-value:
|
|
* SOURCE: RUG — 1,000,000 FT carrying 675 sats (T-001 victim coin)
|
|
* TARGET: whitelist token — also a coin whose circulating UTXOs are
|
|
* dust-backed (snapshot 0024). Its contract nominally has
|
|
* rate=1000, but the swapper's source provides no backing to
|
|
* mint with.
|
|
*
|
|
* Prediction: the swap mints 0 target units, because the source releases
|
|
* only 675 sats of dust → floor(675 / 1000) = 0 → claim(0) reverts. Two
|
|
* zero-value tokens have nothing for the ASM to redistribute.
|
|
*/
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { TestNetWallet } from 'mainnet-js';
|
|
import {
|
|
Contract,
|
|
ElectrumNetworkProvider,
|
|
TransactionBuilder,
|
|
SignatureTemplate,
|
|
} from 'cashscript';
|
|
|
|
const ROOT = join(import.meta.dirname, '..');
|
|
const WL_STATE = join(ROOT, 'ChipnetTests', '.chipnet-whitelist-test.json');
|
|
const WL_ARTIFACT = join(ROOT, 'contracts/dist/DeviantOpenMintFT_Whitelist.json');
|
|
const KEYS_C = join(ROOT, '.chipnet-keys-c.json');
|
|
const RUG_CATEGORY = 'e9d5434a6008f3bbd3f3b7c6cdf0300eef159ba06c5fccb86ebdca0b8383957e';
|
|
|
|
function hexToBin(hex) {
|
|
const out = new Uint8Array(hex.length / 2);
|
|
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
return out;
|
|
}
|
|
function txidToConsensusBytes(txidHex) {
|
|
const d = hexToBin(txidHex); const o = new Uint8Array(d.length);
|
|
for (let i = 0; i < d.length; i++) o[i] = d[d.length - 1 - i];
|
|
return o;
|
|
}
|
|
function p2pkhLockingBytecode(pkh) {
|
|
const o = new Uint8Array(25);
|
|
o[0] = 0x76; o[1] = 0xa9; o[2] = 0x14; o.set(pkh, 3); o[23] = 0x88; o[24] = 0xac;
|
|
return o;
|
|
}
|
|
function divider(ch = '=') { console.log(ch.repeat(80)); }
|
|
function step(n, t) { console.log(''); divider(); console.log(`STEP ${n} — ${t}`); divider(); }
|
|
|
|
async function main() {
|
|
if (!existsSync(WL_STATE)) throw new Error('need .chipnet-whitelist-test.json (the zero-value TARGET contract)');
|
|
const wl = JSON.parse(readFileSync(WL_STATE, 'utf8')).contract;
|
|
const wlArtifact = JSON.parse(readFileSync(WL_ARTIFACT, 'utf8'));
|
|
const { wif } = JSON.parse(readFileSync(KEYS_C, 'utf8'));
|
|
const wallet = await TestNetWallet.fromWIF(wif);
|
|
const provider = new ElectrumNetworkProvider('chipnet');
|
|
const sig = new SignatureTemplate(wallet.privateKey);
|
|
const recipientLockingBytecode = p2pkhLockingBytecode(wallet.publicKeyHash);
|
|
|
|
// Reconstruct the whitelist (zero-value) target contract
|
|
const targetContract = new Contract(wlArtifact, [
|
|
txidToConsensusBytes(wl.categoryId),
|
|
BigInt(wl.rateSats),
|
|
BigInt(wl.feeBps),
|
|
hexToBin(wl.founderLockingBytecodeHex),
|
|
hexToBin(wl.founderPkHex),
|
|
hexToBin(wl.privilegedPkHex),
|
|
], { provider });
|
|
if (targetContract.tokenAddress !== wl.contractTokenAddress) {
|
|
throw new Error(`target reconstruct mismatch: ${targetContract.tokenAddress} vs ${wl.contractTokenAddress}`);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
step(1, 'Both sides are zero-value tokens');
|
|
// ----------------------------------------------------------------
|
|
const utxos = await provider.getUtxos(wallet.cashaddr);
|
|
const rug = utxos.find((u) => u.token?.category === RUG_CATEGORY && !u.token?.nft && u.token?.amount > 0n);
|
|
if (!rug) throw new Error('no RUG UTXO in C — expected the T-001 victim coin');
|
|
console.log(` SOURCE — RUG:`);
|
|
console.log(` ${rug.txid.slice(0, 16)}:${rug.vout} — ${rug.token.amount} FT, ${rug.satoshis} sats (${(Number(rug.satoshis)/Number(rug.token.amount)).toFixed(8)} sats/FT)`);
|
|
console.log(` TARGET — whitelist token (category ${wl.categoryId.slice(0,16)}…):`);
|
|
console.log(` contract nominal rate ${wl.rateSats}; its own circulating UTXOs are dust-backed too (snapshot 0024)`);
|
|
console.log(` Both carry ~0 BCH value.`);
|
|
|
|
// ----------------------------------------------------------------
|
|
step(2, 'What can RUG\'s released backing mint of the target?');
|
|
// ----------------------------------------------------------------
|
|
const released = BigInt(rug.satoshis); // 675
|
|
const targetRate = BigInt(wl.rateSats); // 1000
|
|
const targetUnits = released / targetRate; // floor → 0
|
|
console.log(` ASM source side (P2PKH spend) releases: ${released} sats`);
|
|
console.log(` target units = floor(${released} / ${targetRate}) = ${targetUnits}`);
|
|
console.log(` → ${rug.token.amount} RUG → ${targetUnits} whitelist tokens`);
|
|
|
|
// ----------------------------------------------------------------
|
|
step(3, 'Attempt the swap on-chain (expect revert)');
|
|
// ----------------------------------------------------------------
|
|
const reservoirUtxos = await provider.getUtxos(targetContract.tokenAddress);
|
|
const reservoir = reservoirUtxos.find((u) => u.token?.nft?.capability === 'minting' && u.token?.category === wl.categoryId && u.token?.amount > 0n);
|
|
if (!reservoir) throw new Error('whitelist reservoir not found');
|
|
|
|
console.log(' Building: burn RUG source, claim(0) on the whitelist target...');
|
|
try {
|
|
const minerFee = 6000n;
|
|
const bchOnly = utxos.filter((u) => !u.token).sort((a, b) => Number(b.satoshis) - Number(a.satoshis));
|
|
const picked = []; let tot = 0n;
|
|
for (const u of bchOnly) { picked.push(u); tot += BigInt(u.satoshis); if (tot >= minerFee + 1000n) break; }
|
|
const change = released + tot + BigInt(reservoir.satoshis) - 1000n - minerFee;
|
|
const tb = new TransactionBuilder({ provider, allowImplicitFungibleTokenBurn: true });
|
|
tb.addInput(rug, sig.unlockP2PKH()); // burn zero-value source
|
|
tb.addInput(reservoir, targetContract.unlock.claim(targetUnits, recipientLockingBytecode)); // mint 0 target
|
|
for (const u of picked) tb.addInput(u, sig.unlockP2PKH());
|
|
tb.addOutput({ to: targetContract.tokenAddress, amount: 1000n,
|
|
token: { amount: reservoir.token.amount - targetUnits, category: wl.categoryId, nft: { capability: 'minting', commitment: '' } } });
|
|
tb.addOutput({ to: wallet.tokenaddr, amount: 546n, token: { amount: targetUnits, category: wl.categoryId } });
|
|
tb.addOutput({ to: wallet.cashaddr, amount: change });
|
|
const sent = await tb.send();
|
|
const txid = typeof sent === 'string' ? sent : sent?.txid ?? sent?.txId;
|
|
console.log(` UNEXPECTED: broadcast accepted (TXID ${txid})`);
|
|
} catch (err) {
|
|
console.log(` REJECTED (as predicted): ${err.message?.split('\n')[0]}`);
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
step(4, 'Verdict');
|
|
// ----------------------------------------------------------------
|
|
console.log(' TWO zero-value tokens cannot swap on the ASM.');
|
|
console.log(` RUG (${rug.token.amount} FT) released ${released} sats → minted ${targetUnits} whitelist tokens.`);
|
|
console.log(' The ASM redistributes BCH backing; with ~0 backing on both sides there is');
|
|
console.log(' nothing to redistribute, and claim(0) reverts (require(tokenAmount > 0)).');
|
|
console.log('');
|
|
console.log(' "0-for-0" is a market/barter concept, not an ASM operation. Two willing');
|
|
console.log(' parties could still trade these via a counterparty atomic swap or an AMM at a');
|
|
console.log(' market-agreed ratio — but that needs a counterparty, which is exactly what the');
|
|
console.log(' ASM removes via backing. No backing → no counterparty-free swap.');
|
|
divider();
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('FAILED:', err?.message ?? err);
|
|
if (err?.stack) console.error(err.stack);
|
|
process.exit(1);
|
|
});
|