Covenant breaker

This is a worked example, not a real vault. The address, the balance, and the 10,000 KAS below are made up; nothing here is on any real blockchain.

A covenant is a rule locked into a coin that controls how it can be spent later. This vault holds 10,000 KAS behind four such rules. Get any of it to an address you control without satisfying all four.

Four rules stand between you and the balance below. Try to break one directly, or find the way through that never breaks any of them at all.

Status: the cap, the destination lock, and the delay shown here run on Kaspa mainnet today, using opcodes and covenant identity that activated with the Toccata upgrade (KIP-17 and KIP-20, both status Active). The rule is written below in Argent's syntax; Argent itself is a proposed contract language, still in research, not yet released. Only the notation is illustrative. The enforcement it describes is live.

Try to get money out of the vault

Set up an attempt by hand below, or start from one of these.

Direct attempts, each breaks one rule on purpose

Break no rule at all

How you're trying to get it out

≈ 60 minutes

Vault state (made up, for this example)

What the covenant enforces

Rule definitions and sources
state VaultState {
    byte[32] owner_pk_hash;
    byte[32] recovery_pk_hash;
    byte[32] payout_spk_hash;
    byte[32] recovery_spk_hash;
    int balance;
    int last_spend_daa;
}

actor Vault owns VaultState {

    entry withdraw(int amount, sig owner_sig, pubkey owner_pk)
        emits next: Vault
    {
        require(blake2b(owner_pk) == owner_pk_hash);
        require(checkSig(owner_sig, owner_pk));

        // where it can go: fixed cap, fixed destination
        require(amount <= 500 && amount <= balance);
        require(tx.outputSpkHash(0) == payout_spk_hash);
        require(tx.outputAmount(0) == amount);

        // how soon again: time since this covenant's own last spend
        require(tx.inputDaaScore(self) - last_spend_daa >= 36000);

        // what comes next: same KIP-20 covenant id, updated state
        VaultState new_state = {
            owner_pk_hash: owner_pk_hash,
            recovery_pk_hash: recovery_pk_hash,
            payout_spk_hash: payout_spk_hash,
            recovery_spk_hash: recovery_spk_hash,
            balance: balance - amount,
            last_spend_daa: tx.inputDaaScore(self),
        };
        become next <- Vault(new_state);
    }

    entry recover(sig recovery_sig, pubkey recovery_pk) {
        require(blake2b(recovery_pk) == recovery_pk_hash);
        require(checkSig(recovery_sig, recovery_pk));

        // no cap, no delay: recovery ignores both
        // but the destination is still fixed, and the vault ends
        require(tx.outputSpkHash(0) == recovery_spk_hash);
        require(tx.outputAmount(0) == balance);
        // no `become`: this covenant's lineage ends here
    }
}

Pseudocode in Argent's actor/entry/require/become style, a proposed Kaspa contract language still in research and not yet released; see Argent and Silverscript, explained for that project's actual status. Introspection opcodes named against the primitives KIP-17 actually defines: OpTxOutputAmount, OpTxOutputSpk, OpTxInputDaaScore, OpTxLockTime. Both KIP-17 and its continuation-identity companion, KIP-20, are live on Kaspa mainnet as of the Toccata upgrade: a spend either carries the same covenant_id as the UTXO it consumes, or proves a fresh one through the genesis hash, so a forged lookalike UTXO cannot claim an existing covenant's identity. See Toccata explained for how these primitives compose into real applications.