Built-ins

The VM ships with a set of built-ins. They cover storage, transfers, cross-contract calls, context access, signature checks, and debugging.

This page lists the built-ins that work end-to-end in the current compiler and VM. If it isn't listed here, treat it as unsupported.

Persistent maps

Backed by the node's key-value storage, namespaced per contract. Writes count against your contract's 64 KB storage quota.

fn put(k: Str, v: u64) {
    MAP_SET("kv", k, v);          // returns 1 on success
}

fn get(k: Str) {
    return MAP_GET("kv", k);      // returns stored value, or 0 if not found
}

fn drop(k: Str) {
    return MAP_REMOVE("kv", k);   // returns 1 if removed, 0 if not present
}

fn dump() {
    return MAP_GET_ALL("kv");     // returns [{key: k, value: v}, ...]
}

Keys are serialized from the value you pass. Strings are used as-is, integers are decimal-encoded.

Persistent sets

fn tag(x: Str) {
    SET_ADD("tags", x);              // returns 1 on success; idempotent
}
fn has(x: Str) {
    return SET_CONTAINS("tags", x);  // returns 1 if present, otherwise 0
}
fn untag(x: Str) {
    SET_REMOVE("tags", x);           // returns 1 if removed, 0 if not present
}

Lists

fn second(xs: List) {
    if (LIST_LEN(xs) < 2) {
        return 0;
    }
    return LIST_GET(xs, 1);   // 0-based; returns 0 if out of bounds
}

Execution context

These read values the node injects at execution time, derived from the anchoring Bitcoin transaction.

fn who() {
    let sender = SENDER_ADDRESS();   // Str: caller address from context
    let height = block_height();     // Int: current Bitcoin block height
    let r = RECEIVERS();             // List<Str>, each "txid:vout"
    let s = SENDERS();               // List<Str>
    let me = CONTRACT_ID();          // Str: this contract's ID
    PRINT("sender:", sender);
}

Call SENDERS() and RECEIVERS() with no arguments and treat them as read-only helpers for transaction context. The VM returns an empty list when the node does not provide context. SENDER_ADDRESS() is the standard way to gate functions on the caller; see the token example.

Cross-contract calls

fn move_tokens(token: Str, to: Str, amount: Int) {
    // Call as the current sender: the callee sees the same caller you did
    return CALL(token, "transfer", [to, amount]);
}

fn payout(token: Str, to: Str, amount: Int) {
    // Call as this contract: the callee sees CONTRACT_ID() as the sender
    return CALL_AS_CONTRACT(token, "transfer", [to, amount]);
}

CALL propagates your caller's identity into the callee, which is useful when a helper contract acts on the user's behalf. CALL_AS_CONTRACT makes the call as the contract itself, the pattern for escrow and staking contracts that hold balances in their own name.

Nested calls share one instruction budget: however deep the call chain goes, the whole transaction runs under the same 100k-instruction, 1024-stack, 64-depth ceilings. Events emitted by callees are collected alongside the caller's.

Transfers

fn settle(from: Str, to: Str, amount: Int) {
    TRANSFER([from], [to], [amount]);   // multi-party: lists must balance
}

TRANSFER(senders, receivers, amounts) performs a multi-party balance move and validates that totals match.

Signature verification

fn verify(sig: Bytes, msg: Bytes, pubkey: Bytes) {
    return CHECK_SIG(sig, msg, pubkey);   // 1 if valid, 0 if invalid
}

CHECK_SIG verifies ed25519 (64-byte signature, 32-byte key) and secp256k1 (DER signature, 33-byte compressed key) signatures.

Conversions

let b = int_to_bytes(42);
let n = bytes_to_int(b);
let s = bytes_to_str(str_to_bytes("ok"));

Debugging

PRINT("value:", 123);     // writes to the node log, returns 1
let t = TYPEOF(123);      // "Int", "Str", "Bytes", "List", or "Map"

Quick reference

Built-in Returns Purpose
MAP_SET(name, key, value) 1 on success Write to a persistent map
MAP_GET(name, key) value, or 0 Read from a persistent map
MAP_REMOVE(name, key) 1 or 0 Delete a map entry
MAP_GET_ALL(name) list of {key, value} Read a whole map
SET_ADD(name, value) 1 Add to a persistent set
SET_CONTAINS(name, value) 1 or 0 Membership test
SET_REMOVE(name, value) 1 or 0 Remove from a set
LIST_LEN(list) length List length
LIST_GET(list, i) element, or 0 Indexed access
SENDER_ADDRESS() Str Caller address from context
SENDERS() / RECEIVERS() List<Str> Anchor transaction inputs/outputs
block_height() Int Bitcoin block height from context
CONTRACT_ID() Str This contract's ID
CALL(id, fn, args) callee's return Cross-contract call as current sender
CALL_AS_CONTRACT(id, fn, args) callee's return Cross-contract call as this contract
TRANSFER(senders, receivers, amounts) 1 on success Multi-party balance transfer
CHECK_SIG(sig, msg, pubkey) 1 or 0 ed25519 / secp256k1 verification
PRINT(...) 1 Debug output to node log
TYPEOF(value) Str Runtime type name
int_to_bytes / bytes_to_int / str_to_bytes / bytes_to_str converted value Type conversions

Next Steps

See Events for emitting and querying contract events, then Example Contracts for complete contracts you can compile and call.

Stick to the list above. If you don't see a helper here, assume it isn't supported yet.