chore: initial project baseline — full Deviant codebase under version control
Brings the whole project into git (previously only the on-ramp feature and
a few parallel files were tracked). Includes: wallet core (src/), CashScript
contracts/, the Capacitor shell (shells/), scripts/, tests, and the docs
corpus (wiki/, snapshots/, Parameters/, Archive/, KnowledgeBase text).
Excluded via .gitignore: node_modules/, dist/, .env (secrets), testnet WIF
key files (.chipnet-*.json, devWallets.ts), KnowledgeBase/RAW media (~9.6 GB),
transcripts/ (session logs), and machine-local config (.claude/*.local,
*.lock). Secret-scanned before commit — no credentials in the tree.
Note: src/swapMath.ts, src/swapBuilder.ts, contracts/OpenMintToken.cash are
legacy (OP_RETURN era) but still imported; committed as-is, flagged for a
separate removal once de-referenced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:38:07 +02:00
#!/usr/bin/env node
/ * *
* chipnet - tote2 - mba - test . mjs — Tier - 2 open tote DENOMINATED IN AN MBA ( ADR - 0024
* § 3.1 , ADR - 0025 ) . The MBA - staking variant of chipnet - tote2 - test . mjs : same
* state - NFT + receipt state machine , but the pool is TWO co - spent covenant UTXOs
* — a state - NFT UTXO ( tote category ) + an MBA - FT vault UTXO ( peg - backed money ) .
*
* MBA = canonical DeviantOpenMintFT , rate 1000 ( finely denominated → the 1 - unit
* vault seed + floor residual are penny - scale , per ADR - 0025 ) .
*
* Stakes ( MBA units ) : A = 15 , B = 25 ( side 0 ) ; C = 20 , D = 11 ( side 1 ) ; + 1 seed . Oracle
* attests outcome 0 → side 0 splits pro - rata : A ( minority ) = floor ( 15 * 71 / 40 ) = 26 ;
* B last drains the vault remainder ( 46 ) . Negatives : loser C ( side1 ) and A ' s
* double - claim ( spent receipt ) must both be REJECTED ON - CHAIN by the covenant .
*
* Roles : founder / operator / fees = C . Resumable state JSON .
* /
import { existsSync , readFileSync , writeFileSync } from 'node:fs' ;
import { join } from 'node:path' ;
import { randomBytes } from 'node:crypto' ;
import { TestNetWallet } from 'mainnet-js' ;
import { Contract , ElectrumNetworkProvider , TransactionBuilder , SignatureTemplate } from 'cashscript' ;
import { secp256k1 , sha256 } from '@bitauth/libauth' ;
const ROOT = join ( import . meta . dirname , '..' ) ;
chore: checkpoint working tree — shell Tier-B retirement + accumulated WIP
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>
2026-06-17 01:01:37 +02:00
const STATE = join ( ROOT , 'ChipnetTests' , '.chipnet-tote2-mba-test.json' ) ;
chore: initial project baseline — full Deviant codebase under version control
Brings the whole project into git (previously only the on-ramp feature and
a few parallel files were tracked). Includes: wallet core (src/), CashScript
contracts/, the Capacitor shell (shells/), scripts/, tests, and the docs
corpus (wiki/, snapshots/, Parameters/, Archive/, KnowledgeBase text).
Excluded via .gitignore: node_modules/, dist/, .env (secrets), testnet WIF
key files (.chipnet-*.json, devWallets.ts), KnowledgeBase/RAW media (~9.6 GB),
transcripts/ (session logs), and machine-local config (.claude/*.local,
*.lock). Secret-scanned before commit — no credentials in the tree.
Note: src/swapMath.ts, src/swapBuilder.ts, contracts/OpenMintToken.cash are
legacy (OP_RETURN era) but still imported; committed as-is, flagged for a
separate removal once de-referenced.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:38:07 +02:00
const MBA _ARTIFACT = join ( ROOT , 'contracts/dist/DeviantOpenMintFT.json' ) ;
const TOTE _ARTIFACT = join ( ROOT , 'contracts/dist/ParimutuelToteOpenMBA.json' ) ;
const KEYS = { a : join ( ROOT , '.chipnet-keys.json' ) , b : join ( ROOT , '.chipnet-keys-b.json' ) , c : join ( ROOT , '.chipnet-keys-c.json' ) , d : join ( ROOT , '.chipnet-keys-d.json' ) } ;
const RESERVOIR = 1_000_000_000_000_000_000 n ;
const RATE = 1000 n , FEE = 0 n ;
const SEED = 1 n ; // 1-unit vault seed (penny-scale; absorbed by last claimant)
const DUST = 1000 n ; // receipt / state NFT dust
const MF = 6000 n ;
function 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 ; }
function binToHex ( b ) { return Array . from ( b , ( x ) => x . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ; }
function 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 ; }
function 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 ; }
function u64le ( n ) { let v = BigInt ( n ) ; const o = new Uint8Array ( 8 ) ; for ( let i = 0 ; i < 8 ; i ++ ) { o [ i ] = Number ( v & 0xff n ) ; v >>= 8 n ; } return binToHex ( o ) ; }
function u64leToBig ( hex ) { let v = 0 n ; for ( let i = 0 ; i < 8 ; i ++ ) { v |= BigInt ( parseInt ( hex . slice ( i * 2 , i * 2 + 2 ) , 16 ) ) << BigInt ( 8 * i ) ; } return v ; }
function stateCommit ( phase , outcome , A , B , rws ) { return phase + outcome + u64le ( A ) + u64le ( B ) + u64le ( rws ) ; }
function receiptCommit ( claimed , side , stake ) { return claimed + side + u64le ( stake ) ; }
const sleep = ( ms ) => new Promise ( r => setTimeout ( r , ms ) ) ;
function divider ( c = '=' ) { console . log ( c . repeat ( 80 ) ) ; }
function step ( t ) { console . log ( '' ) ; divider ( ) ; console . log ( t ) ; divider ( ) ; }
function load ( ) { return existsSync ( STATE ) ? JSON . parse ( readFileSync ( STATE , 'utf8' ) ) : { } ; }
function save ( s ) { writeFileSync ( STATE , JSON . stringify ( s , ( k , v ) => typeof v === 'bigint' ? v . toString ( ) : v , 2 ) ) ; }
function pickBch ( u , need ) { const p = [ ] ; let t = 0 n ; for ( const x of u ) { p . push ( x ) ; t += BigInt ( x . satoshis ) ; if ( t >= need ) break ; } return { p , t } ; }
function txidOf ( s ) { return typeof s === 'string' ? s : s ? . txid ? ? s ? . txId ; }
function oracleSign ( privHex , evBin , outByte ) { const m = new Uint8Array ( [ ... evBin , outByte ] ) ; const sig = secp256k1 . signMessageHashDER ( hexToBin ( privHex ) , sha256 . hash ( m ) ) ; if ( typeof sig === 'string' ) throw new Error ( 'sign ' + sig ) ; return { message : m , sig } ; }
async function main ( ) {
const mbaArt = JSON . parse ( readFileSync ( MBA _ARTIFACT , 'utf8' ) ) ;
const art = JSON . parse ( readFileSync ( TOTE _ARTIFACT , 'utf8' ) ) ;
for ( const [ k , f ] of Object . entries ( KEYS ) ) if ( ! existsSync ( f ) ) throw new Error ( ` missing key ${ k } ` ) ;
const A = await TestNetWallet . fromWIF ( JSON . parse ( readFileSync ( KEYS . a , 'utf8' ) ) . wif ) ;
const B = await TestNetWallet . fromWIF ( JSON . parse ( readFileSync ( KEYS . b , 'utf8' ) ) . wif ) ;
const C = await TestNetWallet . fromWIF ( JSON . parse ( readFileSync ( KEYS . c , 'utf8' ) ) . wif ) ;
const D = await TestNetWallet . fromWIF ( JSON . parse ( readFileSync ( KEYS . d , 'utf8' ) ) . wif ) ;
const provider = new ElectrumNetworkProvider ( 'chipnet' ) ;
const sig = { a : new SignatureTemplate ( A . privateKey ) , b : new SignatureTemplate ( B . privateKey ) , c : new SignatureTemplate ( C . privateKey ) , d : new SignatureTemplate ( D . privateKey ) } ;
const flbC = p2pkh ( C . publicKeyHash ) ;
let st = load ( ) ;
if ( ! st . oracle ) { let p ; do { p = randomBytes ( 32 ) ; } while ( typeof secp256k1 . derivePublicKeyCompressed ( p ) === 'string' ) ; st . oracle = { priv : binToHex ( p ) , pub : binToHex ( secp256k1 . derivePublicKeyCompressed ( p ) ) } ; save ( st ) ; }
const oraclePub = hexToBin ( st . oracle . pub ) ;
console . log ( ` Oracle ${ st . oracle . pub } ` ) ;
async function eligible ( w ) {
let u = await w . getUtxos ( ) ; let e = u . filter ( x => x . vout === 0 && ! x . token ) ;
if ( e . length ) return e [ 0 ] ;
const bad = u . filter ( x => ! ( x . vout === 0 && ! x . token ) ) ;
const r = await w . send ( [ { cashaddr : w . cashaddr , value : 2000 n , unit : 'sat' } ] , bad . length ? { utxoIds : bad } : undefined ) ;
console . log ( ` splitter ${ txidOf ( r ) } ` ) ; await sleep ( 12000 ) ;
u = await w . getUtxos ( ) ; e = u . filter ( x => x . vout === 0 && ! x . token ) ; if ( ! e . length ) throw new Error ( 'splitter failed' ) ; return e [ 0 ] ;
}
async function cBch ( need ) { const u = ( await provider . getUtxos ( C . cashaddr ) ) . filter ( x => ! x . token ) . sort ( ( a , b ) => Number ( b . satoshis ) - Number ( a . satoshis ) ) ; return pickBch ( u , need ) ; }
// ---- MBA genesis (founder C) ----
step ( 'Deploy MBA (DeviantOpenMintFT, rate 1000 — finely denominated)' ) ;
if ( ! st . mba ) {
const u = await eligible ( C ) ; const cat = u . txid ;
const c = new Contract ( mbaArt , [ txidLE ( cat ) , RATE , FEE , flbC ] , { provider } ) ;
const r = await C . tokenGenesis ( { cashaddr : c . tokenAddress , amount : RESERVOIR , value : 1000 n , nft : { capability : 'minting' , commitment : '' } } , [ ] , { utxoIds : [ u ] } ) ;
console . log ( ` MBA genesis ${ txidOf ( r ) } — category ${ cat } ` ) ; st . mba = cat ; save ( st ) ; await sleep ( 10000 ) ;
} else console . log ( ` MBA ${ st . mba } ` ) ;
const mbaCat = st . mba ;
const mba = new Contract ( mbaArt , [ txidLE ( mbaCat ) , RATE , FEE , flbC ] , { provider } ) ;
async function mint ( who , s , amount , label ) {
const ru = await provider . getUtxos ( mba . tokenAddress ) ;
const res = ru . find ( x => x . token ? . nft ? . capability === 'minting' && x . token ? . category === mbaCat && x . token ? . amount > 0 n ) ;
const gross = amount * RATE ;
const bch = ( await provider . getUtxos ( who . cashaddr ) ) . filter ( x => ! x . token ) . sort ( ( a , b ) => Number ( b . satoshis ) - Number ( a . satoshis ) ) ;
const { p , t } = pickBch ( bch , gross + MF + 1000 n ) ; if ( t < gross + MF + 1000 n ) throw new Error ( ` ${ label } : low BCH ${ t } ` ) ;
const tb = new TransactionBuilder ( { provider } ) ;
tb . addInput ( res , mba . unlock . claim ( amount , p2pkh ( who . publicKeyHash ) ) ) ;
for ( const x of p ) tb . addInput ( x , s . unlockP2PKH ( ) ) ;
tb . addOutput ( { to : mba . tokenAddress , amount : 1000 n , token : { amount : res . token . amount - amount , category : mbaCat , nft : { capability : 'minting' , commitment : '' } } } ) ;
tb . addOutput ( { to : who . tokenaddr , amount : gross , token : { amount , category : mbaCat } } ) ;
tb . addOutput ( { to : who . cashaddr , amount : BigInt ( res . satoshis ) + t - 1000 n - gross - MF } ) ;
console . log ( ` ${ label } : ${ amount } MBA — ${ txidOf ( await tb . send ( ) ) } ` ) ; await sleep ( 10000 ) ;
}
st . mints = st . mints || { } ;
const mints = [ [ 'a' , A , sig . a , 15 n ] , [ 'b' , B , sig . b , 25 n ] , [ 'c' , C , sig . c , 20 n ] , [ 'd' , D , sig . d , 11 n ] , [ 'seed' , C , sig . c , 1 n ] ] ;
step ( 'Mint MBA stakes A15 B25 C20 D11 + 1 seed' ) ;
for ( const [ k , w , s , amt ] of mints ) { if ( ! st . mints [ k ] ) { await mint ( w , s , amt , ` mint ${ k } ` ) ; st . mints [ k ] = true ; save ( st ) ; } else console . log ( ` ${ k } minted ` ) ; }
async function findMba ( who , amt ) { return ( await provider . getUtxos ( who . cashaddr ) ) . find ( x => x . token ? . category === mbaCat && ! x . token ? . nft && BigInt ( x . token ? . amount ) === amt ) ; }
async function mbaBal ( who ) { return ( await provider . getUtxos ( who . cashaddr ) ) . filter ( x => x . token ? . category === mbaCat && ! x . token ? . nft ) . reduce ( ( s , x ) => s + BigInt ( x . token . amount ) , 0 n ) ; }
// ---- Genesis tote NFT category ----
step ( 'Genesis tote NFT category (state minting NFT)' ) ;
const PHASE0 = stateCommit ( '00' , '00' , 0 , 0 , 0 ) ;
if ( ! st . tote ) { const u = await eligible ( C ) ; const cat = u . txid ; const r = await C . tokenGenesis ( { cashaddr : C . tokenaddr , amount : 0 n , value : DUST , nft : { capability : 'minting' , commitment : PHASE0 } } , [ ] , { utxoIds : [ u ] } ) ; console . log ( ` tote genesis ${ txidOf ( r ) } — ${ cat } ` ) ; st . tote = cat ; save ( st ) ; await sleep ( 10000 ) ; } else console . log ( ` tote ${ st . tote } ` ) ;
const toteCat = st . tote ;
if ( ! st . deadlines ) { const tip = await provider . getBlockHeight ( ) ; st . deadlines = { bet : tip , sweep : tip + 500000 } ; st . eventId = binToHex ( randomBytes ( 16 ) ) ; save ( st ) ; }
const ev = hexToBin ( st . eventId ) ;
const tote = new Contract ( art , [ txidLE ( toteCat ) , txidLE ( mbaCat ) , RATE , oraclePub , ev , BigInt ( st . deadlines . bet ) , BigInt ( st . deadlines . sweep ) , C . publicKeyHash , 0 n , C . publicKeyHash ] , { provider } ) ; // rakeBps=0 (no rake)
console . log ( ` contract ${ tote . tokenAddress } (betDeadline ${ st . deadlines . bet } ) ` ) ;
async function stateUtxo ( ) { return ( await provider . getUtxos ( tote . tokenAddress ) ) . find ( x => x . token ? . category === toteCat && x . token ? . nft ? . capability === 'minting' ) ; }
async function vaultUtxo ( ) { return ( await provider . getUtxos ( tote . tokenAddress ) ) . find ( x => x . token ? . category === mbaCat && ! x . token ? . nft ) ; }
// ---- Setup: relocate state NFT + seed the MBA vault into the covenant ----
step ( 'Setup — state NFT + 1-unit MBA vault into the covenant' ) ;
if ( ! st . setup ) {
const g = ( await provider . getUtxos ( C . cashaddr ) ) . find ( x => x . token ? . category === toteCat && x . token ? . nft ? . capability === 'minting' ) ;
const seedU = await findMba ( C , SEED ) ;
if ( ! g || ! seedU ) throw new Error ( ` setup: missing state NFT= ${ ! ! g } seed= ${ ! ! seedU } ` ) ;
const { p , t } = await cBch ( MF + DUST + 1000 n ) ;
const tb = new TransactionBuilder ( { provider } ) ;
tb . addInput ( g , sig . c . unlockP2PKH ( ) ) ;
tb . addInput ( seedU , sig . c . unlockP2PKH ( ) ) ;
for ( const x of p ) tb . addInput ( x , sig . c . unlockP2PKH ( ) ) ;
tb . addOutput ( { to : tote . tokenAddress , amount : DUST , token : { amount : 0 n , category : toteCat , nft : { capability : 'minting' , commitment : PHASE0 } } } ) ; // [0] state
tb . addOutput ( { to : tote . tokenAddress , amount : SEED * RATE , token : { amount : SEED , category : mbaCat } } ) ; // [1] vault seed
tb . addOutput ( { to : C . cashaddr , amount : BigInt ( g . satoshis ) + BigInt ( seedU . satoshis ) + t - DUST - SEED * RATE - MF } ) ;
st . setup = txidOf ( await tb . send ( ) ) ; save ( st ) ; console . log ( ` setup — ${ st . setup } ` ) ; await sleep ( 10000 ) ;
} else console . log ( ` (setup ${ st . setup } ) ` ) ;
// ---- BET: spend state[0]+vault[1]+bettor MBA stake; mint receipt ----
const bets = [ [ 'a' , A , sig . a , '00' , 15 n ] , [ 'b' , B , sig . b , '00' , 25 n ] , [ 'c' , C , sig . c , '01' , 20 n ] , [ 'd' , D , sig . d , '01' , 11 n ] ] ;
step ( 'BET — A,B side0; C,D side1 (covenant spends state + vault each tx)' ) ;
st . bets = st . bets || { } ; st . tot = st . tot || { A : '0' , B : '0' } ;
for ( const [ k , w , s , sideHex , stake ] of bets ) {
if ( st . bets [ k ] ) { console . log ( ` ${ k } bet ` ) ; continue ; }
const sUtxo = await stateUtxo ( ) , vUtxo = await vaultUtxo ( ) ;
if ( ! sUtxo || ! vUtxo ) throw new Error ( ` bet ${ k } : state= ${ ! ! sUtxo } vault= ${ ! ! vUtxo } ` ) ;
const stakeU = await findMba ( w , stake ) ; if ( ! stakeU ) throw new Error ( ` bet ${ k } : stake UTXO missing ` ) ;
let tA = BigInt ( st . tot . A ) , tB = BigInt ( st . tot . B ) ; if ( sideHex === '00' ) tA += stake ; else tB += stake ;
const newCommit = stateCommit ( '00' , '00' , tA , tB , 0 ) ;
const newPool = BigInt ( vUtxo . token . amount ) + stake ;
const { p , t } = await cBch ( MF + DUST + 1000 n ) ;
const tb = new TransactionBuilder ( { provider } ) ;
tb . addInput ( sUtxo , tote . unlock . bet ( stake , hexToBin ( sideHex ) ) ) ; // [0] state (covenant)
tb . addInput ( vUtxo , tote . unlock . bet ( stake , hexToBin ( sideHex ) ) ) ; // [1] vault (covenant)
tb . addInput ( stakeU , s . unlockP2PKH ( ) ) ; // [2] bettor MBA stake
for ( const x of p ) tb . addInput ( x , sig . c . unlockP2PKH ( ) ) ; // [3+] C fee/dust
tb . addOutput ( { to : tote . tokenAddress , amount : DUST , token : { amount : 0 n , category : toteCat , nft : { capability : 'minting' , commitment : newCommit } } } ) ; // [0] state
tb . addOutput ( { to : tote . tokenAddress , amount : newPool * RATE , token : { amount : newPool , category : mbaCat } } ) ; // [1] vault
tb . addOutput ( { to : w . tokenaddr , amount : DUST , token : { amount : 0 n , category : toteCat , nft : { capability : 'mutable' , commitment : receiptCommit ( '00' , sideHex , stake ) } } } ) ; // [2] receipt
tb . addOutput ( { to : C . cashaddr , amount : BigInt ( sUtxo . satoshis ) + BigInt ( stakeU . satoshis ) + t - DUST - DUST - newPool * RATE + BigInt ( vUtxo . satoshis ) - MF } ) ; // [3] change
console . log ( ` ${ k } bets ${ stake } side ${ sideHex } — ${ txidOf ( await tb . send ( ) ) } ` ) ;
st . bets [ k ] = true ; st . tot = { A : tA . toString ( ) , B : tB . toString ( ) } ; save ( st ) ; await sleep ( 10000 ) ;
}
const totalPool = BigInt ( st . tot . A ) + BigInt ( st . tot . B ) , totalWin = BigInt ( st . tot . A ) ;
// ---- ATTEST outcome 0 ----
step ( ` ATTEST outcome 0 — totalPool= ${ totalPool } , totalWin= ${ totalWin } ` ) ;
if ( ! st . attest ) {
const sUtxo = await stateUtxo ( ) , vUtxo = await vaultUtxo ( ) ;
const { message , sig : oSig } = oracleSign ( st . oracle . priv , ev , 0x00 ) ;
const newCommit = stateCommit ( '01' , '00' , totalPool , totalWin , totalWin ) ;
const { p , t } = await cBch ( MF + 1000 n ) ;
const tb = new TransactionBuilder ( { provider } ) ;
tb . setLocktime ( Number ( st . deadlines . bet ) ) ;
tb . addInput ( sUtxo , tote . unlock . attest ( oSig , message ) ) ;
tb . addInput ( vUtxo , tote . unlock . attest ( oSig , message ) ) ;
for ( const x of p ) tb . addInput ( x , sig . c . unlockP2PKH ( ) ) ;
tb . addOutput ( { to : tote . tokenAddress , amount : BigInt ( sUtxo . satoshis ) , token : { amount : 0 n , category : toteCat , nft : { capability : 'minting' , commitment : newCommit } } } ) ;
tb . addOutput ( { to : tote . tokenAddress , amount : BigInt ( vUtxo . satoshis ) , token : { amount : vUtxo . token . amount , category : mbaCat } } ) ;
tb . addOutput ( { to : C . cashaddr , amount : t - MF } ) ;
st . attest = txidOf ( await tb . send ( ) ) ; save ( st ) ; console . log ( ` attested — ${ st . attest } ` ) ; await sleep ( 10000 ) ;
} else console . log ( ` (attest ${ st . attest } ) ` ) ;
async function findReceipt ( w , sideHex , stake , claimedHex ) { const rc = receiptCommit ( claimedHex , sideHex , stake ) ; return ( await provider . getUtxos ( w . cashaddr ) ) . find ( x => x . token ? . category === toteCat && x . token ? . nft ? . capability === 'mutable' && x . token ? . nft ? . commitment === rc ) ; }
const isReject = ( e ) => / ParimutuelToteOpenMBA \ . cash | Require statement failed | mandatory - script | failed to verify | Verify / i . test ( ( e ? . message ? ? e ) . toString ( ) ) ;
// not-last claim spending the receipt with the given claimed flag
async function notLast ( w , s , sideHex , stake , claimedHex ) {
const sUtxo = await stateUtxo ( ) , vUtxo = await vaultUtxo ( ) ;
const rcpt = await findReceipt ( w , sideHex , stake , claimedHex ) ; if ( ! rcpt ) throw new Error ( 'claim: receipt missing' ) ;
const rwsNow = u64leToBig ( sUtxo . token . nft . commitment . slice ( 36 , 52 ) ) ;
const payout = stake * totalPool / totalWin ;
const newCommit = stateCommit ( '01' , '00' , totalPool , totalWin , rwsNow - stake ) ;
const poolIn = BigInt ( vUtxo . token . amount ) ;
const { p , t } = await cBch ( MF + DUST + 1000 n ) ;
const tb = new TransactionBuilder ( { provider } ) ;
tb . addInput ( sUtxo , tote . unlock . claim ( p2pkh ( w . publicKeyHash ) ) ) ; // [0] state
tb . addInput ( vUtxo , tote . unlock . claim ( p2pkh ( w . publicKeyHash ) ) ) ; // [1] vault
tb . addInput ( rcpt , s . unlockP2PKH ( ) ) ; // [2] receipt
for ( const x of p ) tb . addInput ( x , sig . c . unlockP2PKH ( ) ) ;
tb . addOutput ( { to : tote . tokenAddress , amount : BigInt ( sUtxo . satoshis ) , token : { amount : 0 n , category : toteCat , nft : { capability : 'minting' , commitment : newCommit } } } ) ;
tb . addOutput ( { to : tote . tokenAddress , amount : ( poolIn - payout ) * RATE , token : { amount : poolIn - payout , category : mbaCat } } ) ;
tb . addOutput ( { to : w . tokenaddr , amount : payout * RATE , token : { amount : payout , category : mbaCat } } ) ;
tb . addOutput ( { to : w . tokenaddr , amount : DUST , token : { amount : 0 n , category : toteCat , nft : { capability : 'mutable' , commitment : receiptCommit ( '01' , sideHex , stake ) } } } ) ;
tb . addOutput ( { to : C . cashaddr , amount : BigInt ( vUtxo . satoshis ) + BigInt ( rcpt . satoshis ) + t - ( poolIn - payout ) * RATE - payout * RATE - DUST - MF } ) ;
return { txid : txidOf ( await tb . send ( ) ) , payout } ;
}
async function lastClaim ( w , s , sideHex , stake ) {
const sUtxo = await stateUtxo ( ) , vUtxo = await vaultUtxo ( ) ;
const rcpt = await findReceipt ( w , sideHex , stake , '00' ) ; if ( ! rcpt ) throw new Error ( 'last: receipt missing' ) ;
const poolIn = BigInt ( vUtxo . token . amount ) ;
const { p , t } = await cBch ( MF + DUST + 1000 n ) ;
const tb = new TransactionBuilder ( { provider , allowImplicitFungibleTokenBurn : true } ) ;
tb . addInput ( sUtxo , tote . unlock . claim ( p2pkh ( w . publicKeyHash ) ) ) ;
tb . addInput ( vUtxo , tote . unlock . claim ( p2pkh ( w . publicKeyHash ) ) ) ;
tb . addInput ( rcpt , s . unlockP2PKH ( ) ) ;
for ( const x of p ) tb . addInput ( x , sig . c . unlockP2PKH ( ) ) ;
tb . addOutput ( { to : w . tokenaddr , amount : poolIn * RATE , token : { amount : poolIn , category : mbaCat } } ) ; // all vault MBA to claimer
tb . addOutput ( { to : w . tokenaddr , amount : DUST , token : { amount : 0 n , category : toteCat , nft : { capability : 'mutable' , commitment : receiptCommit ( '01' , sideHex , stake ) } } } ) ;
tb . addOutput ( { to : C . cashaddr , amount : BigInt ( sUtxo . satoshis ) + BigInt ( vUtxo . satoshis ) + BigInt ( rcpt . satoshis ) + t - poolIn * RATE - DUST - MF } ) ;
return { txid : txidOf ( await tb . send ( ) ) , drained : poolIn } ;
}
step ( 'NEG — loser C (side1) claims -> covenant must REJECT on-chain' ) ;
if ( ! st . loserTested ) { let r = false ; try { await notLast ( C , sig . c , '01' , 20 n , '00' ) ; } catch ( e ) { if ( isReject ( e ) ) { r = true ; console . log ( ` rejected on-chain: ${ ( e ? . message ? ? e ) . toString ( ) . slice ( 0 , 110 ) } ` ) ; } else throw e ; } if ( ! r ) throw new Error ( 'SECURITY: loser accepted' ) ; st . loserTested = true ; save ( st ) ; } else console . log ( ' (loser ok)' ) ;
step ( 'CLAIM A (minority) — floor 15*71/40 = 26' ) ;
if ( ! st . claimA ) { const r = await notLast ( A , sig . a , '00' , 15 n , '00' ) ; st . claimA = r . txid ; st . payA = r . payout . toString ( ) ; save ( st ) ; console . log ( ` A= ${ r . payout } — ${ r . txid } ` ) ; await sleep ( 10000 ) ; } else console . log ( ` (A ${ st . claimA } ) ` ) ;
step ( 'NEG — A re-claims spent receipt -> covenant must REJECT on-chain' ) ;
if ( ! st . dblTested ) { let r = false ; try { await notLast ( A , sig . a , '00' , 15 n , '01' ) ; } catch ( e ) { if ( isReject ( e ) ) { r = true ; console . log ( ` rejected on-chain: ${ ( e ? . message ? ? e ) . toString ( ) . slice ( 0 , 110 ) } ` ) ; } else throw e ; } if ( ! r ) throw new Error ( 'SECURITY: double-claim accepted' ) ; st . dblTested = true ; save ( st ) ; } else console . log ( ' (double ok)' ) ;
step ( 'CLAIM B (last) — drains vault + burns state NFT' ) ;
if ( ! st . claimB ) { const r = await lastClaim ( B , sig . b , '00' , 25 n ) ; st . claimB = r . txid ; st . drainedB = r . drained . toString ( ) ; save ( st ) ; console . log ( ` B drained ${ r . drained } — ${ r . txid } ` ) ; await sleep ( 10000 ) ; } else console . log ( ` (B ${ st . claimB } ) ` ) ;
step ( 'VERIFY' ) ;
const aBal = await mbaBal ( A ) , bBal = await mbaBal ( B ) ;
const checks = [
[ ` A holds pro-rata ${ st . payA } MBA ` , st . payA === '26' ] ,
[ 'loser rejected on-chain' , ! ! st . loserTested ] ,
[ 'double-claim rejected on-chain' , ! ! st . dblTested ] ,
[ ` B drained ${ st . drainedB } MBA (incl seed+residual) ` , ! ! st . claimB ] ,
[ 'state NFT + vault consumed' , ! ( await stateUtxo ( ) ) && ! ( await vaultUtxo ( ) ) ] ,
] ;
let ok = true ; for ( const [ l , p ] of checks ) { console . log ( ` [ ${ p ? 'PASS' : 'FAIL' } ] ${ l } ` ) ; ok = ok && p ; }
console . log ( ` MBA balances: A= ${ aBal } B= ${ bBal } ` ) ;
step ( 'SUMMARY — Tier-2 MBA-denominated open tote (2-UTXO)' ) ;
console . log ( ` tote ${ toteCat } \n mba ${ mbaCat } \n oracle ${ st . oracle . pub } ` ) ;
console . log ( ` setup/attest: ${ st . setup } / ${ st . attest } ` ) ;
console . log ( ` claimA/claimB: ${ st . claimA } / ${ st . claimB } ` ) ;
console . log ( ' Covenant computed MBA pro-rata from its state-NFT ledger across a co-spent' ) ;
console . log ( ' state+vault pair; receipts blocked double-claim + losing side on-chain.' ) ;
divider ( ) ;
if ( ! ok ) { console . error ( 'VERIFY FAILED' ) ; process . exit ( 1 ) ; }
process . exit ( 0 ) ;
}
main ( ) . catch ( ( e ) => { console . error ( 'FAILED:' , e ? . message ? ? e ) ; if ( e ? . stack ) console . error ( e . stack ) ; process . exit ( 1 ) ; } ) ;