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.