Skip to content

Data Structures

StxScript provides maps, lists, and tuples for organizing data in contracts.

Maps

Maps are key-value stores declared at the contract level. They are the primary mechanism for persistent storage in Clarity contracts.

Declaration

map balances<principal, uint>;
map allowances<{ owner: principal, spender: principal }, uint>;
map metadata<uint, { name: string, uri: string }>;

Generated Clarity:

(define-map balances principal uint)
(define-map allowances { owner: principal, spender: principal } uint)
(define-map metadata uint { name: (string-utf8 ...), uri: (string-utf8 ...) })

Reading from Maps

get returns an Optional value since the key may not exist:

let balance = balances.get(account);  // Returns Optional<uint>

Generated Clarity:

(map-get? balances account)

Use match or unwrap operators to handle the Optional:

@readonly
function get_balance(account: principal): uint {
    match balances.get(account) {
        some(b) => b,
        none => 0u
    }
}

Writing to Maps

set inserts or updates a value:

balances.set(account, 1000u);

Generated Clarity:

(map-set balances account u1000)

Deleting from Maps

delete removes a key-value pair:

balances.delete(account);

Generated Clarity:

(map-delete balances account)

Composite Keys

Maps can have tuple keys for multi-dimensional lookups:

map allowances<{ owner: principal, spender: principal }, uint>;

@public
function approve(spender: principal, amount: uint): Response<bool, uint> {
    let key = { owner: tx-sender, spender: spender };
    allowances.set(key, amount);
    return ok(true);
}

@readonly
function get_allowance(owner: principal, spender: principal): uint {
    let key = { owner: owner, spender: spender };
    match allowances.get(key) {
        some(amount) => amount,
        none => 0u
    }
}

Lists

Lists are fixed-length sequences of values of the same type.

List Literals

let numbers: List<uint> = [1u, 2u, 3u, 4u, 5u];
let names: List<string> = ["Alice", "Bob", "Charlie"];
let flags: List<bool> = [true, false, true];

Generated Clarity:

(list u1 u2 u3 u4 u5)
(list u"Alice" u"Bob" u"Charlie")
(list true false true)

Higher-Order Functions

StxScript provides map, filter, and fold for list processing:

map

Apply a function to each element, producing a new list:

let doubled = map(numbers, (x) => x * 2u);

Generated Clarity:

(map double-fn numbers)

filter

Keep elements matching a predicate:

let evens = filter(numbers, (x) => x % 2u == 0u);

fold

Reduce a list to a single value with an accumulator:

let sum = fold(numbers, 0u, (acc, x) => acc + x);
let product = fold(numbers, 1u, (acc, x) => acc * x);

Generated Clarity:

(fold + numbers u0)
(fold * numbers u1)

Combining Operations

@public
function process(numbers: List<uint>): Response<uint, uint> {
    let evens = filter(numbers, (x) => x % 2u == 0u);
    let doubled = map(evens, (x) => x * 2u);
    let total = fold(doubled, 0u, (acc, x) => acc + x);
    return ok(total);
}

Tuples

Tuples are named-field structures, similar to objects in TypeScript or structs in Rust.

Creating Tuples

let user = { name: "Alice", balance: 1000u, active: true };
let point = { x: 10, y: 20 };

Generated Clarity:

{ name: u"Alice", balance: u1000, active: true }
{ x: 10, y: 20 }

Typed Tuples

let user: { name: string, balance: uint } = {
    name: "Alice",
    balance: 1000u
};

Accessing Fields

Use dot notation to read fields:

let userName = user.name;
let userBalance = user.balance;

Generated Clarity:

(get name user)
(get balance user)

Tuples as Function Parameters

@public
function update_user(data: { name: string, balance: uint }): Response<bool, uint> {
    let name = data.name;
    let balance = data.balance;
    return ok(true);
}

Tuples as Map Keys

Tuples are commonly used as composite map keys:

map trades<{ maker: principal, id: uint }, { amount: uint, price: uint }>;

Patterns

Token Balance Pattern

map balances<principal, uint>;

function credit(account: principal, amount: uint): bool {
    let current = match balances.get(account) {
        some(b) => b,
        none => 0u
    };
    balances.set(account, current + amount);
    return true;
}

function debit(account: principal, amount: uint): Response<bool, uint> {
    let current = match balances.get(account) {
        some(b) => b,
        none => 0u
    };
    if (current < amount) {
        return err(ERR_INSUFFICIENT_BALANCE);
    }
    balances.set(account, current - amount);
    return ok(true);
}

Registry Pattern

map registry<uint, { owner: principal, data: string }>;
let next_id: uint = 0u;

@public
function register(data: string): Response<uint, uint> {
    let id = next_id + 1u;
    next_id = id;
    registry.set(id, { owner: tx-sender, data: data });
    return ok(id);
}

Next Steps