{ "contractName": "UsdListing", "constructorInputs": [ { "name": "oraclePk", "type": "pubkey" }, { "name": "sellerPkh", "type": "bytes20" }, { "name": "targetCents", "type": "int" }, { "name": "floorSats", "type": "int" }, { "name": "ceilSats", "type": "int" }, { "name": "maxAgeSecs", "type": "int" } ], "abi": [ { "name": "buy", "inputs": [ { "name": "oracleMsg", "type": "bytes" }, { "name": "oracleSig", "type": "datasig" } ] }, { "name": "cancel", "inputs": [ { "name": "pk", "type": "pubkey" }, { "name": "s", "type": "sig" } ] } ], "bytecode": "OP_6 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_TXLOCKTIME OP_CHECKLOCKTIMEVERIFY OP_DROP OP_7 OP_PICK OP_SIZE OP_NIP 28 OP_NUMEQUALVERIFY OP_8 OP_ROLL OP_8 OP_PICK OP_ROT OP_CHECKDATASIGVERIFY OP_6 OP_ROLL OP_4 OP_SPLIT OP_4 OP_SPLIT OP_ROT OP_BIN2NUM OP_ROT OP_BIN2NUM OP_DUP OP_0 OP_GREATERTHAN OP_VERIFY OP_ROT OP_INPUTINDEX OP_OUTPOINTTXHASH OP_EQUALVERIFY OP_TXLOCKTIME 302a OP_ADD OP_2 OP_PICK OP_GREATERTHANOREQUAL OP_VERIFY OP_TXLOCKTIME OP_ROT OP_7 OP_ROLL OP_ADD OP_LESSTHANOREQUAL OP_VERIFY OP_ROT 00e1f505 OP_MUL OP_SWAP OP_DIV OP_DUP OP_3 OP_PICK OP_LESSTHAN OP_IF OP_2 OP_PICK OP_NIP OP_ENDIF OP_DUP OP_4 OP_PICK OP_GREATERTHAN OP_IF OP_3 OP_PICK OP_NIP OP_ENDIF OP_0 OP_OUTPUTBYTECODE 76a914 OP_3 OP_ROLL OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_0 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCOMMITMENT OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_EQUAL OP_NIP OP_NIP OP_NIP OP_ELSE OP_6 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_6 OP_PICK OP_HASH160 OP_ROT OP_EQUALVERIFY OP_6 OP_ROLL OP_6 OP_ROLL OP_CHECKSIGVERIFY OP_2DROP OP_2DROP OP_DROP OP_1 OP_ENDIF", "source": "pragma cashscript ^0.13.0;\n\n// USD-pegged sale of a BCNR name certificate (CashTokens NFT).\n//\n// The certificate sits in this covenant while it is for sale. Anyone may\n// buy it by paying the seller the BCH equivalent of `targetCents` at the\n// oracle's current price; the seller may reclaim it at any time.\n//\n// Oracle message (40 bytes): timestamp as uint32 LE, price in USD cents per\n// BCH as uint32 LE, then the 32-byte txid of the listing's own outpoint,\n// signed by `oraclePk` (Schnorr data signature over the sha256 of the\n// message, as checkDataSig expects).\n//\n// What the chain can and cannot enforce: script can require a transaction\n// to be mined no EARLIER than a time (nLocktime), never no later, so a quote\n// cannot expire on-chain. The [floorSats, ceilSats] band the seller chooses\n// is therefore the real guarantee: whatever quote a buyer presents, the\n// payout stays inside it. Binding the quote to the listing's outpoint means\n// cancelling and relisting voids every quote ever issued. The locktime rule\n// below still stops a quote from being used before the oracle issued it.\ncontract UsdListing(\n pubkey oraclePk,\n bytes20 sellerPkh,\n int targetCents,\n int floorSats,\n int ceilSats,\n int maxAgeSecs\n) {\n // Buyer path. Output 0 pays the seller; output 1 carries the certificate\n // to wherever the buyer wants it. Remaining outputs are the buyer's.\n function buy(bytes oracleMsg, datasig oracleSig) {\n require(oracleMsg.length == 40);\n require(checkDataSig(oracleSig, oracleMsg, oraclePk));\n\n bytes tsBytes, bytes rest = oracleMsg.split(4);\n bytes priceBytes, bytes quotedTxid = rest.split(4);\n int ts = int(tsBytes);\n int priceCents = int(priceBytes);\n require(priceCents > 0);\n require(quotedTxid == tx.inputs[this.activeInputIndex].outpointTransactionHash);\n\n // Not before the quote: nLocktime is checked against median-time-past\n // (about an hour behind the clock), so the buyer sets it a couple of\n // hours behind the quote and the contract allows that slack.\n require(tx.locktime + 10800 >= ts);\n require(tx.locktime <= ts + maxAgeSecs);\n\n int need = targetCents * 100000000 / priceCents;\n if (need < floorSats) { need = floorSats; }\n if (need > ceilSats) { need = ceilSats; }\n\n // Seller is paid first, in plain BCH.\n require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(sellerPkh));\n require(tx.outputs[0].value >= need);\n require(tx.outputs[0].tokenCategory == 0x);\n\n // The certificate leaves intact: same category, same commitment.\n require(tx.outputs[1].tokenCategory == tx.inputs[this.activeInputIndex].tokenCategory);\n require(tx.outputs[1].nftCommitment == tx.inputs[this.activeInputIndex].nftCommitment);\n }\n\n // Seller path: reclaim the certificate (cancel the listing, or re-list\n // at a new target).\n function cancel(pubkey pk, sig s) {\n require(hash160(pk) == sellerPkh);\n require(checkSig(s, pk));\n }\n}\n", "fingerprint": "b4c2176de988b48f6a69c7c93fd3fa5ca03f6fee2e5e6201aa765e9af4c0e01b", "debug": { "bytecode": "5679009c63c5b1755779827701289d587a58797bbb567a547f547f7b817b817600a0697bc0c888c502302a935279a269c57b577a93a1697b0400e1f505957c967653799f6352797768765479a0635379776800cd0376a914537a7e0288ac7e8800cca16900d1008851d1c0ce8851d2c0cf8777777767567a519d5679a97b88567a567aad6d6d755168", "sourceMap": "31:4:60:5;;;;;:53:31:53;::::1;;32:16:32:25:0;;:::32:1;;:36::38:0;:8::40:1;33:29:33:38:0;;:40::49;;:51::59;:8::62:1;35:36:35:45:0;;:52::53;:36::54:1;36:56:36:57:0;:45::58:1;37:21:37:28:0;:17::29:1;38:29:38:39:0;:25::40:1;39:16:39:26:0;:29::30;:16:::1;:8::32;40:16:40:26:0;:40::61;:30::86:1;:8::88;45:16:45:27:0;:30::35;:16:::1;:39::41:0;;:16:::1;:8::43;46:16:46:27:0;:31::33;:36::46;;:31:::1;:16;:8::48;48:19:48:30:0;:33::42;:19:::1;:45::55:0;:19:::1;49:12:49:16:0;:19::28;;:12:::1;:30::51:0;:39::48;;:32::49:1;:30::51;50:12:50:16:0;:19::27;;:12:::1;:29::49:0;:38::46;;:31::47:1;:29::49;53:27:53:28:0;:16::45:1;:49::84:0;:74::83;;:49::84:1;;;:8::86;54:27:54:28:0;:16::35:1;:::43;:8::45;55:27:55:28:0;:16::43:1;:47::49:0;:8::51:1;58:27:58:28:0;:16::43:1;:57::78:0;:47::93:1;:8::95;59:27:59:28:0;:16::43:1;:57::78:0;:47::93:1;:8::95;31:53:60:5;;;:4;64::67::0;;;;65:24:65:26;;:16::27:1;:31::40:0;:8::42:1;66:25:66:26:0;;:28::30;;:8::33:1;64:38:67:5;;;;21:0:68:1", "logs": [], "requires": [ { "ip": 12, "line": 31, "message": "Using tx.locktime requires a non-final sequence number on the spending input" }, { "ip": 19, "line": 32 }, { "ip": 25, "line": 33 }, { "ip": 39, "line": 39 }, { "ip": 43, "line": 40 }, { "ip": 50, "line": 45 }, { "ip": 57, "line": 46 }, { "ip": 89, "line": 53 }, { "ip": 93, "line": 54 }, { "ip": 97, "line": 55 }, { "ip": 102, "line": 58 }, { "ip": 108, "line": 59 }, { "ip": 120, "line": 65 }, { "ip": 125, "line": 66 } ], "sourceTags": "5:7:lg;102:104:sc" }, "compiler": { "name": "cashc", "version": "0.13.3", "options": { "enforceFunctionParameterTypes": true, "enforceLocktimeGuard": true } }, "updatedAt": "2026-09-20T17:10:46.988Z" }