deviant/contracts/GamePoolEscrow.cash
walerikus 518a3ce174 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

92 lines
4.8 KiB
Text
Raw Permalink 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.

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);
}
}