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>
92 lines
4.8 KiB
Text
92 lines
4.8 KiB
Text
pragma cashscript ^0.12.0;
|
||
|
||
// GamePoolEscrow — winner-take-all stake pool for MBA assets.
|
||
//
|
||
// Use case: a pari-mutuel-style game prize. Two players lock MUTUAL EQUAL
|
||
// stakes of the same MBA token into one pool UTXO (created by a single
|
||
// co-signed funding tx — atomic mutuality), and the entire pool is released
|
||
// to one winner under a resolution condition. Three resolution paths cover
|
||
// the spectrum of trust models (snapshot conversation 2026-05-22):
|
||
//
|
||
// resolveArbiter — a designated arbiter (e.g. the game server) signs the
|
||
// winning payout. Off-chain fact → one trusted party.
|
||
// resolveHashlock — whoever reveals the committed secret takes the pool.
|
||
// Trustless commit-reveal (puzzle / HTLC-style games).
|
||
// refund — after a deadline with no resolution, each player's
|
||
// equal stake is returned. Trustless safety net.
|
||
//
|
||
// The pool UTXO holds `poolAmount` FTs of `tokenCategory` backed by `poolSats`
|
||
// sats (= 2 × each player's stake). The covenant forces the FTs + backing to
|
||
// the winner (or split back on refund); the miner fee is paid from a separate
|
||
// BCH input by whoever broadcasts the resolution.
|
||
//
|
||
// Scope: 2-party winner-take-all. True N-way proportional pari-mutuel needs
|
||
// per-winner share enumeration and is a heavier extension.
|
||
|
||
contract GamePoolEscrow(
|
||
bytes tokenCategory, // the MBA being staked (same category both sides)
|
||
int poolAmount, // total pooled FTs = 2 × stakeAmount
|
||
int poolSats, // total backing = 2 × stakeSats
|
||
int stakeAmount, // per-player stake (for the refund split)
|
||
int stakeSats, // per-player backing (for the refund split)
|
||
bytes20 partyA, // player A pkh (refund recipient)
|
||
bytes20 partyB, // player B pkh (refund recipient)
|
||
pubkey arbiterPk, // arbiter for resolveArbiter (+ fee recipient)
|
||
bytes32 secretHash, // sha256(secret) for resolveHashlock
|
||
int deadline, // CLTV height/time for refund
|
||
int arbiterFeeBps // arbiter cut on resolveArbiter, 0..(some cap), bps
|
||
) {
|
||
// ---- Arbiter declares the winner: 99% → winner, arbiterFeeBps → arbiter ----
|
||
//
|
||
// The fee is taken as a SLICE OF THE POOLED FTs (not raw sats) so the peg is
|
||
// preserved: both the winner output and the arbiter-fee output are fully
|
||
// backed at the pool's per-FT rate. Fee goes to the arbiter's own P2PKH
|
||
// address, derived from arbiterPk (hash160). Hashlock/refund are fee-free.
|
||
function resolveArbiter(sig arbiterSig, bytes winnerLockingBytecode) {
|
||
require(checkSig(arbiterSig, arbiterPk));
|
||
|
||
int rate = poolSats / poolAmount; // exact: poolSats == poolAmount * rate
|
||
int feeAmount = poolAmount * arbiterFeeBps / 10000;
|
||
int winnerAmount = poolAmount - feeAmount;
|
||
|
||
// Output 0: winner (the 99% share), fully backed.
|
||
require(tx.outputs[0].lockingBytecode == winnerLockingBytecode);
|
||
require(tx.outputs[0].tokenCategory == tokenCategory);
|
||
require(tx.outputs[0].tokenAmount == winnerAmount);
|
||
require(tx.outputs[0].value == winnerAmount * rate);
|
||
|
||
// Output 1: arbiter fee (the 1% share) to the arbiter's P2PKH address,
|
||
// fully backed. Either zero, or at least dust (mirrors R-002).
|
||
if (feeAmount > 0) {
|
||
require(feeAmount * rate >= 546);
|
||
require(tx.outputs[1].lockingBytecode == new LockingBytecodeP2PKH(hash160(arbiterPk)));
|
||
require(tx.outputs[1].tokenCategory == tokenCategory);
|
||
require(tx.outputs[1].tokenAmount == feeAmount);
|
||
require(tx.outputs[1].value == feeAmount * rate);
|
||
}
|
||
}
|
||
|
||
// ---- Winner reveals the committed secret: entire pool → revealer ----
|
||
function resolveHashlock(bytes secret, bytes winnerLockingBytecode) {
|
||
require(sha256(secret) == secretHash);
|
||
require(tx.outputs[0].lockingBytecode == winnerLockingBytecode);
|
||
require(tx.outputs[0].tokenCategory == tokenCategory);
|
||
require(tx.outputs[0].tokenAmount == poolAmount);
|
||
require(tx.outputs[0].value == poolSats);
|
||
}
|
||
|
||
// ---- No resolution by the deadline: split stakes back to both ----
|
||
function refund() {
|
||
require(tx.time >= deadline);
|
||
bytes25 lbA = new LockingBytecodeP2PKH(partyA);
|
||
bytes25 lbB = new LockingBytecodeP2PKH(partyB);
|
||
require(tx.outputs[0].lockingBytecode == lbA);
|
||
require(tx.outputs[0].tokenCategory == tokenCategory);
|
||
require(tx.outputs[0].tokenAmount == stakeAmount);
|
||
require(tx.outputs[0].value == stakeSats);
|
||
require(tx.outputs[1].lockingBytecode == lbB);
|
||
require(tx.outputs[1].tokenCategory == tokenCategory);
|
||
require(tx.outputs[1].tokenAmount == stakeAmount);
|
||
require(tx.outputs[1].value == stakeSats);
|
||
}
|
||
}
|