pragma arkade ^0.1.0;
// One share is one sat above the pool's 330 sat carrier.
// kind: 0 deposit, 1 pool, 2 withdrawal, 3 staging.
// The constructor names one share script and one split.
// noExitKey is the BIP341 NUMS point: 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
contract Tokenized(
bytes32 shareScript, // witness program that receives the shares
bytes32 shareTxid,
int shareGidx,
bytes32 ctrlTxid,
int ctrlGidx,
bytes32 holderScript, // witness program: veto returns shares here
bytes32 payout1, // witness program, place 1
bytes32 payout2,
bytes32 payout3,
int amount1, // sats of backing, place 1
int amount2,
int amount3,
pubkey guardian,
pubkey noExitKey,
int kind
) {
const int DUST = 330;
private function poolScript() {
require(tx.outputs[0].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 1
), "pool");
require(tx.outputs[0].value >= DUST, "pool dust");
require(tx.outputs[0].assets.lookup(ctrlTxid, ctrlGidx) == 1, "ctrl");
require(tx.outputs[0].assets.length == 1, "pool assets");
}
private function splitOk() {
require(amount1 > 0, "place 1");
if (amount2 == 0) {
require(amount3 == 0, "place 3");
} else {
require(amount2 > DUST, "place 2");
require(amount3 == 0 || amount3 > DUST, "place 3");
}
}
// First deposit. 330 sats carry the share output, 330 carry the pool, and the rest is shares.
function open() {
require(kind == 0, "deposit");
require(tx.numInputs == 1, "one input");
require(this.activeInputIndex == 0, "deposit input");
require(tx.inputs[0].assets.length == 0, "sats");
require(tx.inputs[0].value > DUST + DUST, "dust");
int added = tx.inputs[0].value - DUST - DUST;
require(tx.numOutputs == 2, "two outputs");
poolScript();
require(tx.outputs[0].value >= added + DUST, "pool sats");
require(tx.outputs[1].scriptPubKey == shareScript, "shares");
require(tx.outputs[1].value == DUST, "carrier");
require(tx.outputs[1].assets.lookup(shareTxid, shareGidx) == added, "one per sat");
require(tx.outputs[1].assets.length == 1, "share assets");
let shareGroup = tx.assetGroups.find(shareTxid, shareGidx);
require(shareGroup.delta == added, "issued");
require(shareGroup.isFresh, "fresh shares");
require(shareGroup.controlIs(ctrlTxid, ctrlGidx), "control");
let ctrlGroup = tx.assetGroups.find(ctrlTxid, ctrlGidx);
require(ctrlGroup.delta == 1, "ctrl issued");
require(ctrlGroup.isFresh, "fresh ctrl");
}
// A later deposit. The pool already has its carrier, so every sat above 330 is a new share.
function arrive() {
require(kind == 0 || kind == 1, "kind");
require(tx.numInputs == 2, "pool and deposit");
require(tx.numOutputs == 2, "two outputs");
if (kind == 0) {
require(this.activeInputIndex == 1, "deposit input");
}
if (kind == 1) {
require(this.activeInputIndex == 0, "pool input");
}
require(tx.inputs[1].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 0
), "deposit");
require(tx.inputs[1].assets.length == 0, "sats");
require(tx.inputs[1].value > DUST, "dust");
int added = tx.inputs[1].value - DUST;
require(tx.inputs[0].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 1
), "pool");
require(tx.inputs[0].assets.length == 1, "pool assets");
poolScript();
require(tx.outputs[0].value >= tx.inputs[0].value + added, "pool sats");
require(tx.outputs[1].scriptPubKey == shareScript, "shares");
require(tx.outputs[1].value == DUST, "carrier");
require(tx.outputs[1].assets.lookup(shareTxid, shareGidx) == added, "one per sat");
require(tx.outputs[1].assets.length == 1, "share assets");
let shareGroup = tx.assetGroups.find(shareTxid, shareGidx);
require(shareGroup.delta == added, "issued");
require(shareGroup.controlIs(ctrlTxid, ctrlGidx), "control");
let ctrlGroup = tx.assetGroups.find(ctrlTxid, ctrlGidx);
require(ctrlGroup.delta == 0, "ctrl supply");
}
// The withdrawal output carries 330 sats and exactly the split. Staging holds both.
function propose() {
require(kind == 1 || kind == 2, "kind");
require(tx.numInputs == 2, "pool and withdrawal");
require(tx.numOutputs == 2, "two outputs");
if (kind == 1) {
require(this.activeInputIndex == 0, "pool input");
}
if (kind == 2) {
require(this.activeInputIndex == 1, "withdrawal input");
}
splitOk();
int shares = amount1 + amount2 + amount3;
require(tx.inputs[0].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 1
), "pool");
require(tx.inputs[0].assets.length == 1, "pool assets");
require(tx.inputs[1].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 2
), "withdrawal");
require(tx.inputs[1].value == DUST, "carrier");
require(tx.inputs[1].assets.lookup(shareTxid, shareGidx) == shares, "shares");
require(tx.inputs[1].assets.length == 1, "share assets");
require(tx.inputs[0].value >= shares + DUST, "pool");
poolScript();
require(tx.outputs[0].value >= tx.inputs[0].value - shares, "pool sats");
require(tx.outputs[1].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 3
), "staging");
require(tx.outputs[1].value == shares + DUST, "staging sats");
require(tx.outputs[1].assets.lookup(shareTxid, shareGidx) == shares, "staging shares");
require(tx.outputs[1].assets.length == 1, "share assets");
let shareGroup = tx.assetGroups.find(shareTxid, shareGidx);
require(shareGroup.delta == 0, "share supply");
require(shareGroup.controlIs(ctrlTxid, ctrlGidx), "control");
let ctrlGroup = tx.assetGroups.find(ctrlTxid, ctrlGidx);
require(ctrlGroup.delta == 0, "ctrl supply");
}
function veto(signature guardianSig) {
require(kind == 1 || kind == 3, "kind");
require(checkSig(guardianSig, guardian), "guardian");
require(tx.numInputs == 2, "pool and staging");
require(tx.numOutputs == 2, "two outputs");
if (kind == 1) {
require(this.activeInputIndex == 0, "pool input");
}
if (kind == 3) {
require(this.activeInputIndex == 1, "staging input");
}
int shares = amount1 + amount2 + amount3;
require(tx.inputs[0].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 1
), "pool");
require(tx.inputs[0].assets.length == 1, "pool assets");
require(tx.inputs[1].scriptPubKey == new Tokenized(
shareScript, shareTxid, shareGidx, ctrlTxid, ctrlGidx,
holderScript, payout1, payout2, payout3, amount1, amount2, amount3,
guardian, noExitKey, 3
), "staging");
require(tx.inputs[1].assets.lookup(shareTxid, shareGidx) == shares, "shares");
require(tx.inputs[1].assets.length == 1, "share assets");
require(tx.inputs[1].value == shares + DUST, "staging sats");
poolScript();
require(tx.outputs[0].value >= tx.inputs[0].value + shares, "sats back");
require(tx.outputs[1].scriptPubKey == holderScript, "holder");
require(tx.outputs[1].value == DUST, "holder carrier");
require(tx.outputs[1].assets.lookup(shareTxid, shareGidx) == shares, "shares back");
require(tx.outputs[1].assets.length == 1, "share assets");
let shareGroup = tx.assetGroups.find(shareTxid, shareGidx);
require(shareGroup.delta == 0, "share supply");
require(shareGroup.controlIs(ctrlTxid, ctrlGidx), "control");
let ctrlGroup = tx.assetGroups.find(ctrlTxid, ctrlGidx);
require(ctrlGroup.delta == 0, "ctrl supply");
}
function disburse() {
require(kind == 3, "staging");
require(tx.numInputs == 1, "one input");
require(this.activeInputIndex == 0, "staging input");
splitOk();
int shares = amount1 + amount2 + amount3;
require(tx.inputs[0].assets.lookup(shareTxid, shareGidx) == shares, "shares");
require(tx.inputs[0].assets.length == 1, "share assets");
require(tx.inputs[0].value == shares + DUST, "carrier");
let shareGroup = tx.assetGroups.find(shareTxid, shareGidx);
require(shareGroup.sumOutputs == 0, "not burned");
require(shareGroup.controlIs(ctrlTxid, ctrlGidx), "control");
require(tx.outputs[0].scriptPubKey == payout1, "place 1");
require(tx.outputs[0].value >= amount1 + DUST, "place 1 sats");
require(tx.outputs[0].assets.length == 0, "sats");
if (amount2 == 0) {
require(tx.numOutputs == 1, "one place");
} else {
require(tx.outputs[1].scriptPubKey == payout2, "place 2");
require(tx.outputs[1].value >= amount2, "place 2 sats");
require(tx.outputs[1].assets.length == 0, "sats");
if (amount3 == 0) {
require(tx.numOutputs == 2, "two places");
} else {
require(tx.outputs[2].scriptPubKey == payout3, "place 3");
require(tx.outputs[2].value >= amount3, "place 3 sats");
require(tx.outputs[2].assets.length == 0, "sats");
require(tx.numOutputs == 3, "three places");
}
}
}
function unilateral(signature noExitSig) tapscript {
require(older(serverExitDelay));
require(checkSig(noExitSig, noExitKey));
}
}