CashScript payout contracts for PSFFPP (Permissionless Software Foundation File Pinning Protocol) deposits on Bitcoin Cash.
Users send BCH to a single **PoolContract** address. Once per payout epoch (blockheight-gated), the pool splits 20% to treasury and 80% into three **ShareContract** UTXOs (one per node). A K-of-M (2-of-3) operator quorum must sign before any share pays out.
Project docs live in [`docs/`](docs/). Start with [Why CashScript](docs/why-cashscript.md) for the high-level motivation (replacing PSF-token proof-of-burn with pure BCH payouts). For indexer/pin-service/client integration (pool-address registry, dual-mode validation), see [Infra changes](docs/infra-changes.md). External references: [CashScript](https://cashscript.org/) · Design: circular-economy trust-minimized federation (M=3, K=2).
Each Pool deploy bakes a single `splitBlockheight`. After that height, `split()` may run once the pool is consolidated. Later months are **not** gated by the same contract: deploy a new Pool+Shares with the next height, then K-of-M `migrate()` any remaining pool value into the new Pool.
## Contracts
### ShareContract
Constructor: `node0`, `node1`, `node2` pubkeys + this node's `nodePkh`.
-`claim(s0, s1, s2)` — requires ≥2 valid ECDSA sigs against registered pubs; pays exactly to `nodePkh`; terminating (no self-replication). Unused sigs must be empty (`0x`).
### PoolContract
Constructor: three pubs, `treasuryPkh`, three share **locking bytecodes**, `minConsolidation` (100_000), `splitBlockheight`.
-`consolidate()` — permissionless; outputs must self-replicate (same P2SH locking bytecode), pure BCH, ≥ `minConsolidation`.
-`split()` — permissionless after `tx.time >= splitBlockheight`; 4 outputs: treasury (20% + rounding remainder) + three equal shares locked in the baked ShareContracts. Prefer a companion fee-paying input so the full pool value is apportioned.
-`migrate(s0, s1, s2)` — 2-of-3; destinations are authorized by the signatures (no baked next-pool bytecode).
- Run `npm run consolidate -- --dry-run` then `npm run consolidate` if multiple UTXOs (fee taken from pool; needs ≥2 UTXOs and output ≥ `minConsolidation`)
Permissionless merge of PoolContract UTXOs (via [minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) for broadcast + CashScript unlock). Fee is taken from the pool:
Permissionless after `splitBlockheight`. Apportions **100%** of one pool UTXO (20% treasury + 80%/3 shares). Miner fee must come from a **companion P2PKH** input (`--wif` / `--mnemonic`) — the covenant forbids taking the fee from the pool or adding a 5th change output.
The script never spends a large fee-wallet UTXO as the companion input. It reuses an existing UTXO only if it is within ~500 sats of the estimated fee; otherwise it first `wallet.send`s a small self-payment to carve a dedicated fee UTXO (two txs: carve + split).
npm run claim-cosign -- --wif <COSIGNER_WIF> --share-index 0 --out claim-sig.json
# --share-index = which node's share is being claimed (0|1|2)
# optional: --utxo <txid>:<vout> if that share has multiple UTXOs
```
This writes a JSON file (`type: psffpp-share-claim-sig`) the claimant can collect until they have ≥2 signatures for the same claim parameters, then assemble/broadcast.