SilverScript Covenants
Every K100bet bet, payout, vault, and stake is enforced by an open-source SilverScript smart contract on the Kaspa blockchain. The full source is below and on GitHub. No central server can move locked funds — only the on-chain signatures defined in these contracts can.
How to verify
- Clone the source:
git clone https://github.com/K100bet/contracts.git - Build the compiler:
(cd silverscript && cargo build --release -p silverscript-lang --bin silverc) - Compile a covenant with the deployment's constructor args (pubkeys etc.):
silverc <contract>.sil --constructor-args args.json -o out.json - The compiled redeem script is hashed into the on-chain P2SH address (
aa20<hash>87). Compare the hash from your compile to the hash embedded in the covenant UTXO on a Kaspa explorer — they must match. This proves the deployed contract is exactly the source you read here. - K-UserVault is per-user: each vault has a unique redeem script (it embeds the owner pubkey). Verify a specific vault by reconstructing its redeem script from the owner pubkey + the SECIFIED source.
K-UserVault (Kvault)
Deployed (SECIFIED)The self-custodial user vault. The browser holds the owner private key; the server never sees it. Funds can only move via three entrypoints, each with on-chain signature enforcement. The 'bet' path requires BOTH the platform and oracle keys to co-sign on-chain (2-of-2) — neither the platform nor anyone else can drain a vault unilaterally. This is the SECIFIED version: the earlier production bytecode stubbed the data-signature check, which has been replaced with real on-chain checkSig.
Entrypoints
withdrawOwner signature (checkSig)Output[0] must pay the owner's P2PK; change returns to the vault.betPlatform + Oracle signatures (2-of-2 checkSig)Output[0] is the market/slot destination with value == betAmount; change returns to the vault. The exact tx (including destination) is bound by both signatures.sweepOwner signature (checkSig) + timelockOnly after sweepTimeout; output[0] pays the owner's P2PK. Emergency recovery if the platform disappears.// =============================================================================
// K-UserVault — SECIFIED (on-line checkSig) — TESTNET REVIEW, THEN MAINNET
// =============================================================================
//
// Fixes the CRITICAL authorization gap in the production k_user_vault.sil:
// `bet` (and the dead `withdrawPlatform`/`slotbet`) used the "datasig" scheme,
// but silverc 0.1 stubs `checkDataSig` to `OpTrue` → NO on-chain signature.
// Conservation was enforced (output[0].value == betAmount, change → vault) but
// anyone with the redeem script could drain the vault via `bet` with garbage
// datasigs. See COVENANT_SECURITY_REVIEW.md for the full proof.
//
// THE FIX: `bet` now requires on-chain tx-level `checkSig(platformSig, platformPk)`
// + `checkSig(oracleSig, oraclePk)` (2-of-2). `checkSig` is functional in silverc
// 0.1 (the production `withdraw`/`sweep` already use it). Revealing the pubkeys
// in the redeem script is harmless — forging a schnorr sig needs the PRIVATE keys
// (held only by the deposit-listener).
//
// SLIMMED to fit the Kaspa tx-mass budget (the old `bet` execution blew the
// 500,000 mass limit — your testnet probe showed ~10^9):
// - Dropped the dead `slotbet` + `withdrawPlatform` entrypoints (no callers).
// - Dropped `marketAddrHash` + `nonce` from `bet` — they were unused in the
// body (the covenant can't verify a destination from a 20-byte hash), and
// tx-level `checkSig` already binds output[0] (the platform+oracle sign the
// exact tx) + prevents replay (same input UTXO can't be spent twice).
// - Only `withdraw`, `bet`, `sweep` remain — all params consumed (no
// silverc stack-leak).
//
// Matching code (must ship together):
// - covenant-spender.ts: k_user_vault_bet sigscheme = "house+oracle" (produces
// [platformSig, oracleSig] tx sigs in order).
// - user-vault-service.ts handleVaultBet: args = [{ betAmount }] (non-sig
// params only; no datasig, no marketAddrHash, no nonce).
//
// ── DEPLOY ──
// 1. Compile: silverc contracts/silverscript/k_user_vault_SECIFIED.sil
// --constructor-args <ctor.json> -o build/k_user_vault_secified.json
// (same ctor-arg shape as compileKUserVaultPerVault in covenant-settle.ts:
// ownerPk, platformPk, oraclePk, ownerAddressHash, vaultId, sweepTimeout.)
// 2. Testnet: deploy a vault with the SECIFIED bytecode, run a real bet
// (must succeed) + run scripts/testnet-vault-security-probe.ts (must
// REJECT on signature). Both must pass before mainnet.
// 3. Ship the new covenant + the covenant-spender/handleVaultBet patches
// together; create NEW vaults with the SECIFIED bytecode. Existing vault
// UTXOs are immutable (P2SH) — drain via owner `withdraw` + redeploy.
//
// Entrypoints (abi order → selectors): withdraw=0, bet=1, sweep=2.
// =============================================================================
pragma silverscript ^0.1.0;
contract KUserVault(
pubkey ownerPk,
pubkey platformPk,
pubkey oraclePk,
byte[20] ownerAddressHash,
int vaultId,
int sweepTimeout
) {
// WITHDRAW: user self-custodial withdrawal (UNCHANGED — already secure).
// Owner signs; output[0] must pay the owner's P2PK; change → vault.
entrypoint function withdraw(sig s, int amount) {
require(checkSig(s, ownerPk));
require(amount > 0);
byte[34] ownerSpk = new ScriptPubKeyP2PK(ownerPk);
require(tx.outputs[0].scriptPubKey == byte[](ownerSpk));
require(tx.outputs[0].value >= amount);
int inputValue = tx.inputs[this.activeInputIndex].value;
int change = inputValue - amount;
if (change > 0) {
require(tx.outputs[1].scriptPubKey == this.activeScriptPubKey);
}
}
// BET: platform moves vault funds to a market/slot covenant destination.
//
// SECIFIED: requires on-chain platform + oracle tx signatures (2-of-2).
// Replaces the stubbed datasig (OpTrue) which gave NO on-chain authorization.
// The platform+oracle sign the exact tx (including output[0]'s scriptPubKey),
// so the destination + amount are bound by the signatures — no need for a
// marketAddrHash param. Conservation is unchanged.
//
// Output[0]: destination (market/slot covenant), value == betAmount.
// Output[1] (change, when betAmount < vault value): vault continuation.
entrypoint function bet(
sig platformSig,
sig oracleSig,
int betAmount
) {
require(checkSig(platformSig, platformPk));
require(checkSig(oracleSig, oraclePk));
require(betAmount > 0);
require(tx.outputs[0].value == betAmount);
if (tx.inputs[this.activeInputIndex].value - betAmount > 0) {
require(tx.outputs[1].scriptPubKey == this.activeScriptPubKey);
}
}
// SWEEP: emergency owner recovery after timelock (UNCHANGED — already secure).
entrypoint function sweep(sig s, int minValue) {
require(tx.time >= sweepTimeout);
require(checkSig(s, ownerPk));
byte[34] ownerSpk = new ScriptPubKeyP2PK(ownerPk);
require(tx.outputs[0].scriptPubKey == byte[](ownerSpk));
require(tx.outputs[0].value >= minValue);
}
}K-Market
DeployedThe pooled prediction-market covenant. Every bet stake on a market is added to one shared UTXO ('addBet'). When the market resolves, 'settle' pays every winner their exact payout to their own P2PK lock and the platform fee to the house lock — co-signed by the platform + oracle. If the platform never resolves, 'refund' returns the pool to the house after a timelock.
Entrypoints
addBetHouse signature (checkSig)Stake stays in the pool UTXO (output[0] == self). Conservation maintained.settleHouse + Oracle signatures (2-of-2 checkSig)Each winner output pays the winner's P2PK with >= their payout; house fee output == expected fee; leftover continues the pool. Fee capped at feeBps.refundHouse signature + timelockAfter resolveTimeout, pool returns to the house lock (minus fee). Recovery if resolution never happens.pragma silverscript ^0.1.0;
// ============================================================
// KMarket — Kaspa-native prediction-market escrow covenant
// Kasbet — deployed per market
//
// Holds the market's collected KAS pool as an on-chain UTXO. The
// pool is settled by a 2-of-2 signature from the house wallet
// and the oracle (e.g. UMA-style optimistic oracle, or a Kaspa
// oracle signer). After a hard timeout, the house can reclaim
// the pool (refund path) so funds are never permanently locked.
//
// CALLER CONTRACT (silverc 0.1.x):
// - state_layout: 0/0 (stateless — pool is in the UTXO value, not contract state)
// - without_selector: true (single entrypoint per public method; selectors used
// because the contract has 3 entrypoints, so the generated `script` includes
// the 1-byte selector discriminator per spend)
// - Schnorr-only signatures (BIP-340 / kaspa-wasm k256)
//
// This contract intentionally does NOT use `validateOutputState` for two
// reasons:
// 1. The pool value is the UTXO's `value` field, which is preserved across
// the whole lifetime of the covenant (each spend fully consumes the input
// and produces N outputs that together redeposit the remainder).
// 2. Per-bet state (yes/no totals) is tracked off-chain in Postgres
// (`markets`, `bets`, `bet_pools` tables) — keeping state out of the
// covenant removes the need for state-preserving covenants and keeps
// the script tiny.
//
// Public methods:
// - addBet: house signs to top up the pool (the user has already paid
// the house wallet off-covenant; this just aggregates the bet
// into the same escrow UTXO for atomic settlement).
// - settle: house + oracle sign to distribute the pool to winners.
// N output payouts + 1 optional house-fee output + 1 optional
// change back to the same covenant (preserves the covenant
// address for future bets on the same market).
// - refund: house signs after `resolveTimeout` to reclaim the entire
// pool to the house wallet (graceful close for cancelled
// or never-resolved markets).
//
// Constructor args (embedded in the deployed UTXO's scriptPubKey):
// - pubkey houseKey : house wallet that signs addBet, settle, refund
// - pubkey oracleKey : oracle wallet that co-signs settle
// - int resolveTimeout: unix seconds after which `refund` is allowed
// ============================================================
contract KMarket(pubkey houseKey, pubkey oracleKey, int resolveTimeout) {
// --- addBet: house tops up the pool ---
// CALLER: house wallet signs and broadcasts a tx whose ONLY input is the
// current covenant UTXO, with outputs summing to (input.value - fee).
// The new covenant UTXO is created at output 0 (preserving the covenant
// address); change (if any) goes back to the same covenant as output 1.
entrypoint function addBet(sig houseSig) {
require(checkSig(houseSig, houseKey));
// Output 0 must be a continuation of this same covenant (preserves
// the address and locks new pool value into it). We compare the
// scriptPubKey of the new output to this covenant's own scriptPubKey
// (activeScriptPubKey is the scriptPubKey of the input being spent).
require(tx.outputs[0].scriptPubKey == this.activeScriptPubKey);
}
// --- settle: distribute the pool to winners ---
// CALLER: house + oracle co-sign. Output layout (one of two shapes):
// (A) Distribution to N winners:
// output[0..N-1] = payouts to each winnerPubkey (P2PK locks)
// output[N] = change back to this covenant (preserves
// the address for future bets on same market)
// (B) Distribution to N winners + house fee:
// output[0] = house fee (≥ feeBps of input.value / 10000)
// output[1..N] = payouts to each winnerPubkey
// output[N+1] = change back to this covenant
//
// The `feeBps` and `housePayout` arguments commit the fee size to the
// chain so oracle + house can't disagree on the split after the fact.
//
// `winners` and `payouts` arrays MUST have the same length. Per-winner
// output must be P2PK to the corresponding pubkey and value must be
// ≥ the committed payout (overpay allowed for dust-padding).
entrypoint function settle(
sig houseSig,
sig oracleSig,
pubkey[] winners,
int[] payouts,
int feeBps,
int housePayout
) {
require(checkSig(houseSig, houseKey));
require(checkSig(oracleSig, oracleKey));
// --- Length sanity (deterministic) ---
int n = winners.length;
require(n == payouts.length);
require(n > 0);
require(feeBps >= 0);
require(feeBps <= 10000);
// --- Compute expected fee (commitment-check) ---
int inputValue = tx.inputs[this.activeInputIndex].value;
int expectedFee = inputValue * feeBps / 10000;
require(housePayout == expectedFee);
// --- Output 0: house fee ---
require(tx.outputs[0].value >= housePayout);
byte[34] houseLock = new ScriptPubKeyP2PK(houseKey);
require(tx.outputs[0].scriptPubKey == byte[](houseLock));
// --- Outputs 1..N: winner payouts ---
// NOTE: the for-loop owns the iteration variable `i` — do NOT
// pre-declare `int i = 0;` here, or silverc complains
// "variable '__inline_4_i' is already defined" because the loop
// counter and the explicit declaration collide on the same slot.
for (i, 0, n, 8) {
byte[34] winnerLock = new ScriptPubKeyP2PK(winners[i]);
require(tx.outputs[1 + i].scriptPubKey == byte[](winnerLock));
require(tx.outputs[1 + i].value >= payouts[i]);
}
// --- Output N+1: covenant continuation (preserves market address) ---
require(tx.outputs[1 + n].scriptPubKey == this.activeScriptPubKey);
}
// --- refund: house reclaims pool after timeout ---
// CALLER: house wallet signs and broadcasts a tx whose only input is the
// covenant UTXO, with the entire pool value (minus fee) sent back to the
// house wallet. No oracle co-sign required — this is a unilateral refund
// path for graceful close.
entrypoint function refund(sig houseSig) {
require(checkSig(houseSig, houseKey));
require(tx.time >= resolveTimeout);
// The full input value minus fee goes to the house wallet.
// We require output 0's value to be >= input value - some small fee
// (overpay is fine; the dust difference is just tx fee).
int inputValue = tx.inputs[this.activeInputIndex].value;
require(tx.outputs[0].value >= inputValue - 10000);
byte[34] houseLock = new ScriptPubKeyP2PK(houseKey);
require(tx.outputs[0].scriptPubKey == byte[](houseLock));
}
}
K-Position
DeployedThe per-bet winner escrow. When a market resolves, each winning bet gets its own K-Position UTXO holding the payout. The winner claims it to their own wallet with their signature + the oracle's attestation. If they never claim, the house can reclaim it after a timelock ('graceRefund').
Entrypoints
claimBettor + Oracle signatures (checkSig)Output[0] pays the bettor's P2PK with >= payoutAmount; change returns to self.refundBettor + House signatures (checkSig)Returns the escrow to the bettor's P2PK (mutual cancel).graceRefundHouse signature + timelockAfter claimTimeout, reclaims the unclaimed escrow to the house lock.pragma silverscript ^0.1.0;
// ============================================================
// KPosition — Kaspa-native per-bet escrow covenant
// Kasbet — deployed per bet
//
// Holds ONE bet's KAS in escrow until the bet is either:
// - claimed (bettor wins, oracle co-signs the payout), or
// - refunded (timeout elapsed, bettor + house co-sign, OR
// bettor + oracle co-sign, OR house-only after
// a hard grace period).
//
// This is intentionally simpler than KMarket (no fee, no
// distribution logic). The bettor's wallet is the primary
// authority (they paid the KAS in the first place), and the
// oracle co-signs when the bet resolves in the bettor's favor.
//
// CALLER CONTRACT (silverc 0.1.x):
// - state_layout: 0/0 (stateless)
// - without_selector: false (multiple entrypoints -> selectors)
// - Schnorr-only signatures
//
// Constructor args:
// - pubkey bettor : bettor's wallet (owns the funds in escrow)
// - pubkey oracle : oracle that co-signs claims
// - pubkey houseKey : house for grace-period unilateral refund
// - int claimTimeout : unix seconds after which `graceRefund` is
// allowed (bettor didn't claim; house reclaims)
// ============================================================
contract KPosition(pubkey bettor, pubkey oracle, pubkey houseKey, int claimTimeout) {
// --- claim: bettor wins, oracle co-signs the payout ---
// CALLER: bettor + oracle both sign. Output 0 sends the payout to
// the bettor's wallet. If the bet's full escrow value isn't being
// paid out (e.g. partial settlement for a parlay), the difference
// goes back to the bettor as output 1 (also locked to bettor).
entrypoint function claim(
sig bettorSig,
sig oracleSig,
int payoutAmount
) {
require(checkSig(bettorSig, bettor));
require(checkSig(oracleSig, oracle));
require(payoutAmount > 0);
// Output 0: payout to bettor.
require(tx.outputs[0].value >= payoutAmount);
byte[34] bettorLock = new ScriptPubKeyP2PK(bettor);
require(tx.outputs[0].scriptPubKey == byte[](bettorLock));
// Output 1 (optional change): any remaining escrow value goes
// back to the bettor (refund of the un-paid portion).
int inputValue = tx.inputs[this.activeInputIndex].value;
int change = inputValue - payoutAmount;
if (change > 0) {
require(tx.outputs[1].value == change);
require(tx.outputs[1].scriptPubKey == byte[](bettorLock));
}
}
// --- refund: bettor cancels the bet (pre-resolution) ---
// CALLER: bettor + house co-sign. Funds go back to the bettor.
// This is for explicit cancellations (e.g. user changes mind, or
// the platform admin voids the bet on dispute).
entrypoint function refund(sig bettorSig, sig houseSig) {
require(checkSig(bettorSig, bettor));
require(checkSig(houseSig, houseKey));
// Entire escrow value goes back to the bettor.
int inputValue = tx.inputs[this.activeInputIndex].value;
require(tx.outputs[0].value >= inputValue - 10000);
byte[34] bettorLock = new ScriptPubKeyP2PK(bettor);
require(tx.outputs[0].scriptPubKey == byte[](bettorLock));
}
// --- graceRefund: house reclaims after hard timeout ---
// CALLER: house-only sign, after `claimTimeout` has passed.
// This is the failsafe path: if the bet never resolves and the
// bettor never claims, the house can reclaim so funds don't get
// permanently locked in the covenant.
entrypoint function graceRefund(sig houseSig) {
require(checkSig(houseSig, houseKey));
require(tx.time >= claimTimeout);
// House reclaims the entire escrow value (minus fee).
int inputValue = tx.inputs[this.activeInputIndex].value;
require(tx.outputs[0].value >= inputValue - 10000);
byte[34] houseLock = new ScriptPubKeyP2PK(houseKey);
require(tx.outputs[0].scriptPubKey == byte[](houseLock));
}
}
K-Slot
DeployedThe predict-slot round covenant. Each round is one pooled UTXO ('addBet'). When the round resolves, 'payout' pays every winner their fixed-odds payout to their own P2PK lock and the house rake to the house lock — co-signed by platform + oracle. The rake is hard-capped at 20%. A 'payoutNoRake' variant pays winners with no house cut (used for jackpot rounds).
Entrypoints
addBetHouse signature (checkSig)Stake stays in the round pool (output[0] == self).payoutHouse + Oracle signatures (2-of-2 checkSig)Each winner output >= their payout; house rake == expected fee (capped 20%); leftover continues the pool.payoutNoRakeHouse + Oracle signatures (2-of-2 checkSig)All outputs pay winners directly, no house cut (jackpot rounds).pragma silverscript ^0.1.0;
// ============================================================
// KSlot — Kaspa-native predict-slot round escrow covenant
// Kasbet — deployed per slot round
//
// Holds ONE slot round's KAS pool in escrow. The round's
// outcome is determined by the server-side RNG (seeded from
// block hash + bet intents) and the oracle co-signs the
// payout distribution. After the round ends, either the
// payout path runs (distributed to winners) or the refund
// path runs (everyone gets their bet back if the round is
// voided).
//
// The covenant exposes five entrypoints (4 paths + 1 admin):
// (A) addBet — house adds a bet to the pool (preserves covenant addr)
// (B) payout — house + oracle distribute pool to N winners WITH a
// house fee (rake > 0); output[0]=fee, [1..N]=winners,
// [N+1]=covenant continuation
// (C) payoutNoRake — house + oracle distribute pool to N winners with NO
// house fee (rake == 0); output[0..N-1]=winners,
// [N]=covenant continuation
// (D) jackpot — house + oracle pay 1 big winner + house rake
// (E) refund — house voids the round after `roundEndTime`
//
// The rake vs no-rake choice is encoded by the entrypoint name (not a
// boolean flag) so each path's output layout is statically checkable.
//
// CALLER CONTRACT (silverc 0.1.x):
// - state_layout: 0/0 (stateless)
// - without_selector: false (multiple entrypoints)
// - Schnorr-only signatures
//
// Constructor args:
// - pubkey houseKey : house wallet (signs addBet, payout, refund)
// - pubkey oracleKey : oracle (co-signs payout, jackpot)
// - int roundEndTime: unix seconds at which the round ended;
// `refund` is allowed after this
// ============================================================
contract KSlot(pubkey houseKey, pubkey oracleKey, int roundEndTime) {
// --- addBet: house adds a bet to the round's pool ---
// CALLER: house wallet signs and broadcasts a tx whose ONLY input is
// the current covenant UTXO, with outputs preserving the covenant
// address (output 0 continues the covenant with the new total pool).
entrypoint function addBet(sig houseSig) {
require(checkSig(houseSig, houseKey));
require(tx.outputs[0].scriptPubKey == this.activeScriptPubKey);
}
// --- payout: distribute round to N winners WITH a house fee ---
// CALLER: house + oracle co-sign. Output layout (rake > 0):
// output[0] = house fee → houseKey (must equal inputValue * feeBps / 10000)
// output[1..N] = payouts to each winner (P2PK locks)
// output[N+1] = change back to this covenant (preserves address)
//
// For a 0-house-fee round, use payoutNoRake() instead. Splitting the
// two paths into separate entrypoints avoids a ternary in the output
// index (silverc 0.1.x can't evaluate `(c ? 1 : 0) + i` as an array
// dimension) and makes each path's output layout statically checkable.
//
// `winners` and `payouts` arrays MUST have the same length.
entrypoint function payout(
sig houseSig,
sig oracleSig,
pubkey[] winners,
int[] payouts,
int feeBps,
int housePayout
) {
require(checkSig(houseSig, houseKey));
require(checkSig(oracleSig, oracleKey));
require(housePayout > 0);
int n = winners.length;
require(n == payouts.length);
require(n > 0);
require(feeBps >= 0);
require(feeBps <= 2000); // hard cap 20% rake on slot rounds
int inputValue = tx.inputs[this.activeInputIndex].value;
int expectedFee = inputValue * feeBps / 10000;
require(housePayout == expectedFee);
// Output 0: house fee.
require(tx.outputs[0].value >= housePayout);
byte[34] houseLock = new ScriptPubKeyP2PK(houseKey);
require(tx.outputs[0].scriptPubKey == byte[](houseLock));
// Outputs 1..N: winner payouts.
// NOTE: the for-loop owns the iteration variable `i` — do NOT
// pre-declare `int i = 0;` here, or silverc complains
// "variable '__inline_4_i' is already defined".
for (i, 0, n, 8) {
byte[34] winnerLock = new ScriptPubKeyP2PK(winners[i]);
require(tx.outputs[1 + i].scriptPubKey == byte[](winnerLock));
require(tx.outputs[1 + i].value >= payouts[i]);
}
// Output N+1: covenant continuation (preserves round address).
require(tx.outputs[1 + n].scriptPubKey == this.activeScriptPubKey);
}
// --- payoutNoRake: distribute round to N winners with NO house fee ---
// CALLER: house + oracle co-sign. Output layout (rake == 0):
// output[0..N-1] = payouts to each winner (P2PK locks)
// output[N] = change back to this covenant (preserves address)
//
// `winners` and `payouts` arrays MUST have the same length.
entrypoint function payoutNoRake(
sig houseSig,
sig oracleSig,
pubkey[] winners,
int[] payouts
) {
require(checkSig(houseSig, houseKey));
require(checkSig(oracleSig, oracleKey));
int n = winners.length;
require(n == payouts.length);
require(n > 0);
int inputValue = tx.inputs[this.activeInputIndex].value;
// Outputs 0..N-1: winner payouts (no house fee, no output shift).
// NOTE: the for-loop owns the iteration variable `i` — do NOT
// pre-declare `int i = 0;` here.
for (i, 0, n, 8) {
byte[34] winnerLock = new ScriptPubKeyP2PK(winners[i]);
require(tx.outputs[i].scriptPubKey == byte[](winnerLock));
require(tx.outputs[i].value >= payouts[i]);
}
// Output N: covenant continuation (preserves round address).
require(tx.outputs[n].scriptPubKey == this.activeScriptPubKey);
}
// --- jackpot: single big winner + house rake ---
// CALLER: house + oracle co-sign. Output layout:
// output[0] = jackpot winner (P2PK)
// output[1] = house rake (P2PK to houseKey)
entrypoint function jackpot(
sig houseSig,
sig oracleSig,
pubkey jackpotWinner,
int jackpotPayout,
int housePayout
) {
require(checkSig(houseSig, houseKey));
require(checkSig(oracleSig, oracleKey));
require(jackpotPayout > 0);
require(housePayout >= 0);
// Output 0: jackpot winner.
require(tx.outputs[0].value >= jackpotPayout);
byte[34] winnerLock = new ScriptPubKeyP2PK(jackpotWinner);
require(tx.outputs[0].scriptPubKey == byte[](winnerLock));
// Output 1: house rake.
require(tx.outputs[1].value >= housePayout);
byte[34] houseLock = new ScriptPubKeyP2PK(houseKey);
require(tx.outputs[1].scriptPubKey == byte[](houseLock));
}
// --- refund: void the round, return all bets to bettors ---
// CALLER: house-only sign, after `roundEndTime`. Output 0 sends the
// full pool (minus fee) to the house wallet; the house is then
// responsible for off-covenant refunding to each bettor (or the
// bet is just absorbed into the house bankroll if the void is
// platform-side).
entrypoint function refund(sig houseSig) {
require(checkSig(houseSig, houseKey));
require(tx.time >= roundEndTime);
int inputValue = tx.inputs[this.activeInputIndex].value;
require(tx.outputs[0].value >= inputValue - 10000);
byte[34] houseLock = new ScriptPubKeyP2PK(houseKey);
require(tx.outputs[0].scriptPubKey == byte[](houseLock));
}
}