Example Contracts

Three complete contracts, matching the implemented semantics of the compiler and VM. All three come from the compiler's examples/ directory and compile with sclc today.

A token contract

Demonstrates field protection via @writeable_by, caller gating with SENDER_ADDRESS(), overflow and supply-cap checks, and persistent balances in a map.

contract Token {
    @writeable_by(init, transfer_ownership)
    owner: Str;
    @writeable_by(init, mint, burn)
    total_supply: Int;
    @writeable_by(init, pause, unpause)
    paused: Int;
    @writeable_by(init)
    max_supply: Int;
    @writeable_by(init)
    initialised: Int;

    fn init(owner_addr: Str) {
        if (initialised == 1) {
            return 0;
        }
        owner = owner_addr;
        total_supply = 0;
        paused = 0;
        max_supply = 50000000000000000;
        initialised = 1;
        MAP_SET("balances", owner_addr, 0);
        return 1;
    }

    fn mint(to: Str, amount: Int) {
        let sender = SENDER_ADDRESS();
        if (paused == 1) {
            return 0;
        }
        if (owner != sender) {
            return 0;
        }
        let current = MAP_GET("balances", to);
        let sum = current + amount;
        if (sum < current) {
            return 0;   // overflow check
        }
        let temp = total_supply + amount;
        if (temp > max_supply) {
            return 0;
        }
        MAP_SET("balances", to, sum);
        total_supply = total_supply + amount;
        return 1;
    }

    fn transfer(to: Str, amount: Int) {
        if (paused == 1) {
            return 0;
        }
        let sender = SENDER_ADDRESS();
        let sender_balance = MAP_GET("balances", sender);
        let recipient_balance = MAP_GET("balances", to);
        if (sender_balance < amount) {
            return 0;
        }
        MAP_SET("balances", sender, sender_balance - amount);
        MAP_SET("balances", to, recipient_balance + amount);
        return 1;
    }

    fn pause() {
        let sender = SENDER_ADDRESS();
        if (owner != sender) {
            return 0;
        }
        paused = 1;
        return 1;
    }

    fn balance_of(addr: Str) {
        return MAP_GET("balances", addr);
    }

    fn get_total_supply() {
        return total_supply;
    }
}

Note the patterns: functions return 1 for success and 0 for failure; the initialised flag makes init one-shot; and every state-changing function checks paused and the caller before touching a balance.

A name registry with events

A minimal registry showing event emission for off-chain indexing. Clients subscribe to NameRegistered and NameTransferred via the node's events API instead of polling state.

contract BNS {
    event NameRegistered(name: Str, owner: Str);
    event NameTransferred(name: Str, old_owner: Str, new_owner: Str);

    fn register(name: Str, owner: Str) {
        MAP_SET("names", name, owner);
        emit NameRegistered(name, owner);
        return 1;
    }

    fn transfer(name: Str, new_owner: Str) {
        let old_owner = MAP_GET("names", name);
        MAP_SET("names", name, new_owner);
        emit NameTransferred(name, old_owner, new_owner);
        return 1;
    }

    fn lookup(name: Str) {
        return MAP_GET("names", name);
    }
}

A staking contract with cross-contract calls

Demonstrates CALL (moving tokens in as the user) versus CALL_AS_CONTRACT (paying tokens out as the contract), block-height time-locks, and storage cleanup.

contract Staking {
    @writeable_by(init)
    token_contract_id: Str;

    fn init(token_contract_id_param: Str) {
        token_contract_id = token_contract_id_param;
        return 0;
    }

    fn stake(sender: Str, amount: Int, lock_until: Int, btc_txid: Str) {
        MAP_SET("stakes", sender, amount);
        MAP_SET("locks", sender, lock_until);
        MAP_SET("txids", sender, btc_txid);
        return CALL(token_contract_id, "transfer", ["Staking", amount]);
    }

    fn claim(sender: Str, btc_txid: Str, current_block: Int) {
        let lock_until = MAP_GET("locks", sender);
        let staked_amount = MAP_GET("stakes", sender);

        if (current_block < lock_until) {
            return 0;
        }
        MAP_SET("stakes", sender, 0);
        MAP_SET("locks", sender, 0);
        MAP_SET("txids", sender, "");
        return CALL_AS_CONTRACT(token_contract_id, "transfer", [sender, staked_amount]);
    }
}

The stake path uses CALL, so the token contract sees the original caller and debits their balance. The claim path uses CALL_AS_CONTRACT, so the token contract sees the staking contract as the sender and pays out from the stake pool it holds.

Caveat
Only use built-ins listed in the Built-ins section. Do not rely on helpers that are not present in the compiler registry yet.

More examples (vesting with multiple beneficiaries, signature-gated emitters, and more) live in the compiler repository's examples/ directory.