pragma cashscript ^0.13.0; // Subscription — a prepaid balance the host can draw from once per period, // and the buyer can take back at any time. // (Argus/src/lib/plans.js SUBSCRIPTION_ROADMAP; ROADMAP-MAINNET.md §9 step 4.) // // STATUS: chipnet draft. NOT REVIEWED. // // The buyer funds this script. Three things then hold, and the buyer can check // each from the script alone: // // · the balance stays theirs — cancel() needs only their signature, no // notice, no cooperation, and returns whatever is left; // · the host cannot take more than one period's price per period — pull() // pays the host's lock exactly `priceSats`, sends the rest back to this // same script, and can only run on an output that is `periodBlocks` old, // which the re-created output is not; // · nobody else gains anything — pull() needs no signature, so the host does // not have to be online with a key, but it has at most two outputs: the // price to the host, exactly, and the rest back to this script. Whatever // the fee allowance does not use goes to the miner, not to whoever built // the transaction. // // ONE BALANCE, ONE OUTPUT. The once-per-period limit is a property of an // output, not of an address. A buyer who pays the same address twice has two // balances, and each can be drawn every period. To add funds: cancel and fund // again, in one transaction if you like. // // A pull spends this input ALONE. With two inputs, one "price to the host" // output and one "rest back to the script" output could satisfy both, and the // host would collect two balances for one payment. One input means the fee // comes out of the balance too, bounded by `maxFeeSats`. // // The first period is not special-cased: the balance is drawn one period // after it is funded. A host that wants payment up front takes the first // period in the funding transaction itself. contract Subscription(bytes20 buyerPkh, bytes hostLock, int priceSats, int periodBlocks, int maxFeeSats) { // One period's payment to the host. function pull() { // A relative lock is a 16-bit block count. Anything outside that range // would be read as something else entirely: only its low bits, a // number of seconds, or no lock at all. require(within(periodBlocks, 1, 65536)); require(this.age >= periodBlocks); require(tx.inputs.length == 1); require(tx.outputs.length <= 2); int balance = tx.inputs[0].value; require(tx.outputs[0].lockingBytecode == hostLock); require(tx.outputs[0].value == priceSats); require(tx.outputs[0].tokenCategory == 0x); // Everything else stays in the subscription, less a bounded fee. When // what would remain is too small to be an output, it is the fee. int rest = balance - priceSats - maxFeeSats; if (rest >= 546) { require(tx.outputs[1].lockingBytecode == tx.inputs[0].lockingBytecode); require(tx.outputs[1].value >= rest); require(tx.outputs[1].tokenCategory == 0x); } else { // Nothing is returned, so there must be nothing else: the leftover // is the fee, not a second output for whoever built the draw. require(tx.outputs.length == 1); } } // The buyer ends it and takes the balance. No conditions on where it goes. function cancel(pubkey pk, sig s) { require(hash160(pk) == buyerPkh); require(checkSig(s, pk)); } }