# Built-ins

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

> Part of the SQRL documentation, section "Protocol".
> Rendered page: https://docs.sqrl.tech/protocol/contracts/built-ins

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}, ...]
}
```

  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);
}
```

  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-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\ | 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](/protocol/contracts/events) for emitting and querying contract events, then [Example contracts](/protocol/contracts/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.
