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>
158 lines
12 KiB
JavaScript
158 lines
12 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* chipnet-product2-shares-dividends.mjs — Product ② (shares + dividends), end-to-end.
|
||
*
|
||
* Fixed cap table + dividend ∝ shares + a REDEEMABLE FLOOR (the new bit vs ①).
|
||
* "Floored equity": a shareholder can take income (dividend) OR recover principal (floor).
|
||
*
|
||
* - `DeviantShareMBA` share · rate 100k · fee 500bps · split 4000bps · distributionLB = vault.
|
||
* 1. B claims 10 shares (the issued cap table); fee 50k → company 20k(B) + pool 30k(vault).
|
||
* 2. distribute: 6 → C, 4 → D.
|
||
* 3. DIVIDEND: oracle pays C its pro-rata (6/10 = 18,000) from the vault — C keeps its shares.
|
||
* 4. FLOOR: D redeems its 4 shares → recovers the BCH backing (4×100k = 400,000 − miner),
|
||
* shares burned. Downside protection exercised.
|
||
*
|
||
* Run: node --import tsx scripts/chipnet-product2-shares-dividends.mjs --yes
|
||
*/
|
||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||
import { join } from 'node:path';
|
||
import { TestNetWallet } from 'mainnet-js';
|
||
import { Contract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript';
|
||
import { secp256k1, sha256 as laSha256, cashAddressToLockingBytecode } from '@bitauth/libauth';
|
||
|
||
const ROOT = join(import.meta.dirname, '..');
|
||
const STATE = join(ROOT, 'ChipnetTests', '.chipnet-product2-shares.json');
|
||
const SHARE_ART = JSON.parse(readFileSync(join(ROOT, 'contracts/dist/DeviantShareMBA.json'), 'utf8'));
|
||
const VAULT_ART = JSON.parse(readFileSync(join(ROOT, 'contracts/dist/DistributionVault.json'), 'utf8'));
|
||
|
||
const RATE = 100000n, FEE_BPS = 500n, COMPANY_SPLIT_BPS = 4000n;
|
||
const SUPPLY = 1000n, POSITION_VALUE = 1000n;
|
||
const CAP = 10n; // issued cap table
|
||
const SPLIT = [{ who: 'C', units: 6n }, { who: 'D', units: 4n }];
|
||
const DIVIDEND_TO = 'C'; // C takes income
|
||
const REDEEM_BY = 'D'; // D takes the floor
|
||
|
||
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 txidToConsensusBytes = (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 p2pkh = (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; };
|
||
const u32le = (n) => { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, Number(n), true); return b; };
|
||
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 pick = (cash, need) => { const p = []; let t = 0n; for (const u of cash) { p.push(u); t += BigInt(u.satoshis); if (t >= need) break; } if (t < need) throw new Error(`insufficient ${t}<${need}`); return { p, t }; };
|
||
function oracleSign(priv, msg) { const s = secp256k1.signMessageHashSchnorr(priv, laSha256.hash(msg)); if (typeof s === 'string') throw new Error(s); return s; }
|
||
function lockOf(addr) { const r = cashAddressToLockingBytecode(addr); if (typeof r === 'string') throw new Error(r); return r.bytecode; }
|
||
function payoutMessage(recipientLB, amount, vu) { const m = new Uint8Array(69); m.set(recipientLB, 0); m.set(u64le(amount), 25); m.set(txidToConsensusBytes(vu.txid), 33); m.set(u32le(vu.vout), 65); return m; }
|
||
|
||
async function main() {
|
||
if (!process.argv.includes('--yes')) { console.log('Re-run with --yes.'); return; }
|
||
mkdirSync(join(ROOT, 'ChipnetTests'), { recursive: true });
|
||
const provider = new ElectrumNetworkProvider('chipnet');
|
||
const B = await TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, '.chipnet-keys-b.json'), 'utf8')).wif);
|
||
const C = await TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, '.chipnet-keys-c.json'), 'utf8')).wif);
|
||
const D = await TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, '.chipnet-keys-d.json'), 'utf8')).wif);
|
||
const E = await TestNetWallet.fromWIF(JSON.parse(readFileSync(join(ROOT, '.chipnet-keys-e.json'), 'utf8')).wif);
|
||
const sigB = new SignatureTemplate(B.privateKey), sigD = new SignatureTemplate(D.privateKey);
|
||
const W = { C, D }; const SIG = { C: new SignatureTemplate(C.privateKey), D: sigD };
|
||
const vault = new Contract(VAULT_ART, [E.publicKey], { provider });
|
||
const companyLB = p2pkh(B.publicKeyHash), distributionLB = lockOf(vault.address);
|
||
const st = existsSync(STATE) ? JSON.parse(readFileSync(STATE, 'utf8')) : {};
|
||
const save = () => writeFileSync(STATE, JSON.stringify(st, null, 2));
|
||
const ctor = (cat) => [txidToConsensusBytes(cat), RATE, FEE_BPS, COMPANY_SPLIT_BPS, companyLB, distributionLB];
|
||
const cashOf = async (w) => (await provider.getUtxos(w.cashaddr)).filter((u) => !u.token).sort((a, b) => Number(BigInt(b.satoshis) - BigInt(a.satoshis)));
|
||
const coinBal = async (w) => (await provider.getUtxos(w.tokenaddr)).filter((u) => u.token && u.token.category === st.categoryId && !u.token.nft).reduce((a, u) => a + BigInt(u.token.amount), 0n);
|
||
const bchOf = async (w) => (await provider.getUtxos(w.cashaddr)).filter((u) => !u.token).reduce((a, u) => a + BigInt(u.satoshis), 0n);
|
||
const vaultUtxo = async () => (await provider.getUtxos(vault.address)).filter((x) => !x.token).sort((a, b) => Number(BigInt(b.satoshis) - BigInt(a.satoshis)))[0];
|
||
|
||
console.log(`Product ② shares+dividends · cap table ${CAP} shares · vault ${vault.address.slice(0, 22)}…`);
|
||
|
||
if (!st.categoryId) {
|
||
let u = await provider.getUtxos(B.cashaddr); let e = u.filter((x) => x.vout === 0 && !x.token);
|
||
if (!e.length) { await B.send([{ cashaddr: B.cashaddr, value: 5000n, unit: 'sat' }]); await sleep(12000); u = await provider.getUtxos(B.cashaddr); e = u.filter((x) => x.vout === 0 && !x.token); }
|
||
const utxo = e[0]; const cat = utxo.txid;
|
||
const ct = new Contract(SHARE_ART, ctor(cat), { provider });
|
||
const r = await B.tokenGenesis({ cashaddr: ct.tokenAddress, amount: SUPPLY, value: POSITION_VALUE }, [], { utxoIds: [utxo] });
|
||
console.log(`share contract ${ct.tokenAddress}\ngenesis ${r?.txId ?? r?.txid} · category ${cat}`);
|
||
st.categoryId = cat; save(); await sleep(10000);
|
||
}
|
||
const share = new Contract(SHARE_ART, ctor(st.categoryId), { provider });
|
||
|
||
// 1. claim the cap table (10 shares) → B
|
||
if (!st.claim) {
|
||
const gross = CAP * RATE, fee = gross * FEE_BPS / 10000n, company = fee * COMPANY_SPLIT_BPS / 10000n, shareholder = fee - company;
|
||
const ru = await provider.getUtxos(share.tokenAddress);
|
||
const pos = ru.find((u) => u.token && u.token.category === st.categoryId && !u.token.nft && BigInt(u.token.amount) > 0n);
|
||
const reservoir = BigInt(pos.token.amount);
|
||
const minerFee = 2500n; const { p, t } = pick(await cashOf(B), gross + fee + minerFee + 1000n);
|
||
const change = BigInt(pos.satoshis) + t - POSITION_VALUE - gross - company - shareholder - minerFee;
|
||
const tb = new TransactionBuilder({ provider });
|
||
tb.addInput(pos, share.unlock.claim(CAP, p2pkh(B.publicKeyHash)));
|
||
for (const u of p) tb.addInput(u, sigB.unlockP2PKH());
|
||
tb.addOutput({ to: share.tokenAddress, amount: POSITION_VALUE, token: { amount: reservoir - CAP, category: st.categoryId } });
|
||
tb.addOutput({ to: B.tokenaddr, amount: gross, token: { amount: CAP, category: st.categoryId } });
|
||
tb.addOutput({ to: B.cashaddr, amount: company });
|
||
tb.addOutput({ to: vault.address, amount: shareholder });
|
||
if (change >= 546n) tb.addOutput({ to: B.cashaddr, amount: change });
|
||
const sent = await tb.send(); const txid = typeof sent === 'string' ? sent : sent?.txid ?? sent?.txId;
|
||
console.log(`\n1. issue ${CAP} shares · fee ${fee} → company ${company}(B) + dividend pool ${shareholder}(vault) · ${txid}`);
|
||
st.claim = { pool: shareholder.toString(), txid }; save(); await sleep(9000);
|
||
}
|
||
|
||
// 2. distribute the cap table: 6 → C, 4 → D
|
||
if (!st.distributed) {
|
||
const bu = (await provider.getUtxos(B.tokenaddr)).find((u) => u.token && u.token.category === st.categoryId && !u.token.nft && BigInt(u.token.amount) === CAP);
|
||
const minerFee = 2000n; const { p, t } = pick(await cashOf(B), minerFee + 1000n);
|
||
const tb = new TransactionBuilder({ provider });
|
||
tb.addInput(bu, sigB.unlockP2PKH()); for (const u of p) tb.addInput(u, sigB.unlockP2PKH());
|
||
for (const s of SPLIT) tb.addOutput({ to: W[s.who].tokenaddr, amount: s.units * RATE, token: { amount: s.units, category: st.categoryId } });
|
||
const change = BigInt(bu.satoshis) + t - SPLIT.reduce((a, s) => a + s.units * RATE, 0n) - minerFee;
|
||
if (change >= 546n) tb.addOutput({ to: B.cashaddr, amount: change });
|
||
const sent = await tb.send(); const txid = typeof sent === 'string' ? sent : sent?.txid ?? sent?.txId;
|
||
console.log(`2. distribute cap table: ${SPLIT.map((s) => s.units + '→' + s.who).join(', ')} · ${txid}`);
|
||
st.distributed = { txid }; save(); await sleep(9000);
|
||
}
|
||
|
||
// 3. DIVIDEND to C (∝ shares), from the vault
|
||
if (!st.dividend) {
|
||
const cShares = await coinBal(C); const pool = BigInt(st.claim.pool);
|
||
const amount = pool * cShares / CAP; // 6/10 of 30,000 = 18,000
|
||
const recipientLB = p2pkh(C.publicKeyHash);
|
||
const vu = await vaultUtxo(); const remainder = BigInt(vu.satoshis) - amount;
|
||
const msg = payoutMessage(recipientLB, amount, vu); const sig = oracleSign(E.privateKey, msg);
|
||
const minerFee = 1500n; const { p, t } = pick(await cashOf(B), minerFee + 1000n); const change = t - minerFee;
|
||
const cBefore = await bchOf(C);
|
||
const tb = new TransactionBuilder({ provider });
|
||
tb.addInput(vu, vault.unlock.payout(recipientLB, sig, msg));
|
||
for (const u of p) tb.addInput(u, sigB.unlockP2PKH());
|
||
tb.addOutput({ to: C.cashaddr, amount });
|
||
tb.addOutput({ to: vault.address, amount: remainder });
|
||
if (change >= 546n) tb.addOutput({ to: B.cashaddr, amount: change });
|
||
const sent = await tb.send(); const txid = typeof sent === 'string' ? sent : sent?.txid ?? sent?.txId;
|
||
console.log(`3. DIVIDEND: C (${cShares}/${CAP} shares) → ${amount} sat · vault→${remainder} · ${txid}`);
|
||
st.dividend = { cShares: cShares.toString(), amount: amount.toString(), cBefore: cBefore.toString(), txid }; save(); await sleep(9000);
|
||
}
|
||
|
||
// 4. FLOOR: D redeems its shares → recovers backing (self-backed P2PKH burn, no oracle)
|
||
if (!st.redeem) {
|
||
const du = (await provider.getUtxos(D.tokenaddr)).find((u) => u.token && u.token.category === st.categoryId && !u.token.nft && BigInt(u.token.amount) > 0n);
|
||
if (!du) throw new Error('D shares not found');
|
||
const dShares = BigInt(du.token.amount); const backing = BigInt(du.satoshis);
|
||
const minerFee = 1000n; const recovered = backing - minerFee;
|
||
const dBefore = await bchOf(D);
|
||
const tb = new TransactionBuilder({ provider, allowImplicitFungibleTokenBurn: true });
|
||
tb.addInput(du, sigD.unlockP2PKH());
|
||
tb.addOutput({ to: D.cashaddr, amount: recovered });
|
||
const sent = await tb.send(); const txid = typeof sent === 'string' ? sent : sent?.txid ?? sent?.txId;
|
||
console.log(`4. FLOOR: D redeems ${dShares} shares → recovered ${recovered} sat (backing ${backing}) · ${txid}`);
|
||
st.redeem = { dShares: dShares.toString(), recovered: recovered.toString(), dBefore: dBefore.toString(), txid }; save(); await sleep(9000);
|
||
}
|
||
|
||
const cGot = (await bchOf(C)) - BigInt(st.dividend.cBefore);
|
||
const dGot = (await bchOf(D)) - BigInt(st.redeem.dBefore);
|
||
const dSharesNow = await coinBal(D);
|
||
console.log(`\n=== RESULT — Product ② shares + dividends (floored equity) ===`);
|
||
console.log(` cap table: ${CAP} shares issued (C ${st.dividend.cShares} · D ${st.redeem.dShares})`);
|
||
console.log(` C — INCOME: dividend ${cGot} sat on ${st.dividend.cShares} shares (∝ 6/10) ${cGot === BigInt(st.dividend.amount) ? '✓' : '✗'}; still holds its shares (floor intact)`);
|
||
console.log(` D — FLOOR: redeemed ${st.redeem.dShares} shares → recovered ${dGot} sat (principal) ${dGot === BigInt(st.redeem.recovered) ? '✓' : '✗'}; shares now ${dSharesNow} ${dSharesNow === 0n ? '(burned ✓)' : ''}`);
|
||
console.log(` → floored equity proven: income path (C) + redeemable-floor path (D), on a fixed cap table.`);
|
||
}
|
||
main().then(() => process.exit(0)).catch((e) => { console.error('FAILED:', e?.message ?? e); if (e?.stack) console.error(e.stack); process.exit(1); });
|