Built-ins

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

Updated 08 Nov 20264 min read

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.

scl
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}, ...]
}
info:

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

#Persistent sets

scl
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

scl
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.

scl
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);
}
info:

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

scl
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

scl
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

scl
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

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

#Debugging

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

#Quick reference

Built-inReturnsPurpose
MAP_SET(name, key, value)1 on successWrite to a persistent map
MAP_GET(name, key)value, or 0Read from a persistent map
MAP_REMOVE(name, key)1 or 0Delete a map entry
MAP_GET_ALL(name)list of {key, value}Read a whole map
SET_ADD(name, value)1Add to a persistent set
SET_CONTAINS(name, value)1 or 0Membership test
SET_REMOVE(name, value)1 or 0Remove from a set
LIST_LEN(list)lengthList length
LIST_GET(list, i)element, or 0Indexed access
SENDER_ADDRESS()StrCaller address from context
SENDERS() / RECEIVERS()List<Str>Anchor transaction inputs/outputs
block_height()IntBitcoin block height from context
CONTRACT_ID()StrThis contract's ID
CALL(id, fn, args)callee's returnCross-contract call as current sender
CALL_AS_CONTRACT(id, fn, args)callee's returnCross-contract call as this contract
TRANSFER(senders, receivers, amounts)1 on successMulti-party balance transfer
CHECK_SIG(sig, msg, pubkey)1 or 0ed25519 / secp256k1 verification
PRINT(...)1Debug output to node log
TYPEOF(value)StrRuntime type name
int_to_bytes / bytes_to_int / str_to_bytes / bytes_to_strconverted valueType conversions

#Next steps

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

warning:

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

Was this page helpful?