migrate let the owner supply the new state, so an owner could put a script of their own in front of the approved code and take the certificate out of the covenant, or rewrite owner and expiry on the way. The contract now carries its own state over and the owner supplies only the code. The steward's approval covers source and destination code, so it cannot be replayed in another tier or network. The delivery escrow could be collected with a second protocol message after the agreed one; it now allows at most four outputs, the fourth plain change. A subscription draw could pay its fee allowance to a third output; it now has one or two outputs and pays the host exactly the price. Smaller: the commit salt is exactly 32 bytes (one box could serve several names), expiry stays a block height, and relative-lock terms are range checked in script. Escrow quotes are refused for co-sign TLDs, where a delivery would satisfy the contract and register nothing.
89 lines
4.2 KiB
Text
89 lines
4.2 KiB
Text
pragma cashscript ^0.13.0;
|
|
|
|
// DeliveryEscrow — payment for a name that can only be collected by the
|
|
// transaction that registers it to the buyer.
|
|
// (Decentralized.DNS/DESIGN-trustless-purchase.md §4.)
|
|
//
|
|
// STATUS: chipnet draft. NOT REVIEWED.
|
|
//
|
|
// A buyer who cannot build a registration themselves (pay-by-QR, any wallet)
|
|
// pays this script instead of paying a registrar. The money then has exactly
|
|
// two ways out:
|
|
//
|
|
// deliver() anyone may spend it — in a transaction that mints the agreed
|
|
// certificate to the buyer and publishes the agreed registration.
|
|
// "Took the money and did not register" is not a risk the buyer
|
|
// has to weigh; it is a transaction that cannot be built.
|
|
// refund() the buyer takes it back with their signature once it is
|
|
// `refundAfter` blocks old.
|
|
//
|
|
// What deliver() pins down, output by output:
|
|
// 0 the certificate — a NEW token category created by this transaction
|
|
// (its id is input 0's outpoint), an NFT with no capability, commitment =
|
|
// the name, no fungible amount, locked to the buyer's lock
|
|
// 1 the registration payload, byte for byte as agreed (OP_RETURN)
|
|
// 2 the discovery beacon, at least its dust
|
|
// 3 optional: the deliverer's change — no token, and not a data output
|
|
// and nothing else. Two things are being kept out:
|
|
// · a second token of the certificate's category, which its holder could
|
|
// later use to update the buyer's name;
|
|
// · a second data output. A transaction is read as carrying ONE protocol
|
|
// message; a later OP_RETURN would replace the agreed registration in
|
|
// every resolver, and the deliverer would be paid for a certificate
|
|
// that registers nothing.
|
|
//
|
|
// It cannot deliver under a TLD that requires its owner's co-signature: that
|
|
// rule wants the TLD's certificate in an output of the registration, and no
|
|
// extra token is allowed here. The library refuses to build an escrow for
|
|
// such a name (escrow-covenant.js assertTldAllowsEscrow).
|
|
//
|
|
// The escrow must be input 1 (input 0 is the genesis outpoint). That makes it
|
|
// one escrow per transaction: two payments for the same name could otherwise
|
|
// both be collected against a single certificate.
|
|
//
|
|
// WHAT IT CANNOT PROMISE. Script sees this transaction, not the registry. If
|
|
// the name was registered by someone else between payment and delivery, a
|
|
// deliverer can still mint a certificate that satisfies every line here and
|
|
// that no resolver will honour ("first valid registration wins" is a resolver
|
|
// rule). The window is the buyer's to keep short: check the name is free, pay,
|
|
// and refund if delivery does not come.
|
|
contract DeliveryEscrow(
|
|
bytes20 buyerPkh,
|
|
bytes certLock, // where the certificate must go (the buyer's token address, or their covenant)
|
|
bytes nameCommitment, // the on-chain name, as the certificate's commitment
|
|
bytes payloadLock, // the full OP_RETURN locking bytecode of the agreed REG payload
|
|
bytes beaconLock,
|
|
int beaconDust,
|
|
int refundAfter
|
|
) {
|
|
function deliver() {
|
|
require(this.activeInputIndex == 1);
|
|
require(tx.inputs[0].outpointIndex == 0);
|
|
bytes category = tx.inputs[0].outpointTransactionHash;
|
|
|
|
require(tx.outputs[0].lockingBytecode == certLock);
|
|
require(tx.outputs[0].tokenCategory == category);
|
|
require(tx.outputs[0].nftCommitment == nameCommitment);
|
|
require(tx.outputs[0].tokenAmount == 0);
|
|
|
|
require(tx.outputs[1].lockingBytecode == payloadLock);
|
|
require(tx.outputs[1].tokenCategory == 0x);
|
|
|
|
require(tx.outputs[2].lockingBytecode == beaconLock);
|
|
require(tx.outputs[2].value >= beaconDust);
|
|
require(tx.outputs[2].tokenCategory == 0x);
|
|
|
|
require(tx.outputs.length <= 4);
|
|
if (tx.outputs.length > 3) {
|
|
require(tx.outputs[3].tokenCategory == 0x);
|
|
require(tx.outputs[3].lockingBytecode.split(1)[0] != 0x6a);
|
|
}
|
|
}
|
|
|
|
function refund(pubkey pk, sig s) {
|
|
require(hash160(pk) == buyerPkh);
|
|
require(checkSig(s, pk));
|
|
require(within(refundAfter, 1, 65536)); // a 16-bit block count, see subscription.cash
|
|
require(this.age >= refundAfter);
|
|
}
|
|
}
|