This is the full developer documentation for Thru # Welcome to Thru! > Thru is a high-performance blockchain network for ultra-low latency, ultra-high throughput applications. Built for developers who need enterprise-grade reliability and speed. Caution This documentation is evolving! You may notice that many things are not here yet or have changed since you last looked. We will be adding a changelog, new guides, and more specifications over time, so be sure to come back. ## Quick Start [Setting Up Thru DevKit ](/docs/program-development/setting-up-thru-devkit/)Install and configure your development environment [Quickstart: Build a C Program ](/docs/program-development/building-a-c-program/)Build high-performance programs in C ## Build on Thru [C Programs ](/docs/program-development/building-a-c-program/)Build high-performance programs in C [APIs, SDKs, and CLIs ](/docs/api-ref/overview/)Explore Thru's APIs, SDK packages, and command-line tools [Core Specifications ](/docs/spec/overview/)Deep dive into Thru's technical architecture ## Core Features Ultra-High Performance Thru delivers industry-leading transaction throughput with sub-millisecond latency. Our advanced processing architecture maximizes hardware utilization for unparalleled performance. Developer-Friendly Runtime Write programs in C with direct access to Thru’s VM. Our comprehensive SDK and toolchain make it easy to build, test, and deploy applications on the network. ## Documentation Sections ### For Developers * **[Program Development](/docs/program-development/setting-up-thru-devkit/)** - Build and deploy programs on Thru * **[APIs, SDKs, and CLIs](/docs/api-ref/overview/)** - Browse RPC APIs, SDK packages, and CLI docs ### For Network Operators * **[Core Specifications](/docs/spec/overview/)** - Technical architecture and protocol details * **[Runtime Specifications](/docs/spec/runtime/overview/)** - VM execution environment details ### For Protocol Researchers * **[VM Specifications](/docs/spec/vm/overview/)** - Virtual machine architecture and syscalls *** Ready to build the future? Start by [Setting Up Thru DevKit](/docs/program-development/setting-up-thru-devkit/) and deploy your first program in minutes. # Build with LLMs > Use Thru docs, skills, and Explorer MCP effectively when you are building with an AI coding agent. Note This page is a routing and guardrails guide. Use it to pick the right next page, then switch to the linked SDK, ABI, CLI, or MCP references for exact syntax and API details. ## Install Thru Skills Start by installing the `thru-best-practices` skill. It is the highest-value default skill for agentic Thru development and should be part of the agent setup before working on programs, ABIs, SDK integrations, or CLI workflows. ```bash npx skills add https://thru.org/docs ``` ## Pick Your Path | If the task is… | Start here | Then use | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Build a first C program | [Quickstart: Build a C Program](/docs/program-development/building-a-c-program/) | [C SDK](/docs/sdks/c/), [Recommended Development Pattern](/docs/program-development/program-development-lifecycle/) | | Work on CPI or authorization flow | [Cross Program Invocation](/docs/program-development/cross-program-invocation/) | [C SDK Cross-Program Invocation](/docs/sdks/c-reference/cross-program-invocation/), [Common Gotchas](/docs/sdks/c-reference/common-gotchas/) | | Integrate the hosted wallet in a web app | [Wallet Overview](/docs/wallet/overview/) | [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/), [Approval and Signing](/docs/wallet/approval-and-signing/) | | Write or update an ABI | [Overview](/docs/abi/overview/) | [Authoring Guide](/docs/abi/authoring-guide/), [Examples](/docs/abi/examples/), [Specification](/docs/abi/specification/) | | Prove an ABI is correct before deploy | [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) | [ABI Analyze](/docs/cli-reference/abi-analyze/), [ABI Codegen](/docs/cli-reference/abi-codegen/), [ABI Reflect](/docs/cli-reference/abi-reflect/) | | Make explorer reflection work for a published ABI | [Explorer Compatibility](/docs/abi/explorer-compatibility/) | [Publishing and Iteration](/docs/abi/deployment-and-program-testing/), [Explorer MCP Overview](/docs/api-ref/explorer-mcp/overview/) | | Publish or upgrade program + ABI | [Recommended Development Pattern](/docs/program-development/program-development-lifecycle/) | [Publishing and Iteration](/docs/abi/deployment-and-program-testing/), [Program](/docs/cli-reference/program-commands/), [ABI](/docs/cli-reference/abi-commands/) | | Fund an account so it can pay for test or beta-phase writes | [Faucet](/docs/cli-reference/faucet-commands/) | [Network](/docs/cli-reference/network-commands/), [Transaction](/docs/cli-reference/transaction-commands/) | | Build a web app or backend integration | [SDK Packages](/docs/program-development/developer-resources/) | [Web Overview](/docs/sdks/web/), [Rust](/docs/sdks/rust/), the package reference page you actually need | | Debug deployed behavior | [Explorer MCP Overview](/docs/api-ref/explorer-mcp/overview/) | [Tools Reference](/docs/api-ref/explorer-mcp/tools-reference/), [Transaction](/docs/cli-reference/transaction-commands/), [runtime errors](/docs/spec/runtime/errors/) | ## Use the Docs Site as Context When an agent is already on a docs page, prefer the docs site’s built-in AI-friendly actions: * use `Copy for LLM` when you want to paste the full page into a chat * use `View as Markdown` when you want a cleaner source for an agent to read * keep the copied context narrow and task-specific instead of pasting large parts of the site That works best when you start from the smallest page that fully covers the current task. ## Use Explorer MCP for Live Chain Context For deployed-chain tasks, Explorer MCP is usually the safest way to keep an agent grounded in current state. Use it for: * account lookups * transaction inspection * recent blocks and recent transactions * program ABI lookup * ambiguous identifier search Start with: * [Explorer MCP Overview](/docs/api-ref/explorer-mcp/overview/) * [Explorer MCP Tools Reference](/docs/api-ref/explorer-mcp/tools-reference/) # Setup the DevKit > Install, configure, and verify the Thru CLI Use this guide to install the Thru CLI, configure the RPC endpoint, and verify everything works with a live request. Caution This install workflow will change frequently until v1.0.0. Check back for updates when upgrading between versions. ## Prerequisites * **Recommended install**: Install Node.js 18 or newer from [nodejs.org](https://nodejs.org/). * **Linux package install**: Use an x64 Linux VM with `curl` and `sudo` access. ## Setup 1. **Install the CLI** Install the Thru CLI globally using npm: ```bash npm i -g thru ``` The npm package installs a prebuilt binary for macOS, Windows, and Linux; no Rust toolchain is required. More installation methods: Debian or Ubuntu (.deb) Install the Debian package: ```bash curl -fsSLO https://github.com/Unto-Labs/thru/releases/download/v0.2.27/thru_0.2.27_amd64.deb sudo apt install ./thru_0.2.27_amd64.deb ``` RHEL-compatible Linux (.rpm) Install the RPM package: ```bash sudo dnf install https://github.com/Unto-Labs/thru/releases/download/v0.2.27/thru-0.2.27-1.x86_64.rpm ``` Note To build from source instead, `cargo install thru` is still available. It requires the Rust toolchain ([rustup.rs](https://rustup.rs/)) and the Buf CLI ([buf.build/docs/cli/installation](https://buf.build/docs/cli/installation/)). Verify the install: ```bash thru --help ``` 2. **Install the SDKs** Install the Thru toolchain and C SDK using the CLI: ```bash # Install RISC-V toolchain thru dev toolchain install # Install C SDK thru dev sdk install c ``` Note The SDK will be installed to `~/.thru/sdk/c/` by default. The toolchain will be installed to `~/.thru/sdk/toolchain/`. 3. **Make an initial call** Try a simple command: ```bash thru getversion ``` Caution The default RPC endpoint is `https://rpc.alphanet.thru.org`. You may change this to access other nodes. 4. **Configure RPC endpoint** Edit your CLI configuration file at `~/.thru/cli/config.yaml` Set the base URL: ```yaml rpc_base_url: https://rpc.alphanet.thru.org ``` 5. **Verify with getversion** Run again: ```bash thru --json getversion ``` Tip If you can see the `thru-node` version your CLI can reach the alphanet endpoint. 6. **Generate a keypair** Generate a keypair to use for your account: ```bash thru keys generate $NAME ``` For example, to create a keypair named `default`: ```bash thru keys generate default ``` Caution This command generates a private key. **Never share your private key** with anyone. It’s stored locally as plaintext hex in `~/.thru/cli/config.yaml`. Keep your files safe. Note This keypair will be stored locally and used to sign transactions. You may replace this key later with `thru keys generate --overwrite default` 7. **Create your first account** Create an account on-chain using your keypair: ```bash thru account create $NAME ``` For example, to create an account named `default`: ```bash thru account create default ``` You’ll see output similar to this: ```plaintext Info: Creating account with fee payer proof... Info: Account public key: tayzC11YgWrPpXBon_hBxI_Szh3eUqsHxEelsHxFroPsAB Info: Using slot: 776 Info: Calling makeStateProof RPC method... Success: State proof created successfully Info: Building transaction with fee payer proof... Info: Signing transaction... Info: Submitting transaction... Account Creation Key Name: default Public Key: tayzC11YgWrPpXBon_hBxI_Szh3eUqsHxEelsHxFroPsAB Signature: tsRIKKU4-Hu9nqzwlaZxcHjv7etbguo6g1AUMt5q9ga96dXxvqnTRTm8w6XH9C6OGSgcPc94TMmKzczNg8qqMTCyXX Status: success Success: Account creation transaction completed. Signature: tsRIKKU4-Hu9nqzwlaZxcHjv7etbguo6g1AUMt5q9ga96dXxvqnTRTm8w6XH9C6OGSgcPc94TMmKzczNg8qqMTCyXX ``` ## Updating the CLI When the CLI changes, update it with the same install method you used. For npm installs, run: ```bash npm i -g thru@latest ``` For Debian or Ubuntu VM installs, download and install the newer `.deb` package from the Thru GitHub release. # Quickstart: Build a C Program > Learn how to develop and build programs for the Thru network using the C SDK Thru programs are executable code that runs on the Thru blockchain VM, handling transactions and managing account state. This guide walks you through building a complete counter program using the C SDK. Note While this guide uses C, Thru supports multiple programming languages through different SDKs. The core concepts remain the same across all supported languages. ## What We’re Building In this guide, we’ll create a simple but complete **counter program** that demonstrates essential Thru development concepts: * **Account Creation**: Creating new accounts to store data * **State Management**: Reading and modifying account data * **Event Emission**: Publishing events when state changes * **Input Validation**: Securing your program against invalid data The counter program supports two operations: 1. **Create**: Initialize a new counter account with value 0 2. **Increment**: Increase an existing counter’s value by 1 ## Prerequisites Before you begin, complete the [Thru RPC Developer Kit setup guide](/docs/program-development/setting-up-thru-devkit/) so the CLI, C SDK, and toolchain are installed. You should be comfortable with basic C programming. ## Core Concepts Before diving into program structure, it’s important to understand the fundamental concepts of Thru program development. **Entry Point Function** Tip Every Thru program must define an entry point function with the `TSDK_ENTRYPOINT_FN` attribute. This function serves as the main execution entry point for your program. ```c TSDK_ENTRYPOINT_FN void start(void) { // Your program logic here } ``` For a deeper explanation of how `start(void)` is discovered and what the runtime expects around entrypoint layout, see the [Program Structure](/docs/sdks/c-reference/program-structure/) and [Headers and Entry Points](/docs/sdks/c-reference/headers-and-entry-points/) sections of the C SDK reference. Instruction data is read from the current transaction with: * `tsdk_get_txn()` * `tsdk_txn_get_instr_data(...)` * `tsdk_txn_get_instr_data_sz(...)` The [Accounts and Transaction Context](/docs/sdks/c-reference/accounts-and-transaction-context/) reference explains what data is available on the current transaction, how account indexing works, and which pointers are borrowed from VM-managed memory. **Program Execution Model** Note Thru programs don’t return values in the traditional sense. Instead, they either complete successfully using `tsdk_return()` or terminate with an error using `tsdk_revert()`. The program execution simply exits when complete. Use `tsdk_return()` for successful completion: ```c tsdk_return(TSDK_SUCCESS); ``` Use `tsdk_revert()` to terminate with an error: ```c tsdk_revert(error_code); ``` For the full control-flow and error model, including how `tsdk_return()` and `tsdk_revert()` terminate execution, see [Program Structure](/docs/sdks/c-reference/program-structure/) and [Error Handling and Return Codes](/docs/sdks/c-reference/error-handling-and-return-codes/). **Input Validation** Caution Always validate input data size and format to prevent security vulnerabilities and ensure program reliability. This is critical for preventing buffer overflows and malformed data attacks. We’ll cover detailed validation patterns when we implement the entry point function. For reusable validation patterns and the most common memory-layout mistakes, jump to [Common Patterns](/docs/sdks/c-reference/common-patterns/) and [Common Gotchas](/docs/sdks/c-reference/common-gotchas/). **SDK Imports** When developing programs using the SDK, include these essential headers: * `` - Core SDK functions and macros * `` - System call functions (optional, for advanced operations) The [Headers and Entry Points](/docs/sdks/c-reference/headers-and-entry-points/) reference explains when to include each header and which surfaces are safe to treat as your default entrypoint. ## Setting Up Your Project Before writing any code, let’s set up the project structure and build configuration. 1. **Create Project Directory** Create a directory for your program project: ```bash mkdir -p my-thru-project/examples cd my-thru-project ``` 2. **Create Build Configuration** Create a `GNUmakefile` in your project root: GNUmakefile ```makefile # Simple makefile for my Thru project BASEDIR:=$(CURDIR)/build # Set THRU_C_SDK_DIR to the location Thru SDK install. The default directory # is already set. THRU_C_SDK_DIR:=$(HOME)/.thru/sdk/c/thru-sdk include $(THRU_C_SDK_DIR)/thru_c_program.mk ``` Note The `GNUmakefile` sets up paths to the installed Thru C SDK and includes the program build rules. For a fuller explanation of installed SDK layout, include paths, and what `thru_c_program.mk` provides, see [Build Integration](/docs/sdks/c-reference/build-integration/). 3. **Create Program Build Configuration** Create a `Local.mk` file in your examples directory: examples/Local.mk ```makefile # My Thru C SDK Counter Program $(call make-bin,tn_counter_program_c,tn_counter_program,,-ltn_sdk) ``` Note The `Local.mk` file tells the build system which programs to compile using the `make-bin` macro, linking against the `tn_sdk` library. The names `tn_counter_program_c` (output binary name) and `tn_counter_program` (source file name) will be used for the files we create later in this guide. Your project structure should now look like this: ```plaintext my-thru-project/ ├── GNUmakefile └── examples/ └── Local.mk ``` ## Program Structure Now that your project is set up, let’s create the program files. A Thru program in C requires a minimum of two files: * **Header file (.h)** - Defines interfaces, data structures, and constants * **Implementation file (.c)** - Contains the program logic and entry point Tip While two files is the minimum requirement, larger programs may include additional source files, utility modules, or other dependencies as needed. If you want the runtime-level model behind this split, see [Program Structure](/docs/sdks/c-reference/program-structure/). We’ll create both files in the `examples/` directory. ### Create the Header File Create `examples/tn_counter_program.h` with the following content. This header defines the data structures, error codes, and constants for your counter program: tn\_counter\_program.h ```c #ifndef TN_COUNTER_PROGRAM_H #define TN_COUNTER_PROGRAM_H #include /* Error codes */ #define TN_COUNTER_ERR_INVALID_INSTRUCTION_DATA_SIZE (0x1000UL) #define TN_COUNTER_ERR_INVALID_INSTRUCTION_TYPE (0x1001UL) #define TN_COUNTER_ERR_ACCOUNT_CREATE_FAILED (0x1002UL) #define TN_COUNTER_ERR_ACCOUNT_SET_WRITABLE_FAILED (0x1003UL) #define TN_COUNTER_ERR_ACCOUNT_RESIZE_FAILED (0x1004UL) #define TN_COUNTER_ERR_ACCOUNT_DATA_ACCESS_FAILED (0x1005UL) /* Instruction types */ #define TN_COUNTER_INSTRUCTION_CREATE (0U) #define TN_COUNTER_INSTRUCTION_INCREMENT (1U) /* Create counter instruction arguments */ typedef struct __attribute__((packed)) { uint instruction_type; ushort account_index; uchar counter_program_seed[TN_SEED_SIZE]; uint proof_size; /* proof_data follows dynamically based on proof_size */ } tn_counter_create_args_t; /* Increment counter instruction arguments */ typedef struct __attribute__((packed)) { uint instruction_type; ushort account_index; } tn_counter_increment_args_t; /* Counter account data structure */ typedef struct __attribute__((packed)) { ulong counter_value; } tn_counter_account_t; #endif ``` **Key components:** * **Error codes**: Define unique error codes for debugging * **Instruction types**: Constants identifying each operation (create = 0, increment = 1) * **Data structures**: Define the binary format for instruction arguments and account storage * **`proof_size` field**: Indicates the size of the cryptographic state proof for account verification The [Headers and Entry Points](/docs/sdks/c-reference/headers-and-entry-points/), [State Proofs](/docs/sdks/c-reference/state-proofs/), and [Common Gotchas](/docs/sdks/c-reference/common-gotchas/) pages go deeper on packed structs, header boundaries, and dynamic trailing payloads like `proof_data`. ### Create the Implementation File Create `examples/tn_counter_program.c` with the following content. This file contains the program logic including instruction handlers and the entry point: tn\_counter\_program.c ```c #include #include #include #include "tn_counter_program.h" static void handle_create_counter(uchar const *instruction_data, ulong instruction_data_sz TSDK_PARAM_UNUSED) { tn_counter_create_args_t const *args = (tn_counter_create_args_t const *)instruction_data; /* Use account index from instruction arguments (index 0 is the fee payer, index 1 is the program) */ ushort account_idx = args->account_index; /* Get proof data pointer (follows the struct) */ uchar const *proof_data = NULL; if (args->proof_size > 0) { proof_data = instruction_data + sizeof(tn_counter_create_args_t); } /* Create the account using seed and proof from instruction data */ ulong result = tsys_account_create(account_idx, args->counter_program_seed, proof_data, args->proof_size); if (result != TSDK_SUCCESS) { tsdk_revert(TN_COUNTER_ERR_ACCOUNT_CREATE_FAILED); } /* Set account as writable so we can modify its data */ result = tsys_set_account_data_writable(account_idx); if (result != TSDK_SUCCESS) { tsdk_revert(TN_COUNTER_ERR_ACCOUNT_SET_WRITABLE_FAILED); } /* Resize account to hold counter data */ result = tsys_account_resize(account_idx, sizeof(tn_counter_account_t)); if (result != TSDK_SUCCESS) { tsdk_revert(TN_COUNTER_ERR_ACCOUNT_RESIZE_FAILED); } /* Get account data pointer and initialize counter */ void* account_data = tsdk_get_account_data_ptr(account_idx); if (account_data == NULL) { tsdk_revert(TN_COUNTER_ERR_ACCOUNT_DATA_ACCESS_FAILED); } tn_counter_account_t* counter_account = (tn_counter_account_t*)account_data; counter_account->counter_value = 0UL; tsdk_return(TSDK_SUCCESS); } static void handle_increment_counter(uchar const *instruction_data, ulong instruction_data_sz TSDK_PARAM_UNUSED) { tn_counter_increment_args_t const *args = (tn_counter_increment_args_t const *)instruction_data; ushort account_idx = args->account_index; /* Get account data pointer */ void* account_data = tsdk_get_account_data_ptr(account_idx); if (account_data == NULL) { tsdk_revert(TN_COUNTER_ERR_ACCOUNT_DATA_ACCESS_FAILED); } /* Set account as writable so we can modify the counter value */ ulong result = tsys_set_account_data_writable(account_idx); if (result != TSDK_SUCCESS) { tsdk_revert(TN_COUNTER_ERR_ACCOUNT_SET_WRITABLE_FAILED); } /* Increment the counter */ tn_counter_account_t* counter_account = (tn_counter_account_t*)account_data; counter_account->counter_value++; /* Emit increment event - emit just the counter value */ tsys_emit_event((uchar const *)&counter_account->counter_value, sizeof(ulong)); tsdk_return(TSDK_SUCCESS); } TSDK_ENTRYPOINT_FN void start(void) { tsdk_txn_t const *txn = tsdk_get_txn(); uchar const *instruction_data = tsdk_txn_get_instr_data(txn); ulong instruction_data_sz = tsdk_txn_get_instr_data_sz(txn); /* Check minimum size to safely read instruction type */ if (instruction_data_sz < sizeof(uint)) { tsdk_revert(TN_COUNTER_ERR_INVALID_INSTRUCTION_DATA_SIZE); } uint const *instruction_type = (uint const *)instruction_data; switch (*instruction_type) { case TN_COUNTER_INSTRUCTION_CREATE: /* Check minimum size to safely access struct fields */ if (instruction_data_sz < sizeof(tn_counter_create_args_t)) { tsdk_revert(TN_COUNTER_ERR_INVALID_INSTRUCTION_DATA_SIZE); } tn_counter_create_args_t const *create_args = (tn_counter_create_args_t const *)instruction_data; ulong expected_size = sizeof(tn_counter_create_args_t) + create_args->proof_size; /* Check exact size including proof data */ if (instruction_data_sz != expected_size) { tsdk_revert(TN_COUNTER_ERR_INVALID_INSTRUCTION_DATA_SIZE); } handle_create_counter(instruction_data, instruction_data_sz); break; case TN_COUNTER_INSTRUCTION_INCREMENT: /* Check exact instruction size */ if (instruction_data_sz != sizeof(tn_counter_increment_args_t)) { tsdk_revert(TN_COUNTER_ERR_INVALID_INSTRUCTION_DATA_SIZE); } handle_increment_counter(instruction_data, instruction_data_sz); break; default: tsdk_revert(TN_COUNTER_ERR_INVALID_INSTRUCTION_TYPE); } /* Should never reach here */ tsdk_revert(TN_COUNTER_ERR_INVALID_INSTRUCTION_TYPE); } ``` **Key components:** * **Handler functions**: Separate functions for create and increment operations * **Entry point**: The `start` function validates input and routes to the appropriate handler * **Input validation**: Comprehensive size checks before accessing memory * **Error handling**: Uses `tsdk_revert()` to exit with error codes For the underlying runtime surface used here, see: * [Accounts and Transaction Context](/docs/sdks/c-reference/accounts-and-transaction-context/) for `tsdk_get_txn()` and `tsdk_get_account_data_ptr(...)` * [Syscalls](/docs/sdks/c-reference/syscalls/) for `tsys_account_create(...)`, `tsys_account_resize(...)`, `tsys_set_account_data_writable(...)`, and `tsys_emit_event(...)` * [Common Patterns](/docs/sdks/c-reference/common-patterns/) for safe size checks before casting instruction buffers Your project structure is now complete: ```plaintext my-thru-project/ ├── GNUmakefile └── examples/ ├── Local.mk ├── tn_counter_program.h └── tn_counter_program.c ``` ## Building Your Program With your program files in place, build the program: 1. **Build the Program** Run the build from your project root: ```bash make ``` Note The build system compiles your Thru program for the Thru VM using the installed RISC-V toolchain, generating a `.bin` file in the `build/thruvm/bin/` directory ready for deployment. For more on the installed toolchain, compiler prefix detection, and what the generated targets mean, see [Build Integration](/docs/sdks/c-reference/build-integration/). 2. **Verify Build Output** Confirm the binary exists: ```bash ls build/thruvm/bin/ ``` You should see `tn_counter_program_c.bin` in the output directory. Tip Your program is successfully compiled and ready for deployment to the Thru network. ## Deploying Your Program Great! Your counter program is now compiled and ready. The next step is deploying it to the Thru network, which makes your program available for other users and applications to interact with. Deploy your program to the Thru network using the CLI: If you want the broader write-build-deploy-debug loop, see [Recommended Development Pattern](/docs/program-development/program-development-lifecycle/). For exact CLI flags and related subcommands, see the [Program](/docs/cli-reference/program-commands/) reference page. 1. **Create Program on Network** Upload and create your program using a unique seed name: ```bash thru program create $SEED $PATH_TO_PROGRAM_BINARY ``` For our counter example: ```bash thru program create thru_program ./build/thruvm/bin/tn_counter_program_c.bin ``` You’ll see output similar to this: ```ansi Info: Creating permanent managed program from file: ./build/thruvm/bin/tn_counter_program_c.bin (778 bytes) Info: User seed: thru_program Info: Step 1: Uploading program to temporary buffer (seed: thru_program_temporary) Info: Checking for existing upload state... Info: No existing upload found Info: Starting fresh upload Info: Creating meta and buffer accounts... Success: Transaction completed: tsBBvaXmtyKDn95pAeo03DEiHlobsAc5t80NoFKqFzEwd_5q2PoGTdXNl9gvNU_PmFsSWy168mFcmRMmhL2VsICB8h Info: Writing chunk 1/1 (778 bytes) at offset 0 Success: Transaction completed: ts5ubIqevoNhioFKswmUolG-7-umnXZwNhTFLD00XQFu05xhspnZK4A6JpgwHr80kW4-fMHHbG44skZzCzZSb1CCBg Info: Finalizing upload... Success: Transaction completed: tsXzyg8MIISD4keoNHaTsxEruagay7Q9vcDs69FyDKpjOgiUgrpr5IZLdfesX9n-si-CoFn0bLMJara156AmsADR0j Success: Program uploaded to temporary buffer successfully Info: Temporary meta account: taOxQq4ms4bF2lxs4gM1tkZS90q2ACVo9xvuw-UTzAOO3X Info: Temporary buffer account: taqrAKYvM6KxZMxotJzaEUhkNQxum-AGr4OKrgN6uCvMMp Info: Step 2: Creating managed program from temporary buffer Info: Fee payer: tazLZyk2wT3WO1-_vgH2iqNi0nayD5z2jfFcK10hgrSpan Info: Manager program meta account: ta8GX2vn4xeY-hGHgrnARdDhz9q3EtcKMnQYOCAtMOza9o Info: Manager program account: ta… Info: Creating state proofs for permanent program... Success: Transaction completed: tsq6iZBJwRmShKrVZXl0TrzdVam6LCQ1GdyXXpMuOmfvfpq9BhhHkqwx1jwj3ud4l1V4hxxxoZR1fd94z2UTlYCSEF Success: Managed program created successfully Info: Step 3: Cleaning up temporary buffer account Info: Cleaning up program accounts... Info: Executing cleanup transaction... Success: Transaction completed: ts12OV8Y2p6c-UVaqctw2exA2GOdro7CuXSr5fqLEKMlEWrrilxj-1YuaGsRbtdMpyln5wMuy456OcLjvZRrxLDCK0 Success: Temporary buffer account cleaned up successfully Success: Permanent managed program created successfully Info: Meta account: ta8GX2vn4xeY-hGHgrnARdDhz9q3EtcKMnQYOCAtMOza9o Info: Program account: ta… ``` 2. **Note Your Program Accounts** Save the program account addresses from the output: * **Program account**: `ta…` Note When you deploy a program, two accounts are created: * **Meta account** - Stores management metadata (authority, version counter, pause state) * **Program account** - Contains the actual executable program code The two accounts are cryptographically linked: the meta account is derived from your seed, and the program account is derived from the meta account’s address. You’ll use the program account address when calling your program from client applications. Tip Your counter program is now deployed and ready to receive create and increment instructions on the Thru network. ## Updating Your Program When you need to modify and redeploy your program: 1. **Make Code Changes and Rebuild** After making changes to your program code, rebuild it: ```bash make ``` Note Always rebuild your program after making code changes to ensure the binary file contains your latest updates. 2. **Upgrade the Deployed Program** Use the same seed to upgrade your existing program: ```bash thru program upgrade $SEED $PATH_TO_PROGRAM_BINARY ``` For our counter example: ```bash thru program upgrade thru_program ./build/thruvm/bin/tn_counter_program_c.bin ``` You’ll see output similar to this: ```ansi Info: Upgrading managed program from file: ./build/thruvm/bin/tn_counter_program_c.bin (778 bytes) Info: User seed: thru_program Info: Step 1: Uploading program to temporary buffer (seed: thru_program_upgrade_temporary) Info: Checking for existing upload state... Info: No existing upload found Info: Starting fresh upload Info: Creating meta and buffer accounts... Success: Transaction completed: tsVAIY50dLC3vjkBC0y08yeoXcoa3M6Mzb9jG6pscofprvMs6SEvYUNOZuXJb0Xx1_1kPh_wVMpRMnlukmvgduCiAT Info: Writing chunk 1/1 (778 bytes) at offset 0 Success: Transaction completed: tsN2k0h3HS4G8LW_LYYzAumXLYVOfRA4cXiQHFVVjb__kXKjxAM4BF_dMQasIvXKHT8PlGE673B3nS_os_VnObCR9r Info: Finalizing upload... Success: Transaction completed: tso9ORuCIqpr2hZBgtBPC1aKZrFHP8mdbWrCr849R4LcSOLcQmJyR7foDbsgoM6lsrCK8jM_5JtQ5DOrw-t0_CCh5w Success: Program uploaded to temporary buffer successfully Info: Temporary meta account: taiMvWgn2SAj5ZHmMCYaja5VzOu-8MmZBTujAVDLdcXYL3 Info: Temporary buffer account: taLsLf6_wdbPXMklS2Ss3VqJNCHnqRSy1CXzb0ZdNwtTH_ Info: Step 2: Upgrading managed program from temporary buffer Info: Fee payer: tazLZyk2wT3WO1-_vgH2iqNi0nayD5z2jfFcK10hgrSpan Info: Manager program meta account: ta8GX2vn4xeY-hGHgrnARdDhz9q3EtcKMnQYOCAtMOza9o Info: Manager program account: ta… Success: Transaction completed: tse-0ldYMRrs45XaDbDTfSnFIXtuDQdY_pd5kDUyrf0iJkZE1u-l8KQntTgWlNdx-QsAPInqINLfjru7UtDHMQBR1Q Success: Managed program upgraded successfully Info: Step 3: Cleaning up temporary buffer account Info: Cleaning up program accounts... Info: Executing cleanup transaction... Success: Transaction completed: tsfQJTncHpt-_lFf0aH6Wefparw7_59NQEv6dQ3kDLJuLNW9-_6nOC0El1NOgNr7854zGE9yCM4Kw7hbTwvC-OASSH Success: Temporary buffer account cleaned up successfully Success: Program upgrade completed! New program size: 778 bytes ``` Tip The upgrade process uses the same program accounts but updates the program code. Your program account addresses remain the same, so existing client applications continue to work without changes. The [Program](/docs/cli-reference/program-commands/) reference documents related commands like derive-address, destroy, and cleanup-oriented flows that are useful when deployment or upgrade does not finish cleanly. ## Interacting with Your Deployed Program Perfect! Your counter program is now deployed and ready to use. But before we can send transactions to it, we need to understand how to properly format instructions and organize accounts. ### Understanding Account Indexing When your program executes, it receives an **account array** with accounts organized in a specific order. Understanding this structure is essential for properly constructing transactions and accessing the right accounts in your program. **Account Array Structure:** The transaction system organizes accounts in the following order: 1. **Fee Payer Account** - The account paying transaction fees (always at index 0) 2. **Program Account** - Your deployed program (always at index 1) 3. **Writable Accounts** - Accounts the program can modify (sorted in ascending order by hex key) 4. **Read-only Accounts** - Accounts the program can only read (sorted in ascending order by hex key) **CLI Transaction Parameters:** When using `thru txn execute `: * `` - Program account address (becomes index 1 in the account array) * `` - Hex-encoded instruction data (contains account\_index specifying which account slot to use) **How Account Creation Works:** When you call create counter with a seed, the program uses `tsys_account_create()` to create a new account. The seed determines the account address - the same seed will always generate the same account address. This means you can: 1. Use a unique seed to create a counter account 2. Calculate the resulting account address from the seed 3. Use that account address in increment instructions The account address is deterministically derived from the seed, so you don’t need the program to “return” it - you can calculate it independently. For the full account-array model, borrowed account views, and transaction context helpers available inside the VM, see [Accounts and Transaction Context](/docs/sdks/c-reference/accounts-and-transaction-context/). ## Interacting with Your Program Once your counter program is deployed, you can send transactions to interact with it. This involves constructing the proper instruction data and sending it to your program account. ### Understanding Instruction Data Format To interact with your counter program, you need to understand how to format the instruction data. Let’s examine the instruction structures defined in your program: * title="Create Counter Instruction" ```c /* Create counter instruction arguments */ typedef struct { uint instruction_type; ushort account_index; uchar counter_program_seed[TN_SEED_SIZE]; uint proof_size; } tn_counter_create_args_t; /* proof_data bytes follow this struct based on proof_size */ ``` * title="Increment Counter Instruction" ```c /* Increment counter instruction arguments */ typedef struct { uint instruction_type; ushort account_index; } tn_counter_increment_args_t; ``` **Key Data Types:** * `uint` - 32-bit unsigned integer (4 bytes) * `ushort` - 16-bit unsigned integer (2 bytes) * `uchar counter_program_seed[TN_SEED_SIZE]` - 32-byte seed for deterministic account creation * `proof_size` - 32-bit unsigned integer containing the proof byte length The create counter instruction format is: `instruction_type + account_index + counter_program_seed[32] + proof_size + proof_data[proof_size]`. The proof bytes are variable-length and are appended immediately after the fixed-size struct. ### Constructing Instruction Data To interact with your deployed counter program, you need to construct properly formatted instruction data. This involves three main steps: 1. **Derive the account address** where your counter will be stored 2. **Generate a state proof** to verify account creation 3. **Encode the instruction data** in the format your program expects Note The process below shows how to create a new counter. Once created, incrementing is much simpler (just instruction type + account index). This section intentionally stays tutorial-focused. For deeper reference material on proof bytes, buffer layout, and packed struct handling, see [State Proofs](/docs/sdks/c-reference/state-proofs/), [Headers and Entry Points](/docs/sdks/c-reference/headers-and-entry-points/), and [Common Gotchas](/docs/sdks/c-reference/common-gotchas/). 1. **Derive Account Address** First, derive the account address where your counter will be stored using the program address and a seed. Note that you can re-derive the program address using the original seed: `thru program derive-program-account ` ```bash thru program derive-address ta… count_acc ``` Output: ```plaintext Program ID: ta… Seed: count_acc Ephemeral: false Derived Address: ta… ``` Note The derived address is deterministic - the same program and seed will always generate the same account address. The [Program](/docs/cli-reference/program-commands/) reference documents the related derivation helpers in more detail. 2. **Create State Proof** Generate a state proof for the account you want to create: ```bash thru txn make-state-proof creating ta… ``` Output: ```plaintext Success: State proof created successfully Account: ta… Proof Type: creating Proof Size: 104 bytes Proof Data (hex): 3502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` Note State proofs verify that an account exists or doesn’t exist in the blockchain state tree. For account creation, we use a “creating” proof. The [State Proofs](/docs/sdks/c-reference/state-proofs/) reference explains what the proof bytes represent and how to reason about them in C programs. 3. **Construct Create Counter Instruction Data** Now construct the instruction data by encoding each field according to the struct definition. Recall our create instruction structure: ```c typedef struct __attribute__((packed)) { uint instruction_type; // 4 bytes ushort account_index; // 2 bytes uchar counter_program_seed[TN_SEED_SIZE]; // 32 bytes uint proof_size; // 4 bytes /* proof_data follows dynamically based on proof_size */ } tn_counter_create_args_t; ``` Caution The `__attribute__((packed))` attribute is crucial - it tells the compiler to remove padding between struct members, allowing us to concatenate the hex values directly without considering alignment bytes. See [Common Gotchas](/docs/sdks/c-reference/common-gotchas/) for when packed structs are appropriate, when to be more defensive with byte parsing, and how to avoid layout mismatches between off-chain encoders and on-chain C code. The instruction payload layout is: ```text instruction_data = instruction_type + account_index + counter_program_seed[32] + proof_size + proof_bytes ``` Encode the fields in this order: 1. `instruction_type`, create-counter instruction identifier. This is explicitly set by the example counter program. * Value: `0` (for creation) * Format: `uint` little-endian: `00000000` 2. `account_index`, first account in the read/write account list * Value: `2` (idx 0 is fee-payer, idx 1 = program address) * Format: `ushort` little-endian: `0200` 3. `counter_program_seed`, seed used to derive the counter account address * Value: `count_acc` (or whatever seed you chose) * Convert the seed string to padded 32-byte hex with `thru program seed-to-hex count_acc` * Result: `636f756e745f6163630000000000000000000000000000000000000000000000` The [Program](/docs/cli-reference/program-commands/) reference documents `seed-to-hex` and related derivation helpers. 4. `proof_size`, number of proof bytes appended after the fixed-size fields. This is part of the output of `thru txn make-state-proof creating`. * Value: `104` * Format: `uint` little-endian: `68000000` 5. `proof_bytes`, appended exactly as returned by the CLI * Value: output of `thru txn make-state-proof creating ` Concatenate in order: ```text 00000000 + 0200 + + 68000000 + ``` Example final hex string: ```text 000000000200636f756e745f6163630000000000000000000000000000000000000000000000680000003502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` 4. **Increment Counter Instruction** For incrementing an existing counter, the instruction is much simpler as it only needs the instruction type and account index: ```c typedef struct __attribute__((packed)) { uint instruction_type; // 4 bytes ushort account_index; // 2 bytes } tn_counter_increment_args_t; ``` **1. Instruction Type** (INCREMENT = 1): `uint` in little endian ```plaintext Value: 1 → 01000000 ``` **2. Account Index** (2): `ushort` in little endian ```plaintext Value: 2 → 0200 ``` **Complete Instruction Data**: ```plaintext 010000000200 ``` ## Sending Transactions Once you have constructed your instruction data, you can send transactions to interact with your deployed counter program. For the broader deploy-test-debug loop on devnet, including ABI roundtrip validation and failure recovery, see [Recommended Development Pattern](/docs/program-development/program-development-lifecycle/). For exact transaction flags and output fields, see [Transaction](/docs/cli-reference/transaction-commands/). 1. **Execute Create Counter Transaction** Command structure: * `thru txn execute` - Base command for transaction execution * `--fee 0` - Set transaction fee to 0 for testing * `—readwrite-accounts ` - The program-derived address where the counter will be created/stored * `` - Your deployed program account * `` - The hex-encoded instruction data you constructed Send the create counter transaction using the CLI: ```bash thru txn execute \ --fee 0 \ --readwrite-accounts ta… \ ta… \ 000000000200636F756E745F6163630000000000000000000000000000000000000000000000680000003502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ``` Caution **Important**: The `--readwrite-accounts` parameter uses `ta…` - this is the **program-derived address we computed earlier** using the seed “count\_acc”. This is the address where our counter account will be created and stored. Output: ```plaintext Success: Transaction executed successfully Signature: tsWCCCBvTAx4pMkYbeW8hMrf6cPax6ujQ7o_uGV378qS_lm25sN2tbMXvfrL-5ouYObwCMYpgvi1AEumc_fFeRAh-6 Slot: 567 Compute Units Consumed: 7524 State Units Consumed: 1 Execution Result: 0 VM Error: 0 User Error Code: 0 Events Count: 0 Events Size: 0 Pages Used: 2 ``` Note The account ordering follows the transaction specification: fee payer (index 0), program (index 1), then writable accounts (index 2+). Our counter account will be at index 2. The `--readwrite-accounts` parameter tells the transaction system which accounts the program is allowed to modify - in this case, the counter account we’re about to create. Note that `State Units Consumed: 1` confirms the account was successfully created. 2. **Execute Increment Counter Transaction** After successfully creating a counter, you can increment it: ```bash thru txn execute \ --fee 0 \ --readwrite-accounts ta… \ ta… \ 010000000200 ``` Note **Reminder**: We use the same `—readwrite-accounts ta…` because we’re modifying the same counter account that was created in the previous step. Output: ```plaintext Success: Transaction executed successfully Signature: ts0dxkQCkCr2pGwhchAD5ub_OfYll1gjCemMkxLl2xHWD0QvkrzQpKFzdBRSI28855HvMoptHBIExh3_37xKE0ABzc Slot: 568 Compute Units Consumed: 5980 State Units Consumed: 0 Execution Result: 0 VM Error: 0 User Error Code: 0 Events Count: 1 Events Size: 18 Pages Used: 3 Events: Event 1: call_idx=0, program_idx=1 Data (hex): 0100000000000000 ``` Tip The increment instruction is much shorter since it only contains the instruction type (1) and account index (2). **Event Explanation**: The event shown above is emitted by our program when the counter is successfully incremented. Notice how the counter value went from 0 (initial value after creation) to 1 (after increment). The event data `0100000000000000` represents our new counter value `1` in little endian 64-bit format - this is the value we emitted using `tsys_emit_event()` in our program code. Tip 🎉 **Congratulations!** You have successfully: * Built a complete Thru program in C * Deployed it to the Thru network * Created a counter account with state proof * Incremented the counter and verified the event emission Your counter program demonstrates all the essential concepts: account creation, state management, input validation, and event emission. ## Next Steps Now that you’ve mastered the basics, you can: * **Extend the counter**: Add decrement, reset, or set value operations * **Add access control**: Restrict who can modify specific counters * **Build more complex programs**: Try programs that interact with multiple accounts * **Explore the SDK**: Check out advanced syscalls for more complex operations Note The patterns you’ve learned here - program structure, input validation, account management, and event emission - form the foundation for any Thru program, regardless of complexity. # Authoring Guide > Write handwritten ABI YAML that stays understandable, validates cleanly, and is easy for agents to test and publish. Use this page when you are creating or editing ABI YAML by hand. Caution Thru ABIs are handwritten today. They do not automatically stay in sync with a program, so every ABI change should be treated like real source code and validated explicitly. ## Best Starting Point Do not start from a blank file unless you have to. The fastest path is: 1. copy a shape from [ABI Examples](/docs/abi/examples/) 2. rename packages and types 3. trim it down to the smallest schema that still represents your program 4. validate it with [ABI Analyze](/docs/cli-reference/abi-analyze/) ## Authoring Priorities | Priority | Why it matters | | ------------------------------------------------ | ------------------------------------------------------------------ | | Stable package and type names | Codegen output and import resolution stay predictable. | | Clear field names for counts, tags, and payloads | Reflection and generated code become much easier to use. | | Small, reusable imported packages | Shared types stay manageable without bloating the root file. | | Earlier size-driving fields | The resolver rejects forward references in dynamic layouts. | | A root ABI that is easy to read | Agents can reason about the file without loading too much context. | ## What To Put In The File At minimum, most ABIs need: * `abi.package` * `abi.abi-version` * `abi.package-version` * `abi.description` * `types` Program-facing ABIs often also need: * imports for shared or on-chain types * `options.program-metadata.root-types` so tooling knows which types are the instruction root, account root, errors, and events ## Imports Use imports intentionally: * `path` imports are the easiest authoring-time choice * `onchain` imports are useful when you intentionally depend on a published ABI package * remote imports are powerful, but they make publishing rules stricter Practical rule: * use local imports while you are iterating * move to publish-safe normalization later with [ABI Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) ## Handwritten ABI Gotchas * The ABI will not auto-update when the program changes. * A valid-looking schema can still be wrong for the actual wire format your program emits or expects. * Packing everything blindly is risky if the consumer expects alignment behavior. * A schema can resolve successfully and still be awkward to use once you generate code from it. ## Special Note On Authorization The ABI describes data layout. It does not describe the full authorization model of your program. That matters most for CPI-heavy programs: * account indices and instruction payloads may be correct * but the callee can still fail because the authorization or account-access model is wrong For CPI behavior, validate the program logic and SDK flow separately. The ABI only gives you the wire format. ## Best Next Step Once the file exists, move immediately to [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/). That is where you prove the ABI actually matches the bytes you intend to send. # Publishing and Iteration > Prepare an ABI for publication, publish it against the right program seed, and verify the on-chain ABI artifact. Use this page when the ABI is locally validated and you are ready to publish or upgrade the ABI artifact that goes with a deployed program. ## Publish With The Program Seed The program and ABI should use the same seed. ```bash thru program create my_program ./build/thruvm/bin/my_program_c.bin thru abi account create my_program ./program.abi.yaml ``` That seed match matters because it is how downstream tooling can associate the deployed program with the ABI it should use for reflection. ## ABI-Focused Flow 1. validate the ABI locally first with [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) 2. confirm the published artifact will be explorer-compatible with [Explorer Compatibility](/docs/abi/explorer-compatibility/) 3. if the ABI still depends on local imports, normalize it with [ABI Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) 4. create or upgrade the ABI account with [ABI Account](/docs/cli-reference/abi-account/) 5. read the ABI back with `get --include-data` to confirm the published artifact is the one you expected ## Common ABI Publishing Gotchas * publishing an ABI that was never roundtrip-tested * publishing an ABI without `program-metadata.root-types` * publishing only separate per-instruction structs instead of one discriminated instruction root * publishing an ABI with a different seed than the deployed program * forgetting that local imports need to be normalized before publication * finalizing too early and losing the ability to upgrade or close the ABI account ## Typical Follow-Up Commands * [ABI Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) * [ABI Account](/docs/cli-reference/abi-account/) * [Explorer Compatibility](/docs/abi/explorer-compatibility/) * [Recommended Development Pattern](/docs/program-development/program-development-lifecycle/) for the broader deploy, test, and debug loop # Examples > Use real ABI excerpts for core primitives, token instructions, flattened program ABIs, and advanced proof structures. Use this page when you want real ABI shapes to copy from instead of inventing a schema from scratch. Tip These are verbatim excerpts from public ABI examples shipped with Thru. Start here, then trim or adapt them for your own package. ## Core Primitives Use this pattern for fixed-size binary primitives and a simple variable-size payload wrapper. ```yaml abi: package: "thru.common.primitives" abi-version: 1 package-version: "1.0.0" description: "Basic primitive types for Thru blockchain (signatures, hashes, pubkeys)" imports: [] types: - name: "Hash" kind: struct: packed: true fields: - name: "bytes" field-type: array: size: literal: u64: 32 element-type: primitive: u8 - name: "Pubkey" kind: struct: packed: true fields: - name: "bytes" field-type: array: size: literal: u64: 32 element-type: primitive: u8 - name: "InstructionData" kind: struct: packed: true fields: - name: "program_idx" field-type: primitive: u16 - name: "data_size" field-type: primitive: u64 - name: "data" field-type: array: size: field-ref: path: ["data_size"] element-type: primitive: u8 ``` ## Token Program Pattern Use this pattern when you need program metadata, shared imports, and a tagged instruction root. ```yaml abi: package: thru.program.token name: "Token Program" abi-version: 1 package-version: 0.1.0 description: Ideal ABI for the Thru token program instruction envelopes and account state imports: - type: onchain address: ta1blxgaYR0dei5aldWJe1vbUtt-LkVEBOzNJtSRhHQcTG target: abi network: mainnet revision: latest - type: onchain address: ta1l4yBjT6a7PQ4Wu_sJPQwb8jJiJ8Ei71gOuE5Dnotfrl target: abi network: mainnet revision: latest options: program-metadata: root-types: instruction-root: "TokenInstruction" account-root: "TokenProgramAccount" errors: "TokenError" events: "TokenEvent" types: - name: TransferInstruction kind: struct: packed: true fields: - name: source_account_index field-type: primitive: u16 - name: dest_account_index field-type: primitive: u16 - name: amount field-type: primitive: u64 - name: TokenInstruction kind: struct: packed: true fields: - name: tag field-type: primitive: u8 - name: payload field-type: enum: packed: true ``` ## Explorer-Compatible Publishing Pattern Use this pattern when the ABI must decode correctly in explorer after publication, not just during local analyze or codegen. ```yaml abi: package: thru.example.counter name: "Counter Program" abi-version: 1 package-version: 1.0.0 description: Minimal explorer-compatible counter ABI imports: [] options: program-metadata: root-types: instruction-root: "CounterInstruction" account-root: "CounterAccount" errors: "CounterError" events: "CounterEvent" types: - name: CounterInstruction kind: struct: packed: true fields: - name: tag field-type: primitive: u8 - name: payload field-type: enum: packed: true tag-ref: field-ref: path: - tag variants: - name: initialize tag-value: 0 variant-type: type-ref: name: InitializeArgs - name: increment tag-value: 1 variant-type: type-ref: name: IncrementArgs - name: CounterAccount kind: struct: packed: true fields: - name: value field-type: primitive: u64 - name: CounterEvent kind: struct: packed: true fields: - name: value field-type: primitive: u64 ``` This is the smallest useful shape for explorer reflection: * a configured `instruction-root` * a configured `account-root` * a configured `events` type * one discriminated instruction envelope instead of only separate instruction structs ## Flattened Program ABI Pattern Use a flattened style like this when you want a publish-ready artifact with no external imports left to resolve. ```yaml abi: package: thru.program.nft_token name: "NFT Token Program" abi-version: 1 package-version: '1.0.0' description: NFT token program for minting, transferring, and managing NFTs on Thru blockchain imports: [] options: program-metadata: root-types: instruction-root: NftInstruction account-root: NftProgramAccount errors: null events: null types: - name: Hash kind: struct: packed: true aligned: 0 comment: null fields: - name: bytes field-type: array: packed: false aligned: 0 comment: null size: literal: u64: 32 element-type: primitive: u8 jagged: false ``` ## Advanced Dynamic Proof Pattern Use this pattern when the ABI needs nested field references, expressions, and variable-size proof bodies. ```yaml abi: package: "thru.blockchain.state_proof" abi-version: 1 package-version: "1.0.0" description: "State proof structures for Merkle tree verification" imports: - type: onchain address: ta1blxgaYR0dei5aldWJe1vbUtt-LkVEBOzNJtSRhHQcTG target: abi network: mainnet revision: latest types: - name: "StateProofHeader" kind: struct: packed: true fields: - name: "type_slot" field-type: primitive: u64 - name: "path_bitset" field-type: type-ref: name: "Hash" package: "thru.common.primitives" - name: "StateProof" kind: struct: packed: true fields: - name: "hdr" field-type: type-ref: name: "StateProofHeader" - name: "proof_body" field-type: enum: packed: true tag-ref: bit-and: left: right-shift: left: field-ref: path: ["hdr", "type_slot"] right: literal: u8: 62 right: literal: u8: 3 ``` ## How To Use These Examples * Start from the smallest example that matches your need. * Move to [ABI Authoring Guide](/docs/abi/authoring-guide/) to adapt it safely. * Move to [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) before you deploy anything. # Explorer Compatibility > Publish ABIs with the root types explorer reflection expects so instruction, account, and event decoding works on-chain. Use this page when an ABI works locally for analyze or codegen, but explorer reflection still fails or returns incomplete results. ## Why Explorer Compatibility Matters Explorer-side reflection uses the published ABI plus the configured root types to decide how to decode instruction, account, and event bytes. The reflector APIs used by explorer tooling expect: * `instruction-root` for instruction payloads * `account-root` for account data * `events` for emitted event payloads If those root types are missing, local authoring may still feel fine while explorer decoding fails later. ## Required Metadata Set `abi.options.program-metadata.root-types` on the published ABI. ```yaml options: program-metadata: root-types: instruction-root: "CounterInstruction" account-root: "CounterAccount" errors: "CounterError" events: "CounterEvent" ``` ## What Each Root Type Does | Root type | What explorer uses it for | What happens if it is missing | | ------------------ | ------------------------------------ | ----------------------------- | | `instruction-root` | Decode transaction instruction bytes | Instruction reflection fails | | `account-root` | Decode account data views | Account reflection fails | | `events` | Decode emitted events | Event reflection fails | ## Prefer A Single Discriminated Instruction Root Explorer instruction reflection works best when the ABI exposes one discriminated instruction envelope such as `CounterInstruction`, instead of only a set of unrelated per-instruction structs. That root type should: * include a tag or discriminator field * point the payload enum at the correct tag field * contain every published instruction variant ## Minimal Canonical Example ```yaml abi: package: thru.example.counter name: "Counter Program" abi-version: 1 package-version: 1.0.0 description: Minimal explorer-compatible counter ABI imports: [] options: program-metadata: root-types: instruction-root: "CounterInstruction" account-root: "CounterAccount" errors: "CounterError" events: "CounterEvent" types: - name: InitializeArgs kind: struct: packed: true fields: - name: seed field-type: primitive: u64 - name: IncrementArgs kind: struct: packed: true fields: - name: amount field-type: primitive: u64 - name: CounterInstruction kind: struct: packed: true fields: - name: tag field-type: primitive: u8 - name: payload field-type: enum: packed: true tag-ref: field-ref: path: - tag variants: - name: initialize tag-value: 0 variant-type: type-ref: name: InitializeArgs - name: increment tag-value: 1 variant-type: type-ref: name: IncrementArgs - name: CounterAccount kind: struct: packed: true fields: - name: value field-type: primitive: u64 - name: CounterEvent kind: struct: packed: true fields: - name: value field-type: primitive: u64 - name: CounterError kind: enum: packed: true variants: - name: overflow tag-value: 0 ``` ## Publishing Checklist 1. add `program-metadata.root-types` 2. make the instruction root a single discriminated envelope 3. validate locally with [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) 4. publish the ABI with [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) 5. inspect the published artifact with [Explorer MCP](/docs/api-ref/explorer-mcp/overview/) if explorer reflection still looks wrong ## Open Next * [Examples](/docs/abi/examples/) for the same pattern alongside other public ABI excerpts * [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) for the publish flow * [ABI Account](/docs/cli-reference/abi-account/) for the exact CLI syntax # Overview > Choose the right workflow for authoring, validating, deploying, and debugging ABI-driven program development. Use this section when you are writing ABI YAML by hand, validating that it round-trips correctly, and deploying it alongside a program. Caution Thru ABIs are handwritten today. They do not automatically stay in sync with the program the way macro-generated IDLs do in some other ecosystems, so agent workflows should always validate the ABI explicitly before publishing it. ## Start Here [Authoring Guide ](/docs/abi/authoring-guide/)Write ABI YAML with the right metadata, imports, dynamic fields, and practical guardrails for handwritten schemas. [Examples ](/docs/abi/examples/)Start from real ABI snippets for primitives, token instructions, flattened program ABIs, and advanced proof structures. [Explorer Compatibility ](/docs/abi/explorer-compatibility/)Publish root types that let explorer tooling reflect instruction, account, and event bytes correctly from the on-chain ABI. [Validation and roundtrip testing ](/docs/abi/validation-and-roundtrip-testing/)Use analyze, codegen, reflect, and real payload roundtrips to prove that the ABI actually works before deployment. [Publishing and Iteration ](/docs/abi/deployment-and-program-testing/)Publish the ABI with the program seed, verify the on-chain artifact, and iterate safely when the schema changes. [Specification ](/docs/abi/specification/)Learn the ABI model: deterministic layout, little-endian encoding, import rules, dynamic parameters, and flexible array members. ## Choose The Right Page | If your task is… | Open this page | Common CLI follow-up | | ---------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | Start from real ABI shapes | [Examples](/docs/abi/examples/) | [ABI Codegen](/docs/cli-reference/abi-codegen/) | | Make explorer reflection work with the published ABI | [Explorer Compatibility](/docs/abi/explorer-compatibility/) | [ABI Account](/docs/cli-reference/abi-account/) or [Explorer MCP](/docs/api-ref/explorer-mcp/overview/) | | Write or refactor ABI YAML safely | [Authoring Guide](/docs/abi/authoring-guide/) | [ABI Analyze](/docs/cli-reference/abi-analyze/) | | Prove the ABI actually works | [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) | [ABI Analyze](/docs/cli-reference/abi-analyze/) or [ABI Reflect](/docs/cli-reference/abi-reflect/) | | Publish or upgrade the ABI artifact on-chain | [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) | [ABI Account](/docs/cli-reference/abi-account/) | | Reason about layout and binary rules | [Specification](/docs/abi/specification/) | [ABI Analyze](/docs/cli-reference/abi-analyze/) | ## Progressive Workflow 1. Start with [Examples](/docs/abi/examples/) or the [Authoring Guide](/docs/abi/authoring-guide/) so you do not invent schema shapes from scratch. 2. Use [Explorer Compatibility](/docs/abi/explorer-compatibility/) if the ABI must be reflected correctly from the published on-chain artifact. 3. Use [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) to run the local proof loop before deployment. 4. Move to [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) only once the ABI roundtrips correctly. 5. Open [Specification](/docs/abi/specification/) only when you need deeper rules about layout, imports, or dynamic parameters. Tip The CLI reference is the source of truth for exact flags and command shapes. This section focuses on the workflow and authoring model so agents do not have to load every CLI detail too early. # Specification > Use a high-signal summary of the ABI type system, layout rules, import model, and dynamic parameter semantics. Use this page when you need the model behind ABI YAML before you start debugging layout behavior or designing advanced dynamic types. Note This page is intentionally shorter than the internal ABI design material. Treat the ABI tooling itself, especially `analyze`, `reflect`, and `codegen`, as the current source of truth when command behavior and prose ever diverge. ## Design Goals Deterministic ABI layouts are byte-for-byte reproducible so every backend sees the same structure. Zero-Copy Friendly The format is designed so generated code can access buffers directly instead of copying data out first. Cross-Language The same ABI definition can drive C, Rust, TypeScript, and reflection tooling. ## Core Rules | Rule | What to keep in mind | | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Endianness | Multi-byte values are little-endian. | | Primitive types | The core type system includes signed and unsigned integers, floating-point types, and `char`. | | Struct layout | Packed structs have no padding. Aligned structs insert padding based on natural alignment. | | Arrays | Arrays can be fixed-size or runtime-sized through field references. | | Flexible Array Members | Once the first runtime-sized field appears, offsets for later fields become runtime-dependent. | | Enums and unions | Variant layout and discriminator rules must still yield deterministic validation behavior. | | Dynamic parameters | Runtime-sized fields, tags, and payload sizes are tracked explicitly so validators, reflection, and codegen can agree on the same layout math. | ## What ABI YAML Represents At a high level, an ABI file contains: * package metadata such as package name and version * optional imports * named type definitions The type system supports: * primitive fields * structs * unions * enums * fixed-size arrays * variable-size arrays whose size is driven by earlier fields * references to imported or previously defined types ## Imports The ABI toolchain supports multiple import sources: * local path imports * git imports * HTTP imports * on-chain imports That flexibility is useful, but it changes the safe publishing workflow: * local imports are fine while you are writing and testing * on-chain publishing requires those local imports to be inlined or otherwise normalized first The important authoring rule is that remote files cannot depend on local filesystem paths. Use [Authoring Guide](/docs/abi/authoring-guide/) for how to structure imports and [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) for the publish-safe workflow. ## Dynamic Layouts The hardest ABI cases are the ones with runtime-sized fields. Common examples: * a `name_len` field followed by a byte array of that size * nested field references such as `box.first` * arrays whose element counts depend on earlier fields * expressions over nested array elements such as state proof bitsets Those layouts are valid, but they are also where mistakes tend to surface first. Prefer to validate them with: * [ABI Analyze](/docs/cli-reference/abi-analyze/) for type resolution and layout inspection * [ABI Reflect](/docs/cli-reference/abi-reflect/) for real payload decoding ## Practical Interpretation * Put the field that drives a variable-length array before the variable field itself. * Expect fully flattened or publish-ready artifacts to differ from your authoring-time source if you use local imports. * Treat `analyze --print-ir` as the fastest way to inspect what the toolchain thinks the layout really is. ## Use This Spec Efficiently * Load this page when you need the underlying rules. * Move to [Examples](/docs/abi/examples/) or [Authoring Guide](/docs/abi/authoring-guide/) once you are ready to author YAML. * Move to [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) when you need CLI-backed validation rather than more specification detail. # Validation and roundtrip testing > Validate ABI YAML locally by analyzing it, generating code, building real payloads, and reflecting them back. Use this page when the ABI file exists and you need to prove it actually matches the bytes your program expects. ## The Core Loop The highest-value local loop is: ```text write ABI -> analyze -> codegen TypeScript -> build instruction data -> reflect it back ``` That loop catches the most common handwritten-ABI failures before you deploy anything. ## Recommended Workflow 1. **Analyze the schema** Run [ABI Analyze](/docs/cli-reference/abi-analyze/) first to resolve imports, validate layout rules, and inspect IR if needed. 2. **Generate TypeScript** Run [ABI Codegen](/docs/cli-reference/abi-codegen/) and use the generated TypeScript types/helpers to build real instruction data. 3. **Reflect the bytes back** Use [ABI Reflect](/docs/cli-reference/abi-reflect/) on the resulting binary payload. If the reflected values do not match what you intended to send, the ABI is not ready yet. 4. **Only then move to deployment** Once the payload roundtrip is correct, move to [Publishing and Iteration](/docs/abi/deployment-and-program-testing/). ## Why This Matters ABIs are handwritten, so “looks right” is not good enough. The roundtrip loop proves: * the schema resolves cleanly * codegen can consume it * the produced bytes match the schema when decoded back out ## Useful Command Split | Goal | Best tool | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Resolve imports and type graph | [ABI Analyze](/docs/cli-reference/abi-analyze/) | | Inspect layout IR or helper previews | [ABI Analyze](/docs/cli-reference/abi-analyze/) with `--print-ir`, `--print-footprint`, or `--print-validate` | | Generate TS for payload building | [ABI Codegen](/docs/cli-reference/abi-codegen/) | | Decode or validate a captured payload | [ABI Reflect](/docs/cli-reference/abi-reflect/) | | Normalize imports before publish or browser use | [ABI Flatten](/docs/cli-reference/abi-flatten/), [ABI Prep for Publish](/docs/cli-reference/abi-prep-for-publish/), or [ABI Bundle](/docs/cli-reference/abi-bundle/) | ## Tooling Reality Today A practical testing stack today often looks like: * use ABI codegen to generate TypeScript * use `@thru/sdk` to submit the transaction * use ABI reflection tooling or the explorer to decode instruction, account, or transaction data afterward That reflection step is still somewhat fragmented. In some flows, CLI or explorer tooling is the cleanest way to decode payloads even if you used `@thru/sdk` to send the transaction. ## Common Gotchas * A schema can pass `analyze` and still be wrong for the actual bytes you are building. * Generated code can expose awkward package or type structure that is hard to notice from YAML alone. * Dynamic layouts need special attention; always prefer a real reflection roundtrip over eyeballing the schema. * If context is tight, `analyze` plus `reflect --validate-only` is the fastest high-signal check. # Overview > Use the Explorer MCP server to let AI agents inspect blocks, transactions, accounts, and on-chain ABIs. Use the Explorer MCP server when an agent needs live chain context from the explorer without scraping pages or guessing from stale context. ## Use This When * you want an agent to inspect a block, transaction, account, or program ABI * you want to debug deployed behavior against a specific network * you want recent chain activity or account transaction history * you want a tool-friendly way to search unknown `ta...`, `ts...`, or slot values Choose another interface when: * you need to send transactions or mutate chain state: use [CLI Reference](/docs/cli-reference/overview/) or the relevant SDK * you need a typed application integration: use the [gRPC API](/docs/api-ref/grpc/overview/) or the appropriate SDK * you are still deciding which docs to read: start with [Build with LLMs](/docs/getting-started/build-with-an-llm/) ## Installation Use the public Explorer MCP endpoint: ```text https://scan.thru.org/api/mcp ``` ### Claude Code ```bash claude mcp add --transport http thru-explorer https://scan.thru.org/api/mcp claude mcp list ``` ### Codex ```bash codex mcp add thruExplorer --url https://scan.thru.org/api/mcp codex mcp list ``` ## What It Exposes The explorer app hosts an MCP server at `/api/mcp` and exposes these tools: On the public explorer, the MCP endpoint is: ```text https://scan.thru.org/api/mcp ``` * `get_block` * `get_transaction` * `get_account` * `list_account_transactions` * `list_recent_blocks` * `list_recent_transactions` * `search` * `get_program_abi` Every tool ultimately calls the explorer API with `format=toon`, which means the returned content is optimized for LLM consumption instead of browser rendering. ## RPC Override The MCP endpoint accepts an optional `?rpc=` query parameter. Use that when: * you are debugging a non-default network * you want the same agent workflow against devnet, staging, or another explorer-compatible RPC endpoint If the `rpc` value is missing or invalid, the explorer falls back to its default network. ## Start Here [Tools Reference ](/docs/api-ref/explorer-mcp/tools-reference/)See each Explorer MCP tool, its arguments, and when to use it. [Build with LLMs ](/docs/getting-started/build-with-an-llm/)Learn the recommended doc traversal and guardrails for agent-driven Thru development. # Tools Reference > Reference for the Explorer MCP tools exposed by the Thru explorer. Use this page when you already know you want Explorer MCP and need the exact tool to call. ## Tool Map | Tool | Inputs | Best for | | --------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `get_block` | `slot` | Inspecting a specific block, its producer, timestamps, and transaction rows. | | `get_transaction` | `signature` | Debugging a single transaction, including accounts, instructions, and events. | | `get_account` | `address` | Looking up account metadata, balance, owner, and flags. | | `list_account_transactions` | `address`, optional `pageSize`, optional `pageToken` | Walking an account’s recent history without fetching each tx manually. | | `list_recent_blocks` | optional `limit` | Recent network activity and slot-level monitoring. | | `list_recent_transactions` | optional `limit` | Recent chain-wide transaction activity. | | `search` | `query` | Resolving whether an unknown value is a slot, transaction signature, or account address. | | `get_program_abi` | `program` | Fetching the on-chain ABI that explorer tooling uses for reflection. | ## Shared Input Notes * Account addresses should use `thrufmt` addresses like `ta...` * Transaction signatures should use `thrufmt` signatures like `ts...` * Block slots are non-negative integers * `list_recent_blocks` and `list_recent_transactions` accept `limit` values from `1` to `50` * `list_account_transactions` accepts `pageSize` values from `1` to `100` ## Returned Shape Every tool returns explorer output in an LLM-oriented text format generated from the same underlying API response model. In practice, that means: * you get a natural-language summary * you get the structured data represented in a tool-friendly text format * the response is better for agent reasoning than HTML scraping ## Tool Details ### `get_block` Use when you already know the slot and want block-level context. ```json { "slot": 12345 } ``` Best follow-up tools: * `get_transaction` for a specific signature found in the block * `list_recent_blocks` if you were only trying to inspect the latest chain activity ### `get_transaction` Use when you want the full execution picture for a known transaction signature. ```json { "signature": "ts..." } ``` This is usually the best tool for: * checking execution status * inspecting read/write accounts * inspecting instruction bytes * inspecting emitted events ### `get_account` Use when you need current account metadata by address. ```json { "address": "ta..." } ``` This is the fastest way to inspect: * owner * balance * flags like program / compressed / ephemeral * data size and sequence metadata ### `list_account_transactions` Use when one account is the center of the investigation and you need transaction history. ```json { "address": "ta...", "pageSize": 25 } ``` Use `pageToken` from a previous response when you need to continue pagination. ### `list_recent_blocks` Use when you need a quick view of recent slots. ```json { "limit": 10 } ``` ### `list_recent_transactions` Use when you want a chain-wide recent transaction feed. ```json { "limit": 10 } ``` ### `search` Use when you do not yet know whether a value is a slot, signature, or address. ```json { "query": "ta..." } ``` This is often the best first tool in an agent workflow because it routes ambiguous identifiers into the correct explorer entity. ### `get_program_abi` Use when you need the ABI currently published for a deployed program. ```json { "program": "ta..." } ``` This is especially useful when: * verifying that the deployed ABI matches the expected program seed * debugging reflection mismatches * inspecting event and instruction definitions without leaving the agent workflow ## Recommended Usage Pattern If the task is deployed-program debugging: 1. `search` if the identifier is ambiguous 2. `get_transaction` for a failing or surprising tx 3. `get_program_abi` if the payload may be reflected against the wrong ABI 4. `get_account` or `list_account_transactions` for account-level follow-up If the task is recent network inspection: 1. `list_recent_blocks` or `list_recent_transactions` 2. `get_block` or `get_transaction` for the specific item worth inspecting # APIs, SDKs, and CLIs Overview > Browse Thru's RPC APIs, SDK packages, and command-line tooling. Use this section to find the right interface for your integration, whether you are calling Thru over RPC, working with one of the SDK packages, or using `thru`. ## SDKs For package-level integration guidance, start with the SDK resource index. [SDK Packages ](/docs/program-development/developer-resources/)Browse Thru packages for TypeScript, browser, React, Rust, and CLI-based workflows. ## CLI For command-line workflows such as configuring RPC endpoints, deploying programs, and managing on-chain resources, use the CLI reference. [CLI Reference ](/docs/cli-reference/overview/)Find command docs for network configuration, tokens, name service, registrars, and ABI tooling. ## Explorer MCP For agent-driven workflows that need live chain context from the explorer, use the Explorer MCP server. [Explorer MCP ](/docs/api-ref/explorer-mcp/overview/)Let coding agents inspect blocks, transactions, accounts, recent activity, search results, and on-chain ABIs through MCP tools. ## gRPC For high-performance applications requiring efficient binary serialization and streaming capabilities, gRPC provides the most powerful interface to the Thru blockchain. gRPC-Web is also available for browser-based applications. [gRPC API ](/docs/api-ref/grpc/overview/)High-performance Protocol Buffers-based API with support for streaming, bidirectional communication, and efficient binary serialization. Includes gRPC-Web for browsers. # Account > Create, upgrade, finalize, close, and inspect on-chain ABI accounts with thru abi account Use `thru abi account` when you are publishing ABI YAML to chain state or reading an existing ABI account back out. ## Use This When * you are attaching an ABI to a managed program you control * you are publishing a third-party or standalone ABI account * you need to inspect an ABI account address and optionally export its YAML contents Choose another ABI command when: * you want to generate client code locally: [Codegen](/docs/cli-reference/abi-codegen/) * you want to validate or inspect ABI types without touching chain state: [Analyze](/docs/cli-reference/abi-analyze/) * you want to decode a captured binary payload: [Reflect](/docs/cli-reference/abi-reflect/) ## Account Types Program Use for the official ABI attached to a managed program you control. Third-Party Use when you are publishing an ABI for someone else’s deployed program. Standalone Use for shared type packages or ABI files not tied to one program account. | Type | Use it for | Seed input | Signer flag | Extra requirement | | ------------- | -------------------------------------- | ------------------------- | ------------- | ------------------------------ | | `program` | ABI for a managed program you control. | The managed program seed. | `--authority` | None | | `third-party` | ABI for a program you do not control. | A 32-byte hex seed. | `--publisher` | `--target-program` is required | | `standalone` | ABI not tied to a specific program. | A standalone seed string. | `--publisher` | None | `--authority` is still accepted as a compatibility alias for non-`program` account types, but `--publisher` is the clearer choice for third-party and standalone flows. ## Shared Flags | Flag | Use it for | | --------------------------- | ---------------------------------------------------------------------------- | | \`—account-type program | third-party | | `—target-program
` | Required for `third-party` accounts. | | `--ephemeral` | Match ephemeral program mode when the ABI targets an ephemeral program. | | `—authority ` | Choose the configured authority key for `program` ABIs. | | `—publisher ` | Choose the configured publisher key for `third-party` and `standalone` ABIs. | | `—fee-payer ` | Override the configured fee payer for state-changing commands. | | `--include-data` | Include ABI YAML contents when using `get`. | | `—out ` | Write ABI YAML contents from `get` to a file. | ## Command Shapes | Command | Syntax | | ---------- | ------------------------------------------------------------------ | | `create` | `thru abi account create [FLAGS] ` | | `upgrade` | `thru abi account upgrade [FLAGS] ` | | `finalize` | `thru abi account finalize [FLAGS] ` | | `close` | `thru abi account close [FLAGS] ` | | `get` | `thru abi account get [—include-data] [—out ]` | ## Minimal Patterns * Managed program ABI ```bash thru abi account create my_program ./program.abi.yaml ``` * Third-party ABI ```bash thru abi account create \ --account-type third-party \ --target-program ta...$program_id \ --publisher default \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \ ./community.abi.yaml ``` * Standalone ABI ```bash thru abi account create \ --account-type standalone \ --publisher default \ shared_types \ ./types.abi.yaml ``` * Inspect and export an ABI ```bash thru abi account get --include-data --out ./downloaded.abi.yaml ta...$abi_account ``` ## Common Workflows ### Publish a managed program ABI ```bash thru program create my_program ./build/program.bin thru abi account create my_program ./program.abi.yaml thru abi account get --include-data ta...$abi_account ``` ### Upgrade before finalization ```bash thru abi account upgrade my_program ./program_v2.abi.yaml thru abi account get --include-data ta...$abi_account ``` ### Finalize once stable ```bash thru abi account finalize my_program ``` ## Common Failures * `third-party` accounts require `--target-program`. Without it, the command cannot derive the ABI account. * `upgrade` and `close` fail once an ABI account has been finalized. * The `seed` meaning changes with `--account-type`, so do not reuse a human-readable standalone seed where a third-party 32-byte hex seed is expected. * `get` takes an ABI account address, not the original program seed. Tip Use `thru --json abi account get ...` when another tool or agent needs the result in a machine-readable form. # Analyze > Inspect resolved ABI types, shared layout IR, and helper previews with thru abi analyze Use `thru abi analyze` when you need to understand how ABI types resolve before you generate code, publish an ABI, or debug a reflection failure. ## Use This When * you want to confirm imports and type resolution succeed * you need the shared layout IR in JSON or protobuf form * you want to preview generated footprint or validate helpers for one type Choose another ABI command when: * you want generated source code: [Codegen](/docs/cli-reference/abi-codegen/) * you want to decode real bytes: [Reflect](/docs/cli-reference/abi-reflect/) * you want to prepare a file for publishing: [Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) ## Syntax ```bash thru abi analyze \ --files ... \ [--include-dir ...] \ [--print-ir] \ [--ir-format json|protobuf] \ [--print-footprint ] \ [--print-validate ] ``` ## Important Flags | Flag | Use it for | | ------------------------- | ------------------------------------------------------------------- | | `--print-ir` | Print the shared layout IR after analysis. | | \`—ir-format json | protobuf\` | | `—print-footprint ` | Preview the generated legacy and IR footprint helpers for one type. | | `—print-validate ` | Preview the generated legacy and IR validate helpers for one type. | ## What It Prints Even without optional flags, `analyze` prints: * loaded files and resolved packages * discovered type definitions * detailed type analysis from the resolved graph Optional flags add focused output on top of that base analysis. ## Minimal Patterns * Load and analyze an ABI set ```bash thru abi analyze \ --files ./program.abi.yaml \ --include-dir ./abi ``` * Print shared layout IR as JSON ```bash thru abi analyze \ --files ./program.abi.yaml \ --print-ir ``` * Preview helpers for one type ```bash thru abi analyze \ --files ./program.abi.yaml \ --print-footprint TransferArgs \ --print-validate TransferArgs ``` ## Notes * `analyze` is a good first stop when codegen or reflection fails because it exercises the same import and type-resolution path. * `--print-ir` is the most useful flag when you need to compare multiple ABIs or inspect cross-language layout behavior. * If you only care whether an input file is ready for on-chain publishing, use [Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) instead of loading the whole analysis output into context. # Bundle > Create a JSON ABI dependency manifest for tooling with thru abi bundle Use `thru abi bundle` when you want a machine-readable dependency manifest rather than a flattened YAML file. ## Use This When * you are preparing ABI dependencies for WASM-backed or browser tooling * you want resolved package content in one JSON artifact * you need both local and remote imports resolved before another tool consumes them Choose another ABI command when: * you want a YAML output artifact: [Flatten](/docs/cli-reference/abi-flatten/) or [Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) * you want to inspect one binary payload: [Reflect](/docs/cli-reference/abi-reflect/) ## Syntax ```bash thru abi bundle \ --file \ --output \ [--include-dir ...] \ [--verbose] ``` ## Output `bundle` resolves dependencies and writes a pretty-printed JSON manifest that maps packages to resolved ABI YAML content. ## Minimal Pattern ```bash thru abi bundle \ --file ./program.abi.yaml \ --include-dir ./abi \ --output ./dist/program.manifest.json \ --verbose ``` ## Notes * `bundle` is the ABI command most clearly aimed at downstream tooling rather than human review. * Verbose mode prints the resolved package count and package names before writing the manifest. * If your task only needs a local, publish-ready YAML file, `bundle` is usually more output than you need. # Codegen > Generate C, Rust, or TypeScript code from ABI YAML with thru abi codegen Use `thru abi codegen` when you already have ABI YAML and want generated client-side or runtime-facing code, not an on-chain ABI account. ## Use This When * you want generated C headers and helpers from ABI type definitions * you want Rust or TypeScript types organized by package * you have one or more ABI files plus imported dependencies on disk Choose another ABI command when: * you want to inspect type resolution or IR before generating code: [Analyze](/docs/cli-reference/abi-analyze/) * you want to publish an ABI on-chain: [Account](/docs/cli-reference/abi-account/) * you need a flattened or publish-ready YAML artifact instead of generated source: [Flatten](/docs/cli-reference/abi-flatten/) or [Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) ## Syntax ```bash thru abi codegen \ --files ... \ --language c|rust|typescript \ [--include-dir ...] \ [--output ] \ [--verbose] ``` ## Required Inputs | Input | What it does | | --------------------- | ------------------------------------------------------------- | | `—files …` | One or more root ABI YAML files to load. | | \`—language c | rust | | `—include-dir …` | Adds search roots for imported ABI files. | | `—output ` | Output directory for generated code. Defaults to `generated`. | ## Output Shape The generator resolves imports, groups types by package, and writes package directories underneath the output directory. C Emits package directories containing `types.h` and `functions.c`. Rust Emits package directories with `types.rs`, helper modules, and `mod.rs` scaffolding. TypeScript Emits package directories with `types.ts`. | Language | Typical output | | ------------ | ------------------------------------------------------------------------------ | | `c` | Package directories with `types.h` and `functions.c`. | | `rust` | Package directories with `types.rs`, helper modules, and `mod.rs` scaffolding. | | `typescript` | Package directories with `types.ts`. | ## Minimal Patterns * C output ```bash thru abi codegen \ --files ./program.abi.yaml \ --language c \ --output ./generated/c ``` * Rust output with imports ```bash thru abi codegen \ --files ./program.abi.yaml \ --include-dir ./abi \ --language rust \ --output ./generated/rust ``` * TypeScript output ```bash thru abi codegen \ --files ./program.abi.yaml ./shared.abi.yaml \ --include-dir ./abi \ --language typescript \ --output ./generated/ts ``` ## Notes * `--files` can be repeated so one run can generate code for multiple root packages. * The command creates the output directory if it does not already exist. * `--verbose` is useful when import resolution is the risky part of the task because it prints loaded files, packages, and output locations. * Use [Analyze](/docs/cli-reference/abi-analyze/) first if you want to inspect layout or validation helpers before you commit to generated output. # Overview > Choose the right thru abi command for ABI account management, analysis, reflection, and code generation Use `thru abi` when your task is about ABI YAML files or ABI accounts, not program binaries. Tip **Transaction Fees**: Currently all transaction fees are set to 0. Transaction fees will be implemented and charged in future releases. ## Use This When * you want to create, upgrade, finalize, close, or inspect an on-chain ABI account * you want to generate C, Rust, or TypeScript code from ABI YAML * you want to analyze ABI types, print layout IR, or preview generated helpers * you want to decode binary payloads against an ABI type * you need to flatten imports, prepare an ABI for publishing, or bundle dependencies for WASM-style consumers Choose another family when: * you are deploying or upgrading binaries: [Program](/docs/cli-reference/program-commands/) * you are working with raw upload buffers: [Uploader](/docs/cli-reference/uploader-commands/) * you only need chain reads or transaction inspection: [RPC](/docs/cli-reference/rpc-commands/) or [Transaction](/docs/cli-reference/transaction-commands/) ## Start Here [Account ](/docs/cli-reference/abi-account/)Create, upgrade, finalize, close, and inspect on-chain ABI accounts. [Codegen ](/docs/cli-reference/abi-codegen/)Generate C, Rust, or TypeScript code from ABI type definitions. [Analyze ](/docs/cli-reference/abi-analyze/)Inspect resolved ABI types, print shared layout IR, and preview helper output. [Reflect ](/docs/cli-reference/abi-reflect/)Decode or validate binary data against an ABI type and print JSON results. [Flatten ](/docs/cli-reference/abi-flatten/)Resolve imports into a single ABI YAML file for review or downstream tooling. [Prep for Publish ](/docs/cli-reference/abi-prep-for-publish/)Inline local imports and validate the file before you publish an ABI on-chain. [Bundle ](/docs/cli-reference/abi-bundle/)Resolve dependencies into a JSON manifest for WASM-backed or browser tooling. ## Command Map | Command | Use it for | Reads | Writes | | ------------------------------- | ------------------------------------------------- | --------------------------------------------- | -------------------------------------------------- | | `thru abi account ...` | Manage on-chain ABI accounts. | CLI config, ABI YAML, or ABI account address. | Chain state and optional local YAML export. | | `thru abi codegen ...` | Generate typed client code from ABI YAML. | ABI YAML files and imported dependencies. | A generated source tree under an output directory. | | `thru abi analyze ...` | Inspect resolved types and preview helper output. | ABI YAML files and imported dependencies. | stdout only. | | `thru abi reflect ...` | Decode or validate binary payloads. | ABI YAML files plus a binary data file. | stdout JSON only. | | `thru abi flatten ...` | Collapse imports into one YAML file. | ABI YAML files and imported dependencies. | A flattened YAML file. | | `thru abi prep-for-publish ...` | Produce a publishable ABI YAML file. | ABI YAML files and local imports. | A publish-ready YAML file. | | `thru abi bundle ...` | Build a dependency manifest for tooling. | ABI YAML files plus resolved imports. | A JSON manifest file. | ## Minimal Patterns ```bash thru abi account create my_program ./program.abi.yaml thru abi codegen --files ./program.abi.yaml --language typescript --output ./generated thru abi reflect --abi-file ./program.abi.yaml --type-name TransferArgs --data-file ./payload.bin --pretty ``` ## Notes * The `account` subcommands are the only ABI commands here that mutate chain state. * The tooling commands are local file workflows, so they are safe to use before you publish anything on-chain. * The examples here use the `thru abi ...` form. The same tooling surface is also exposed by the standalone `abi` binary that lands with the same implementation. # Flatten > Resolve ABI imports into a single YAML file with thru abi flatten Use `thru abi flatten` when you want one self-contained ABI YAML file for review, diffing, or downstream tooling. ## Use This When * you want to remove the need for separate local include directories * you need a single-file ABI artifact before another tooling step * you want a reviewable snapshot of a multi-file ABI package Choose another ABI command when: * you want an on-chain publishing artifact that enforces network rules: [Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) * you want a JSON dependency manifest instead of YAML: [Bundle](/docs/cli-reference/abi-bundle/) ## Syntax ```bash thru abi flatten \ --file \ --output \ [--include-dir ...] \ [--verbose] ``` ## What It Does * loads the root ABI file * resolves imports using the supplied include directories * writes one flattened YAML file to `--output` ## Minimal Pattern ```bash thru abi flatten \ --file ./program.abi.yaml \ --include-dir ./abi \ --output ./dist/program.flat.abi.yaml \ --verbose ``` ## Notes * `flatten` is local-file oriented. It does not publish anything on-chain. * `--verbose` is useful when you want to confirm which include directories were actually used. * If the next step is publishing, move to [Prep for Publish](/docs/cli-reference/abi-prep-for-publish/) instead of stopping at a flattened file. # Prep for Publish > Inline local imports and validate publish rules with thru abi prep-for-publish Use `thru abi prep-for-publish` when you need a publishable ABI YAML artifact before `thru abi account create` or `upgrade`. ## Use This When * you have local path imports that need to be inlined before publishing * you want to enforce that remaining imports are valid on-chain imports for one network * you need a stable artifact to review before pushing ABI contents on-chain Choose another ABI command when: * you only need one flattened YAML file without publish rules: [Flatten](/docs/cli-reference/abi-flatten/) * you want to publish immediately: [Account](/docs/cli-reference/abi-account/) ## Syntax ```bash thru abi prep-for-publish \ --file \ --target-network \ --output \ [--include-dir ...] \ [--verbose] ``` ## Publish Rules | Rule | Result | | -------------------------------------- | ----------------------------- | | Local path imports | inlined into the output file. | | On-chain imports on the target network | Kept in the output file. | | On-chain imports on another network | Command fails. | | `git` imports | Command fails. | | `http` imports | Command fails. | ## Minimal Pattern ```bash thru abi prep-for-publish \ --file ./program.abi.yaml \ --include-dir ./abi \ --target-network testnet \ --output ./dist/program.publish.abi.yaml \ --verbose ``` ## Notes * This command is the safest handoff point before `abi account create` because it strips out local-only packaging assumptions. * The `thru` command uses `--target-network`. The standalone `abi` binary uses the same workflow with a shorter `--network` flag. * Verbose mode prints which local imports were inlined and how many packages were resolved. # Reflect > Decode or validate binary payloads against ABI types with thru abi reflect Use `thru abi reflect` when you have binary data plus ABI type definitions and need a JSON view of the decoded structure. ## Use This When * you captured instruction, account, or event bytes and want to decode them * you only need to validate that a buffer matches an ABI type * you want byte offsets or extracted values for debugging tools Choose another ABI command when: * you want to inspect the ABI type graph itself: [Analyze](/docs/cli-reference/abi-analyze/) * you want generated client code: [Codegen](/docs/cli-reference/abi-codegen/) * you want a dependency manifest for runtime tooling: [Bundle](/docs/cli-reference/abi-bundle/) ## Syntax ```bash thru abi reflect \ --abi-file ... \ --type-name \ --data-file \ [--include-dir ...] \ [--pretty] \ [--values-only | --validate-only | --include-byte-offsets] \ [--show-params] ``` ## Output Modes Decode JSON Default mode prints the full reflected structure with type information. Values Only Use `--values-only` when you only care about decoded values. Validate Only Use `--validate-only` to confirm the buffer matches the ABI without decoding it. Byte Offsets Use `--include-byte-offsets` when you need layout-aware debugging output. | Flag | Output | | ------------------------ | -------------------------------------------------------- | | none | Full reflected JSON with type information. | | `--values-only` | JSON values only, without type metadata. | | `--validate-only` | Validation success plus bytes consumed. No decoded JSON. | | `--include-byte-offsets` | Reflected JSON annotated with byte offsets. | `--values-only`, `--validate-only`, and `--include-byte-offsets` are mutually exclusive. ## Important Flags | Flag | Use it for | | --------------------- | ------------------------------------------------------------------------- | | `—abi-file …` | Load one or more ABI YAML files. | | `—include-dir …` | Resolve imported ABI files. | | `—type-name ` | Choose the concrete type to decode. | | `—data-file ` | Binary payload to parse. | | `--show-params` | Print dynamic parameters inferred from the buffer before the main result. | | `--pretty` | Pretty-print JSON output for human inspection. | ## Minimal Patterns * Decode a payload ```bash thru abi reflect \ --abi-file ./program.abi.yaml \ --type-name TransferArgs \ --data-file ./payload.bin \ --pretty ``` * Validate only ```bash thru abi reflect \ --abi-file ./program.abi.yaml \ --type-name TransferArgs \ --data-file ./payload.bin \ --validate-only ``` * Include byte offsets ```bash thru abi reflect \ --abi-file ./program.abi.yaml \ --type-name TransferArgs \ --data-file ./payload.bin \ --include-byte-offsets \ --pretty ``` ## Common Failures * The command resolves types before it reflects data, so missing imports and invalid type names fail early. * `--show-params` can be combined with any output mode, but it prints an extra prelude before the main result. * `--validate-only` is the best choice when an agent only needs to answer “does this buffer match the ABI?” without spending context on the full decoded JSON. # Account > Create accounts, inspect activity, and work with account compression flows Use `thru account` when the task is centered on account lifecycle rather than generic reads or token-specific actions. ## Commands | Command | Use it for | | ----------------------------------------- | --------------------------------------------------------- | | `create [key_name]` | Create a new account with fee payer proof. | | `info [key_name]` | Alias-style account inspection. | | `transactions [account]` | List transactions involving an account. | | `compress [fee_payer]` | Compress an account. | | `decompress [fee_payer]` | Decompress an account. | | `prepare-decompression ` | Fetch account data and proof for decompression workflows. | ## Minimal Pattern ```bash thru account info default thru account transactions default --page-size 20 ``` ## Notes * Use `getaccountinfo` for the simplest top-level read; use `account` when you need the lifecycle helpers grouped together. * Compression and decompression are stateful operations; read the account state first if you are unsure which mode it is already in. # Debug > Re-execute transactions and resolve DWARF-backed traces with thru debug Use `thru debug` when the task is diagnosis rather than normal transaction submission. ## Commands | Command | Use it for | | ----------------------------------------------------------- | ---------------------------------------------------- | | `re-execute ` | Re-run a transaction in a simulated environment. | | `resolve —elf (—response or —signature )` | Resolve DWARF debug info into a source-level report. | ## High-Signal Flags | Flag | Applies to | Why it matters | | ---------------------------- | ------------ | ---------------------------------------------------------- | | `--include-state-before` | `re-execute` | Include account state snapshots before execution. | | `--include-state-after` | `re-execute` | Include account state snapshots after execution. | | `--include-account-data` | `re-execute` | Include full account data in snapshots. | | `—output-trace ` | `re-execute` | Save VM execution trace to a file. | | `--include-memory-dump` | `re-execute` | Include stack and heap dump data. | | `—elf ` | `resolve` | Point to a program ELF with DWARF info. | | `--response` / `--signature` | `resolve` | Choose either a saved response or a live signature lookup. | ## Minimal Patterns ```bash thru debug re-execute ts...[signature] --include-state-after thru debug resolve --elf ./build/thruvm/bin/my_program_c.elf --signature ts...[signature] ``` ## Notes * Use `resolve` only when you have a debug-built ELF with DWARF info available. * This family is the right place to go after a failing transaction when plain CLI errors are not enough. # dev > Install toolchains and SDKs, inspect install paths, and scaffold projects with thru dev Use `thru dev` for developer-environment setup rather than on-chain program interaction. ## Command Areas | Area | Use it for | | ----------- | --------------------------------------------------------------------- | | `toolchain` | Install, update, uninstall, or inspect the Thru toolchain path. | | `sdk` | Install, update, uninstall, or inspect SDK install paths by language. | | `init` | Scaffold new C, C++, or Rust projects from bundled templates. | ## Toolchain Commands | Command | Use it for | | --------------------- | ---------------------------------------------------------- | | `toolchain install` | Install the toolchain, optionally pinning version or repo. | | `toolchain update` | Update the installed toolchain. | | `toolchain uninstall` | Remove the installed toolchain. | | `toolchain path` | Print the resolved toolchain path. | ## SDK Commands | Command | Use it for | | -------------------------- | ----------------------------------------- | | `sdk install ` | Install an SDK for `c`, `cpp`, or `rust`. | | `sdk update ` | Update an installed SDK. | | `sdk uninstall ` | Remove an installed SDK. | | `sdk path ` | Print the resolved SDK install path. | ## Init Commands | Command | Use it for | | -------------------------- | ---------------------------- | | `init c ` | Scaffold a new C project. | | `init cpp ` | Scaffold a new C++ project. | | `init rust ` | Scaffold a new Rust project. | ## Minimal Patterns ```bash thru dev toolchain install thru dev sdk install c thru dev init c hello_thru ``` ## Notes * This is the first family to reach for when the task is local environment setup. * The clap definitions mark `init cpp` and `init rust` as “not yet implemented,” so agents should avoid assuming they are fully production-ready without checking runtime behavior. # Faucet > Deposit to or withdraw from the faucet program Use `thru faucet` when working with the faucet program’s deposit and withdrawal flow. This is also the simplest built-in way to fund an account for beta, test, or dev workflows when the account exists locally but cannot yet pay transaction fees. ## Commands | Command | Use it for | | ----------------------------- | ------------------------------- | | `deposit ` | Deposit funds into the faucet. | | `withdraw ` | Withdraw funds from the faucet. | ## Important Details * both commands accept `--fee-payer` * `withdraw` is capped at `10000` per transaction according to the clap definition ## Common Funding Pattern If your account exists locally but has no balance for writes, use `withdraw` to fund it from the faucet before retrying deployment or transaction submission. ```bash thru faucet withdraw default 1000 ``` You can also target a specific account name or raw Thru address: ```bash thru faucet withdraw my-key 1000 thru faucet withdraw ta... 1000 ``` ## Minimal Pattern ```bash thru faucet deposit default 1000 thru faucet withdraw default 500 ``` ## Notes * These commands operate on the faucet program, not arbitrary token mints. * The `account` argument can be a key name or a raw address, depending on the stored config and validation path. # Global Flags and configuration > Shared thru flags, config path rules, network resolution, and identifier conventions Use this page before deeper command reference pages when you need the shared rules that apply across the CLI. ## Global Flags The root `thru` parser defines these flags globally, so they apply to every command family: | Flag | What it does | Defined in | | ----------------- | --------------------------------------------------------------------------------- | ------------- | | `--json` | Emit machine-readable output instead of the default text output. | `Cli.json` | | `--quiet` | Suppress non-essential output, including interactive version check notifications. | `Cli.quiet` | | `—url ` | Override the RPC base URL for this one invocation. | `Cli.url` | | `—network ` | Use a named network profile from config for this one invocation. | `Cli.network` | ## Configuration File Location The CLI loads and saves its config at: ```text ~/.thru/cli/config.yaml ``` That config stores: * the default RPC base URL * saved keys * program IDs for built-in programs * named network profiles * the default named network * `dev` toolchain and SDK install metadata ## Network Resolution Order When a command needs an RPC target, the CLI resolves it in this order: 1. `--url` 2. `--network` 3. the configured `default_network` 4. the base `rpc_base_url` already stored in config Use `--url` when you want a one-off override. Use `--network` when you want to target a saved profile without changing the default. ## Output Modes For agent workflows, prefer `--json` whenever you expect to parse a result or branch on output. Use text output when: * you are reading an error interactively * the command is primarily instructional * the command prints human-oriented follow-up guidance ## Key Names vs Raw Addresses Identifier handling is not uniform across the CLI. Some commands accept: * key names from config such as `default` or `treasury` * raw Thru public addresses in `ta...` format * 32-byte hex public keys Other commands require raw public keys or addresses and do not resolve key names for all arguments. Practical rule: * if the page says “key name from config,” a config alias is valid * if the page says “public key” or “Thru address,” prefer a raw `ta...` address or hex pubkey * if a command takes `--fee-payer`, that flag usually resolves through configured key names ## Configuration-Backed Program IDs Several command families default to program IDs stored in config unless you override them with flags. Common examples: * `program` and `abi` use configured manager program IDs * `token` supports `--token-program` * `registrar` and `nameservice` use configured name service and registrar program IDs * `wthru` supports both `--program` and `--token-program` ## Notes * Load this page once, then move to a family page instead of repeating config rules in context. * Prefer `--json` for automation or follow-up tool use. * If a command can mutate config, state, or on-chain accounts, the family page should be treated as the source of truth for accepted identifiers and side effects. # Keys > Manage keys stored in thru config Use `thru keys` when you need to inspect or mutate the keys saved in CLI config. ## Commands | Command | Use it for | | ------------------ | -------------------------------- | | `list` | List saved key names. | | `add ` | Add a hex private key to config. | | `get ` | Print a saved key value. | | `generate ` | Generate a new random key. | | `rm ` | Remove a saved key. | ## Important Details * key names are normalized case-insensitively * `add` expects a 64-character hex private key * `generate` and `add` support `--overwrite` ## Minimal Pattern ```bash thru keys list thru keys generate alice thru keys get alice ``` ## Notes * Treat this family as local config mutation, not on-chain state mutation. * Many other command families rely on these key names for flags like `--fee-payer` and `--authority`. # Name Service > Manage base name service registrars, subdomains, and records with thru > For registry pricing and lease lifecycle (initialize registry, purchase, renew, claim expired), use the [`registrar` commands](/docs/cli-reference/registrar-commands/). ## Prerequisites * [CLI setup](/docs/program-development/setting-up-thru-devkit/) with keys and RPC endpoint configured * Name service program ID available (defaults to config value) * Fee payer funded for signer costs ## Command Overview [Init Root Registrar ](#init-root-registrar)Create a base registrar for a root name [Register Subdomain ](#register-subdomain)Add subdomains under a registrar or domain [Manage Records ](#manage-records)Append or delete key/value records on a domain [Unregister Subdomain ](#unregister-subdomain)Remove a subdomain under a registrar or domain [Resolve & List ](#resolve-and-list)Read domain metadata and records [Derive Addresses ](#derive-addresses)Calculate registrar, domain, lease, and config addresses ## Init Root Registrar Set up a base registrar on the name service program for a root (for example, `thru`). The registrar address can be derived automatically from the root name. ```bash thru nameservice init-root $root_name [OPTIONS] ``` **`root_name` · *string* · **required**** Root segment to manage (1-64 characters) ***string*** Override base name service program address ***string*** Explicit registrar account address; derived from the root name if omitted ***string*** Authority address for root management, formatted as a 46-char `ta...` address or 64-char hex pubkey. Key names are not accepted. Must match the fee payer address because only the fee payer signature is included. ***string*** Hex state proof for registrar account creation; auto-generated if omitted ***string*** Signer for the transaction **Example:** ```bash thru nameservice init-root thru ``` ## Register Subdomain Create a subdomain under a parent registrar or domain. The CLI can derive the domain account address from the parent and name. ```bash thru nameservice register-subdomain \ $domain_name \ $parent_account \ [OPTIONS] ``` **`domain_name` · *string* · **required**** Subdomain segment (1-64 characters) **`parent_account` · *string* · **required**** Registrar or parent domain address ***string*** Override base name service program address ***string*** Explicit domain account address; derived if omitted ***string*** Domain owner address, formatted as a 46-char `ta...` address or 64-char hex pubkey. Key names are not accepted. Must match the fee payer address because only the fee payer signature is included. ***string*** Authority address for subdomain registration, formatted as a 46-char `ta...` address or 64-char hex pubkey. Key names are not accepted. Must match the fee payer address because only the fee payer signature is included. ***string*** Hex state proof for domain creation; auto-generated if omitted ***string*** Signer for the transaction ## Manage Records Append or delete records on a domain. ```bash thru nameservice append-record \ $domain_account \ $key \ $value \ [--owner $owner] [--fee-payer $fee_payer] [--name-service-program $program] thru nameservice delete-record \ $domain_account \ $key \ [--owner $owner] [--fee-payer $fee_payer] [--name-service-program $program] ``` **`domain_account` · *string* · **required**** Domain account address **`key` · *string* · **required**** Record key (<= 32 bytes) **`value` · *string* · **required**** Record value (<= 256 bytes) for append-record ***string*** Owner address authorizing the record change, formatted as a 46-char `ta...` address or 64-char hex pubkey. Key names are not accepted. Must match the fee payer address because only the fee payer signature is included. ***string*** Signer for the transaction ***string*** Override base name service program address ## Unregister Subdomain Delete a subdomain under a registrar or domain. ```bash thru nameservice unregister-subdomain \ $domain_account \ [--owner $owner] [--fee-payer $fee_payer] [--name-service-program $program] ``` **`domain_account` · *string* · **required**** Domain account address to remove ***string*** Owner address authorizing unregister, formatted as a 46-char `ta...` address or 64-char hex pubkey. Key names are not accepted. Must match the fee payer address because only the fee payer signature is included. ***string*** Signer for the transaction ***string*** Override base name service program address ## Resolve and List Fetch domain metadata and records. ```bash thru nameservice resolve $domain_account [--key $key] [--name-service-program $program] thru nameservice list-records $domain_account [--name-service-program $program] ``` **`domain_account` · *string* · **required**** Domain account to inspect ***string*** Record key to fetch when resolving ***string*** Override base name service program address ## Derive Addresses Helpers to compute deterministic addresses. ```bash thru nameservice derive-domain-account $parent_account $domain_name [--name-service-program $program] thru nameservice derive-registrar-account $root_name [--name-service-program $program] thru nameservice derive-config-account [--thru-registrar-program $program] thru nameservice derive-lease-account $domain_name [--thru-registrar-program $program] ``` **`parent_account` · *string* · **required**** Registrar or parent domain address for domain derivation **`domain_name` · *string* · **required**** Domain segment (1-64 characters) **`root_name` · *string* · **required**** Root segment (for registrar derivation) ***string*** Override base name service program address for `derive-domain-account` and `derive-registrar-account` ***string*** Override thru registrar program address for `derive-config-account` and `derive-lease-account` ## Common Flow 1. Use `thru registrar initialize-registry` and `purchase-domain` to create a .thru lease (see [Registrar Commands](/docs/cli-reference/registrar-commands/)). 2. (Optional) Register subdomains with `thru nameservice register-subdomain`. 3. Manage and query records with `append-record`, `delete-record`, `resolve`, and `list-records`. # Network > Manage network profiles for connecting to different Thru RPC endpoints The `thru network` command manages named network profiles, letting you configure and switch between different RPC endpoints without editing config files manually. Tip Network profiles are stored in your CLI configuration at `~/.thru/cli/config.yaml`. You can also override the RPC URL for any single command with the `--url` flag. ## Prerequisites * [CLI setup](/docs/program-development/setting-up-thru-devkit/) ## Command Overview [Add ](#add-network)Create a new named network profile [Set Default ](#set-default)Choose which profile to use by default [Update ](#update-network)Modify an existing network profile [List ](#list-networks)Show all configured profiles [Remove ](#remove-network)Delete a network profile ## Add Network Create a new named network profile with an RPC endpoint URL and optional authorization token. ```bash thru network add $NAME --url $URL [OPTIONS] ``` **`NAME` · *string* · **required**** Profile name (case-insensitive). Used to reference this network in other commands. ***string* · **required**** RPC endpoint URL for this network (e.g., `http://localhost:8899` or `https://rpc.thru.dev`). ***string*** Optional authorization token included with RPC requests to this endpoint. **Example:** * Local development ```bash thru network add local --url http://localhost:8899 ``` * Remote with auth ```bash thru network add mainnet --url https://rpc.thru.dev --auth-token mytoken123 ``` Caution If a profile with the same name already exists, the command fails. Use [`network set`](#update-network) to modify an existing profile. ## Set Default Set which network profile the CLI uses when no `--network` or `--url` flag is provided. ```bash thru network set-default $NAME ``` **`NAME` · *string* · **required**** Name of an existing network profile to use as the default. **Example:** ```bash thru network set-default mainnet ``` Note The default network is stored in your config file and persists across CLI sessions. You can always override it per-command with `—network ` or `—url `. ## Update Network Update the URL or authorization token on an existing network profile. ```bash thru network set $NAME [OPTIONS] ``` **`NAME` · *string* · **required**** Name of the network profile to update. ***string*** New RPC endpoint URL. ***string*** New authorization token. Pass an empty string (`""`) to clear the token. **Example:** * Change URL ```bash thru network set local --url http://localhost:9000 ``` * Update auth token ```bash thru network set mainnet --auth-token newtoken456 ``` * Clear auth token ```bash thru network set mainnet --auth-token "" ``` ## List Networks Display all configured network profiles, including which one is set as the default. ```bash thru network list ``` **Example:** * Standard output ```bash thru network list ``` * JSON output ```bash thru --json network list ``` ## Remove Network Delete a network profile from the configuration. If the removed profile was the default, the default is cleared. ```bash thru network rm $NAME ``` **`NAME` · *string* · **required**** Name of the network profile to remove. **Example:** ```bash thru network rm local ``` Caution If the removed profile is currently the default, the default is cleared and you will need to set a new default or provide `--network`/`--url` on subsequent commands. ## Per-Command Overrides Any CLI command accepts these flags to override the network for a single invocation, without changing your saved profiles: ```bash # Use a specific saved profile thru --network mainnet getversion # Use an arbitrary URL thru --url http://localhost:8899 getversion ``` ## Common Workflow 1. **Add your network profiles** ```bash thru network add local --url http://localhost:8899 thru network add testnet --url https://testnet.thru.dev thru network add mainnet --url https://rpc.thru.dev --auth-token mytoken ``` 2. **Set your default** ```bash thru network set-default local ``` 3. **Run commands against the default** ```bash thru getversion thru getbalance ``` 4. **Switch networks when needed** ```bash thru --network mainnet getbalance ``` # CLI Overview > Get oriented with thru, how the command tree is organized, and where to jump next The `thru` command is the main command-line interface for interacting with the Thru network. Use this page to decide which command family fits your task before you load a more detailed reference page. Use this page to decide which command family fits your task before you load a more detailed reference page. ## Prerequisites * [CLI setup](/docs/program-development/setting-up-thru-devkit/) ## Start Here Read [Global Flags and configuration](/docs/cli-reference/global-flags-and-config/) first if you need to understand: * `--json`, `--quiet`, `--url`, and `--network` * where config lives on disk * how named networks override the default RPC target * when a command accepts key names versus raw `ta...` addresses ## Command Families [RPC ](/docs/cli-reference/rpc-commands/)Query node state, account data, balances, slot metrics, and use the top-level native transfer command. [Program ](/docs/cli-reference/program-commands/)Create, upgrade, pause, finalize, destroy, and derive managed program accounts. [Uploader ](/docs/cli-reference/uploader-commands/)Upload raw program binaries and inspect or clean up uploader accounts. [ABI ](/docs/cli-reference/abi-commands/)Publish or inspect on-chain ABI accounts, or use ABI tooling such as codegen, reflection, flattening, and bundling. [Keys ](/docs/cli-reference/keys-commands/)List, add, generate, inspect, and remove keys stored in CLI config. [Account ](/docs/cli-reference/account-commands/)Create accounts, inspect them, list transactions, and work with compression flows. [Transaction ](/docs/cli-reference/transaction-commands/)Sign, execute, inspect, and sort transaction inputs, or generate state proofs. [Utility ](/docs/cli-reference/utility-commands/)Convert public keys and signatures between hex and Thru address formats. [Token ](/docs/cli-reference/token-commands/)Create mints, manage token accounts, and transfer, mint, burn, freeze, or thaw tokens. [Faucet ](/docs/cli-reference/faucet-commands/)Deposit to or withdraw from the faucet program. [Registrar ](/docs/cli-reference/registrar-commands/)Initialize the \`.thru\` registry and manage lease lifecycle commands. [Name Service ](/docs/cli-reference/name-service-commands/)Initialize root registrars, manage records, register subdomains, and derive addresses. [Wrapped Thru ](/docs/cli-reference/wthru-commands/)Initialize wrapped Thru infrastructure, then deposit and withdraw wrapped balances. [Dev ](/docs/cli-reference/dev-commands/)Install toolchains and SDKs, inspect install paths, and scaffold projects. [Network ](/docs/cli-reference/network-commands/)Configure named RPC endpoints and choose the default network for future commands. [Debug ](/docs/cli-reference/debug-commands/)Re-execute transactions and resolve DWARF-backed debug traces. ## Choose The Right Family | If your task is… | Open this page | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | | Query a node or inspect account state | [RPC](/docs/cli-reference/rpc-commands/) | | Deploy or manage a program | [Program](/docs/cli-reference/program-commands/) | | Work with raw upload buffers | [Uploader](/docs/cli-reference/uploader-commands/) | | Publish or inspect ABIs, or work with ABI YAML tooling | [ABI](/docs/cli-reference/abi-commands/) | | Manage local signing keys | [Keys](/docs/cli-reference/keys-commands/) | | Create or compress accounts | [Account](/docs/cli-reference/account-commands/) | | Build or submit a transaction directly | [Transaction](/docs/cli-reference/transaction-commands/) | | Manage token mints and token accounts | [Token](/docs/cli-reference/token-commands/) | | Work with name registration or DNS-style records | [Registrar](/docs/cli-reference/registrar-commands/) or [Name Service](/docs/cli-reference/name-service-commands/) | | Install the `dev` toolchain or scaffold a project | [`dev`](/docs/cli-reference/dev-commands/) | | Re-execute or symbolicate a failing transaction | [Debug](/docs/cli-reference/debug-commands/) | ## Typical Workflow For Agents 1. Configure an RPC endpoint with [network commands](/docs/cli-reference/network-commands/). 2. Check [Global Flags and configuration](/docs/cli-reference/global-flags-and-config/) if you need to know how config, key lookup, or `--json` output works. 3. Jump to the relevant command family page instead of scanning the whole CLI surface. 4. Only load the detailed command section you need for the task at hand. Tip Most commands share `--json`, `--quiet`, `--network`, and `--url`, but command-specific flags and accepted identifier formats vary by family. # Program > Create, upgrade, inspect, and manage managed program accounts with thru program Use `thru program` when you are working with managed programs rather than raw uploader buffers. ## Use This When * you want to create or upgrade a managed program from a binary * you need to pause, finalize, destroy, or inspect a managed program * you need address derivation helpers such as `seed-to-hex` or manager account derivation Choose another family when: * you want raw upload-buffer lifecycle commands: [Uploader Commands](/docs/cli-reference/uploader-commands/) * you want to publish or inspect ABI accounts: [ABI](/docs/cli-reference/abi-commands/) ## Commands | Command | Use it for | | ------------------------- | -------------------------------------------------------------- | | `create` | Upload a binary and create a managed program. | | `upgrade` | Upload a new binary for an existing managed program. | | `set-pause` | Pause or unpause a managed program. | | `destroy` | Destroy a managed program and its meta account. | | `finalize` | Make a managed program immutable. | | `set-authority` | Set a new authority candidate. | | `claim-authority` | Claim authority as the candidate. | | `derive-address` | Derive a program address from a program ID and seed. | | `derive-manager-accounts` | Derive both manager metadata and program accounts from a seed. | | `seed-to-hex` | Convert a UTF-8 seed into a zero-padded 32-byte hex string. | | `derive-program-account` | Derive the managed program account only. | | `status` | Inspect program and related manager accounts. | ## High-Signal Flags | Flag | Why it matters | | -------------- | --------------------------------------------------------------- | | `--manager` | Override the configured manager program ID. | | `--ephemeral` | Switch derivation and lifecycle to ephemeral program mode. | | `--authority` | Choose the configured authority key for program creation. | | `--fee-payer` | Override the signer used for destructive or finalizing actions. | | `--chunk-size` | Control upload chunk size for `create` and `upgrade`. | ## Minimal Patterns ```bash thru program seed-to-hex my_program thru program create my_program ./build/thruvm/bin/my_program_c.bin thru program status my_program ``` ## Notes * For seed conversion, the correct command is `thru program seed-to-hex`, not a top-level `seed-to-hex`. * Start with `status` when you need to understand whether a seed already has live manager or program accounts. * Reach for `uploader` only when you intentionally want the lower-level upload account flow. # Registrar > Manage the Thru registrar program: registry setup and lease lifecycle ## Prerequisites * [CLI setup](/docs/program-development/setting-up-thru-devkit/) with keys and RPC endpoint configured * Registry payment token mint and treasurer token account ready * Fee payer funded for signer costs ## Command Overview [Initialize Registry ](#initialize-registry)Create the .thru registry config and root registrar [Purchase Domain ](#purchase-domain)Buy a new .thru domain and create its lease [Renew Lease ](#renew-lease)Extend an existing domain lease [Claim Expired Domain ](#claim-expired-domain)Reclaim domains whose leases have expired ## Initialize Registry Create the `.thru` registry and price configuration. The CLI derives the config account (seed `config`) and validates the token mint/treasurer accounts against the provided token program, including requiring the treasurer token account to be active (not frozen). ```bash thru registrar initialize-registry \ $root_registrar_account \ $treasurer_account \ $token_mint_account \ $price_per_year \ [root_domain_name] \ [OPTIONS] ``` **`root_registrar_account` · *string* · **required**** Root registrar address for the base name service program (must not yet exist) **`treasurer_account` · *string* · **required**** Token account that receives payments; must use the registry mint, match the token program, and be active (not frozen) **`token_mint_account` · *string* · **required**** Registry payment token mint address **`price_per_year` · *u64* · **required**** Price in base units per year of registration as an unsigned 64-bit integer (applied to purchases and renewals) **`root_domain_name` · *string*** Optional root name stored in the registry config (default: `thru`, max 64 characters) ***string*** Override base name service program address ***string*** Override token program address ***string*** Hex state proof for the config account; auto-generated if omitted ***string*** Hex state proof for the root registrar account; auto-generated if omitted ***string*** Signer paying transaction costs ***string*** Override the thru registrar program address **Example:** ```bash thru registrar initialize-registry \ ta...[root_registrar_account] \ ta...[treasurer_account] \ ta...[token_mint_account] \ 10_000_000 \ --token-program ta...[token_program] ``` ## Purchase Domain Buy a `.thru` domain for a number of years. The CLI derives the lease account from the domain name and validates your payer token account before submitting. ```bash thru registrar purchase-domain \ $domain_name \ $years \ $config_account \ $payer_token_account \ [OPTIONS] ``` **`domain_name` · *string* · **required**** Domain segment without `.thru` (1-64 characters) **`years` · *u8* · **required**** Lease term in years as an unsigned 8-bit integer (valid range: 1-255) **`config_account` · *string* · **required**** Initialized registry config account address **`payer_token_account` · *string* · **required**** Token account for the registry mint owned by the fee payer ***string*** Hex state proof for the lease account; auto-generated if omitted ***string*** Hex state proof for the domain account; auto-generated if omitted ***string*** Signer for the transaction ***string*** Override the thru registrar program address **Example:** ```bash thru registrar purchase-domain \ example \ 2 \ ta...[config_account] \ ta...[payer_token_account] ``` Caution The treasurer and payer token accounts must use the registry mint and token program, and the payer token account must be owned by the fee payer; otherwise the CLI rejects the transaction. ## Renew Lease Extend a domain’s lease using the same registry mint and treasurer validation as purchases. ```bash thru registrar renew-lease \ $lease_account \ $years \ $config_account \ $payer_token_account \ [OPTIONS] ``` **`lease_account` · *string* · **required**** Existing lease account address for the domain **`years` · *u8* · **required**** Additional years to add as an unsigned 8-bit integer (valid range: 1-255) **`config_account` · *string* · **required**** Registry config account address **`payer_token_account` · *string* · **required**** Registry mint token account owned by the fee payer ***string*** Signer for the transaction ***string*** Override the thru registrar program address **Example:** ```bash thru registrar renew-lease \ ta...[lease_account] \ 1 \ ta...[config_account] \ ta...[payer_token_account] ``` ## Claim Expired Domain Reclaim a domain whose lease has expired by paying for a new term. ```bash thru registrar claim-expired-domain \ $lease_account \ $years \ $config_account \ $payer_token_account \ [OPTIONS] ``` **`lease_account` · *string* · **required**** Expired lease account address to reclaim **`years` · *u8* · **required**** Years to apply to the new lease as an unsigned 8-bit integer (valid range: 1-255) **`config_account` · *string* · **required**** Registry config account address **`payer_token_account` · *string* · **required**** Registry mint token account owned by the fee payer ***string*** Signer for the transaction ***string*** Override the thru registrar program address # RPC > Use thru for top-level node queries, account reads, and the native transfer command Use this page when you need the top-level `thru` commands that are not nested under a program-specific family. ## Use This When * you want to query node or chain state * you want to inspect account data or balances * you want the top-level native `transfer` command Choose another family when: * you are working with token accounts or mints: [Token Commands](/docs/cli-reference/token-commands/) * you are building or executing raw transactions: [Transaction Commands](/docs/cli-reference/transaction-commands/) * you are creating or managing programs: [Program Commands](/docs/cli-reference/program-commands/) ## Commands | Command | Use it for | | --------------------------------------------------------------- | ----------------------------------------------------------------------- | | `thru getversion` | Fetch node version information. | | `thru gethealth` | Check node health. | | `thru getheight` | Read cluster block heights. | | `thru getaccountinfo [account] [--data-start N] [--data-len N]` | Inspect account metadata and optionally slice returned account data. | | `thru getbalance [account]` | Fetch balance for a key name or address. | | `thru getslotmetrics [end_slot]` | Inspect state counters and collected fees for one slot or a slot range. | | `thru transfer ` | Send native Thru between accounts. | ## Common Patterns ```bash thru getversion thru --json gethealth thru getaccountinfo default --data-start 0 --data-len 64 thru transfer default ta...[recipient] 1000 ``` ## Notes * These are the fastest commands to try first for read-only health, balance, or account inspection tasks. * `getaccountinfo` and `getbalance` can resolve config key names as account identifiers. * Use `--json` if the next step depends on parsing returned values programmatically. # Token > Complete reference for Thru CLI token program commands The `thru token` command provides comprehensive token management functionality, including minting, transfers, account management, and administrative operations. Tip **Transaction Fees**: Currently all transaction fees are set to 0. Transaction fees will be implemented and charged in future releases. ## Prerequisites * [CLI setup](/docs/program-development/setting-up-thru-devkit/) with configured keys and RPC endpoint ## Command Overview [Initialize Mint ](#initialize-mint)Create a new token with specified properties [Initialize Account ](#initialize-account)Create token accounts for holding tokens [Transfer ](#transfer)Move tokens between accounts [Mint To ](#mint-to)Create new tokens and add to account [Burn ](#burn)Permanently destroy tokens [Close Account ](#close-account)Close token accounts and reclaim balance [Freeze Account ](#freeze-account)Freeze accounts to prevent transfers [Thaw Account ](#thaw-account)Unfreeze previously frozen accounts [Derive Mint Account ](#derive-mint-account-address)Calculate mint account address from creator and seed [Derive Token Account ](#derive-token-account-address)Calculate token account address from mint, owner, and seed ## Initialize Mint Create a new mint account on the blockchain and initialize it as a token mint with specified properties. ```bash thru token initialize-mint $creator $ticker $seed [OPTIONS] ``` **`creator` · *string* · **required**** Creator address used for mint account derivation, formatted as a 46-char ta…\[address] or 64-char hex key. Effectively, this must be the feepayer. **`ticker` · *string* · **required**** Token symbol, maximum 8 characters (e.g., “USDC”, “ETH”) **`seed` · *string* · **required**** 32-byte hex string for deterministic mint address derivation. To convert a readable string into a zero-padded 32-byte seed, run `thru program seed-to-hex $seed`. ***string*** Optional mint authority address. If omitted, the creator is used as mint authority. ***string*** Optional hex-encoded state proof for mint account creation. If not provided, the state proof is automatically generated. ***string*** Address that can freeze/unfreeze token accounts (optional) ***integer*** Number of decimal places for token precision (0-18) ***string*** Account to pay transaction fees **Example:** * Basic mint ```bash thru token initialize-mint \ ta...[creator] \ GOLD \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` * With freeze authority ```bash thru token initialize-mint \ ta...[creator] \ SILVER \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \ --freeze-authority ta...[freeze_authority] \ --decimals 6 ``` * Custom state proof ```bash thru token initialize-mint \ ta...[creator] \ BRONZE \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \ --state-proof a1b2c3d4e5f6789... \ --fee-payer treasury ``` Tip On success, the mint is created and can be used for token operations ## Initialize Account Create a new token account on the blockchain that can hold tokens from a specific mint. Note **Address Derivation**: Token account addresses are automatically derived using SHA256(owner + mint + user\_seed), creating a unique address for each mint and owner combination. You can verify the derived address using [`derive-token-account`](#derive-token-account-address). ```bash thru token initialize-account $mint $owner $seed [OPTIONS] ``` **`mint` · *string* · **required**** The mint address for which to create an account **`owner` · *string* · **required**** Address that will own and control this token account **`seed` · *string* · **required**** 32-byte hex string for deterministic account address derivation. This seed is hashed with the owner and mint to create the final token account address. ***string*** Optional hex-encoded state proof for token account creation. If not provided, the state proof is automatically generated. ***string*** Account to pay transaction fees **Example:** * Create account ```bash thru token initialize-account \ ta...[mint] \ ta...[owner] \ fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 ``` * With custom fee payer ```bash thru token initialize-account \ ta...[mint] \ ta...[owner] \ fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 \ --fee-payer treasury ``` * Custom state proof ```bash thru token initialize-account \ ta...[mint] \ ta...[owner] \ fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 \ --state-proof f9e8d7c6b5a4321... \ --fee-payer treasury ``` Tip Use [`derive-token-account`](#derive-token-account-address) to verify the account address that will be automatically created ## Transfer Transfer tokens between two token accounts. Both accounts must be associated with the same mint, and neither account can be frozen. The fee payer must be the owner of the source token account to authorize the transfer. ```bash thru token transfer $from $to $amount [OPTIONS] ``` **`from` · *string* · **required**** Source token account address **`to` · *string* · **required**** Destination token account address **`amount` · *integer* · **required**** Amount to transfer (in smallest token units) ***string*** Account to pay transaction fees (must be the owner of the source token account) **Example:** * Simple transfer ```bash thru token transfer \ ta...[from_token_account] \ ta...[to_token_account] \ 1000000 ``` * With custom fee payer ```bash thru token transfer \ ta...[from_token_account] \ ta...[to_token_account] \ 500000 \ --fee-payer alice ``` Caution The amount is specified in the smallest token units. For a token with 9 decimals, 1000000000 represents 1.0 tokens ## Mint To Mint new tokens directly to a token account. The destination account must be associated with the specified mint and cannot be frozen. Only the designated mint authority can perform this operation. ```bash thru token mint-to $mint $to $authority $amount [OPTIONS] ``` **`mint` · *string* · **required**** The mint address from which to create new tokens **`to` · *string* · **required**** Destination token account to receive the minted tokens **`authority` · *string* · **required**** Mint authority address (must match the mint’s authority) **`amount` · *integer* · **required**** Amount to mint (in smallest token units) ***string*** Account to pay transaction fees **Example:** * Mint tokens ```bash thru token mint-to \ ta...[mint] \ ta...[destination_token_account] \ alice \ 5000000000 ``` * Large mint operation ```bash thru token mint-to \ ta...[mint] \ ta...[destination_token_account] \ treasury \ 1000000000000 \ --fee-payer treasury ``` Tip Only the designated mint authority can mint new tokens ## Burn Permanently destroy tokens from an account. The account must be associated with the specified mint and cannot be frozen. Only the account owner can perform this operation. ```bash thru token burn $account $mint $authority $amount [OPTIONS] ``` **`account` · *string* · **required**** Token account containing tokens to burn **`mint` · *string* · **required**** The mint address of the tokens being burned **`authority` · *string* · **required**** Account owner address (must be the owner of the token account being burned) **`amount` · *integer* · **required**** Amount to burn (in smallest token units) ***string*** Account to pay transaction fees **Example:** * Burn tokens ```bash thru token burn \ ta...[token_account] \ ta...[mint] \ alice \ 1000000 ``` * Burn with authority ```bash thru token burn \ ta...[token_account] \ ta...[mint] \ burn_authority \ 5000000 \ --fee-payer treasury ``` Caution Burned tokens are permanently destroyed and cannot be recovered ## Close Account Close a token account and transfer any remaining balance. The account must have a zero balance before closing. Only the account owner can perform this operation. ```bash thru token close-account $account $destination $authority [OPTIONS] ``` **`account` · *string* · **required**** Token account to close **`destination` · *string* · **required**** Account to receive any remaining token balance **`authority` · *string* · **required**** Account owner address (must be the owner of the token account being closed) ***string*** Account to pay transaction fees **Example:** * Close account ```bash thru token close-account \ ta...[token_account] \ ta...[destination_account] \ alice ``` * Close with custom fee payer ```bash thru token close-account \ ta...[token_account] \ ta...[destination_account] \ alice \ --fee-payer treasury ``` Caution The account must have exactly zero tokens before it can be closed ## Freeze Account Freeze a token account to prevent transfers. The account must be associated with the specified mint. Only the mint’s freeze authority can perform this operation, and the mint must have freeze authority enabled. ```bash thru token freeze-account $account $mint $authority [OPTIONS] ``` **`account` · *string* · **required**** Token account to freeze **`mint` · *string* · **required**** The mint address associated with the account **`authority` · *string* · **required**** Freeze authority address (must match the mint’s freeze authority) ***string*** Account to pay transaction fees **Example:** ```bash thru token freeze-account \ ta...[token_account] \ ta...[mint] \ freeze_authority ``` Caution This operation will fail if the mint does not have freeze authority enabled during mint creation Note Frozen accounts cannot send or receive tokens until unfrozen ## Thaw Account Unfreeze a previously frozen token account. The account must be associated with the specified mint. Only the mint’s freeze authority can perform this operation, and the mint must have freeze authority enabled. ```bash thru token thaw-account $account $mint $authority [OPTIONS] ``` **`account` · *string* · **required**** Token account to unfreeze **`mint` · *string* · **required**** The mint address associated with the account **`authority` · *string* · **required**** Freeze authority address (must match the mint’s freeze authority) ***string*** Account to pay transaction fees **Example:** ```bash thru token thaw-account \ ta...[token_account] \ ta...[mint] \ freeze_authority ``` Caution This operation will fail if the mint does not have freeze authority enabled during mint creation Tip After thawing, the account can participate in transfers normally ## Derive Mint Account Address Calculate the deterministic **mint account** address that would be created for a given creator and seed combination. Use this to predict the mint address before calling `initialize-mint`. Caution **Mint vs Token Account**: This command derives the address for a **mint account** (the token definition itself). To derive the address for a **token account** (which holds token balances), use [`derive-token-account`](#derive-token-account-address) instead. Note **Address Derivation**: Mint account addresses are derived using SHA256(creator + seed), then used with the token program for final PDA derivation. This ensures each creator can create unique mints with different seeds. ```bash thru token derive-mint-account $creator $seed ``` **`creator` · *string* · **required**** Creator address **`seed` · *string* · **required**** Seed for derivation (32 bytes hex, 64 hex characters) **Example:** * Derive mint address ```bash thru token derive-mint-account \ ta...[creator] \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` * JSON output ```bash thru --json token derive-mint-account \ ta...[creator] \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` Tip Returns the calculated mint account address that will be used when creating the mint Tip Use this command before running [`initialize-mint`](#initialize-mint) to know the mint address that will be created ## Derive Token Account Address Calculate the deterministic **token account** address that would be created for a given mint, owner, and seed combination. Use this to predict token account addresses before calling `initialize-account`. Caution **Token vs Mint Account**: This command derives the address for a **token account** (which holds token balances for an owner). To derive the address for a **mint account** (the token definition itself), use [`derive-mint-account`](#derive-mint-account-address) instead. ```bash thru token derive-token-account $mint $owner [OPTIONS] ``` **`mint` · *string* · **required**** Mint account address **`owner` · *string* · **required**** Account owner address ***string*** Seed for derivation (32 bytes hex, optional - defaults to all zeros) **Example:** * Default seed (all zeros) ```bash thru token derive-token-account \ ta...[mint] \ ta...[owner] ``` * Custom seed ```bash thru token derive-token-account \ ta...[mint] \ ta...[owner] \ --seed fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 ``` * JSON output ```bash thru --json token derive-token-account \ ta...[mint] \ ta...[owner] \ --seed fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 ``` Tip Returns the calculated token account address that can be used with other commands Note **Address Formula**: The derived address is calculated as SHA256(owner + mint + seed), then used with the token program as the owner for final PDA derivation. This ensures unique addresses for each combination. ## Common Usage Patterns ### Initialize a Token Mint 1. **(Optional) Verify the mint address** You can optionally verify the mint account address that will be created: ```bash thru token derive-mint-account \ ta...[creator] \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` Where: * First argument: creator address * Second argument: seed for mint derivation (save this securely!) Note The seed is crucial - save it securely as you’ll need it for creating the mint 2. **Initialize the mint** Create the mint account - the address and state proof are automatically derived: ```bash thru token initialize-mint \ ta...[creator] \ MYTOKEN \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \ --freeze-authority ta...[freeze_authority] \ --decimals 6 ``` Where: * First argument: creator address * Second argument: token symbol/ticker * Third argument: seed for deterministic address derivation Tip The command automatically derives the mint address and generates the required state proof ### Initialize a Token Account Creating a token account is now simplified with automatic address and state proof derivation: 1. **(Optional) Verify the token account address** You can optionally verify the token account address that will be created: ```bash thru token derive-token-account \ ta...[mint] \ ta...[owner] \ --seed fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 ``` Where: * First argument: mint address (from the mint initialization) * Second argument: account owner address * `--seed`: unique seed for this token account (save this!) 2. **Initialize the token account** Create the token account - the address and state proof are automatically derived: ```bash thru token initialize-account \ ta...[mint] \ ta...[owner] \ fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210 ``` Where: * First argument: mint address * Second argument: account owner address * Third argument: seed for deterministic address derivation Tip The command automatically derives the token account address and generates the required state proof ## Best Practices Tip **Seed Management**: Store seeds securely and use deterministic generation for reproducible addresses Tip **Authority Management**: Use dedicated key management for mint and freeze authorities Caution **Security**: Never share private keys or store them in plain text. Use the CLI’s key management system # Transaction > Sign, execute, inspect, and sort transactions with thru txn Use `thru txn` when you need to build or inspect transactions directly instead of using a higher-level command family. ## Commands | Command | Use it for | | ------------------ | ----------------------------------------------------- | | `sign` | Build and sign a transaction, then emit it as base64. | | `execute` | Build, sign, and submit a transaction directly. | | `make-state-proof` | Generate a cryptographic state proof for an account. | | `get` | Fetch transaction details by signature. | | `sort` | Sort public keys for transaction account lists. | ## Important Inputs * `program` * `instruction_data` * optional fee/resource fields such as `fee`, `compute_units`, `state_units`, and `memory_units` * ordered `--readwrite-accounts` and `--readonly-accounts` ## Minimal Patterns ```bash thru txn sign ta...[program] deadbeef thru txn get ts...[signature] --retry-count 5 ``` ## Notes * Use this family when you already know the raw instruction data and account list you need. * Use a higher-level family like `token`, `program`, or `registrar` when it already wraps the transaction-building logic for your task. # Uploader > Use the raw uploader account flow for program binaries with thru uploader Use `thru uploader` when you need the lower-level upload-buffer lifecycle rather than managed program operations. ## Use This When * you want to upload a binary without going through the managed `program` flow * you need to inspect or clean up uploader accounts directly Choose another family when: * you want the higher-level managed workflow: [Program Commands](/docs/cli-reference/program-commands/) ## Commands | Command | Use it for | | --------- | -------------------------------------------------- | | `upload` | Upload a program binary through uploader accounts. | | `cleanup` | Remove uploader-related accounts for a seed. | | `status` | Inspect uploader account state for a seed. | ## High-Signal Flags | Flag | Why it matters | | -------------- | --------------------------------------------- | | `--uploader` | Override the configured uploader program ID. | | `--chunk-size` | Control chunk sizing for upload transactions. | ## Minimal Pattern ```bash thru uploader upload my_program ./build/thruvm/bin/my_program_c.bin thru uploader status my_program ``` ## Notes * Prefer `program create` or `program upgrade` for most app-facing deployment tasks. * Use `cleanup` when a previous upload left partial uploader state behind. # Utility > Convert public keys and signatures between hex and Thru formats Use `thru util` for pure format conversion tasks. ## Commands | Command | Use it for | | ------------------------------------------------------ | ------------------------------------------- | | `convert pubkey hex-to-thrufmt ` | Convert a hex public key to `ta...` format. | | `convert pubkey thrufmt-to-hex ` | Convert a `ta...` address to hex. | | `convert signature hex-to-thrufmt ` | Convert a hex signature to `ts...` format. | | `convert signature thrufmt-to-hex ` | Convert a `ts...` signature to hex. | ## Minimal Pattern ```bash thru util convert pubkey hex-to-thrufmt 0123... thru util convert signature thrufmt-to-hex ts... ``` ## Notes * This family is read-only and local. * Reach for it whenever another command requires `ta...` or `ts...` formatting and you only have hex. # Wrapped Thru > Initialize, deposit into, and withdraw from the wrapped Thru program Use `thru wthru` when the task involves wrapped Thru rather than generic token-account operations. ## Commands | Command | Use it for | | ----------------------------------------------------- | ---------------------------------------------------- | | `initialize` | Initialize the wrapped Thru mint and vault accounts. | | `deposit ` | Wrap native Thru into wrapped Thru. | | `withdraw ` | Burn wrapped Thru and receive native Thru. | ## High-Signal Flags | Flag | Why it matters | | ----------------- | ---------------------------------------------------------------------------- | | `--program` | Override the configured wrapped Thru program ID. | | `--token-program` | Override the token program ID used by wrapped Thru flows. | | `--skip-transfer` | On `deposit`, skip the native transfer and only run the deposit instruction. | ## Minimal Pattern ```bash thru wthru initialize thru wthru deposit ta...[dest_token_account] 1000000 ``` ## Notes * Use this family instead of `token` when the goal is wrapping or unwrapping native Thru. * The program and token program IDs can both be overridden explicitly. # Accounts > An introduction to the account storage model on the Thru network Every piece of data that is stored on the Thru network lives inside an account. The purpose of the blockchain is to keep the state of all accounts up to date. ## What is an Account? An account is a container for data that has: * **An address**: Also known as its public key (or pubkey for short), this is a unique 32-byte identifier for the account. * **Data**: The actual bytes stored in the account, which is allowed to be up to 16 MiB. * **Metadata**: Information about the account’s state and properties. In the C SDK, account metadata is exposed as: ```c /* From sdks/c/thru-sdk/c/tn_sdk_txn.h */ struct __attribute__(( packed )) tsdk_account_meta { uchar version; /* Metadata format version */ uchar flags; /* Lifecycle/type flags (program, ephemeral, deleted, compressed, etc.) */ uint data_sz; /* Account data size in bytes (up to 16 MiB) */ ulong seq; /* Sequence counter incremented when account state changes */ tn_pubkey_t owner; /* Program that is allowed to modify this account's data */ ulong balance; /* Native-token balance held by this account */ ulong nonce; /* Account nonce used by transaction/signing flows */ }; ``` Common flag values are defined in `sdks/c/thru-sdk/c/tn_sdk.h`. Conceptually, you can think of the Thru network as one giant map from 32-byte addresses to sequences of bytes. ## Accounts and Programs Accounts are only useful to us if they can be read and manipulated. **Programs**, or smart contracts, allow accounts to be updated. The logic of how accounts should be updated is written in code. Programs are themselves accounts, and their compiled code is stored in their account data. When executing, they are allowed to read from any account, and can write to the accounts that they own. For more information, see [Programs](/docs/core-concepts/programs/). ## Addresses Every account is identified by a 32-byte address, which is a public key. There are two ways accounts get their addresses: * **Keypair-derived accounts**: You can think of these as being accounts that can be logged into. They are generated from a cryptographic keypair, which consists of a private and public key, where anyone with the private key can prove their identity, which can be verified with the public key. * **Program-derived addresses (PDAs)**: These are addresses derived from a program’s address and a seed. These accounts have no private key. The owning program can authorize how they are used during invocation, and can mutate their data. ## Ownership Every account is owned by exactly one program, which is the program that created it. The owning program has exclusive write access to the account’s data. Other programs can read the account, but only the owner can modify it. Note that for keypair-derived accounts, the owner of the account is the system program. ## Data The data for an account is just a byte array. Programs interpret these bytes however they want; there is not an enforced schema. Common patterns include: * **Fixed-size structs**: Store a C struct directly in the account * **Variable-size data**: Use a header that indicates data layout * **Nested accounts**: Store references (addresses) to other accounts ```c /* Example: A simple account data structure */ struct user_profile { tn_pubkey_t owner; /* 32 bytes */ ulong balance; /* 8 bytes */ uchar username[32]; /* 32 bytes */ }; /* Total: 72 bytes */ ``` When you create an account, you specify its size. To store the `user_profile` struct above, you’d create an account with at least 72 bytes. Accounts can be resized later, but this requires the owning program’s permission. ## Account Lifecycle ### Creation New accounts are created by programs using state proofs. A **creation proof** cryptographically demonstrates that an address is available—no account currently exists at that address. ```c /* Creating a new account with a creation proof */ tsys_account_create( account_index, seed, proof_data, proof_size ); ``` Once created, an account exists on the blockchain and can be read or modified by programs. ### Modification The owning program can modify an account’s data at any time. Before writing to an account, the program must mark it as writable within the current transaction: ```c /* Make account data writable for this transaction */ tsys_set_account_data_writable( account_index ); /* Now we can write to account data */ struct counter_account { ulong count; }; struct counter_account * account = (struct counter_account *)tsdk_get_account_data_ptr( (ushort)account_index ); account->count += 1UL; ``` ### Compression To save on-chain storage, accounts can be **compressed**. A compressed account’s data is removed from active validator storage and stored off-chain. The account still exists—its current state is committed to the blockchain through a Merkle tree—but it cannot be accessed until it’s decompressed. Compressing and decompressing accounts requires state proofs to maintain security guarantees. ### Deletion Programs can delete accounts they own. This removes the account from the blockchain entirely and frees up its storage. ```c /* Delete an account */ tsys_account_delete( account_index ); ``` ## Compressed vs. Uncompressed Accounts The Thru network distinguishes between compressed and uncompressed accounts: **Uncompressed accounts** are active and stored in validator memory. They can be accessed and modified immediately by any transaction. **Compressed accounts** are archived off-chain. Their state is cryptographically committed to the blockchain via a Merkle tree, but validators don’t store the full data. To use a compressed account, you must first decompress it by providing a state proof. Compression dramatically reduces storage costs for accounts that are accessed infrequently. For example, a user might compress their account when they’re not actively using the network, then decompress it months later when they want to make a transaction. See [Account Compression and State Proofs](/docs/spec/accounts/account-compression/) for technical details on how compression works. ## Accounts in Transactions When you submit a transaction, you specify which accounts it will access. Each account is passed to the program by index: ```c /* Instruction data includes the account index */ struct instruction_args { ushort account_index; /* Which account to operate on */ /* ... other instruction data ... */ }; ``` Programs access accounts through these indices. The runtime ensures that programs can only write to accounts they’re permitted to modify. ## Working with Accounts in Programs Here’s a simple example of creating and using an account in a program: ```c #include #include /* Account data structure */ struct counter_account { ulong count; }; TSDK_ENTRYPOINT_FN void start( void ) { tsdk_txn_t const * txn = tsdk_get_txn( ); uchar const * instruction_data = tsdk_txn_get_instr_data( txn ); ulong instruction_data_sz = tsdk_txn_get_instr_data_sz( txn ); /* Parse instruction data (account index and creation proof) */ ushort account_index = /* extracted from instruction_data */; uchar seed[TN_SEED_SIZE] = /* extracted from instruction_data */; uchar const * proof_data = /* extracted from instruction_data */; ulong proof_size = /* calculated from proof header */; /* Create the account */ tsys_account_create( account_index, seed, proof_data, proof_size ); /* Make it writable and set size */ tsys_set_account_data_writable( account_index ); tsys_account_resize( account_index, sizeof(struct counter_account) ); /* Initialize the data */ struct counter_account * account = tsdk_get_account_data_ptr( account_index ); account->count = 0; tsdk_return( TSDK_SUCCESS ); } ``` Later transactions can read and modify the account’s `count` field, implementing a simple counter. For a more complete example, see [Quickstart: Build a C Program](/docs/program-development/building-a-c-program/). ## Key Takeaways * **Accounts are containers for data** identified by 32-byte addresses * **Programs own accounts** and modify them * **Account data is unstructured** — programs define their own data layouts * **Accounts can be compressed** to reduce storage costs when not actively used * **Transactions specify which accounts they access** and programs use indices to reference them Understanding these fundamentals will help you design efficient programs and manage state effectively on the Thru network. ## Next Steps * Learn how to [build a C program](/docs/program-development/building-a-c-program/) that creates and uses accounts * Explore [account compression](/docs/spec/accounts/account-compression/) for advanced state management * Review the [syscalls reference](/docs/spec/vm/syscalls/overview/) for account manipulation functions # Programs > Smart contracts on the Thru network The point of a blockchain is to run programs that manipulate accounts, also known as smart contracts. When running a program on the Thru network, it is executed simultaneously on physical validator nodes. However, with the abstraction of the network, you can think of it as the program just executing once on the network and modifying the state of the [accounts](/docs/core-concepts/accounts/) on the network. ## What are Programs? Programs are accounts that contain executable code in their data. They can be invoked by anyone, and they run on the Thru blockchain virtual machine. They can read data from any account. They can also create new accounts that they own, and write to them. To enable potentially complex calculations, the Thru network provides a **virtual machine (VM)**. This is like a shared, simulated computer that everyone can trust to run programs exactly as written. It is able to perform ordinary computations, as well as update the state of the network. The Thru VM is based on the 64-bit RISC-V architecture, which is a simplified instruction set typically used for physical CPUs in microcontrollers. Because of its simplicity, it can run programs efficiently. For more information, see [Virtual Machine](/docs/spec/vm/overview/). ## Transactions When someone wants to run a program, we call this a **transaction**. The transaction contains the program to run, the accounts that the program should operate on, and the instructions to pass to the program. More precisely, the transaction format is specified in the [transaction specification](/docs/spec/core/transactions/). ### Accounts A transaction must include a list of the addresses of the accounts that the program should operate on. Typically, the instruction data then specifies to the program the semantics of the accounts. #### The Fee Payer Computing power isn’t free, and so someone needs to pay for each transaction. The **fee payer** of the transaction is the account that requests the transaction to be executed, and pays for the transaction to be run. The fee is charged in the native token of the Thru network. Notably, the fee payer can choose the fee they wish to pay for the transaction, which may even be zero. The value of a transaction is determined based on market value; the fee payer should determine the fee they are willing to pay for the transaction. Note Each transaction has a maximum number of **compute units (CUs)** that it is allowed to consume. The number of compute units consumed is computed based on the number of instructions executed, the amount of memory used, and the number of additional bytes in account storage that are consumed by the program. See [resources](/docs/spec/runtime/resources/) for more information. The maximum compute units of a transaction can be used in the calculation of the fair fee for the transaction. Additionally, because the fee payer must authorize the transaction, programs can use this to see that the fee payer has given consent to certain operations. ### Instructions Note that the instruction data for a program is just a sequence of bytes, which is left up to the interpretation of the program. ### Calling Other Programs Programs can call other programs using the [`invoke` syscall](/docs/spec/vm/syscalls/invoke/). This mechanism is more important for more than just reusing code. Because each program is responsible for its own accounts, having programs be able to call other programs provides a mechanism for encapsulating state. ### Success and Failure Once a program has finished executing, it should use the [`exit` syscall](/docs/spec/vm/syscalls/exit/). It produces an exit code, and can either succeed or revert the transaction. If it reverts, the execution of the transaction is halted and all changes made to accounts are rolled back. If the top-level program succeeds, the transaction is committed and the changes made to accounts are persisted. ## Writing Programs Since the Thru VM uses the popular RISC-V architecture, it is easy to write programs using popular languages like C, C++, and Rust, using existing compilers. You can learn how to write a C program [here](/docs/program-development/building-a-c-program/). ### Memory Model To make writing programs easy, all the information made available to the program can be accessed at certain memory addresses. For example, the data of the accounts passed into the program are each located at an address `0x03____000000`, where the blanks are the index of the account. To read and write account data, the program can simply read and write to the appropriate address. See [Memory Layout](/docs/spec/vm/memory-layout/) for more information. ### System Calls (Syscalls) To perform special operations, such as creating an account and transferring account balances, the virtual machine provides a number of syscalls. These are special operations which are triggered by the `ecall` instruction, and are handled by the VM. The Thru SDKs provide functions such as [`tsys_account_create`](/docs/spec/vm/syscalls/account_create/) which wrap the syscalls and make them easy to call from high-level languages. [Learn more about syscalls here](/docs/spec/vm/syscalls/overview/). ### Software Development Kits (SDKs) To make it easy to develop programs for the Thru VM, software development kits (SDKs) are available for C, C++, and Rust. They provide build scripts to help you compile programs, as well as a variety of useful functions including syscall wrappers. # What is Thru? > Motivation for the Thru network ## What’s the point of Thru? If you’re here to learn about Thru, it may be helpful to first understand why we built it! The challenge that blockchains like Thru solve is the problem of decentralized trust. As you already know, computer programs are very useful for managing lots of information. However, traditional software requires that we run code on a single machine. For many applications, this is undesirable, since it requires us to trust an authority to run the code. The authority could be unreliable, or it could even be malicious and change the output of the software. Blockchains like Thru allow people to publish and execute [**programs**](/docs/core-concepts/programs/), so that anyone can trust that the code has been executed correctly, and we are not reliant on any single machine to run the code. The blockchain consists of many servers, called **validators**, that execute programs as requested by users; an execution is called a **transaction**. The validators agree upon the results in a process called **consensus**, in which it is impossible for bad actors to manipulate the results (unless they have a huge amount of financial resources, which they would risk losing). Because validators are paid to participate in consensus, and anyone can become a validator (with some initial investment), the network runs in a decentralized manner. It can survive losing validators, so even if some servers crash, the network can continue to run. Analogous to traditional storage mediums, programs can persist data on the network in [**accounts**](/docs/core-concepts/accounts/). ## Why is this useful? Here are some classic applications of blockchains: * **Financial transactions**: We can process the exchange of digital currencies without having to rely on centralized banks or payment networks, which can be unreliable or slow. Thru doesn’t rely on a particular company to process transactions, and is designed to allow programs to execute in seconds. On Thru, the [token program](/docs/cli-reference/token-commands/) is used to represent currencies. ## Why Thru? There are many other blockchains, so why did we make Thru? Thru has been designed from the ground up to allow programs to execute quickly and cheaply. A key feature is that programs on Thru are written using RISC-V, a popular computer architecture. You can easily write programs in your favourite programming languages, such as C, C++, or Rust, and they execute quickly thanks to the RISC-V architecture. Because the validators can run programs quicker, you can get your results faster, and less computing resources are used, ultimately leading to financial savings. ## The Thru token Unfortunately, computing resources aren’t free. To keep the network running, validators need to receive payment for running the network. They are paid in a special currency called the Thru token. When requesting transactions to be run, you pay a fee which incentivizes validators to run your transaction; each validator gets a share of the fee. ## Interacting with the network Directly interacting with the Thru network is difficult, since the validator software requires a lot of setup and hardware. When you want to use the blockchain, you interact with special servers via [**remote procedure call (RPC)**](/docs/api-ref/overview/). RPC servers participate in the network. If you want to submit a transaction, or you want to query the state of the blockchain, you can do so by an RPC request. ## Ecosystem We’ve built a variety of software to help you use Thru to solve your problems: * The [Thru program SDKs](/docs/program-development/setting-up-thru-devkit/) for C, C++, and Rust help you write and build programs that run on the Thru network. * The [Thru CLI](/docs/cli-reference/overview/) helps you interact with the Thru network over RPC, including uploading and executing programs. * The [Thru web SDKs](/docs/program-development/developer-resources/) helps you interact with Thru over RPC, so you can make web apps that use Thru programs on the backend. See [Developer Resources](/docs/program-development/developer-resources/) for more information. # NFT Program > Understand the Thru NFT program account model, instruction surface, and the current ABI-guided developer view of how it works. The NFT Program manages NFT mint accounts and individual NFT accounts on Thru. From a developer perspective, the current public view of the program is driven mainly by the flattened NFT ABI and the runtime tests that exercise minting, transfers, burns, metadata updates, and authority changes. ## Use This When * you need an on-chain mint plus per-NFT account model * you need to reason about NFT ownership, mint authority, or metadata updates * you are working from the ABI and runtime behavior rather than a first-class public SDK ## Quickstart Fetch the ABI, generate TypeScript builders, then build instruction bytes and send the transaction. ```bash thru abi account get --include-data --out ./nft-program.abi.yaml thru abi codegen \ --files ./nft-program.abi.yaml \ --language typescript \ --output ./generated ``` ```ts import { decodeAddress } from "@thru/sdk/helpers"; import { NftInstruction, NftTransferArgs, } from "./generated/thru/program/nft_token/types"; const instructionData = NftInstruction.builder() .payload() .select("transfer") .writePayload( NftTransferArgs.builder() .set_nft_account_idx(2) .set_dest_account_idx(3) .set_mint_account_idx(4) ) .finish() .build(); const tx = await thru.transactions.build({ feePayer: { publicKey: decodeAddress(feePayerAddress) }, program: nftProgramAddress, accounts: { readWrite: [nftAccountAddress, destinationOwnerAddress, mintAccountAddress], readOnly: [], }, instructionData, }); const signedWire = await signTransaction(tx.toWireForSigning()); const signature = await thru.transactions.send(signedWire); ``` ## How It Works The program separates collection-level mint state from per-NFT state: * the mint account stores the mint authority, total supply, and the next sequential NFT id * each NFT account stores its mint, current owner, id, and metadata blob Typical flows look like this: 1. initialize the NFT mint with a state proof for the new mint account 2. mint an NFT, which creates a new NFT account tied to that mint and the next id 3. transfer, burn, or update metadata on the per-NFT account 4. update the mint authority if ownership of the mint itself changes ## Account Model | Account | What it stores | Notes | | ------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------ | | `NftMintAccount` | `mint_authority`, `supply`, `next_id` | Tracks collection-level state and the next id to mint. | | `NftAccount` | `mint`, `owner`, `id`, `metadata` | Represents one NFT instance under a mint. | | `NftMetadata` | `flags`, `external_metadata_uri` | The flattened ABI reserves 256 bytes for the external metadata URI. | | `NftProgramAccount` | Size-discriminated envelope over mint and NFT accounts | The ABI distinguishes `mint_account` and `nft_account` by expected size. | ## Events The current flattened NFT ABI does not define an event envelope. That means agents should not assume a stable NFT event stream exists in the same way it does for the Token Program or Passkey Manager Program. For current integration work, the safer sources of truth are account state and transaction-level inspection. ## Instructions | Instruction | Use it when | Notes | | ----------------- | ------------------------------------- | ---------------------------------------------------------------------- | | `initialize_mint` | Create a new NFT mint | Includes the mint seed and a state proof for the new mint account. | | `mint_to` | Mint a new NFT account under the mint | The mint account controls supply and the next sequential NFT id. | | `transfer` | Transfer ownership of an NFT account | Changes the `owner` field on the NFT account. | | `burn` | Burn an NFT | Removes the NFT from circulation and updates mint state. | | `update_metadata` | Change the NFT metadata payload | Use when the external metadata URI or flags need to change. | | `set_authority` | Change the mint authority | Use when control over future minting should move to another authority. | ## What Is Not Covered Here This page is grounded in the published flattened ABI and the runtime tests, not in a dedicated public SDK package. If you need deeper binary-layout rules, open [Specification](/docs/abi/specification/) or the related ABI pages next. ## Related References * [ABI Examples](/docs/abi/examples/) * [Specification](/docs/abi/specification/) * [Explorer MCP Overview](/docs/api-ref/explorer-mcp/overview/) ## On-Chain Link * Public explorer page: not currently documented in these docs # Overview > Use the core program guides to understand the built-in Thru programs that most app and protocol integrations build around. Use this section when you need to understand the built-in programs themselves, not just the SDK package that talks to them. ## Core Programs | Program | What it does | Main accounts | Events | Open next | Explorer | | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | [Passkey Manager Program](/docs/core-programs/passkey-manager-program/) | Turns WebAuthn/passkey proofs into on-chain authorization for nonce-bound program actions. | `WalletAccount`, `CredentialLookup` | `wallet_created`, `wallet_validated`, `wallet_transfer`, `credential_registered` | [`@thru/programs`](/docs/sdks/web-packages/programs/), [`@thru/passkey`](/docs/sdks/web-packages/passkey/) | [View program](https://scan.thru.org/address/taUDdQyFxvM5i0HFRkEK3W45kWLyblAHSnMg4zplgUnz6Z) | | [Token Program](/docs/core-programs/token-program/) | Manages fungible token mints and token accounts. | `TokenMintAccount`, `TokenAccount` | `initialize_mint`, `initialize_account`, `transfer`, `mint_to`, `burn`, `close_account`, `freeze_account`, `thaw_account` | [`@thru/programs`](/docs/sdks/web-packages/programs/) | [View program](https://scan.thru.org/address/taAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqq) | | [NFT Program](/docs/core-programs/nft-program/) | Manages NFT mint accounts, per-NFT accounts, transfers, burns, and metadata updates. | `NftMintAccount`, `NftAccount` | No event envelope is defined in the current flattened ABI. | [ABI Examples](/docs/abi/examples/), [Specification](/docs/abi/specification/) | Public explorer page not currently documented in these docs. | ## Start Here [Passkey Manager Program ](/docs/core-programs/passkey-manager-program/)Learn the nonce, authority, and validate-then-execute model behind passkey-backed authorization flows. [Token Program ](/docs/core-programs/token-program/)Understand mint accounts, token accounts, lifecycle events, and the standard fungible-token instruction surface. [NFT Program ](/docs/core-programs/nft-program/)See the NFT mint and NFT account model, instruction surface, and the current limits of the published ABI. ## Choose The Right Page | If your task is… | Open this page | Then open… | | ------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- | | Build or debug a passkey-backed authorization flow | [Passkey Manager Program](/docs/core-programs/passkey-manager-program/) | [`@thru/programs`](/docs/sdks/web-packages/programs/) | | Mint, transfer, burn, or freeze fungible assets | [Token Program](/docs/core-programs/token-program/) | [`@thru/programs`](/docs/sdks/web-packages/programs/) | | Mint or transfer non-fungible tokens, or inspect NFT metadata state | [NFT Program](/docs/core-programs/nft-program/) | [ABI Examples](/docs/abi/examples/) | ## Next Steps 1. Read the program page for the contract you are actually targeting. 2. Move to the matching SDK package page when you need client helpers and builders. 3. Use the ABI docs when you need the binary layout or reflection model in more detail. # Passkey Manager Program > Understand the on-chain program behind passkey-backed authorization, account state, events, and instruction flow. The Passkey Manager Program is the core authorization program for passkey-backed flows on Thru. From a developer perspective, the key idea is that the passkey does not sign the outer Thru transaction directly. Instead, the program verifies a WebAuthn signature over a challenge built from the wallet nonce, the ordered account list, and the trailing instruction bytes that the program action is authorizing. ## Use This When * you need passkey-backed authorization for a transfer, CPI flow, or other nonce-bound program action * you need to reason about authority records, credential lookup accounts, or nonce advancement * you are integrating WebAuthn or native passkey signatures into a Thru transaction flow ## Quickstart Fetch the ABI, generate TypeScript builders, then build instruction bytes and send the outer transaction. ```bash thru abi account get --include-data --out ./passkey-manager.abi.yaml thru abi codegen \ --files ./passkey-manager.abi.yaml \ --language typescript \ --output ./generated ``` ```ts import { decodeAddress } from "@thru/sdk/helpers"; import type { Thru } from "@thru/sdk/client"; import { PasskeyInstruction, TransferArgs, } from "./generated/thru/program/passkey_manager/types"; const instructionData = PasskeyInstruction.builder() .payload() .select("transfer") .writePayload( TransferArgs.builder() .set_wallet_account_idx(2) .set_to_account_idx(3) .set_amount(1_000_000) ) .finish() .build(); const tx = await thru.transactions.build({ feePayer: { publicKey: decodeAddress(feePayerAddress) }, program: "taUDdQyFxvM5i0HFRkEK3W45kWLyblAHSnMg4zplgUnz6Z", accounts: { readWrite: [walletAddress, destinationAddress], readOnly: [], }, instructionData, }); const signedWire = await signTransaction(tx.toWireForSigning()); const signature = await thru.transactions.send(signedWire); ``` Tip Real user-facing passkey flows usually prepend a generated `validate` instruction before the trailing program action. If you want the full validate-and-send path instead of raw generated builders, use `@thru/programs/passkey-manager`. Caution The passkey proof and the outer transaction signature are different layers. A successful `validate` step authorizes the wallet action inside the program, but the outer transaction still needs to be built and submitted normally. ## How It Works Most passkey-managed flows follow this pattern: 1. fetch the nonce from the on-chain wallet account 2. build the trailing instruction payload you want the program to authorize 3. hash `nonce || ordered account addresses || trailing instruction bytes` 4. sign that challenge with WebAuthn 5. encode `validate` 6. concatenate `validate` with the trailing instruction payload 7. submit the resulting transaction against the Passkey Manager Program This validate-then-execute model works whether the caller is a first-party wallet, a custom web app, or a mobile/backend integration. ## Account Model | Account | What it stores | Notes | | ------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------- | | `WalletAccount` | Wallet header with `num_auth` and `nonce` | In practice, wallet data also includes trailing 65-byte authority records after the fixed header. | | `CredentialLookup` | Wallet pubkey for a registered credential | Useful when a passkey needs a lookup account for registration or recovery flows. | ### Authority Records Wallet authority entries are 65-byte tagged records: * `tag = 1`: passkey authority, stored as P-256 `x[32] + y[32]` * `tag = 2`: pubkey authority, stored as `pubkey[32] + padding[32]` ## Required Accounts Passkey-manager instructions rely on stable account indices: * the fee payer is index `0` * the Passkey Manager Program is index `1` * the wallet account must appear as a non-fee-payer account in the transaction * trailing instructions reference accounts by their position in the final ordered account list If you are using the Web SDK, `buildAccountContext(...)` handles this ordering and index lookup. ## Events | Event | What it means | | ----------------------- | ---------------------------------------------------------- | | `wallet_created` | A new passkey-managed wallet account was created. | | `wallet_validated` | A `validate` step succeeded and advanced the wallet nonce. | | `wallet_transfer` | The wallet transferred balance to another account. | | `credential_registered` | A credential lookup account was registered for the wallet. | ## Instructions | Instruction | Use it when | Notes | | --------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- | | `create` | Create a new passkey-managed wallet | Includes the initial authority and a state proof for the new wallet account. | | `validate` | Submit a WebAuthn proof for the trailing wallet action | Carries `r`, `s`, `authenticatorData`, and `clientDataJSON`. | | `transfer` | Move native balance from the managed wallet | Usually follows `validate` in the same transaction payload. | | `invoke` | Have the managed wallet call another program | This is the main path for wallet-controlled CPI flows. | | `add_authority` | Add another passkey or pubkey authority | Useful for multi-authority or recovery flows. | | `remove_authority` | Remove an existing authority by index | Use with care because authority ordering matters. | | `register_credential` | Create a credential lookup account | Used when you want a stable on-chain mapping for a credential. | ## Integration Surfaces The program stands on its own, but most developers will interact with it through one of these integration patterns: ### Web * Use `@thru/programs/passkey-manager` when your app needs to build `validate`, `transfer`, `invoke`, or authority-management instructions directly. * Use [`@thru/passkey`](/docs/sdks/web-packages/passkey/) when you need the browser WebAuthn registration and signing layer that feeds signatures into passkey-manager transactions. * Embedded-wallet integrations typically use the wallet or provider layer for the outer transaction while relying on passkey-manager for the inner authorization payload. ### Mobile * Mobile apps can use native passkey surfaces to register a credential and produce the WebAuthn proof locally. * A common mobile pattern is challenge-submit: fetch or construct the passkey-manager challenge, sign it on-device, then send the signature components and WebAuthn payload back to a backend or transaction service that assembles the final transaction. ## Related References * [`@thru/programs`](/docs/sdks/web-packages/programs/) * [`@thru/passkey`](/docs/sdks/web-packages/passkey/) * [Cross Program Invocation](/docs/program-development/cross-program-invocation/) ## On-Chain Link * Program explorer: [scan.thru.org/address/taUDdQyFxvM5i0HFRkEK3W45kWLyblAHSnMg4zplgUnz6Z](https://scan.thru.org/address/taUDdQyFxvM5i0HFRkEK3W45kWLyblAHSnMg4zplgUnz6Z) # Token Program > Understand the standard Thru fungible-token program from the perspective of accounts, events, instructions, and client integration. The Token Program is the standard fungible-asset program on Thru. From a developer perspective, it owns mint accounts and token accounts, emits lifecycle and balance-change events, and expects clients to derive deterministic account addresses before building instruction payloads. ## Use This When * you need a fungible-token mint and token account model on Thru * you need to transfer, mint, burn, freeze, or thaw token balances * you need to parse mint or token account state from chain data ## Quickstart Fetch the ABI, generate TypeScript builders, then build instruction bytes and send the transaction. ```bash thru abi account get --include-data --out ./token-program.abi.yaml thru abi codegen \ --files ./token-program.abi.yaml \ --language typescript \ --output ./generated ``` ```ts import { decodeAddress } from "@thru/sdk/helpers"; import { TokenInstruction, TransferInstruction, } from "./generated/thru/program/token/types"; const instructionData = TokenInstruction.builder() .payload() .select("transfer") .writePayload( TransferInstruction.builder() .set_source_account_index(2) .set_dest_account_index(3) .set_amount(1_000_000) ) .finish() .build(); const tx = await thru.transactions.build({ feePayer: { publicKey: decodeAddress(feePayerAddress) }, program: "taAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqq", accounts: { readWrite: [sourceTokenAccount, destinationTokenAccount], readOnly: [], }, instructionData, }); const signedWire = await signTransaction(tx.toWireForSigning()); const signature = await thru.transactions.send(signedWire); ``` ## How It Works The program has two main account types: * mint accounts define token-wide metadata and authorities * token accounts hold balances for a specific owner and mint In typical client flows: 1. derive the mint or token account address 2. create the account with a state proof for the new address 3. submit lifecycle or balance-changing instructions against those accounts 4. read account state or emitted events to verify the new balances and authorities The Web SDK package mirrors this structure closely: it gives you address derivation helpers, instruction builders, and account parsers for the same program surface. ## Account Model | Account | What it stores | Notes | | --------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `TokenMintAccount` | `decimals`, `supply`, `creator`, `mint_authority`, `freeze_authority`, `has_freeze_authority`, `ticker` | The mint defines token-wide configuration and supply. | | `TokenAccount` | `mint`, `owner`, `amount`, `is_frozen` | Each token account is tied to one mint and one owner. | | `TokenProgramAccount` | Size-discriminated envelope over mint and token accounts | The ABI distinguishes `mint_account` and `token_account` by expected size. | ### Address Derivation Common client flows derive addresses deterministically: * mint address: from `mintAuthority + seed`, then PDA-derived under the Token Program * token account address: from `owner + mint + seed`, then PDA-derived under the Token Program ## Required Accounts Token instructions refer to transaction accounts by index: * lifecycle instructions include the mint, token account, authority, and sometimes owner account indices * creation flows also include a state proof for the new address * the same mint or authority may appear across multiple instructions in one transaction, so the final account list must stay stable If you are using the Web package, the generated builders and helpers keep the instruction payload aligned with the ABI field names and account-index model. ## Events | Event | What it means | | -------------------- | ------------------------------------------------------- | | `initialize_mint` | A new mint was created and configured. | | `initialize_account` | A new token account was created for a mint and owner. | | `transfer` | Tokens moved between two token accounts. | | `mint_to` | New supply was minted into a destination token account. | | `burn` | Supply was burned from a token account. | | `close_account` | A token account was closed and cleaned up. | | `freeze_account` | A token account was frozen by the freeze authority. | | `thaw_account` | A previously frozen token account was thawed. | ## Instructions | Instruction | Use it when | Notes | | -------------------- | ------------------------------------------------- | -------------------------------------------------------------- | | `initialize_mint` | Create a new fungible token mint | Includes ticker, decimals, authorities, seed, and state proof. | | `initialize_account` | Create a token account for an owner and mint | Includes the new account seed and state proof. | | `transfer` | Move tokens between token accounts | The common balance-changing path for user transfers. | | `mint_to` | Increase supply and credit a token account | Requires the mint authority. | | `burn` | Decrease supply and remove tokens from an account | Requires the account authority. | | `close_account` | Close a token account and reclaim its balance | Often used for cleanup once a balance is empty. | | `freeze_account` | Freeze a token account | Requires the freeze authority on the mint. | | `thaw_account` | Unfreeze a token account | Also requires the freeze authority. | ## Related References * [`@thru/programs`](/docs/sdks/web-packages/programs/) * [Build an Indexer](/docs/indexing/build-an-indexer/) * [Explorer MCP Overview](/docs/api-ref/explorer-mcp/overview/) ## On-Chain Link * Program explorer: [scan.thru.org/address/taAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqq](https://scan.thru.org/address/taAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqq) # Build an Indexer > Build a production-shaped Thru backend indexer with Postgres, resumable checkpoints, Drizzle tables, custom queries, and live validation. Use this guide to go from zero to a production-oriented Thru indexer. It starts with the smallest working setup and then walks through the concerns you will need for a real backend service. ## Prerequisites * Node 20+ and TypeScript * PostgreSQL * Drizzle for schema and migrations ## Install ```bash pnpm add @thru/indexer @thru/replay @thru/sdk @thru/programs postgres drizzle-orm pnpm add -D drizzle-kit tsx typescript ``` ## Step 1: Define A Token Transfer Event Stream Use the token ABI type to decode the token program event payload, then return one row per matching transfer. ```ts import { create } from "@bufbuild/protobuf"; import { decodeAddress, encodeAddress, encodeSignature } from "@thru/sdk/helpers"; import { defineEventStream, t } from "@thru/indexer"; import { FilterParamValueSchema, FilterSchema, type Event } from "@thru/replay"; import { TokenEvent } from "./abi/thru/program/token/types"; const TOKEN_PROGRAM = process.env.TOKEN_PROGRAM_ID!; const tokenTransfers = defineEventStream({ name: "token-transfers", description: "Transfer events emitted by the token program", schema: { id: t.text().primaryKey(), slot: t.bigint().notNull().index(), txnSignature: t.text().notNull(), source: t.text().notNull().index(), dest: t.text().notNull().index(), amount: t.bigint().notNull(), indexedAt: t.timestamp().notNull().defaultNow(), }, filterFactory: () => { const programBytes = new Uint8Array(decodeAddress(TOKEN_PROGRAM)); return create(FilterSchema, { expression: "event.program.value == params.address", params: { address: create(FilterParamValueSchema, { kind: { case: "bytesValue", value: programBytes }, }), }, }); }, parse: (event: Event) => { if (!event.payload || event.slot === undefined) return null; const envelope = TokenEvent.from_array(event.payload); const transfer = envelope?.payload()?.asTransfer(); if (!transfer) return null; return { id: event.eventId, slot: event.slot, txnSignature: encodeSignature(event.transactionSignature?.value ?? new Uint8Array()), source: encodeAddress(new Uint8Array(transfer.source.get_bytes())), dest: encodeAddress(new Uint8Array(transfer.dest.get_bytes())), amount: transfer.amount, indexedAt: new Date(), }; }, }); export const tokenTransferEvents = tokenTransfers.table; export default tokenTransfers; ``` ## Step 2: Define A Token Account Stream For token account state inside the indexer runtime, decode the raw account bytes directly with the token program account type. ```ts import { decodeAddress, encodeAddress } from "@thru/sdk/helpers"; import { defineAccountStream, t } from "@thru/indexer"; import { TokenAccount } from "@thru/programs/token"; const TOKEN_PROGRAM = process.env.TOKEN_PROGRAM_ID!; const TOKEN_ACCOUNT_SIZE = 73; const tokenAccounts = defineAccountStream({ name: "token-accounts", description: "Latest token account balances by address", ownerProgramFactory: () => new Uint8Array(decodeAddress(TOKEN_PROGRAM)), expectedSize: TOKEN_ACCOUNT_SIZE, schema: { address: t.text().primaryKey(), mint: t.text().notNull().index(), owner: t.text().notNull().index(), amount: t.bigint().notNull(), isFrozen: t.boolean().notNull(), slot: t.bigint().notNull(), seq: t.bigint().notNull(), updatedAt: t.timestamp().notNull().defaultNow(), }, parse: (account) => { if (account.data.length !== TOKEN_ACCOUNT_SIZE) return null; const parsed = TokenAccount.from_array(account.data); if (!parsed) return null; return { address: encodeAddress(account.address), mint: encodeAddress(new Uint8Array(parsed.mint.get_bytes())), owner: encodeAddress(new Uint8Array(parsed.owner.get_bytes())), amount: parsed.amount, isFrozen: parsed.is_frozen !== 0, slot: account.slot, seq: account.seq, updatedAt: new Date(), }; }, }); export const tokenAccountsTable = tokenAccounts.table; export default tokenAccounts; ``` ## Step 3: Export The Tables For Drizzle ```ts export { checkpointTable } from "@thru/indexer"; export { tokenAccountsTable } from "./account-streams/token-accounts"; export { tokenTransferEvents } from "./streams/token-transfers"; ``` Use those exports as your Drizzle schema input. ```ts import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./src/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL!, }, }); ``` ## Step 4: Create The Indexer Runtime ```ts import { Indexer } from "@thru/indexer"; import { ChainClient } from "@thru/replay"; import tokenAccounts from "./account-streams/token-accounts"; import { db } from "./db"; import tokenTransfers from "./streams/token-transfers"; const indexer = new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [tokenTransfers], accountStreams: [tokenAccounts], defaultStartSlot: 0n, safetyMargin: 64, pageSize: 512, logLevel: "info", }); await indexer.start(); ``` ## What You Get After this setup: * `token_transfer_events` stores immutable token transfer rows * `token_accounts` stores the latest token account state by address * `indexer_checkpoints` tracks resumable progress per stream ## Production Concerns The steps above give you a working indexer. The sections below cover what you need when you move that indexer into a real backend service. ### Separate Worker And API Processes In production, run the indexer worker and the API server as separate processes. This keeps write-heavy backfill work from competing with read traffic, and lets you scale or restart each side independently. A common layout: ```text src/ streams/ # shared stream definitions account-streams/ # shared account stream definitions schema.ts # shared Drizzle schema exports db.ts # shared database client worker.ts # indexer process, runs Indexer.start() queries/ # app-owned Drizzle query helpers api.ts # app-owned API process, if your backend exposes one ``` The worker owns the `Indexer` instance and calls `indexer.start()`. It writes rows and updates checkpoints. ```ts import { Indexer } from "@thru/indexer"; import { ChainClient } from "@thru/replay"; import tokenAccounts from "./account-streams/token-accounts"; import { db } from "./db"; import tokenTransfers from "./streams/token-transfers"; const indexer = new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [tokenTransfers], accountStreams: [tokenAccounts], }); process.on("SIGINT", () => indexer.stop()); process.on("SIGTERM", () => indexer.stop()); await indexer.start(); ``` The API process should query the Drizzle tables directly and own its response shape. ```ts import { desc, eq } from "drizzle-orm"; import { db } from "./db"; import { tokenAccountsTable, tokenTransferEvents } from "./schema"; export async function getRecentTransfersForOwner(owner: string) { const accounts = await db .select() .from(tokenAccountsTable) .where(eq(tokenAccountsTable.owner, owner)); const accountAddresses = new Set(accounts.map((account) => account.address)); const transfers = await db .select() .from(tokenTransferEvents) .orderBy(desc(tokenTransferEvents.slot)) .limit(200); return transfers.filter( (transfer) => accountAddresses.has(transfer.source) || accountAddresses.has(transfer.dest) ); } ``` ### Resumability The checkpoint table records progress per stream. On restart, the runtime resumes from the last stored checkpoint instead of replaying from the default start slot. Use a conservative `safetyMargin` so the worker stays behind the live tip during backfill-to-live handoff. Event streams checkpoint under the stream name. They resume from the checkpoint slot and, when available, the last processed event ID so same-slot events are not skipped after a partial commit. Account streams checkpoint under `account:${stream.name}`. They resume by `last_updated_slot` and persist the last account slot actually handled. A block boundary alone does not advance an account checkpoint if no account update was processed. ### Runtime Validation Enable runtime validation in development when you want parse outputs checked against generated schemas: ```ts const indexer = new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [tokenTransfers], accountStreams: [tokenAccounts], validateParse: process.env.NODE_ENV !== "production", }); ``` ### Health Checks Expose `indexer.getStatus()` from an internal admin endpoint or process health check. It reports runtime state, stream counters, last checkpoint slots, restart counts, stale markers, and normalized errors. ```ts const status = indexer.getStatus(); if (!status.healthy) { console.warn("indexer is unhealthy", status.streams); } ``` Configure `streamStaleMs` when your deployment should flag streams that remain running but stop receiving records for longer than expected. ### Custom SQL Because indexed data lands in ordinary Drizzle tables, you can query it with Drizzle or raw SQL through your database layer for analytics, reporting, joins, and aggregates. ## Related Pages * [Streams](/docs/indexing/streams/) * [Running the Indexer](/docs/indexing/running-the-indexer/) * [Querying Indexed Data](/docs/indexing/querying-indexed-data/) * [`@thru/indexer`](/docs/sdks/web-packages/indexer/) * [`@thru/replay`](/docs/sdks/web-packages/replay/) # Overview > Choose the right Thru indexing package and build Postgres-backed backend indexers with @thru/indexer, @thru/replay, and @thru/sdk. Use this section when you need historical or stateful chain data in a Postgres-backed shape instead of one-off RPC reads. Whether you are building a backend indexer, a read model, an ETL pipeline, or a historical replay service, this is the starting point. ## Use This When * you need a backend to keep a SQL view of on-chain events or account state * you want to query indexed chain data from backend code * you need filtering, joins, and pagination over chain data that would be awkward to do directly from RPC * you are building an analytics pipeline, read model, or historical replay service ## Choosing A Package | Package | Use it when | What it gives you | | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | [`@thru/sdk`](/docs/sdks/web-packages/sdk/) | You need direct chain reads, writes, simple streaming, or ad-hoc RPC queries without persistence. | Typed RPC client for accounts, transactions, blocks, events, and chain state. No storage or checkpointing. | | [`@thru/replay`](/docs/sdks/web-packages/replay/) | You need an ordered backfill-plus-live feed for analytics, ETL, or event processing without managed database writes. | Historical replay with live handoff, reconnect handling, and typed async iterators. You bring your own storage. | | [`@thru/indexer`](/docs/sdks/web-packages/indexer/) | You need a persistent backend indexer with Postgres storage and resumable checkpoints. | Stream definitions, Drizzle table generation, checkpoint management, and a runtime built on top of `@thru/replay`. | If you are building a backend indexer, start with `@thru/indexer`. If you only need an ordered feed and want full control over where and how data lands, use `@thru/replay` directly. If you only need current chain state without indexing history, `@thru/sdk` is sufficient. ## Core Packages | Package | Role | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | [`@thru/indexer`](/docs/sdks/web-packages/indexer/) | Defines streams, manages checkpoints, and writes rows to Postgres-backed Drizzle tables. | | [`@thru/replay`](/docs/sdks/web-packages/replay/) | Supplies the historical-plus-live replay layer and the `ChainClient` used by the indexer runtime. | ## The Shape Of A Thru Indexer Most projects follow this pattern: 1. define one or more event or account streams 2. export the resulting stream tables in the Drizzle schema 3. construct `new Indexer(...)` with a DB client, `ChainClient` factory, and stream arrays 4. query the Drizzle tables directly from your backend ## Start Here [Build an Indexer ](/docs/indexing/build-an-indexer/)Build a working token indexer and learn the production concerns for a real backend service. [Streams ](/docs/indexing/streams/)Learn how event streams and account streams differ, and how to write each safely. [Running the Indexer ](/docs/indexing/running-the-indexer/)See the runtime architecture, tech-stack assumptions, and the indexer boot flow. [Querying Indexed Data ](/docs/indexing/querying-indexed-data/)Query stream tables directly with Drizzle. ## Next Steps 1. Read [Build an Indexer](/docs/indexing/build-an-indexer/) for the first successful end-to-end setup and production guidance. 2. Move to [Streams](/docs/indexing/streams/) when you need to add or customize event or account parsing. 3. Use [Running the Indexer](/docs/indexing/running-the-indexer/) when you are wiring runtime config, checkpoints, migrations, or deployment shape. 4. Use [Querying Indexed Data](/docs/indexing/querying-indexed-data/) once data is landing in Postgres. ## Related * [Web SDKs Overview](/docs/sdks/web/) for the full list of web packages. * [`@thru/indexer` reference](/docs/sdks/web-packages/indexer/) for the complete indexer surface. * [`@thru/replay` reference](/docs/sdks/web-packages/replay/) for replay primitives and client details. # Querying Indexed Data > Query indexed Thru stream tables directly with Drizzle. Use this page when the indexer is already writing rows and you want to consume them from your app or internal tooling. This page covers app-owned queries over tables created by `@thru/indexer`; package-level replay behavior lives in the [`@thru/replay` reference](/docs/sdks/web-packages/replay/). ## Direct Table Queries Export the stream tables from your schema package, then query them with Drizzle like any other app table. ```ts import { desc, eq } from "drizzle-orm"; import { db } from "./db"; import { tokenAccountsTable, tokenTransferEvents } from "./schema"; const recentTransfers = await db .select() .from(tokenTransferEvents) .where(eq(tokenTransferEvents.dest, "ta...")) .orderBy(desc(tokenTransferEvents.slot)) .limit(20); const ownerBalances = await db .select() .from(tokenAccountsTable) .where(eq(tokenAccountsTable.owner, "ta...")); ``` ## Query Shape Keep product-specific query logic in your backend, not inside stream definitions. Stream definitions decide what rows exist. Repository functions and routes decide which rows a user can see, how they are joined, and how response objects are shaped. ```ts import { desc, eq, or } from "drizzle-orm"; import { db } from "./db"; import { tokenAccountsTable, tokenTransferEvents } from "./schema"; export async function getWalletActivity(accountAddress: string, limit = 25) { return await db .select({ id: tokenTransferEvents.id, slot: tokenTransferEvents.slot, amount: tokenTransferEvents.amount, source: tokenTransferEvents.source, dest: tokenTransferEvents.dest, sourceAmount: tokenAccountsTable.amount, }) .from(tokenTransferEvents) .leftJoin( tokenAccountsTable, eq(tokenTransferEvents.source, tokenAccountsTable.address), ) .where(or( eq(tokenTransferEvents.source, accountAddress), eq(tokenTransferEvents.dest, accountAddress), )) .orderBy(desc(tokenTransferEvents.slot), desc(tokenTransferEvents.id)) .limit(limit); } export async function getCurrentTokenAccounts(owner: string) { return await db .select() .from(tokenAccountsTable) .where(eq(tokenAccountsTable.owner, owner)); } ``` ## Cursor Pagination For user-facing history, prefer cursor pagination over offset pagination. Slots can contain multiple rows, so use a stable tie-breaker such as the event row `id` or another unique column from your stream schema. ```ts import { and, desc, eq, lt, or } from "drizzle-orm"; import { db } from "./db"; import { tokenTransferEvents } from "./schema"; interface TransferCursor { slot: bigint; id: string; } export async function listTransfersPage(options: { accountAddress: string; cursor?: TransferCursor; limit?: number; }) { const limit = options.limit ?? 50; const accountFilter = or( eq(tokenTransferEvents.source, options.accountAddress), eq(tokenTransferEvents.dest, options.accountAddress), ); const before = options.cursor ? and( accountFilter, or( lt(tokenTransferEvents.slot, options.cursor.slot), and( eq(tokenTransferEvents.slot, options.cursor.slot), lt(tokenTransferEvents.id, options.cursor.id), ), ), ) : accountFilter; const rows = await db .select() .from(tokenTransferEvents) .where(before) .orderBy(desc(tokenTransferEvents.slot), desc(tokenTransferEvents.id)) .limit(limit + 1); const page = rows.slice(0, limit); const next = rows.length > limit ? page.at(-1) : null; return { rows: page, nextCursor: next ? { slot: next.slot, id: next.id } : null, }; } ``` If your stream schema does not have a natural unique event ID, add one when you define the stream. Avoid paginating only by `slot` because that can skip or duplicate rows at slot boundaries. ## Freshness Indexed rows are eventually consistent with the chain endpoint used by the worker. If the API needs to expose freshness, read the stream checkpoint or runtime status from an internal endpoint and return the latest processed slot alongside the query response. ```ts import { getCheckpoint } from "@thru/indexer"; import { db } from "./db"; export async function getTransfersWithFreshness(accountAddress: string) { const [rows, checkpoint] = await Promise.all([ listTransfersPage({ accountAddress }), getCheckpoint(db, "token-transfers"), ]); return { ...rows, indexedThroughSlot: checkpoint?.slot ?? null, }; } ``` Event streams checkpoint under the stream name. Account streams checkpoint under `account:${stream.name}`. ## When To Query Tables Directly Direct queries are a good fit when: * the backend route is internal to your app * the query needs joins across multiple indexed tables * you need auth, aggregation, or business-specific response shapes * the result shape should evolve with your product rather than with stream definitions ## Practical Recommendation For most apps: 1. query tables directly inside the backend for core product endpoints 2. paginate by `(slot, uniqueRowId)` rather than offset 3. wrap common queries in small repository functions 4. keep auth, aggregation, and response shaping in application code 5. expose indexed freshness when stale reads matter to the product ## Related Pages * [Indexing Overview](/docs/indexing/overview/) * [Build an Indexer](/docs/indexing/build-an-indexer/) * [Streams](/docs/indexing/streams/) * [Running the Indexer](/docs/indexing/running-the-indexer/) * [`@thru/indexer`](/docs/sdks/web-packages/indexer/) * [`@thru/replay`](/docs/sdks/web-packages/replay/) * [Web SDKs Overview](/docs/sdks/web/) # Running the Indexer > Wire the Thru indexer runtime to Postgres, Drizzle, replay clients, and application-owned read paths. Use this page when the stream definitions are ready and you need to wire the runtime, database, migrations, and process shape. ## Architecture The typical Thru indexing stack looks like this: ```text Chain RPC -> @thru/replay ChainClient -> @thru/indexer runtime -> Postgres tables | v app-owned queries/routes ``` ## Tech Stack Assumptions The current `@thru/indexer` runtime assumes: * PostgreSQL-backed tables generated through Drizzle * a Drizzle database client passed as `db` * a `clientFactory` that returns a `ChainClient` from `@thru/replay` * Drizzle Kit or equivalent migration management * a standalone Node service to run the indexer continuously If you are not using Postgres plus Drizzle, start with the package reference for [`@thru/indexer`](/docs/sdks/web-packages/indexer/) and validate whether the current runtime fits your stack. ## Runtime Setup ```ts import { Indexer } from "@thru/indexer"; import { ChainClient } from "@thru/replay"; import { db } from "./db"; import tokenAccounts from "./account-streams/token-accounts"; import tokenTransfers from "./streams/token-transfers"; export function createIndexer() { return new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [tokenTransfers], accountStreams: [tokenAccounts], defaultStartSlot: 0n, safetyMargin: 64, pageSize: 512, logLevel: "info", }); } ``` ## What Each Runtime Option Does | Option | What it controls | | ---------------------------- | ---------------------------------------------------------------------------------------- | | `db` | The Drizzle client used for inserts, updates, and checkpoints. | | `clientFactory` | Fresh replay client creation for backfill and live streaming. | | `eventStreams` | Append-only streams for event rows. | | `accountStreams` | Current-state streams for account rows. | | `defaultStartSlot` | Starting slot when no checkpoint exists yet. | | `safetyMargin` | How far behind the live tip replay should stay during backfill-to-live switchover. | | `pageSize` | How many records to request per backfill page. | | `logLevel` | Runtime verbosity. | | `logger` | Structured logger used by the runtime, stream processors, and replay layer. | | `endpointLabel` | Human-readable endpoint label included in normalized stream errors. | | `supervisorInitialBackoffMs` | First stream-supervisor restart delay after an unexpected stream failure. | | `supervisorMaxBackoffMs` | Maximum stream-supervisor restart delay. | | `streamStaleMs` | Marks a running stream stale after this much time without activity. Disabled by default. | | `validateParse` | Validates parsed stream rows against generated Zod schemas. Useful in development. | ## Runtime Supervision `Indexer.start()` supervises every configured stream. If a stream fails or ends unexpectedly, the supervisor restarts it with backoff unless shutdown has been requested. `indexer.stop()` asks streams to stop gracefully; calling `stop()` a second time forces process exit. ```ts const indexer = createIndexer(); process.on("SIGINT", () => indexer.stop()); process.on("SIGTERM", () => indexer.stop()); await indexer.start(); ``` Use `supervisorInitialBackoffMs` and `supervisorMaxBackoffMs` to tune restart behavior for your deployment. ## Runtime Status Use `indexer.getStatus()` for process health checks, admin endpoints, and debugging. Status is in-memory runtime state; checkpoints remain the durable resume source. ```ts const status = indexer.getStatus(); console.log(status.running); console.log(status.healthy); console.log(status.streams.map((stream) => ({ name: stream.name, kind: stream.kind, state: stream.state, checkpointSlot: stream.checkpointSlot, lastProcessedSlot: stream.lastProcessedSlot, stale: stream.stale, restartCount: stream.restartCount, counters: stream.counters, lastError: stream.lastError, }))); ``` When `streamStaleMs` is configured, a running stream is marked `stale` if it has not seen activity since `lastEventAt` or `lastStartedAt`. Staleness is a signal for monitoring, not a restart trigger by itself. ## Checkpoints And Schema Your Drizzle schema needs the checkpoint table plus every stream table. ```ts export { checkpointTable } from "@thru/indexer"; export { tokenAccountsTable } from "./account-streams/token-accounts"; export { tokenTransferEvents } from "./streams/token-transfers"; ``` Without `checkpointTable`, the runtime cannot resume safely after restarts. ## Process Shape In practice, most apps run the indexer as its own long-lived service: 1. load environment and connect to Postgres 2. run or verify migrations 3. build the `Indexer` 4. call `await indexer.start()` 5. stop gracefully on `SIGTERM` or `SIGINT` ```ts const indexer = createIndexer(); process.on("SIGINT", () => indexer.stop()); process.on("SIGTERM", () => indexer.stop()); await indexer.start(); ``` ## Operations Runbook Run migrations before the worker starts. The schema must include `checkpointTable` and every stream table exported by your stream modules. If a new deploy changes a stream schema, deploy the migration before starting a worker that writes the new row shape. Expose `indexer.getStatus()` from an internal-only health endpoint or process supervisor hook. Treat `healthy: false`, a stream state other than `running`, a non-null `lastError`, or a growing `restartCount` as signals for operator attention. ```ts export function indexerHealthResponse(indexer: Indexer) { const status = indexer.getStatus(); return { ok: status.healthy, uptimeMs: status.uptimeMs, streams: status.streams.map((stream) => ({ name: stream.name, kind: stream.kind, state: stream.state, checkpointSlot: stream.checkpointSlot, lastProcessedSlot: stream.lastProcessedSlot, stale: stream.stale, restartCount: stream.restartCount, lastError: stream.lastError, })), }; } ``` Recommended alerts: * `healthy` stays false after startup * any stream is `retrying` for longer than the expected endpoint outage window * `restartCount` increases repeatedly * `stale` is true when `streamStaleMs` is configured * `lastError.phase` is `parse`, `filterBatch`, or `onCommit` Parser and hook errors are usually application issues. Backfill, live, commit, and supervisor errors are more likely to be endpoint, database, or process lifecycle issues. ## Checkpoint Resets Use checkpoint resets deliberately. A reset causes the affected stream to replay from `defaultStartSlot` or from the new checkpoint value you write manually. Make sure the target tables can tolerate replayed rows before resetting: event streams should have stable primary keys, and account streams should use slot-aware current-state rows. ```ts import { deleteCheckpoint, getAllCheckpoints, updateCheckpoint, } from "@thru/indexer"; import { db } from "./db"; console.table(await getAllCheckpoints(db)); // Replay one event stream from the configured defaultStartSlot. await deleteCheckpoint(db, "token-transfers"); // Replay one account stream from the configured defaultStartSlot. await deleteCheckpoint(db, "account:token-accounts"); // Or move a stream to a known-good slot after an operator decision. await updateCheckpoint(db, "token-transfers", 1_250_000n, null); ``` If the table contents are not idempotent for the replay window, truncate or delete the affected rows in the same maintenance window as the checkpoint reset. For event streams, keep the last event ID with the checkpoint when resuming inside a slot. For account streams, the checkpoint name is prefixed with `account:` and records the last handled account update slot. ## Development Validation Enable `validateParse` in development or staging when changing stream schemas. It validates `parse` output against the generated schema before rows are committed, which catches missing fields and type mismatches earlier. Leave it off for hot production paths unless the extra validation cost is acceptable. ## Reading The Results The indexer writes standard Drizzle tables. Query those tables directly from your backend or expose routes owned by your application. ```ts import { desc } from "drizzle-orm"; import { db } from "./db"; import { tokenTransferEvents } from "./schema"; export async function listRecentTransfers(limit = 50) { return await db .select() .from(tokenTransferEvents) .orderBy(desc(tokenTransferEvents.slot)) .limit(limit); } ``` ## Next Steps * Open [Querying Indexed Data](/docs/indexing/querying-indexed-data/) once rows are landing in Postgres. * See [Build an Indexer](/docs/indexing/build-an-indexer/) for production guidance on worker/API separation, resumability, and live validation. * See [`@thru/indexer`](/docs/sdks/web-packages/indexer/) and [`@thru/replay`](/docs/sdks/web-packages/replay/) for full package references. # Streams > Define event streams and account streams for Thru indexing, and understand when to use each one. Use this page when the indexer runtime is clear but you need to design or extend the actual streams. ## Event Streams Vs Account Streams | Stream type | Best for | Data model | | -------------- | ------------------------------------------------------------------------------------- | -------------------------------------------- | | Event stream | Immutable logs such as transfers, mints, fills, or lifecycle events | Append-only rows keyed by event identity | | Account stream | Current on-chain account state such as balances, configuration accounts, or inventory | Current-state rows keyed by account identity | Use an event stream when the chain emits the thing you want directly. Use an account stream when the important answer is “what is the latest state of this account now?” ## Event Stream Shape An event stream needs: * `name` * `schema` * `filter` or `filterFactory` * `parse(event)` It can also define `filterBatch(events, ctx)` and `onCommit(batch, ctx)` hooks for event-only workflows that need database-aware filtering or side effects. Token transfer example: ```ts import { create } from "@bufbuild/protobuf"; import { decodeAddress, encodeAddress, encodeSignature } from "@thru/sdk/helpers"; import { defineEventStream, t } from "@thru/indexer"; import { FilterParamValueSchema, FilterSchema } from "@thru/replay"; import { TokenEvent } from "./abi/thru/program/token/types"; const tokenTransfers = defineEventStream({ name: "token-transfers", schema: { id: t.text().primaryKey(), slot: t.bigint().notNull().index(), txnSignature: t.text().notNull(), source: t.text().notNull().index(), dest: t.text().notNull().index(), amount: t.bigint().notNull(), }, filterFactory: () => { const programBytes = new Uint8Array(decodeAddress(process.env.TOKEN_PROGRAM_ID!)); return create(FilterSchema, { expression: "event.program.value == params.address", params: { address: create(FilterParamValueSchema, { kind: { case: "bytesValue", value: programBytes }, }), }, }); }, parse: (event) => { if (!event.payload || event.slot === undefined) return null; const tokenEvent = TokenEvent.from_array(event.payload); const transfer = tokenEvent?.payload()?.asTransfer(); if (!transfer) return null; return { id: event.eventId, slot: event.slot, txnSignature: encodeSignature(event.transactionSignature?.value ?? new Uint8Array()), source: encodeAddress(new Uint8Array(transfer.source.get_bytes())), dest: encodeAddress(new Uint8Array(transfer.dest.get_bytes())), amount: transfer.amount, }; }, }); ``` ## Account Stream Shape An account stream needs: * `name` * `schema` * `ownerProgram` or `ownerProgramFactory` * optional `expectedSize` or `dataSizes` * `parse(account)` Token account example: ```ts import { decodeAddress, encodeAddress } from "@thru/sdk/helpers"; import { defineAccountStream, t } from "@thru/indexer"; import { TokenAccount } from "@thru/programs/token"; const tokenAccounts = defineAccountStream({ name: "token-accounts", ownerProgramFactory: () => new Uint8Array(decodeAddress(process.env.TOKEN_PROGRAM_ID!)), expectedSize: 73, schema: { address: t.text().primaryKey(), mint: t.text().notNull().index(), owner: t.text().notNull().index(), amount: t.bigint().notNull(), isFrozen: t.boolean().notNull(), slot: t.bigint().notNull(), seq: t.bigint().notNull(), }, parse: (account) => { if (account.data.length !== 73) return null; const parsed = TokenAccount.from_array(account.data); if (!parsed) return null; return { address: encodeAddress(account.address), mint: encodeAddress(new Uint8Array(parsed.mint.get_bytes())), owner: encodeAddress(new Uint8Array(parsed.owner.get_bytes())), amount: parsed.amount, isFrozen: parsed.is_frozen !== 0, slot: account.slot, seq: account.seq, }; }, }); ``` ## Practical Rules * Use `filterFactory` and `ownerProgramFactory` when values come from environment or config so migration tooling can still import the schema files safely. * Use `expectedSize` when the account layout is fixed and size mismatches should be skipped early. * Return `null` from `parse` when an event or non-delete account update should be ignored. Account delete updates are handled by the runtime before parsing and remove the row identified by `api.idField` or `address`. * Export the generated `.table` from every stream so Drizzle can include it in migrations. ## Event Hooks Use `filterBatch` when a parsed event needs to be filtered against current database state before insert. ```ts const tokenTransfers = defineEventStream({ name: "token-transfers", schema, filterFactory, parse, filterBatch: async (events, { db }) => { const allowed = await loadAllowedAccounts(db); return events.filter( (event) => allowed.has(event.source) || allowed.has(event.dest) ); }, }); ``` If `filterBatch` returns an empty array, the runtime still checkpoints the batch so already-seen events are not replayed forever. If `filterBatch` throws, the stream fails and the supervisor restarts it from the last durable checkpoint. Use `onCommit` for side effects after rows are inserted. ```ts const tokenTransfers = defineEventStream({ name: "token-transfers", schema, filterFactory, parse, onCommit: async (batch, { db }) => { await notifyTransferSubscribers(db, batch.events); }, }); ``` `onCommit` receives only rows that were actually inserted. Duplicate rows skipped by `onConflictDoNothing()` are not passed to the hook. Hook failures are logged and counted, but they do not block indexing. ## Account Stream Deletes Account streams use slot-aware upserts for normal updates and delete rows for account delete updates. The delete key is taken from `stream.api?.idField` and defaults to `address`, so custom account-stream primary keys should set `api.idField`. ```ts const tokenAccounts = defineAccountStream({ name: "token-accounts", ownerProgramFactory, expectedSize: TOKEN_ACCOUNT_SIZE, schema, parse, api: { idField: "address" }, }); ``` The account table must include a `slot` column for the runtime’s slot-aware upsert guard. Older account updates do not overwrite newer rows. ## Next Steps * Move to [Running the Indexer](/docs/indexing/running-the-indexer/) once the stream definitions exist. * See [Build an Indexer](/docs/indexing/build-an-indexer/) for a full end-to-end guide including production concerns. * See [`@thru/indexer`](/docs/sdks/web-packages/indexer/) and [`@thru/replay`](/docs/sdks/web-packages/replay/) for package reference details. # Cross Program Invocation > Use a minimal, compile-ready skeleton to wrap an instruction and forward it with tsys_invoke. This tutorial will give you a tiny, working scaffold that shows how to package instruction data and forward it to another program with `tsys_invoke`. The example compiles but leaves business logic as placeholders so you can extend it for your real payloads. ## Prerequisites * Thru C SDK installed (see [Quickstart: Build a C Program](/docs/program-development/building-a-c-program/)) * Basic familiarity with transaction [account indexing](/docs/core-concepts/accounts/) (`0` = fee payer, `1` = current program, `2+` = user-supplied accounts) ## Minimal instruction shape (CPI stub) The header defines a pared-down instruction struct: only indices plus a small payload that you can repurpose. Add or replace fields to match your program. programs/c/examples/tn\_cpi\_stub\_program.h ```c #ifndef HEADER_tn_programs_c_examples_tn_cpi_stub_program_h #define HEADER_tn_programs_c_examples_tn_cpi_stub_program_h #include #define TN_CPI_STUB_INSTRUCTION (0U) /* Tag understood by the callee */ #define TN_CPI_STUB_ERR_INVALID_INPUT (1UL) #define TN_CPI_STUB_ERR_UNSUPPORTED (2UL) /* Placeholder-friendly instruction forwarded to another program. */ typedef struct __attribute__((packed)) { uint instruction_type; ushort target_program_account_idx; ushort mint_account_idx; ushort authority_account_idx; uchar payload[8]; /* Replace with your real data */ } tn_cpi_stub_args_t; #endif /* HEADER_tn_programs_c_examples_tn_cpi_stub_program_h */ ``` ## Minimal entrypoint with tsys\_invoke This entrypoint validates indices, builds a tiny buffer, and forwards it. Swap the buffer layout for your real instruction body. programs/c/examples/tn\_cpi\_stub\_program.c ```c #include #include #include #include #include "tn_cpi_stub_program.h" TSDK_ENTRYPOINT_FN void start( void ) { tsdk_txn_t const * txn = tsdk_get_txn( ); uchar const * instruction_data = tsdk_txn_get_instr_data( txn ); ulong instruction_data_sz = tsdk_txn_get_instr_data_sz( txn ); if( instruction_data_sz < sizeof( tn_cpi_stub_args_t ) ) { tsdk_revert( TN_CPI_STUB_ERR_INVALID_INPUT ); } tn_cpi_stub_args_t const * args = (tn_cpi_stub_args_t const *)instruction_data; if( !tsdk_is_account_idx_valid( args->target_program_account_idx ) || !tsdk_is_account_idx_valid( args->mint_account_idx ) || !tsdk_is_account_idx_valid( args->authority_account_idx ) ) { tsdk_revert( TN_CPI_STUB_ERR_INVALID_INPUT ); } /* Minimal payload: tag + mint index + authority index + one byte */ uchar token_instr[1 + sizeof( ushort ) + sizeof( ushort ) + sizeof( uchar )]; ulong offset = 0UL; token_instr[offset] = TN_CPI_STUB_INSTRUCTION; offset++; memcpy( token_instr + offset, &args->mint_account_idx, sizeof( ushort ) ); offset += sizeof( ushort ); memcpy( token_instr + offset, &args->authority_account_idx, sizeof( ushort ) ); offset += sizeof( ushort ); token_instr[offset] = args->payload[0]; offset++; ulong invoke_err; ulong invoke_result = tsys_invoke( token_instr, offset, args->target_program_account_idx, NULL, &invoke_err ); if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result ); if( invoke_err != TSDK_SUCCESS ) tsdk_revert( invoke_err ); tsdk_return( TSDK_SUCCESS ); } ``` ## How the pieces fit * **Payload layout**: The buffer starts with a single-byte tag (`TN_CPI_STUB_INSTRUCTION`), followed by the two indices and the first payload byte. Replace this layout with your own wire format. * **Invocation**: `tsys_invoke` receives the buffer pointer, its length (`offset`), the target program account index, and an optional invoke-auth descriptor (`NULL` in this minimal example). Both syscall errors (`invoke_result`) and callee errors (`invoke_err`) are bubbled up with `tsdk_revert`. * **Account ordering reminder**: idx0 is the fee payer, idx1 is the current program; the rest are whatever you supply in the transaction. Make sure the indices you place in the struct match the order in the transaction header. Caution This example is deliberately minimal and does not perform real work. Always align your payload and account indices with the program you are calling, and validate any variable-length data before forwarding it. # Program Debugging > Learn how to debug Thru programs ## Replaying Transactions The `thru-replay` tool can be used to replay transactions run on Thru, and supports debugging with GDB. This can help you debug crashes and other issues in your program. ### Step 1: Build and run with debug information To make it easier to debug programs, you should build your program with debug information. * C/C++ In C and C++, you can do this with the `SDK_EXTRAS` environment variable: ```bash SDK_EXTRAS=debug make ``` * Rust In Rust, build the program in debug mode: ```bash cargo build ``` In addition to the `.bin` file, you’ll find a file at `target/thruvm/bin/.elf` that contains the program’s debug information. Make a note of this file for later use. After this, upload your program in debug mode to the network, and run a transaction. Note Note that this step is optional, and may not be possible if you are debugging a transaction that has already been run. However, it makes it significantly easier to debug your program. ### Step 2: Prepare the command to run with GDB Next, you’ll need to gather all the information to replay your program. You’ll need: * The transaction signature. It will look something like this: ```plaintext tsX2EQZzPxVgI23KmdAByeVj-yZ9JE-46q_AhjURj0vRnHdMRP3at6H_dVXDFdUtUwnW_ICnLnk0ROJWWdTIWbDh1P ``` * For each program in your transaction, you’ll need the program’s address as well as the path to the program’s debug information. For instance: ```plaintext tafhdJHH_OVQYN4-Cimt7Y1XclSytf9Ox_iuetV8CKmz=./build/thruvm/bin/tn_token_program_rust.elf ``` If you don’t have the debug information for a program, you can skip it. However, you won’t be able to view the source information for that program. ### Step 3: Run the command Now you can run the command to replay the transaction with GDB. You’ll need to pass the transaction signature, and for each program in your transaction, you’ll need to pass the program’s address and the path to the program’s debug information. ```bash thru-replay --gdb \ --signature $transaction-signature \ --elf $program-address-1=$program-debug-info-path-1 \ --elf $program-address-2=$program-debug-info-path-2 ... ``` For example: ```bash thru-replay --gdb \ --signature tsX2EQZzPxVgI23KmdAByeVj-yZ9JE-46q_AhjURj0vRnHdMRP3at6H_dVXDFdUtUwnW_ICnLnk0ROJWWdTIWbDh1P \ --elf tafhdJHH_OVQYN4-Cimt7Y1XclSytf9Ox_iuetV8CKmz=./build/thruvm/bin/tn_token_program_rust.elf ``` You should see some output like this. You’ll be in an interactive GDB session. ```bash Fetching transaction tsCRsKwMAupg5jUbus7GSeN8XgUS_lafAIo6YPUCfJW_zIYTgNJ5xCufxghWfiL1LGAPBWsCWHFHa0srws70WEAx38... Fetching 3 account states at slot 66 (pre-transaction)... Fetching state roots ending at slot 66... --log-path not specified; using autogenerated path Log at "/tmp/fd-0.0.0_3872496_user_machine-1_2025_12_16_17_38_45_883488412_GMT+00" Note: RW account ta1kDh1kmoJuRkoBChLLRk-ZOvzAHFNWNrK1iDCBJToaxR not in pre-tx state - will be created by transaction Starting debug server on port 9001... GDB connected! add symbol table from file "./build/thruvm/bin/tn_token_program_rust.elf" at .text_addr = 0x3000000 Reading symbols from ./build/thruvm/bin/tn_token_program_rust.elf... add symbol table from file "./build/thruvm/bin/tn_token_program_rust.elf" at .text_addr = 0x30001000000 Reading symbols from ./build/thruvm/bin/tn_token_program_rust.elf... Remote debugging using localhost:9001 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. 0x0000030001000000 in _start () (gdb) ``` You can debug the program as you would normally do with GDB: ```bash (gdb) break start Breakpoint 1 at 0x300006c: start. (2 locations) (gdb) c Continuing. Breakpoint 1.2, token_program::start (instr_data=0x0 $core::intrinsics::cold_path, instr_data_sz=0) at examples/token_program/main.rs:20 20 #[entry(stack_size = 8192)] ``` # SDK Overview > Choose the right Thru SDK for C, Rust, and web development. Use this section when you need to choose the right Thru SDK surface for your project. Source code can be found on [GitHub](https://github.com/Unto-Labs/thru). [C SDK ](/docs/sdks/c/)Build low-level Thru programs in C with the DevKit and program development guides. [Rust SDKs ](/docs/sdks/rust/)Integrate Thru into Rust services and tooling with typed crates and client libraries. [Web SDKs ](/docs/sdks/web/)Build browser and React applications on top of Thru RPC, wallet, and UI packages. ## Pick a path * Choose [C SDK](/docs/sdks/c/) when you are writing on-chain programs and want the lowest-level developer workflow. * Choose [Rust SDKs](/docs/sdks/rust/) when you are building typed backend services, automation, or Rust-based tools. * Choose [Web SDKs](/docs/sdks/web/) when you are building browser apps, React interfaces, or wallet-connected experiences. Need command reference instead of a package guide? Use the [CLI reference](/docs/cli-reference/overview/). ## Common entry points * [Set Up Thru DevKit](/docs/program-development/setting-up-thru-devkit/) * [Build a C Program](/docs/program-development/building-a-c-program/) * [CLI Overview](/docs/cli-reference/overview/) * [gRPC API](/docs/api-ref/grpc/overview/) ## Rust ### [`thru-base`](https://crates.io/crates/thru-base) Core Rust primitives for Thru, including transaction builders, proof utilities, cryptographic helpers, and shared data models. ### [`thru-grpc-client`](https://crates.io/crates/thru-grpc-client) Generated gRPC bindings for the Thru RPC services using tonic and prost, giving you strongly typed clients for every public endpoint. ### [`thru`](https://crates.io/crates/thru) The command-line interface for interacting with Thru nodes; it reuses the Rust crates above to query network data, manage keys, and upload programs. # Recommended Development Pattern > Follow the recommended agent-driven loop for writing, validating, deploying, testing, and upgrading Thru programs. Use this page when you are building a Thru program end to end with agents, not just writing one function or ABI file in isolation. Note This page is a workflow map, not the source of truth for exact command syntax or SDK APIs. Use it to choose the next step, then follow the linked SDK and CLI reference pages for implementation details. ## Start Here The typical flow looks like this: ```text set up toolchain -> write program and local tests -> write ABI -> roundtrip test ABI -> deploy program + ABI -> test on devnet -> debug and iterate -> upgrade ``` This page gives the workflow. Use the linked SDK and CLI reference pages when you need exact commands or API details. ## Use This Page To Route Use this page for: * deciding what to do next in the program development loop * understanding the required order of ABI validation, deployment, and testing * spotting the common failure points before they cost you time Do not use this page as the only source for: * exact CLI flags * exact ABI command syntax * exact C SDK API behavior * detailed runtime or VM semantics If you already know your task, jump directly to: * [Build a C Program](/docs/program-development/building-a-c-program/) for the program scaffold and binary layout * [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) for ABI proof loops * [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) for ABI publishing flows * [Program](/docs/cli-reference/program-commands/), [ABI](/docs/cli-reference/abi-commands/), [Transaction](/docs/cli-reference/transaction-commands/), and [Network](/docs/cli-reference/network-commands/) for exact CLI usage ## Prerequisites Before writing code, make sure the environment is ready: * [Setup the DevKit](/docs/program-development/setting-up-thru-devkit/) * [C SDK Overview](/docs/sdks/c/) * [CLI Overview](/docs/cli-reference/overview/) For most C program work, the important install steps are: ```bash npm i -g thru thru dev toolchain install thru dev sdk install c ``` ## Phase 1: Write the Program Start from the C SDK and the minimal program guides: * [Quickstart: Build a C Program](/docs/program-development/building-a-c-program/) * [Cross Program Invocation](/docs/program-development/cross-program-invocation/) * [C SDK Reference](/docs/sdks/c/) What tends to help most: * start from a small working scaffold * keep local C tests close to the program * validate account ordering and CPI assumptions early Stop here and fix the program before moving on if: * account ordering is still unclear * the program entrypoint or instruction layout is still changing rapidly * CPI authorization assumptions are not yet nailed down ### Authorization and CPI One recurring gotcha is that a correct wire format is not enough if the authorization model is wrong. This shows up most often around CPI: * the instruction bytes may be correct * but the program still fails because account access or CPI authorization is wrong Treat the authorization model as part of the program design, not part of the ABI. ## Phase 2: Write the ABI Move to the ABI guides once the instruction and account model is stable enough to describe: * [ABI Overview](/docs/abi/overview/) * [Authoring Guide](/docs/abi/authoring-guide/) * [Examples](/docs/abi/examples/) * [Specification](/docs/abi/specification/) Important reality: * ABIs are handwritten today * they do not stay in sync with the program automatically * agents should assume the ABI must be validated explicitly after every meaningful program change Do not publish or rely on the ABI yet. First prove that the handwritten schema matches the intended wire format. ## Phase 3: Validate the ABI roundtrip Before deployment, run the local proof loop: * [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) * [ABI Analyze](/docs/cli-reference/abi-analyze/) * [ABI Codegen](/docs/cli-reference/abi-codegen/) * [ABI Reflect](/docs/cli-reference/abi-reflect/) The high-value loop is: ```text write ABI -> codegen TypeScript -> build instruction data -> reflect it back ``` If the reflected payload does not match what you meant to send, stop and fix the ABI or the transaction-building code before deployment. This is the main guardrail for agents. It catches a large share of handwritten-ABI mistakes before any on-chain deployment happens. ## Phase 4: Deploy the Program and ABI When the program and ABI are both ready: ```bash thru program create my_program ./build/thruvm/bin/my_program_c.bin thru abi account create my_program ./program.abi.yaml ``` Important rule: * the ABI and program should use the same seed That seed match is what lets downstream tooling associate a deployed program with the ABI that should be used to reflect its data. Do not treat seed selection as a minor naming choice. Changing it breaks the expected linkage between the deployed program and its ABI. For the ABI-specific side of this step, use [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) and [ABI Account](/docs/cli-reference/abi-account/). ## Phase 5: Test Real On-Chain Behavior Beyond local C tests, meaningful program testing currently means interacting with a live chain, usually a devnet. Important constraints: * there is no localnet support yet * you need a devnet RPC URL * [Network](/docs/cli-reference/network-commands/) is the easiest CLI surface for managing endpoints Common practical flow: 1. use ABI-generated TypeScript to build instruction data 2. use [@thru/sdk](/docs/sdks/web-packages/sdk/) to submit the transaction 3. read transaction or account data back from the chain 4. reflect it with ABI tooling or inspect it in the explorer Keep this phase narrow: * verify the deployed program accepts the instruction bytes you think it accepts * verify the resulting account or event data reflects the way you expect * avoid inventing new ABI or program structure changes while debugging deployment behavior If the fee payer or wallet account is unfunded, fund it before retrying. On networks where the faucet program is available, use [`thru faucet`](/docs/cli-reference/faucet-commands/) to withdraw funds into the account you want to use for live writes. The explorer at [scan.thru.org](https://scan.thru.org) supports custom RPC endpoint configuration through query parameters, which is useful when debugging a non-default network. ## Phase 6: Debug Failures When deployment or testing fails, start by classifying the failure: * `-765` means something in the program threw * other failures are runtime or system errors For runtime and VM-side issues, see [runtime errors](/docs/spec/runtime/errors/). Common causes include: * invalid memory access * ABI drift between the program and the handwritten schema * CPI account ordering or authorization mistakes * partial deployment state left behind from a failed previous attempt Treat deployment failures and ABI mismatches separately: * if the payload shape is wrong, go back to ABI roundtrip validation * if the payload shape is right but execution still fails, inspect program logic, CPI authorization, memory access, and runtime errors ## Phase 7: Recover and Iterate If a create or upgrade flow fails partway through, cleanup is often necessary before retrying. The usual recovery tools are: * [Uploader](/docs/cli-reference/uploader-commands/) `cleanup` * [Program](/docs/cli-reference/program-commands/) `destroy` * [ABI Account](/docs/cli-reference/abi-account/) `close` If the program changes, rerun the ABI validation loop before you publish an updated ABI or send new transactions against the changed binary. When you are ready to ship a new binary: ```bash thru program upgrade my_program ./build/thruvm/bin/my_program_c.bin ``` If the ABI also changed, upgrade the ABI account too: * [ABI Account](/docs/cli-reference/abi-account/) ## Recommended Traversal For Agents 1. [Setup the DevKit](/docs/program-development/setting-up-thru-devkit/) 2. [Build a C Program](/docs/program-development/building-a-c-program/) 3. [ABI Overview](/docs/abi/overview/) 4. [Validation and roundtrip testing](/docs/abi/validation-and-roundtrip-testing/) 5. [Publishing and Iteration](/docs/abi/deployment-and-program-testing/) 6. [Program](/docs/cli-reference/program-commands/), [ABI](/docs/cli-reference/abi-commands/), and [Network](/docs/cli-reference/network-commands/) for exact commands # C SDK Overview > Build on-chain Thru programs in C with the SDK headers, syscall layer, and RISC-V toolchain. Use the C SDK when you want direct access to the Thru VM and the lowest-level program development workflow. ## Install Before using the C SDK, install the CLI and development prerequisites through [Set Up Thru DevKit](/docs/program-development/setting-up-thru-devkit/). Then make sure you have installed both the toolchain and the C SDK with `thru`: ```bash thru dev toolchain install thru dev sdk install c ``` This gives you the RISC-V toolchain plus the installed C SDK headers and build integration used by downstream C programs. After installation, the C SDK lands under `~/.thru/sdk/c`, and the downstream build hook lives at `~/.thru/sdk/c/thru-sdk/thru_c_program.mk`. ## Verify your install ```bash test -d ~/.thru/sdk/c test -f ~/.thru/sdk/c/thru-sdk/thru_c_program.mk test -d ~/.thru/sdk/toolchain/bin test -x ~/.thru/sdk/toolchain/bin/riscv64-unknown-elf-gcc || test -x ~/.thru/sdk/toolchain/bin/riscv64-none-elf-gcc ``` ## Start here | If you need… | Open this page | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | A compact mental model of how a program is laid out and exits | [Program Structure](/docs/sdks/c-reference/program-structure/) | | The rules of the Thru VM C environment and what differs from normal user-space C | [VM C Environment](/docs/sdks/c-reference/vm-c-environment/) | | Which header to include and which macros/helpers are foundational | [Headers and Entry Points](/docs/sdks/c-reference/headers-and-entry-points/) | | How to read accounts, transaction fields, block context, and shadow stack data | [Accounts and Transaction Context](/docs/sdks/c-reference/accounts-and-transaction-context/) | | What each syscall does and when to use it | [Syscalls](/docs/sdks/c-reference/syscalls/) | | How `tsys_invoke` and `tsdk_invoke_auth_t` work | [Cross-Program Invocation](/docs/sdks/c-reference/cross-program-invocation/) | | How proof headers and proof bodies are laid out | [State Proofs](/docs/sdks/c-reference/state-proofs/) | | How success, revert, syscall, and CPI errors should be handled | [Error Handling and Return Codes](/docs/sdks/c-reference/error-handling-and-return-codes/) | | How to wire the installed SDK into a downstream C project | [Build Integration](/docs/sdks/c-reference/build-integration/) | | Short, copyable program patterns | [Common Patterns](/docs/sdks/c-reference/common-patterns/) | | The main failure modes agents should avoid | [Common Gotchas](/docs/sdks/c-reference/common-gotchas/) | ## When to use it | Use the C SDK when you want to… | Why it fits | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | Write on-chain programs that run directly inside the Thru VM | The SDK exposes raw transaction context, account pointers, syscalls, and VM-oriented helpers. | | Control account mutation and CPI explicitly | The syscall layer gives you direct access to create, resize, transfer, compress, decompress, and invoke operations. | | Minimize abstraction and stay close to runtime layout | The public structs and helper functions mirror transaction, account, block, shadow stack, and state proof shapes closely. | ## Core entry points | Entry point | What it provides | | ---------------------------------------- | ------------------------------------------------------------------------------------ | | `#include ` | Main umbrella include for most programs. | | `#include ` | Explicit syscall wrappers for account mutation, CPI, logging, events, and exit. | | `#include ` | Transaction, account metadata, block context, shadow stack, and state proof structs. | | `thru_c_program.mk` | The downstream makefile entrypoint for compiling against the installed SDK. | ## Additional headers | Header | What it provides | | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `#include ` | SHA-256 hashing (incremental and one-shot). | | `#include ` | BLS12-381 cryptography: key generation, signing, verification, aggregation. Available when the installed toolchain includes `libblst.a` and headers. | | `#include ` | Run-length encoding for compact bitset representation. | | `#include ` | Base public types (`tn_pubkey_t`, `tn_hash_t`, `tn_signature_t`). | | `#include ` | Low-level alignment, scratch-allocation, and utility macros. | ## Related guides * [Set Up Thru DevKit](/docs/program-development/setting-up-thru-devkit/) * [Build a C Program](/docs/program-development/building-a-c-program/) * [Cross-program invocation](/docs/program-development/cross-program-invocation/) * [VM syscalls overview](/docs/spec/vm/syscalls/overview/) * [Runtime overview](/docs/spec/runtime/overview/) # Accounts and Transaction Context > Read transaction fields, account metadata, block context, and shadow-stack data from the C SDK. Use this page when you need to inspect the current transaction, understand account indexing, or safely read account and block metadata. ## Transaction root Most in-program reads start from: ```c tsdk_txn_t const * txn = tsdk_get_txn( ); ``` From there, the inline helpers in `tn_sdk_txn.h` expose the fields you usually care about. ## Transaction helper reference | Helper | What it returns | | ----------------------------------------------- | ------------------------------------------------------------- | | `tsdk_txn_get_instr_data(txn)` | Pointer to instruction bytes. | | `tsdk_txn_get_instr_data_sz(txn)` | Instruction byte length. | | `tsdk_txn_get_msg(txn)` | Message portion of the transaction. | | `tsdk_txn_get_msg_sz(txn_sz)` | Message size excluding trailing signature. | | `tsdk_txn_get_fee_payer_signature(txn, txn_sz)` | Pointer to the trailing fee-payer signature. | | `tsdk_txn_get_fee(txn)` | Requested fee. | | `tsdk_txn_get_nonce(txn)` | Transaction nonce. | | `tsdk_txn_get_start_slot(txn)` | First valid slot. | | `tsdk_txn_get_expiry_slot(txn)` | Last valid slot derived from `start_slot + expiry_after`. | | `tsdk_txn_get_chain_id(txn)` | Chain identifier. | | `tsdk_txn_get_requested_compute_units(txn)` | Requested compute units. | | `tsdk_txn_get_requested_memory_units(txn)` | Requested memory units. | | `tsdk_txn_has_fee_payer_state_proof(txn)` | Whether a fee-payer proof is present. | | `tsdk_txn_get_acct_addrs(txn)` | Pointer to fee payer, program, and remaining account pubkeys. | | `tsdk_txn_account_cnt(txn)` | Total account count exposed to the transaction. | | `tsdk_txn_readwrite_account_cnt(txn)` | Read-write account count from the header. | | `tsdk_txn_readonly_account_cnt(txn)` | Read-only account count from the header. | | `tsdk_txn_is_account_idx_writable(txn, idx)` | Whether the account index is writable in the transaction. | `tsdk_txn_get_msg_sz(txn_sz)` is the one helper in this group that only needs the total transaction size. It computes `message size = txn_sz - TN_TXN_SIGNATURE_SZ`, so it does not need the transaction pointer. ## Account indexing The public helpers assume transaction account indexing like this: | Index range | Meaning | | ----------- | ----------------------------------------------- | | `0` | Fee payer | | `1` | Current program account | | `2+` | User-supplied read-write and read-only accounts | Use `tsdk_is_account_idx_valid(idx)` before indexing transaction-linked account data. ## Account metadata `tsdk_get_account_meta(account_idx)` returns a `tsdk_account_meta_t const *` with: | Field | Meaning | | --------- | ---------------------------------------------------------- | | `version` | Whether the metadata is present and versioned. | | `flags` | Bitfield of account properties (see flag constants below). | | `data_sz` | Account data size. | | `seq` | Sequence number. | | `owner` | Owner program pubkey. | | `balance` | Native token balance. | | `nonce` | Account nonce. | `tsdk_account_exists(account_idx)` is a convenient existence check built on top of metadata version. ## Account flag constants The `flags` field on `tsdk_account_meta_t` is a bitfield. Test individual flags with bitwise AND: | Constant | Value | Meaning | | ---------------------------------- | ------ | --------------------------------------------------------- | | `TSDK_ACCOUNT_FLAG_PROGRAM` | `0x01` | Account contains executable program code. | | `TSDK_ACCOUNT_FLAG_PRIVILEGED` | `0x02` | Account has privileged system-level access. | | `TSDK_ACCOUNT_FLAG_UNCOMPRESSABLE` | `0x04` | Account cannot be compressed. | | `TSDK_ACCOUNT_FLAG_EPHEMERAL` | `0x08` | Account exists only for the current transaction. | | `TSDK_ACCOUNT_FLAG_DELETED` | `0x10` | Account has been deleted. | | `TSDK_ACCOUNT_FLAG_NEW` | `0x20` | Account has never existed before this transaction. | | `TSDK_ACCOUNT_FLAG_COMPRESSED` | `0x40` | Account state is compressed (removed from active ledger). | ```c tsdk_account_meta_t const * meta = tsdk_get_account_meta( account_idx ); if( meta->flags & TSDK_ACCOUNT_FLAG_PROGRAM ) { /* account is a program */ } ``` Use `tsys_account_set_flags(account_idx, flags)` to modify flags on accounts owned by the current program. ## Account data pointers `tsdk_get_account_data_ptr(account_idx)` returns a raw pointer into the VM account-data segment. Typical pattern: ```c ushort account_idx = 2U; if( !tsdk_is_account_idx_valid( account_idx ) ) tsdk_revert( 1UL ); if( !tsdk_account_exists( account_idx ) ) tsdk_revert( 2UL ); void * data = tsdk_get_account_data_ptr( account_idx ); tsdk_account_meta_t const * meta = tsdk_get_account_meta( account_idx ); ``` ## Block context | Helper | Use it for | | ----------------------------------------- | ------------------------------------------------------------------------------------------ | | `tsdk_get_current_block_ctx()` | Current slot, block time, block price, state root, current block hash, and block producer. | | `tsdk_get_past_block_ctx(blocks_in_past)` | Access a previous block context spaced by `TSDK_BLOCK_CTX_VM_SPACING`. | The block context struct is `tsdk_block_ctx_t`. ## Shadow stack and current program identity | Helper | Use it for | | ----------------------------------------------- | -------------------------------------------------------------------------------- | | `tsdk_get_shadow_stack()` | Read call depth and CPI frame information. | | `tsdk_get_current_program_acc_idx()` | Resolve the current program’s transaction account index. | | `tsdk_get_current_program_acc_addr()` | Resolve the current program’s pubkey. | | `tsdk_is_program_reentrant()` | Detect whether the current program already appears lower in the call chain. | | `tsdk_is_account_authorized_by_idx(...)` | Check effective authorization by account index, including CPI auth/deauth state. | | `tsdk_is_account_authorized_by_pubkey(...)` | Check effective authorization by pubkey. | | `tsdk_is_account_owned_by_current_program(...)` | Check whether the current program owns an account. | ## Notes * Do not assume every account index is valid just because the instruction payload contains it. * Metadata and data pointers come from VM-managed memory; treat them as borrowed pointers, not owned allocations. * Writable status comes from transaction context and syscalls, not from the presence of a data pointer alone. # Build Integration > Wire the installed C SDK into a downstream project with the CLI-installed toolchain and makefiles. Use this page when you need to compile a C program against the installed SDK instead of just reading header-level reference docs. ## Prerequisites Start with [Set Up Thru DevKit](/docs/program-development/setting-up-thru-devkit/), then install the C SDK surfaces with: ```bash thru dev toolchain install thru dev sdk install c ``` The CLI installs the C SDK under `~/.thru/sdk/c` and the toolchain under `~/.thru/sdk/toolchain`. ## Verify the installed layout ```bash test -d ~/.thru/sdk/c test -f ~/.thru/sdk/c/thru-sdk/thru_c_program.mk test -d ~/.thru/sdk/toolchain/bin test -x ~/.thru/sdk/toolchain/bin/riscv64-unknown-elf-gcc || test -x ~/.thru/sdk/toolchain/bin/riscv64-none-elf-gcc ``` The downstream makefiles auto-detect either `riscv64-unknown-elf-` or `riscv64-none-elf-`, so either compiler prefix is valid. ## Main build entrypoint The installed downstream build hook is `thru_c_program.mk`. Its job is to: * require `THRU_C_SDK_DIR` * add SDK include paths through `CPPFLAGS` * add SDK library paths through `LDFLAGS` * include machine and config files for the Thru VM toolchain ## Minimal `GNUmakefile` ```makefile THRU_C_SDK_DIR := $(HOME)/.thru/sdk/c/thru-sdk include $(THRU_C_SDK_DIR)/thru_c_program.mk ``` ## Minimal `Local.mk` ```makefile $(call make-bin,my_program_c,my_program,,-ltn_sdk) ``` ## What `THRU_C_SDK_DIR` should point to Point it at the installed SDK root that contains: * `thru_c_program.mk` * `include/` * `lib/` * `config/` For the default CLI install, that means: ```makefile THRU_C_SDK_DIR := $(HOME)/.thru/sdk/c/thru-sdk ``` ## Project layout that works well ```text my-program/ ├── GNUmakefile └── examples/ ├── Local.mk ├── my_program.c └── my_program.h ``` ## Build-system guidance | Need | Recommendation | | ------------------------- | ------------------------------------------------------------------------ | | One or two small programs | Keep a single `GNUmakefile` plus per-directory `Local.mk` files. | | Multiple example binaries | Add more `make-bin` calls in `Local.mk`. | | SDK extras | Use `SDK_EXTRAS` when you intentionally want extra configuration layers. | ## Expected build output For the standard downstream project layout used in the C guide, the built binary lands under: ```text build/thruvm/bin/ ``` That is the path shape the rest of the docs currently assume for deployment examples. ## Notes * The docs page is the right place to learn the installed entrypoint. The repo-local `sdks/c/setup.sh` is not the end-user install path you should default to. * If a task is “build a downstream program,” start with `thru_c_program.mk`, not the SDK’s internal `GNUmakefile`. # Common Gotchas > Avoid the highest-probability mistakes when implementing Thru C programs. Use this page when you want the shortest list of things most likely to break an implementation. ## 1. Treat `tsdk_return` and `tsdk_revert` as terminal They both end execution through `tsys_exit`. Any code after them is unreachable. ## 2. Do not trust account indices from instruction payloads Always run `tsdk_is_account_idx_valid(...)` before reading metadata, data pointers, or issuing syscalls with an account index taken from instruction bytes. ## 3. A data pointer is not the same thing as writable permission `tsdk_get_account_data_ptr(...)` gives you an address, not permission. If you intend to mutate data, the account usually needs to be writable in context, and many flows should call `tsys_set_account_data_writable(...)` first. ## 4. `tsys_invoke` has two failure channels Check both: * the syscall return value * the callee error in `invoke_err_code` Ignoring one of them causes misleading CPI behavior. ## 5. Auth entries for CPI must be owned by the current program The wrapper validates this before the syscall and can revert locally if you authorize an account your program does not own. ## 6. State proofs are variable-size Do not use `sizeof( tsdk_state_proof_t )` as the received proof length. Read the header and call `tsdk_state_proof_footprint_from_header(...)`. ## 7. Transaction and proof structs are layout-sensitive Several public structs are packed or represent wire-aligned layouts. Avoid casual casting from arbitrary byte buffers without checking size and alignment assumptions. ## 8. `tsdk_printf` is for debugging, not unlimited output It formats into a fixed local buffer and ultimately logs through `tsys_log`. ## 9. Prefer helper accessors over manual VM address math The segment constants and `TSDK_ADDR(...)` are public, but most tasks are safer with helpers like `tsdk_get_txn`, `tsdk_get_account_meta`, and `tsdk_get_current_block_ctx`. ## 10. Start with `tn_sdk.h` unless the task is clearly lower level Agents can easily overcomplicate C SDK usage by pulling in too many low-level headers too early. The umbrella include is the right default. ## 11. Build against the installed SDK, not the repo-local SDK internals For end-user workflows, prefer: * `thru dev toolchain install` * `thru dev sdk install c` * `thru_c_program.mk` not direct reliance on repo-local setup scripts. ## 12. Keep entrypoints thin Large monolithic entrypoints make it harder for both humans and agents to reason about validation and mutation ordering. Validate early, dispatch quickly, and push real logic into helpers. # Common Patterns > Copyable C SDK patterns for instruction validation, account mutation, CPI, logging, and derived addresses. Use this page when you want a small, copyable pattern rather than a full API tour. ## Validate the instruction header first ```c if( instr_sz < sizeof( my_instr_hdr_t ) ) tsdk_revert( 0x1000UL ); ``` ## Validate account indices before dereferencing ```c if( !tsdk_is_account_idx_valid( account_idx ) ) { tsdk_revert( 0x1001UL ); } ``` ## Read transaction instruction data once ```c tsdk_txn_t const * txn = tsdk_get_txn( ); uchar const * instr = tsdk_txn_get_instr_data( txn ); ulong instr_sz = tsdk_txn_get_instr_data_sz( txn ); ``` ## Make account data writable before mutation ```c if( tsys_set_account_data_writable( account_idx ) != TSDK_SUCCESS ) { tsdk_revert( 0x1002UL ); } ``` ## Resize before writing a larger account payload ```c if( tsys_account_resize( account_idx, sizeof( my_account_t ) ) != TSDK_SUCCESS ) { tsdk_revert( 0x1003UL ); } ``` ## Read and write packed account data carefully ```c my_account_t * account = (my_account_t *)tsdk_get_account_data_ptr( account_idx ); account->counter++; ``` If alignment is uncertain because you are reading from instruction bytes rather than account storage, prefer `TSDK_LOAD` and `TSDK_STORE`. ## Bubble up CPI failures explicitly ```c ulong invoke_err = 0UL; ulong invoke_result = tsys_invoke( payload, payload_sz, target_program_idx, NULL, &invoke_err ); if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result ); if( invoke_err != TSDK_SUCCESS ) tsdk_revert( invoke_err ); ``` ## Emit human-readable logs ```c tsdk_printf( "counter=%lu", counter_value ); ``` ## Emit raw event payloads ```c if( tsys_emit_event( event_bytes, event_sz ) != TSDK_SUCCESS ) { tsdk_revert( 0x1004UL ); } ``` ## Derive a program-defined address ```c tn_pubkey_t derived[1]; tsdk_create_program_defined_account_address( owner, 0U, seed, derived ); ``` ## Parse proof tails safely ```c if( remaining_sz < sizeof( tsdk_state_proof_hdr_t ) ) tsdk_revert( 0x1005UL ); tsdk_state_proof_hdr_t const * hdr = (tsdk_state_proof_hdr_t const *)proof_ptr; ulong proof_sz = tsdk_state_proof_footprint_from_header( hdr ); if( remaining_sz < proof_sz ) tsdk_revert( 0x1006UL ); ``` ## Notes * The most reliable flow is: validate sizes, validate indices, validate ownership or auth, then mutate or invoke. * If a page elsewhere shows a bigger end-to-end example, use this page as the short-form reference while implementing. # Cross-Program Invocation > Use tsys_invoke and tsdk_invoke_auth_t safely for CPI flows in C. Use this page when you need to call another program from a C program and want the smallest correct model for account auth, deauth, and error handling. ## Main entrypoint ```c ulong tsys_invoke( void const * instr_data, ulong instr_data_sz, ushort program_account_idx, tsdk_invoke_auth_t const * auth, ulong * invoke_err_code ); ``` ## What each argument means | Argument | Meaning | | --------------------- | ------------------------------------------------------------------------ | | `instr_data` | Pointer to the callee instruction payload. | | `instr_data_sz` | Payload size in bytes. | | `program_account_idx` | Transaction account index of the program being invoked. | | `auth` | Optional authorization descriptor for CPI account auth and deauth rules. | | `invoke_err_code` | Optional output for the callee’s own error code. | ## `tsdk_invoke_auth_t` layout The public auth descriptor is a header plus a flexible array: | Field | Meaning | | ------------ | ---------------------------------------------------------------------------- | | `magic` | Must equal `TSDK_INVOKE_AUTH_MAGIC`. | | `auth_cnt` | Number of authorized account indices at the start of `acc_idxs`. | | `deauth_cnt` | Number of deauthorized account indices after the auth entries. | | `acc_idxs[]` | First `auth_cnt` authorized indices, then `deauth_cnt` deauthorized indices. | ## Validation the wrapper performs before CPI If `auth` is non-null, the wrapper checks: * the magic value is correct * every auth entry references a valid account index * every auth entry is owned by the current program * every deauth entry references a valid account index If any of those checks fail, the wrapper reverts before issuing the syscall. ## Minimal no-auth pattern ```c ulong invoke_err = 0UL; ulong invoke_result = tsys_invoke( payload, payload_sz, target_program_idx, NULL, &invoke_err ); if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result ); if( invoke_err != TSDK_SUCCESS ) tsdk_revert( invoke_err ); ``` ## Minimal auth descriptor pattern ```c struct my_invoke_auth { ulong magic; ushort auth_cnt; ushort deauth_cnt; ushort acc_idxs[1]; }; struct my_invoke_auth auth = { .magic = TSDK_INVOKE_AUTH_MAGIC, .auth_cnt = 1U, .deauth_cnt = 0U, .acc_idxs = { writable_account_idx }, }; ``` Pass `&auth` as the `auth` argument to `tsys_invoke`. ## How authorization actually resolves The current SDK’s authorization helpers walk the shadow stack from the most recent parent frame downward: * deeper frames override shallower ones * deauth entries are checked before auth entries * auth entries are only accepted if the invoking frame’s program owns the account * fee payer and current program remain implicitly authorized ## CPI checklist Before calling `tsys_invoke`, verify: * `program_account_idx` is valid * the callee payload layout matches what the target program expects * every account index embedded in the payload matches the transaction ordering * any auth entries refer only to accounts owned by the current program * you handle both `invoke_result` and `invoke_err` ## Notes * For most first-pass CPI work, start with `auth = NULL`; only add explicit auth when the callee needs it. * Do not collapse the two error channels from `tsys_invoke` into one check. * The easiest CPI bugs come from mismatched account indices, not from the syscall callsite itself. # Error Handling and Return Codes > Handle success, revert codes, syscall statuses, and CPI error channels consistently in Thru C programs. Use this page when you need one compact reference for how success and failure are represented in the C SDK. ## Success baseline `TSDK_SUCCESS` is the canonical success code and is defined as `0UL`. Use it for: * successful `tsdk_return(...)` * success checks after most syscalls * success checks on the callee error channel returned through `tsys_invoke` ## Local program failure Use `tsdk_revert(error_code)` when your program detects an invalid condition and wants to terminate with a local error. Typical examples: * instruction payload too short * invalid account index * unexpected instruction tag * failed precondition before a syscall ## Successful termination Use `tsdk_return(return_code)` when the program has completed successfully and wants to exit. In most application-level code, the return code should be `TSDK_SUCCESS`. ## Syscall error pattern Most syscalls return a single `ulong` status: ```c ulong rc = tsys_account_resize( account_idx, 128UL ); if( rc != TSDK_SUCCESS ) tsdk_revert( rc ); ``` ## CPI error pattern `tsys_invoke` returns two different statuses: * return value: syscall-level result * `invoke_err_code`: callee-level result Use both: ```c ulong invoke_err = 0UL; ulong invoke_result = tsys_invoke( payload, payload_sz, target_program_idx, NULL, &invoke_err ); if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result ); if( invoke_err != TSDK_SUCCESS ) tsdk_revert( invoke_err ); ``` ## Wrapper-level CPI validation failures The current C wrapper can revert before issuing the CPI syscall if the auth descriptor is malformed or invalid. Examples include: * bad auth magic * invalid account indices in auth or deauth lists * auth entries for accounts not owned by the current program ## Recommended error strategy | Situation | Recommended handling | | -------------------------- | ----------------------------------------------------------------------- | | Bad instruction layout | Revert with a local program error code. | | Invalid index or ownership | Revert with a local program error code. | | Failed syscall | Revert with the syscall return code, unless you intentionally remap it. | | Failed CPI callee | Revert with `invoke_err`, unless you intentionally remap it. | ## Notes * Do not mix up program-level validation errors with runtime or syscall return codes. * Do not ignore the second result channel from `tsys_invoke`. * Prefer a small local error-code namespace per program for validation failures, then bubble up syscall and CPI runtime codes directly when that is the clearest behavior. # Headers and Entry Points > Choose the right C SDK header and understand the macros and helpers that define program entry. Use this page when you need to know which header to include, which macros are foundational, and what the public C entry surface actually is. ## Include selection | Include | Use it when | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `#include ` | Default starting point for almost every downstream C program. It pulls in the base macros, transaction helpers, and core runtime helpers. | | `#include ` | You need explicit syscall wrappers such as account mutation, CPI, logging, event emission, or direct exit. | | `#include ` | You need the public struct layouts for transactions, account metadata, block context, shadow stack, or state proofs. | | `#include ` | You only need base public types like `tn_pubkey_t`, `tn_hash_t`, or `tn_signature_t`. | | `#include ` | You need the low-level alignment, scratch-allocation, or utility macros directly. Most programs can stay at `tn_sdk.h`. | | `#include ` | You need SHA-256 hashing for address derivation, data integrity, or custom hashing logic. | | `#include ` | You need BLS12-381 cryptography: key generation, signing, verification, or signature aggregation. Use it when the installed toolchain includes `libblst.a` and headers. | | `#include ` | You need run-length encoding for compact bitset representation (e.g., guardian sets, validator bitmaps). | ## Main entry macro `TSDK_ENTRYPOINT_FN` marks a function as the VM entrypoint by placing it in the startup text section and marking it `noreturn`. ```c #include TSDK_ENTRYPOINT_FN void start( void ) { tsdk_return( TSDK_SUCCESS ); } ``` ## Core macros | Macro | What it does | | ------------------------------------------ | ------------------------------------------------------------------ | | `TSDK_ENTRYPOINT_FN` | Marks the entrypoint function. | | `TSDK_SUCCESS` | Canonical success return code (`0UL`). | | `TSDK_ASSERT_OR_REVERT(cond, error_code)` | Revert immediately if a required condition is false. | | `TSDK_LOAD(T, src)` | Safely load a value of type `T` from potentially unaligned memory. | | `TSDK_STORE(T, dst, val)` | Safely store a value of type `T` to potentially unaligned memory. | | `TSDK_PARAM_UNUSED` | Silence unused-parameter warnings in helper functions. | | `TSDK_LIKELY` and `TSDK_UNLIKELY` | Branch prediction hints. | | `TSDK_LAYOUT_*` and `TSDK_SCRATCH_ALLOC_*` | Helpers for computing and carving out scratch memory regions. | ## Core runtime helpers from `tn_sdk.h` | Helper | Purpose | | ----------------------------------------- | ----------------------------------------------------------------------- | | `tsdk_get_txn()` | Root pointer for transaction reads. | | `tsdk_get_account_meta(account_idx)` | Account metadata for a transaction account index. | | `tsdk_get_account_data_ptr(account_idx)` | Raw account-data pointer for a transaction account index. | | `tsdk_get_current_block_ctx()` | Current block context pointer. | | `tsdk_get_past_block_ctx(blocks_in_past)` | Historical block context pointer spaced by `TSDK_BLOCK_CTX_VM_SPACING`. | | `tsdk_get_shadow_stack()` | Shadow stack pointer for CPI and call-depth context. | | `tsdk_printf(...)` | Format a string and emit it through `tsys_log`. | | `tsdk_return(code)` | Exit successfully with a return code. | | `tsdk_revert(code)` | Exit unsuccessfully with a revert code. | ## Segment and address constants Most applications do not need to build VM addresses manually, but the SDK exposes the segment constants that back helpers like `tsdk_get_txn()` and `tsdk_get_account_meta()`: * `TSDK_SEG_TYPE_READONLY_DATA` * `TSDK_SEG_TYPE_ACCOUNT_METADATA` * `TSDK_SEG_TYPE_ACCOUNT_DATA` * `TSDK_SEG_TYPE_STACK` * `TSDK_SEG_TYPE_HEAP` * `TSDK_SEG_IDX_TXN_DATA` * `TSDK_SEG_IDX_SHADOW_STACK` * `TSDK_SEG_IDX_PROGRAM` * `TSDK_SEG_IDX_BLOCK_CTX` If you are not doing very low-level VM work, prefer the helper functions over manual `TSDK_ADDR(...)` usage. ## SHA-256 hashing helpers from `tn_sdk_sha256.h` | Helper | Purpose | | ----------------------------------- | ------------------------------------------------------------------ | | `tsdk_sha256_init(sha)` | Initialize a `tsdk_sha256_t` context for incremental hashing. | | `tsdk_sha256_append(sha, data, sz)` | Append `sz` bytes to the hash context. Returns `sha` for chaining. | | `tsdk_sha256_fini(sha, hash)` | Finalize and write the 32-byte digest to `hash`. | | `tsdk_sha256_hash(data, sz, hash)` | One-shot: hash `sz` bytes and write the 32-byte digest to `hash`. | ```c #include tsdk_sha256_t sha[1]; tsdk_sha256_init( sha ); tsdk_sha256_append( sha, data, data_len ); uchar hash[32]; tsdk_sha256_fini( sha, hash ); ``` ## BLS cryptography helpers from `tn_crypto.h` The Thru VM machine config includes the `with-blst.mk` layer automatically. In practice, `tn_crypto.h` is available when the installed toolchain ships `libblst.a` and the matching headers. | Helper | Purpose | | ------------------------------------------------------------------------------- | --------------------------------------------------------- | | `tn_crypto_generate_keypair(pubkey, private_key, seed)` | Generate a BLS keypair from a seed. | | `tn_crypto_derive_pubkey(pubkey, private_key)` | Derive a public key from a private key. | | `tn_crypto_sign_message(signature, message, message_len, private_key)` | Sign a message. | | `tn_crypto_verify_signature(signature, pubkey, message, message_len)` | Verify a single signature. | | `tn_crypto_aggregate_signatures(aggregate, sig1, sig2)` | Aggregate two signatures. | | `tn_crypto_aggregate_pubkeys(aggregate, pk1, pk2)` | Aggregate two public keys. | | `tn_crypto_verify_aggregate(aggregate_sig, aggregate_pk, message, message_len)` | Verify an aggregate signature. | | `tn_crypto_serialize_pubkey(serialized, pubkey)` | Serialize a G1 pubkey to 96-byte uncompressed format. | | `tn_crypto_deserialize_pubkey(pubkey, serialized)` | Deserialize from 96-byte uncompressed format. | | `tn_crypto_serialize_signature(serialized, signature)` | Serialize a G2 signature to 192-byte uncompressed format. | | `tn_crypto_deserialize_signature(signature, serialized)` | Deserialize from 192-byte uncompressed format. | All functions return `TN_CRYPTO_SUCCESS` (0) on success or a negative error code. ## RLE helpers from `tn_rle.h` Run-length encoding for compact bitset representation. | Helper | Purpose | | ----------------------------------------------------------- | ----------------------------------------------------- | | `tn_rle_footprint(max_runs)` | Memory footprint in bytes for an RLE with `max_runs`. | | `tn_rle_new(shmem, max_runs)` | Initialize an RLE structure in pre-allocated memory. | | `tn_rle_encode(rle, max_runs, bitset, bit_count)` | Encode a `ulong` bitset into RLE format. | | `tn_rle_decode(rle, bitset, max_bits, out_bit_count)` | Decode RLE back to a `ulong` bitset. | | `tn_rle_decode_bytes(rle, bitset, max_bits, out_bit_count)` | Decode RLE to a byte-oriented bitset. | | `tn_rle_iter_init(iter, rle)` | Initialize a memory-efficient iterator over set bits. | | `tn_rle_iter_next_set(iter, max_bits)` | Return the next set bit index, or `TN_RLE_ITER_DONE`. | ## Notes * Start with `tn_sdk.h` unless the task clearly requires lower-level syscall or struct work. * Prefer `TSDK_LOAD` and `TSDK_STORE` when reading or writing fields out of byte buffers with uncertain alignment. * Treat `tsdk_return` and `tsdk_revert` as terminal control-flow points, not ordinary function calls. * For downstream user code, prefer the installed include path form `` consistently. * The additional headers (`tn_sdk_sha256.h`, `tn_crypto.h`, `tn_rle.h`) are opt-in. Most programs only need `tn_sdk.h` and `tn_sdk_syscall.h`. # Program Structure > Understand the minimal shape of a Thru C program and the control flow an agent should expect. Use this page when you need the smallest reliable mental model for how a C program starts, reads input, mutates state, and exits. ## Minimal program shape ```c #include TSDK_ENTRYPOINT_FN void start( void ) { tsdk_txn_t const * txn = tsdk_get_txn( ); if( tsdk_txn_get_instr_data_sz( txn ) == 0UL ) { tsdk_revert( 1UL ); } tsdk_return( TSDK_SUCCESS ); } ``` ## Execution model | Step | What normally happens | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Enter | The VM jumps to a function marked with `TSDK_ENTRYPOINT_FN`. | | Read inputs | The program reads transaction and account context through helpers like `tsdk_get_txn`, `tsdk_get_account_meta`, and `tsdk_get_account_data_ptr`. | | Decode | Instruction bytes are read from the transaction body, usually through `tsdk_txn_get_instr_data` and `tsdk_txn_get_instr_data_sz`. | | Validate | Programs check instruction size, account indices, ownership, authorization, and any variable-length payload boundaries before mutating state. | | Mutate or invoke | Programs call syscalls such as `tsys_account_resize`, `tsys_account_transfer`, or `tsys_invoke`. | | Exit | Programs terminate with `tsdk_return(...)` for success or `tsdk_revert(...)` for failure. | ## Recommended file split | File | Purpose | | ---------------- | ------------------------------------------------------------------------------------------------- | | `your_program.h` | Instruction tags, error codes, packed payload structs, account-data structs, and local constants. | | `your_program.c` | Entry point, validation, instruction dispatch, syscall usage, and local helper functions. | This split keeps payload layout definitions easy to reuse without forcing an agent to reread the whole implementation file. ## Typical instruction dispatch pattern ```c #include typedef struct __attribute__(( packed )) { uint instruction_type; } my_instr_hdr_t; TSDK_ENTRYPOINT_FN void start( void ) { tsdk_txn_t const * txn = tsdk_get_txn( ); uchar const * instr = tsdk_txn_get_instr_data( txn ); ulong instr_sz = tsdk_txn_get_instr_data_sz( txn ); if( instr_sz < sizeof( my_instr_hdr_t ) ) tsdk_revert( 0x1000UL ); my_instr_hdr_t const * hdr = (my_instr_hdr_t const *)instr; switch( hdr->instruction_type ) { case 0U: /* handle create */ break; case 1U: /* handle update */ break; default: tsdk_revert( 0x1001UL ); } tsdk_return( TSDK_SUCCESS ); } ``` ## Control-flow rules * `tsdk_return` does not come back. * `tsdk_revert` does not come back. * `tsys_exit` underlies both helpers and is also terminal. * If you need cleanup, do it before calling either exit helper. ## Common structure choices | Goal | Common choice | | --------------------------- | ------------------------------------------------------------------------------------- | | Small fixed instruction | One packed struct with a leading tag. | | Variable-length instruction | Fixed header plus byte tail, with explicit size checks before casting or copying. | | Stateful program | One or more packed account-data structs plus helper functions to read and write them. | | CPI-heavy program | Entry point plus small helpers to build invoke payloads and auth descriptors. | ## Notes * Prefer `tsdk_get_txn()` as the root of most program reads. * Prefer a local header for your instruction/account layouts so the implementation page can stay focused on logic. * Keep the entrypoint thin: validate, dispatch, return. # State Proofs > Understand the C SDK state proof structs, proof types, and the sizing helpers needed for variable-length proofs. Use this page when you need to carry, inspect, or size state proofs in C instruction payloads. ## Public proof types The C SDK exposes: * `tsdk_state_proof_hdr_t` * `tsdk_state_proof_t` * `tsdk_state_proof_header_type(...)` * `tsdk_state_proof_header_slot(...)` * `tsdk_state_proof_footprint_from_header(...)` All of these live in `tn_sdk_txn.h`. ## Header layout `tsdk_state_proof_hdr_t` contains: | Field | Meaning | | ------------- | -------------------------------------------------------- | | `type_slot` | Top 2 bits encode proof type; lower 62 bits encode slot. | | `path_bitset` | Bitset used to derive sibling-hash count. | The helper functions decode this packed header: | Helper | Meaning | | --------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `tsdk_state_proof_header_type(hdr)` | Returns the proof type encoded in the top 2 bits. | | `tsdk_state_proof_header_slot(hdr)` | Returns the slot encoded in the lower 62 bits. | | `tsdk_state_proof_footprint_from_header(hdr)` | Returns the actual byte footprint of the full proof payload based on type and sibling-hash count. | ## Proof body variants The full `tsdk_state_proof_t` body covers three logical shapes: | Variant | Layout | | -------- | ------------------------------------------------------------- | | Existing | Sibling hashes only | | Updating | Existing leaf hash plus sibling hashes | | Creation | Existing leaf pubkey, existing leaf hash, then sibling hashes | The public struct is intentionally oversized to cover the max shape. Real proof payloads are often shorter. ## Most important sizing rule Do not assume `sizeof( tsdk_state_proof_t )` is the size of the proof payload you received. Instead: 1. read a `tsdk_state_proof_hdr_t` 2. call `tsdk_state_proof_footprint_from_header(...)` 3. validate the instruction buffer contains that many bytes ## Minimal parsing pattern ```c if( instr_sz < sizeof( tsdk_state_proof_hdr_t ) ) tsdk_revert( 1UL ); tsdk_state_proof_hdr_t const * hdr = (tsdk_state_proof_hdr_t const *)proof_bytes; ulong proof_sz = tsdk_state_proof_footprint_from_header( hdr ); if( instr_sz < proof_offset + proof_sz ) tsdk_revert( 2UL ); tsdk_state_proof_t const * proof = (tsdk_state_proof_t const *)proof_bytes; ``` ## Where proofs usually show up | Use case | Related syscall or pattern | | --------------------------- | --------------------------------------------------- | | Account creation | `tsys_account_create` | | Compression | `tsys_account_compress` | | Decompression | `tsys_account_decompress` | | Custom instruction payloads | Packed instruction header plus trailing proof bytes | ## Notes * Proofs are one of the most layout-sensitive parts of the SDK. * Always validate the available byte count before casting proof tails. * Use the header helper, not `sizeof`, to reason about how many bytes the proof consumes. # Syscalls > Reference the public C syscall wrappers for state mutation, logging, CPI, memory, and exit. Use this page when you need the low-level mutation and control operations exposed by `tn_sdk_syscall.h`. ## Return convention Most syscall wrappers return a `ulong` status code in `a0`. The main exception is `tsys_invoke`, which has two result channels: * return value: syscall-level success or failure * `invoke_err_code`: callee-level success or failure `tsys_exit` never returns. ## Account and balance syscalls ### `tsys_set_account_data_writable` ```c ulong tsys_set_account_data_writable( ulong account_idx ); ``` Mark an account’s data segment as writable. Must be called before writing to `tsdk_get_account_data_ptr(...)`. Returns `TSDK_SUCCESS` on success. ### `tsys_account_transfer` ```c ulong tsys_account_transfer( ulong from_account_idx, ulong to_account_idx, ulong amount ); ``` Transfer `amount` native tokens from one account to another. Both indices must be valid and the source account must be authorized. ### `tsys_account_create` ```c ulong tsys_account_create( ulong account_idx, uchar const seed[TN_SEED_SIZE], void const * proof, ulong proof_sz ); ``` Create a program-defined account. The 32-byte `seed` determines the account address deterministically. The `proof` is a state proof validating the account does not already exist. ### `tsys_account_create_ephemeral` ```c ulong tsys_account_create_ephemeral( ulong account_idx, uchar const seed[TN_SEED_SIZE] ); ``` Create a transaction-scoped ephemeral account. No state proof required because ephemeral accounts do not persist across transactions. ### `tsys_account_create_eoa` ```c ulong tsys_account_create_eoa( ulong account_idx, tn_signature_t const * signature, void const * proof, ulong proof_sz ); ``` Create an externally owned account (EOA). Requires a valid signature and state proof. ### `tsys_account_delete` ```c ulong tsys_account_delete( ulong account_idx ); ``` Delete an account. The account must be owned by the current program. ### `tsys_account_resize` ```c ulong tsys_account_resize( ulong account_idx, ulong new_size ); ``` Resize an account’s data region to `new_size` bytes. Maximum size is `TSDK_ACCOUNT_DATA_SZ_MAX` (16 MB). The account must already be writable. ### `tsys_account_set_flags` ```c ulong tsys_account_set_flags( ushort account_idx, uchar flags ); ``` Update the account’s flags byte. See [Accounts and Transaction Context](/docs/sdks/c-reference/accounts-and-transaction-context/) for flag constants. ### `tsys_account_compress` ```c ulong tsys_account_compress( ulong account_idx, void const * proof, ulong proof_sz ); ``` Compress an account’s state, removing it from the active ledger while preserving a proof of its existence. ### `tsys_account_decompress` ```c ulong tsys_account_decompress( ulong account_idx, void const * meta, void const * data, void const * proof, ulong proof_sz ); ``` Decompress a previously compressed account by providing its metadata, data payload, and validity proof. ## Invocation, logging, and exit ### `tsys_invoke` ```c ulong tsys_invoke( void const * instr_data, ulong instr_data_sz, ushort program_account_idx, tsdk_invoke_auth_t const * auth, ulong * invoke_err_code ); ``` Cross-program invocation. Returns a syscall-level status in the return value and a callee-level status in `invoke_err_code`. Pass `NULL` for `auth` when no explicit authorization is needed. See [Cross-Program Invocation](/docs/sdks/c-reference/cross-program-invocation/) for details. ### `tsys_log` ```c ulong tsys_log( void const * data, ulong data_len ); ``` Emit raw log bytes. `tsdk_printf` is a formatted wrapper around this (capped at 1024 bytes per call). ### `tsys_emit_event` ```c ulong tsys_emit_event( void const * data, ulong data_sz ); ``` Emit an event payload that downstream consumers can read from the transaction result. ### `tsys_exit` ```c ulong __attribute__(( noreturn )) tsys_exit( ulong exit_code, ulong revert ); ``` Terminate execution. If `revert` is non-zero, the transaction state is rolled back. Prefer `tsdk_return` or `tsdk_revert` unless you need direct control. ## Anonymous segment syscalls These are lower-level memory-management syscalls that most programs will not need immediately. ### `tsys_set_anonymous_segment_sz` ```c ulong tsys_set_anonymous_segment_sz( void * addr ); ``` Set the anonymous (heap) segment size to `addr`. ### `tsys_increment_anonymous_segment_sz` ```c ulong tsys_increment_anonymous_segment_sz( void * segment_addr, ulong delta, void ** addr ); ``` Grow an anonymous segment by `delta` bytes and optionally receive the resulting address in `addr`. ## Typical mutation flow ```c ushort account_idx = 2U; if( !tsdk_is_account_idx_valid( account_idx ) ) tsdk_revert( 1UL ); if( tsys_set_account_data_writable( account_idx ) != TSDK_SUCCESS ) { tsdk_revert( 2UL ); } if( tsys_account_resize( account_idx, 128UL ) != TSDK_SUCCESS ) { tsdk_revert( 3UL ); } ``` ## Practical guidance * Call `tsys_set_account_data_writable` before writing account bytes. * Validate account indices before every syscall that takes an index from instruction data. * Treat proof-carrying syscalls as layout-sensitive. Validate sizes before forwarding raw payload tails. * Use `tsdk_printf` for human-readable debugging and `tsys_emit_event` for machine-consumed event payloads. ## Notes * `tsys_invoke` is the only wrapper here that can both return a syscall status and a callee status. * The C wrapper performs extra auth validation before issuing the CPI syscall, so bad auth descriptors can revert locally before the callee runs. # VM C Environment > Understand the freestanding execution model, memory model, and runtime constraints of C programs on the Thru VM. Use this page when you need to reason about what kind of C environment a Thru program actually runs in. ## What environment you are in Thru C programs run inside the Thru VM, not as normal hosted user-space processes. That means: * there is no conventional `main` * there is no argv or environment-variable interface * transaction, account, block, and shadow-stack data come from VM-mapped segments * control flow ends through `tsdk_return`, `tsdk_revert`, or `tsys_exit` ## Memory model Most of the public SDK reads are borrowed pointers into VM-managed memory: * `tsdk_get_txn()` * `tsdk_get_account_meta(...)` * `tsdk_get_account_data_ptr(...)` * `tsdk_get_current_block_ctx()` * `tsdk_get_shadow_stack()` Treat these as borrowed views over runtime-managed memory, not heap allocations you own. ## Hosted vs freestanding assumptions The SDK is designed for freestanding builds targeting RISC-V. The toolchain links against [picolibc](https://github.com/picolibc/picolibc), a lightweight C library for embedded targets. What the build files clearly establish: * programs are compiled as freestanding C, not hosted user-space binaries * the Thru VM machine config uses a picolibc sysroot * normal process and OS facilities are not part of the program model What the build files do **not** guarantee: * that every familiar libc helper is available in every installed toolchain * that POSIX-style APIs are present * that you can treat the environment like a normal Linux process Practical takeaway: * prefer SDK helpers first * assume extra libc usage needs to be validated against the installed toolchain * keep dependencies on non-SDK runtime facilities narrow and explicit ### Facilities you should not assume exist | Category | Notes | | ----------------------- | -------------------------------------------------------------------------------------------- | | File I/O | No `fopen`, `fread`, `printf` (use `tsdk_printf` instead) | | Dynamic memory | No `malloc`, `free`, `calloc`, `realloc` | | Process/OS | No `exit`, `fork`, `getenv`, `signal` | | Networking | No sockets or network calls | | Threads | No `pthread`, no concurrency primitives | | Hosted runtime behavior | No argv, environment variables, filesystem process model, or normal process startup/teardown | ### Floating-point expectations The default Thru VM build target does not enable the RISC-V floating-point ISA extensions in its `-march` flags. Practical takeaway: * do not assume a full normal libc or POSIX environment * prefer SDK helpers and simple explicit code * be careful with patterns that rely on hosted-process facilities * prefer integer math unless you have validated that floating-point code is appropriate for your target and toolchain * use stack allocation or the anonymous segment syscalls for temporary memory ## Temporary memory For simple programs, stack allocation is often enough. If you need structured scratch-space carving, the SDK exposes: * `TSDK_LAYOUT_INIT` * `TSDK_LAYOUT_APPEND` * `TSDK_LAYOUT_FINI` * `TSDK_SCRATCH_ALLOC_INIT` * `TSDK_SCRATCH_ALLOC_APPEND` * `TSDK_SCRATCH_ALLOC_FINI` These are safer than ad hoc pointer arithmetic when you need multiple aligned temporary regions. ## Limits | Limit | Value | Defined as | | --------------------- | ------------------- | ----------------------------- | | Max account data size | 16 MB | `TSDK_ACCOUNT_DATA_SZ_MAX` | | `tsdk_printf` buffer | 1024 bytes per call | Internal fixed buffer | | Max CPI call depth | 17 frames | `TSDK_SHADOW_STACK_FRAME_MAX` | ## Logging and output There is no normal stdout or stderr channel in the usual process sense. Use: * `tsdk_printf(...)` for formatted debugging output (capped at 1024 bytes per call) * `tsys_log(...)` for raw log bytes * `tsys_emit_event(...)` for event payloads intended for downstream consumers ## Alignment and raw byte parsing When reading from raw instruction bytes or any layout with uncertain alignment: * prefer `TSDK_LOAD` * prefer `TSDK_STORE` Direct casts are easier to get wrong in variable-length or tightly packed payloads. ## Notes * If a task sounds like “normal C application code,” the C SDK is probably the wrong layer. * If a task sounds like “VM entrypoint, account mutation, CPI, proof parsing, or instruction decoding,” this environment model is the right one. # External Signers > Integrate custody providers, hardware security modules, embedded wallets, or backend signers with Thru transaction signing. Use this page when your app does not sign Thru transactions directly in the first-party wallet UI. Typical external signer integrations include: * custody providers * HSM or KMS-backed signing services * backend transaction services * custom wallet adapters * embedded wallet providers ## Mental Model Treat the Thru transaction lifecycle as four separate steps: 1. Build the transaction payload. 2. Hand the signing payload to the external signer. 3. Receive the signed wire transaction back. 4. Submit and track the transaction with Thru RPC. Caution External signers are responsible for the outer Thru transaction signature only. Some programs, such as the Passkey Manager Program, may also require an inner authorization payload, but that does not replace the outer transaction signature. ## Canonical Flow For external signers, the recommended SDK flow is: 1. Build the transaction with `thru.transactions.build(...)`. 2. Produce a signing payload with `tx.toWireForSigning()`. 3. Pass that serialized payload to your signer. 4. Receive the signed wire transaction from the signer. 5. Submit it with `thru.transactions.send(...)` or `thru.transactions.sendAndTrack(...)`. This mirrors a generic external-signer boundary. The hosted browser wallet uses a higher-level `signTransaction(intent)` contract where the wallet builds and signs the final transaction from a program instruction intent. ## What The Signer Must Preserve Once a signing payload has been produced, the signer should treat the transaction contents as fixed. In particular, do not mutate: * fee payer public key * program public key * account ordering * instruction data * nonce * chain ID * validity window fields such as `start_slot` and `expiry_after` * requested resource limits If any of those values need to change, rebuild the transaction and generate a new signing payload. ## Generic Signing Contract For custom external signers, a common contract is: * input: base64-encoded serialized transaction payload * output: signed base64-encoded wire transaction payload Thru transaction headers include fields such as: * `fee_payer_signature` * `nonce` * `start_slot` * `expiry_after` * `fee_payer_pubkey` * `program_pubkey` * `chain_id` In practice, this means the external signer should be given the final signing payload after the app or backend has already selected the fee payer, program, accounts, instruction bytes, and validity settings. ## Generic TypeScript Example ```ts import { createThruClient } from "@thru/sdk/client"; import { decodeAddress } from "@thru/sdk/helpers"; type ExternalSigner = { signTransaction: (serializedTransaction: string) => Promise; }; const thru = createThruClient({ baseUrl: "https://rpc.alphanet.thru.org", }); async function sendWithExternalSigner( signer: ExternalSigner, feePayerAddress: string, programAddress: string, readWriteAccounts: string[], readOnlyAccounts: string[], instructionData: Uint8Array ) { const tx = await thru.transactions.build({ feePayer: { publicKey: decodeAddress(feePayerAddress) }, program: programAddress, accounts: { readWrite: readWriteAccounts, readOnly: readOnlyAccounts, }, instructionData, }); const serialized = tx.toWireForSigning(); const signedWire = await signer.signTransaction(serialized); return await thru.transactions.sendAndTrack(signedWire); } ``` ## When To Use `buildAndSign` Use `transactions.buildAndSign(...)` only when your signing implementation already lives inside your Thru wallet or SDK layer. If the signer is outside that layer, prefer: 1. `transactions.build(...)` 2. `tx.toWireForSigning()` 3. external `signTransaction(...)` 4. `transactions.send(...)` That keeps the signing boundary explicit and makes it easier to integrate a custody or backend signing service. ## Passkey-Managed Flows Passkey-managed flows add an inner authorization step, but the outer signing model stays the same. The high-level sequence is: 1. Fetch or derive the wallet nonce. 2. Build the trailing instruction payload you want the wallet to authorize. 3. Create the passkey challenge from the nonce, ordered accounts, and trailing instruction bytes. 4. Produce the `validate` instruction from the WebAuthn result. 5. Concatenate `validate` and the trailing instruction payload. 6. Build the outer Thru transaction. 7. Sign the outer transaction with the external signer. 8. Submit the signed wire transaction. Tip Use `@thru/programs/passkey-manager` when you need helpers for wallet address derivation, account ordering, nonce fetching, challenge generation, or `validate` instruction encoding. ## Common Failure Modes Watch for these issues when integrating an external signer: * stale fee payer nonce * expired transaction validity window * wrong chain ID * mismatched fee payer key * account reordering after the signing payload was generated * modifying instruction data after the signer has already approved the payload * confusing inner program authorization with the outer Thru transaction signature ## Recommended Integration Boundary For most teams, the cleanest split looks like this: * app or backend: resolves accounts, instruction bytes, fee payer, and validity rules * external signer: signs the serialized transaction payload * app or backend: submits the signed wire transaction and tracks status This keeps policy, custody, and audit responsibilities inside the signer while leaving chain-specific transaction assembly in the Thru integration layer. ## Related References * [Web SDKs Overview](/docs/sdks/web/) * [@thru/sdk](/docs/sdks/web-packages/sdk/) * [@thru/wallet](/docs/sdks/web-packages/wallet/) * [@thru/programs](/docs/sdks/web-packages/programs/) * [Passkey Manager Program](/docs/core-programs/passkey-manager-program/) * [Transaction message reference](/docs/api-ref/grpc/messages/thru/core/v1/transaction/) # Rust SDKs Overview > Build Rust backends, services, and tooling on top of Thru transaction, proof, and gRPC crates. Use the Rust SDKs when you are building Rust services, CLIs, automation, or backend infrastructure on top of Thru. ## Start here * Start with [`thru-base`](/docs/sdks/rust-packages/thru-base/) if you need Thru keys, addresses, transaction builders, proof helpers, or shared Rust-side types. * Use [`thru-grpc-client`](/docs/sdks/rust-packages/thru-grpc-client/) if you want the generated tonic and prost bindings for the public Thru gRPC services. * Reach for a higher-level wrapper such as `thru-client` when you want ergonomic RPC helpers instead of raw generated service clients. ## Packages | Package | Description | Useful when | | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | [`thru-base`](/docs/sdks/rust-packages/thru-base/) | Core Rust primitives for Thru, including key and address types, transaction builders, state proof helpers, and RPC config types. | Building transactions, deriving program addresses, validating Thru addresses, or working with state proofs in Rust services and tooling. | | [`thru-grpc-client`](/docs/sdks/rust-packages/thru-grpc-client/) | Generated tonic and prost bindings for the Thru public gRPC surface. | Calling Query, Command, Debug, or Streaming services directly from Rust while staying close to the wire format. | ## Related guides * [gRPC API overview](/docs/api-ref/grpc/overview/) # thru-base > Core Rust primitives for Thru, including key and address types, transaction builders, state proof helpers, and RPC config types. Use `thru-base` when you need the shared Rust building blocks behind Thru accounts, transactions, address handling, and proof workflows. ## Install ```bash cargo add thru-base ``` ## When to use it Choose this crate when you want to: * Generate or load Thru keypairs and validate Thru-formatted addresses and signatures. * Build raw Thru transactions in Rust, including transfers, account operations, token flows, uploader flows, and program-management instructions. * Derive deterministic program, uploader, and manager account addresses. * Encode, decode, or inspect state proofs and related proof metadata. * Reuse shared request and filter types around Rust-side RPC tooling. ## Choose another crate when * You want to call Thru gRPC services directly: use `thru-grpc-client`. * You want a more ergonomic RPC client instead of raw primitives plus generated service types: use a higher-level wrapper such as `thru-client`. ## Entry points | Module or export | What it provides | | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `thru_base::tn_tools::{KeyPair, Pubkey, Signature}` | Key generation, private-key loading, Thru address validation, and signature encoding helpers. | | `thru_base::TransactionBuilder` and `thru_base::txn_tools` | Transaction builders plus program constants such as `EOA_PROGRAM`, `SYSTEM_PROGRAM`, `TOKEN_PROGRAM`, `UPLOADER_PROGRAM`, and more. | | `thru_base::{StateProof, StateProofBody, StateProofHeader, StateProofType}` | Rust-side state proof creation, serialization, and parsing helpers. | | `thru_base::{derive_manager_program_accounts, derive_uploader_program_accounts}` | Deterministic derived-account helpers for common program flows. | | `thru_base::tn_public_address` exports | Encode and decode Thru addresses and create program-defined addresses. | | `thru_base::rpc_types` | Shared Rust request-side types such as `GetProgramAccountsConfig`, `ProgramAccountFilter`, and `MakeStateProofConfig`. | ## Start here This crate is most useful at the edges of a Rust integration: parse keys, build a transaction, sign it, then hand the bytes to a client layer. ```rust use thru_base::tn_tools::{KeyPair, Pubkey}; use thru_base::txn_tools::EOA_PROGRAM; use thru_base::TransactionBuilder; fn build_transfer( from_private_key_hex: &str, to: &str, amount: u64, nonce: u64, start_slot: u64, ) -> Result, Box> { let from = KeyPair::from_hex_private_key("sender", from_private_key_hex)?; let to = Pubkey::new(to.to_string())?; let mut tx = TransactionBuilder::build_transfer( from.public_key, EOA_PROGRAM, to.to_bytes()?, amount, 1, nonce, start_slot, )?; tx.sign(&from.private_key)?; Ok(tx.to_wire()) } ``` ## Common workflows | Task | Main entrypoints | | ----------------------------------- | --------------------------------------------------------------------------------------------------------- | | Parse or validate account addresses | `Pubkey::new`, `Pubkey::from_hex`, `Pubkey::to_bytes`, `Pubkey::from_bytes` | | Parse or format signatures | `Signature::new`, `Signature::to_bytes`, `Signature::from_bytes` | | Create or load a signer | `KeyPair::generate`, `KeyPair::from_hex_private_key` | | Build basic transactions | `TransactionBuilder::build_transfer`, `build_create_account`, `build_resize_account`, `build_write_data` | | Build program-management flows | `build_manager_create`, `build_manager_upgrade`, `build_manager_set_pause`, `build_manager_set_authority` | | Build token and wrapped-token flows | `build_token_initialize_mint`, `build_token_transfer`, `build_wthru_deposit`, `build_wthru_withdraw` | | Work with proofs | `StateProof`, `StateProofHeader`, `StateProofBody`, `MakeStateProofConfig`, `ProofType` | | Derive deterministic accounts | `derive_program_address`, `derive_manager_program_accounts`, `derive_uploader_program_accounts` | ## Notes for agents * `Pubkey` and `Signature` are string-backed wrapper types for Thru-formatted `ta...` addresses and `ts...` signatures, not raw byte arrays. * The builder methods usually expect raw `[u8; 32]` public keys for account arguments, so convert `Pubkey` wrappers with `to_bytes()` when needed. * `thru-base` is broad: if your task is “call node RPC and read chain data,” this crate alone is probably not enough without a client layer. # thru-grpc-client > Generated tonic and prost bindings for the Thru public gRPC surface. Use `thru-grpc-client` when you want direct Rust access to Thru’s generated protobuf messages and gRPC service clients. ## Install ```bash cargo add thru-grpc-client tonic cargo add tokio --features macros,rt-multi-thread ``` Install the `buf` CLI before building this crate. `thru-grpc-client` generates bindings in `build.rs`, and local builds call `buf build` directly. If `cargo build` fails with a missing `buf` error, install `buf` with the official instructions from [buf.build](https://buf.build/docs/cli/installation/). ## When to use it Choose this crate when you want to: * Instantiate Thru protobuf messages directly in Rust. * Call `Query`, `Command`, `Debug`, or `Streaming` services with tonic clients. * Stay close to the wire format for custom middleware, testing, or low-level service integrations. * Reuse the same generated service and message modules that higher-level Rust clients build on top of. ## Choose another crate when * You need keys, transaction builders, address parsing, or proof helpers: use `thru-base`. * You want a more ergonomic Rust RPC client with helper methods like `get_account_info`, `get_block_height`, or `execute_transaction`: use a higher-level wrapper such as `thru-client`. ## Entry points The crate has one public root entrypoint: `thru_grpc_client::thru`. | Import path | What it provides | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `thru_grpc_client::thru::common::v1` | Shared message types such as primitives, filters, pagination, and consensus-related definitions. | | `thru_grpc_client::thru::core::v1` | Core chain types such as accounts, blocks, state, transactions, and related enums. | | `thru_grpc_client::thru::services::v1` | Generated request and response types plus tonic clients including `QueryServiceClient`, `CommandServiceClient`, `DebugServiceClient`, and `StreamingServiceClient`. | ## Start here Most direct integrations begin by creating a tonic transport channel, instantiating one of the generated service clients, and sending a generated request type. ```rust use thru_grpc_client::thru::services::v1::{ query_service_client::QueryServiceClient, GetVersionRequest, }; #[tokio::main] async fn main() -> Result<(), Box> { let channel = tonic::transport::Channel::from_static("http://127.0.0.1:8472") .connect() .await?; let mut client = QueryServiceClient::new(channel); let response = client.get_version(GetVersionRequest {}).await?; println!("{:?}", response.into_inner().versions); Ok(()) } ``` ## Service groups | Service client | Useful for | | ------------------------ | --------------------------------------------------------------------------------------------------------------------- | | `QueryServiceClient` | Read-side RPC calls such as account lookups, block and height queries, state roots, metrics, and version information. | | `CommandServiceClient` | Write-side RPC calls such as transaction submission and proof-related command flows. | | `DebugServiceClient` | Debug and re-execution workflows. | | `StreamingServiceClient` | Subscription-style or continuous data flows over the Thru gRPC API. | ## Notes for agents * This crate is intentionally low level. You will usually need to assemble tonic channels, metadata, timeouts, and request types yourself. * The generated modules are namespaced under `common::v1`, `core::v1`, and `services::v1`, so start by locating the request type and service client in `services::v1`. * In this repository, `rpc/thru-client` is the main example of how to layer ergonomic Rust APIs on top of `thru-grpc-client`. # Web SDKs Overview > Build browser, React, native, backend, wallet, and indexing applications on top of the current Thru TypeScript package surface. Use the Web SDKs when you are building TypeScript applications, wallet-connected experiences, React interfaces, backend indexers, or tooling on top of Thru. ## Start Here * Start with [`@thru/sdk`](/docs/sdks/web-packages/sdk/) for the typed RPC client, domain models, protobufs, helpers, crypto utilities, and ABI reflection. * Use [`@thru/wallet`](/docs/sdks/web-packages/wallet/) for embedded wallet integrations in browser, React, React UI, and native apps. * Use [`@thru/programs`](/docs/sdks/web-packages/programs/) for token, passkey-manager, multicall, and AMM program bindings. * Use [`@thru/replay`](/docs/sdks/web-packages/replay/) and [`@thru/indexer`](/docs/sdks/web-packages/indexer/) for chain data pipelines. ## Packages | Package | Description | Useful when | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | [`@thru/sdk`](/docs/sdks/web-packages/sdk/) | Main TypeScript SDK with the RPC client, domain models, protobufs, helpers, crypto utilities, and ABI reflection subpaths. | Integrating any app or backend with the Thru chain. | | [`@thru/programs`](/docs/sdks/web-packages/programs/) | Program-specific bindings under `@thru/programs/token`, `@thru/programs/passkey-manager`, `@thru/programs/multicall`, and `@thru/programs/amm`. | Building program instructions, account parsers, derivation helpers, multicall payloads, and AMM helpers. | | [`@thru/wallet`](/docs/sdks/web-packages/wallet/) | Embedded wallet package for browser, React, React UI, and native wallet integration. | Adding connect, account selection, signing, and hosted wallet UI to apps. | | [`@thru/passkey`](/docs/sdks/web-packages/passkey/) | WebAuthn package for passkey registration, signing, popup fallback, mobile helpers, auth flows, and server helpers. | Adding passkey signing or auth helpers to wallet and dApp flows. | | [`@thru/replay`](/docs/sdks/web-packages/replay/) | Historical plus live replay library for ordered block, transaction, account, and event streams. | Backfilling and tailing chain data for analytics, ETL, or event processing services. | | [`@thru/indexer`](/docs/sdks/web-packages/indexer/) | Drizzle-backed indexing framework for stream definitions, checkpoints, and background indexing runtimes. | Building a backend indexer that syncs chain data into Postgres. | ## Common Imports | Need | Import | | -------------------------------- | -------------------------------- | | Main RPC SDK | `@thru/sdk` | | Minimal RPC client constructor | `@thru/sdk/client` | | Generated protobufs | `@thru/sdk/proto` | | Address/signature helpers | `@thru/sdk/helpers` | | Mnemonics and HD wallets | `@thru/sdk/crypto` | | ABI reflection | `@thru/sdk/abi` | | Token program bindings | `@thru/programs/token` | | Passkey-manager program bindings | `@thru/programs/passkey-manager` | | Multicall program bindings | `@thru/programs/multicall` | | AMM program bindings | `@thru/programs/amm` | | Browser wallet SDK | `@thru/wallet` | | React wallet hooks | `@thru/wallet/react` | | Prebuilt React wallet UI | `@thru/wallet/react-ui` | | Native wallet React hooks/UI | `@thru/wallet/native/react` | ## Related Guides * [Wallet Integration](/docs/wallet/overview/) * [Core Programs](/docs/core-programs/overview/) * [Indexing Overview](/docs/indexing/overview/) * [gRPC API overview](/docs/api-ref/grpc/overview/) # @thru/indexer > Reusable indexing framework for Thru chain streams, schemas, checkpoints, and Drizzle-backed runtimes. `@thru/indexer` is the public root entrypoint for building persistent Thru indexers. The package exposes a single published entrypoint at the package root; there are no public subpath exports. ## Install ```bash npm install @thru/indexer @thru/replay @thru/sdk postgres drizzle-orm ``` You will also need a database driver and a Drizzle database/schema setup in the application that runs the indexer. ## When To Use It Use `@thru/indexer` when you want to: * define event streams for historical chain data * define account streams for mutable on-chain state * build Drizzle tables from stream definitions * persist checkpoints so an indexer can resume after a restart * run a background indexer that processes event and account streams together Choose a different package when: * you only need an ordered feed and do not want to store results yourself: use [`@thru/replay`](/docs/sdks/web-packages/replay/) * you only need direct chain reads or writes: use [`@thru/sdk`](/docs/sdks/web-packages/sdk/) * you only need low-level encoding helpers: use `@thru/sdk/helpers` ## Main Exports | Area | Export | What it does | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | Schema builder | `t`, `columnBuilder` | Define typed columns for stream-backed Drizzle tables. | | Validation | `generateZodSchema`, `validateParsedData` | Validate parsed rows during development or debugging. | | Stream definitions | `defineEventStream`, `defineAccountStream` | Compile event and account stream definitions into runnable stream objects with `.table` exports. | | Checkpoints | `checkpointTable`, `getCheckpoint`, `updateCheckpoint`, `deleteCheckpoint`, `getAllCheckpoints`, `getSchemaExports` | Store resumable progress and collect tables for migrations. | | Runtime | `Indexer` | Run configured event and account streams against a database and replay client factory. | | Shared types | `ApiConfig`, `StreamBatch`, `HookContext`, `IndexerConfig`, `IndexerResult` | Type stream hooks, config, and runtime results. | ## Typical Workflow ```ts import { Indexer, checkpointTable, defineAccountStream, defineEventStream, t, } from "@thru/indexer"; import { ChainClient } from "@thru/replay"; const indexer = new Indexer({ db, clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), eventStreams: [transfers], accountStreams: [tokenAccounts], defaultStartSlot: 0n, safetyMargin: 64, pageSize: 512, }); await indexer.start(); ``` `defineEventStream` is for append-only chain events. `defineAccountStream` is for current account state that gets updated as chain data changes. Both return compiled stream objects with a Drizzle table on `.table`, which keeps schema and runtime definitions aligned. `Indexer` is the runtime layer. Its config requires a database client and a `clientFactory`, and it supports event streams, account streams, a default start slot, safety margin, page size, and optional runtime validation. Additional runtime options include: | Option | Use it for | | ---------------------------- | ------------------------------------------------------- | | `logger` | Structured runtime, processor, and replay logs. | | `endpointLabel` | Including an endpoint name in normalized stream errors. | | `supervisorInitialBackoffMs` | Tuning the first restart delay after stream failure. | | `supervisorMaxBackoffMs` | Capping stream supervisor restart backoff. | | `streamStaleMs` | Marking running streams stale after no activity. | | `validateParse` | Checking parsed rows against generated Zod schemas. | ## Runtime Status `Indexer` exposes in-memory status for health checks and observability. ```ts const status = indexer.getStatus(); for (const stream of status.streams) { console.log(stream.name, stream.state, stream.restartCount); } ``` Each stream status includes the stream kind, state, checkpoint slot, last processed slot, stale marker, restart count, normalized last error, and counters for records, commits, parser skips, validation failures, and hook failures. The runtime supervises streams continuously. A stream that fails or ends unexpectedly is restarted with backoff unless `indexer.stop()` has been called. ## Reading Indexed Data `@thru/indexer` writes ordinary Drizzle tables. Query them directly from your backend. ```ts import { desc, eq } from "drizzle-orm"; import { db } from "./db"; import { tokenTransferEvents } from "./schema"; const recentTransfers = await db .select() .from(tokenTransferEvents) .where(eq(tokenTransferEvents.dest, "ta...")) .orderBy(desc(tokenTransferEvents.slot)) .limit(20); ``` ## Related Guides * [Indexing Overview](/docs/indexing/overview/) for package selection and the full indexing guide structure. * [Build an Indexer](/docs/indexing/build-an-indexer/) for a step-by-step guide with production concerns. * [`@thru/replay`](/docs/sdks/web-packages/replay/) for the replay primitives that power the indexer runtime. * [`@thru/sdk`](/docs/sdks/web-packages/sdk/) for the app-facing RPC SDK. # @thru/passkey > Browser WebAuthn package for passkey registration and signing flows. `@thru/passkey` is the WebAuthn package for browser passkey registration, signing, popup-based fallback flows, and supporting helpers. ## Install ```bash npm install @thru/passkey ``` ## Entry Points | Import | What it provides | | ---------------------- | -------------------------------------------------------------------------------------- | | `@thru/passkey/web` | Browser WebAuthn registration, signing, capability checks, and shared passkey helpers. | | `@thru/passkey/popup` | Popup bridge helpers for iframe or restricted WebAuthn environments. | | `@thru/passkey` | Root package entrypoint. Prefer `@thru/passkey/web` for browser code. | | `@thru/passkey/mobile` | Mobile passkey helpers. Not covered by this browser reference. | | `@thru/passkey/auth` | Higher-level auth flow helpers. | | `@thru/passkey/server` | Server-side challenge and submit helpers. Not covered by this browser reference. | ## When To Use It Choose `@thru/passkey/web` when you need WebAuthn registration, signing, popup fallback behavior, or browser capability checks. Choose a different package when: * you need to build passkey-manager transaction instructions and wallet account context: use [`@thru/programs`](/docs/sdks/web-packages/programs/) with `@thru/programs/passkey-manager` * you only need byte or address helpers without WebAuthn flows: use `@thru/sdk/helpers` ## Browser Import ```ts import { getPasskeyClientCapabilities, isWebAuthnSupported, registerPasskey, signWithDiscoverablePasskey, signWithPasskey, signWithStoredPasskey, } from "@thru/passkey/web"; ``` ## Main Flows | Export | Use for | | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | `registerPasskey(alias, userId, rpId)` | Creating a new platform passkey for a user profile. | | `signWithPasskey(credentialId, challenge, rpId)` | Signing with a known credential ID. | | `signWithStoredPasskey(challenge, rpId, preferredPasskey, allPasskeys, context?)` | Signing with stored metadata, including embedded and iframe flows. | | `signWithDiscoverablePasskey(challenge, rpId)` | Letting the browser prompt the user to choose from available passkeys. | `registerPasskey` and the signing helpers require browser WebAuthn support. When the browser is inside an iframe or permissions policy blocks inline prompts, the package can fall back to its popup flow. ## Example ```ts import { isWebAuthnSupported, registerPasskey, signWithPasskey, } from "@thru/passkey/web"; if (!isWebAuthnSupported()) { throw new Error("WebAuthn is not available"); } const registration = await registerPasskey( "Primary device", "user-123", "wallet.thru.org" ); const result = await signWithPasskey( registration.credentialId, challengeBytes, "wallet.thru.org" ); ``` ## Capability Checks Use these exports to decide whether inline WebAuthn is available and whether you should preopen or prefer the popup bridge: `isWebAuthnSupported`, `preloadPasskeyClientCapabilities`, `getPasskeyClientCapabilities`, `getCachedPasskeyClientCapabilities`, `shouldUsePasskeyPopup`, `isInIframe` ## Popup Bridge The package exports the pieces used by the popup window and its parent window: `PASSKEY_POPUP_PATH`, `PASSKEY_POPUP_READY_EVENT`, `PASSKEY_POPUP_REQUEST_EVENT`, `PASSKEY_POPUP_RESPONSE_EVENT`, `PASSKEY_POPUP_CHANNEL`, `openPasskeyPopupWindow`, `closePopup`, `requestPasskeyPopup` Popup-side helpers: `toPopupSigningResult`, `buildSuccessResponse`, `decodeChallenge`, `getPopupDisplayInfo`, `getResponseError`, `signWithPreferredPasskey`, `buildStoredPasskeyResult` ## Shared Helpers For compatibility with passkey-manager flows, the package also exports P-256 and byte utilities: `parseDerSignature`, `normalizeLowS`, `normalizeSignatureComponent`, `P256_N`, `P256_HALF_N`, `bytesToBigIntBE`, `bigIntToBytesBE`, `arrayBufferToBase64Url`, `base64UrlToArrayBuffer`, `bytesToBase64Url`, `base64UrlToBytes`, `bytesToHex`, `hexToBytes`, `bytesEqual`, `compareBytes`, `uniqueAccounts` ## Related * [`@thru/programs`](/docs/sdks/web-packages/programs/) * [Passkey Manager Program](/docs/core-programs/passkey-manager-program/) * [`@thru/wallet`](/docs/sdks/web-packages/wallet/) # @thru/programs > Program-specific TypeScript bindings for Thru core programs. `@thru/programs` contains program-specific bindings for built-in Thru programs. It is published as one package with subpath exports for each program surface. ## Install ```bash npm install @thru/programs @thru/sdk ``` ## Entry Points | Import | What it provides | | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@thru/programs/token` | Token program instruction builders, account parsers, address derivation, ABI builders, and formatting helpers. | | `@thru/programs/passkey-manager` | Passkey-manager instruction encoders, challenge helpers, account-context builders, derivation helpers, account parsers, and P-256/WebAuthn encoding utilities. | | `@thru/programs/multicall` | Multicall instruction encoding for batching calls to multiple programs in one instruction payload. | | `@thru/programs/amm` | AMM pool derivation, instruction builders, pool metadata parsing, swap quoting, constants, and generated ABI views. | There is no root runtime import. Import from the program subpath you need. ## Token Program Use `@thru/programs/token` when you are creating token mints, creating token accounts, transferring or minting tokens, parsing token account state, or formatting raw token amounts. ```ts import { createTransferInstruction, deriveTokenAccountAddress, formatRawAmount, parseTokenAccountData, } from "@thru/programs/token"; const destination = deriveTokenAccountAddress( ownerAddress, mintAddress, tokenProgramAddress ); const instructionData = createTransferInstruction({ sourceAccountBytes, destinationAccountBytes: destination.bytes, amount: 1_000_000n, }); const parsed = parseTokenAccountData(account); const displayAmount = formatRawAmount(parsed.amount, 6); ``` Important token exports include: * `createInitializeMintInstruction` * `createInitializeAccountInstruction` * `createMintToInstruction` * `createTransferInstruction` * `deriveMintAddress` * `deriveTokenAccountAddress` * `deriveWalletSeed` * `parseMintAccountData` * `parseTokenAccountData` * `formatRawAmount` ## Passkey Manager Program Use `@thru/programs/passkey-manager` when you need to build passkey-managed wallet instructions, create validate challenges, derive wallet or credential lookup addresses, parse wallet state, or compose passkey-manager instruction bytes. ```ts import { buildAccountContext, concatenateInstructions, createValidateChallenge, encodeTransferInstruction, encodeValidateInstruction, } from "@thru/programs/passkey-manager"; const accountContext = buildAccountContext({ feePayerAddress, walletAddress, readWriteAccounts: [destinationAddress], }); const transfer = encodeTransferInstruction({ accountContext, toAddress: destinationAddress, amount: 1_000_000n, }); const challenge = createValidateChallenge({ nonce, accountAddresses: accountContext.accountAddresses, instructionData: transfer, }); const validate = encodeValidateInstruction({ accountContext, authorityIndex, challenge, signature, authenticatorData, clientDataJSON, }); const instructionData = concatenateInstructions(validate, transfer); ``` Important passkey-manager exports include: * `encodeCreateInstruction` * `encodeValidateInstruction` * `encodeTransferInstruction` * `encodeInvokeInstruction` * `encodeAddAuthorityInstruction` * `encodeRemoveAuthorityInstruction` * `encodeRegisterCredentialInstruction` * `createValidateChallenge` * `deriveWalletAddress` * `deriveCredentialLookupAddress` * `buildAccountContext` * `parseWalletNonce` * `fetchWalletNonce` * `parseWalletAuthorities` * P-256 and byte/base64 helpers used by WebAuthn flows ## Multicall Program Use `@thru/programs/multicall` when you need to encode a single multicall payload that dispatches multiple inner program instructions. ```ts import { MULTICALL_PROGRAM_ADDRESS, buildMulticallInstruction, } from "@thru/programs/multicall"; const instructionData = buildMulticallInstruction([ { programIdx: 2, instructionData: tokenTransferInstruction, }, { programIdx: 5, instructionData: anotherInstruction, }, ]); ``` Each call uses the target program’s account index in the transaction account context plus the already-encoded instruction bytes for that program. Important multicall exports include: * `MULTICALL_PROGRAM_ADDRESS` * `MULTICALL_PROGRAM_PUBKEY` * `buildMulticallInstruction` * `MulticallCall` * Generated `InstructionData`, `InstructionDataBuilder`, `MulticallArgs`, `MulticallArgsBuilder`, and `MulticallError` ABI types ## AMM Program Use `@thru/programs/amm` when you need to derive AMM pool addresses, create pool/liquidity/swap instructions, parse AMM pool metadata, or quote exact-input swaps. ```ts import { AMM_PROGRAM_ADDRESS, createSwapInstruction, deriveAmmPoolAddresses, parseAmmPoolMetadata, quoteAmmSwapExactIn, } from "@thru/programs/amm"; const pool = deriveAmmPoolAddresses(thru, { ammProgramAddress: AMM_PROGRAM_ADDRESS, mintAAddress, mintBAddress, }); const quote = quoteAmmSwapExactIn({ amountIn: 1_000_000n, reserveIn, reserveOut, swapFeeBps: pool.swapFeeBps, }); const swapInstruction = createSwapInstruction({ poolAccountBytes: pool.poolBytes, userTransferAuthorityBytes, userInputAccountBytes, userOutputAccountBytes, vaultInputAccountBytes, vaultOutputAccountBytes, lpMintAccountBytes, tokenProgramAccountBytes, amountIn: quote.amountIn, }); const metadata = parseAmmPoolMetadata(poolAccount); ``` The AMM instruction builders return an async `InstructionData` function. Pass `swapInstruction` the same account lookup context used to build the transaction so account indexes are resolved against the final account list. Important AMM exports include: * `AMM_PROGRAM_ADDRESS` * `AMM_DEFAULT_SWAP_FEE_BPS` * `AMM_MAX_SWAP_FEE_BPS` * `AMM_MINIMUM_LIQUIDITY` * `AMM_POOL_METADATA_SIZE` * `sortAmmMints` * `deriveAmmPoolAddresses` * `deriveAmmLpMintSeed` * `createInitPoolInstruction` * `createAddLiquidityInstruction` * `createWithdrawLiquidityInstruction` * `createSwapInstruction` * `parseAmmPoolMetadata` * `quoteAmmSwapExactIn` * Generated AMM instruction, event, metadata, and error ABI types ## Related * [Token Program](/docs/core-programs/token-program/) * [Passkey Manager Program](/docs/core-programs/passkey-manager-program/) * [`@thru/passkey`](/docs/sdks/web-packages/passkey/) * [`@thru/sdk`](/docs/sdks/web-packages/sdk/) # @thru/replay > Historical plus live replay library for ordered block, transaction, account, and event streams. `@thru/replay` turns chain RPC backfill plus live streaming into a single ordered feed. ## Install ```bash npm install @thru/replay @thru/sdk ``` ## When To Use It Choose this package when you need a durable ordered feed for analytics, ETL, or event processing and want to decide where the data lands yourself. Choose a different package when: * you need persistence, checkpoints, and Drizzle-backed stream definitions on top of the feed: use [`@thru/indexer`](/docs/sdks/web-packages/indexer/) * you need a full app-facing RPC SDK instead of replay primitives: use [`@thru/sdk`](/docs/sdks/web-packages/sdk/) ## Entry Point The package is root-only. Import what you need from `@thru/replay`; there are no public subpath exports. ## Main Exports | Export | Use it for | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `ChainClient` | Connecting to Thru query and streaming services with one client wrapper. | | `createBlockReplay` | Ordered block replay with optional filters, consensus floor, and block view settings. | | `createTransactionReplay` | Ordered transaction replay, including optional event payloads. | | `createEventReplay` | Ordered event replay with reconnect support. | | `createAccountReplay` | Replaying one account’s state over time. | | `createAccountsByOwnerReplay` | Replaying all accounts owned by a program with backfill plus live updates. | | `AccountSeqTracker` and `MultiAccountReplay` | Tracking sequence numbers and managing multiple single-account replay streams. | | `ReplayStream` | The async iterator that merges backfill and live data into one ordered stream. | | `PageAssembler` | Reassembling multi-page account updates into complete account payloads. | | `ReplaySink` and `ConsoleSink` | Writing replay output to a sink implementation or the console. | | `createConsoleLogger` and `NOOP_LOGGER` | Structured logging for replay runs. | | `DEFAULT_RETRY_CONFIG`, `calculateBackoff`, `withTimeout`, `delay`, `TimeoutError` | Retry and timeout helpers used by replay and available to consumers. | ## Common Workflows * Use `createBlockReplay`, `createTransactionReplay`, or `createEventReplay` when you want a typed ordered feed for analytics, ETL, or event processing. * Use `createAccountsByOwnerReplay` when you need to index all accounts owned by a program and keep them current. * Use `ReplayStream` directly when you already have your own backfill fetcher and live subscriber. * Use `PageAssembler` when you need to assemble multi-page account updates into complete payloads before processing them. ## Replay Lifecycle `ReplayStream` joins a historical backfill source and a live streaming source into one `AsyncIterable`. 1. `backfill`: fetches ordered pages from `startSlot`. 2. `switching`: drains live items buffered while backfill was catching up. 3. `streaming`: yields live items and reconnects when the stream errors or ends unexpectedly. The live stream starts while backfill is running. `safetyMargin` controls how far behind the observed live tip the backfill must reach before the stream switches to live output. Items are deduplicated across the backfill/live overlap using each replay type’s key function. Backfill pages must be ordered by ascending slot. If a custom `ReplayStream` backfill source returns pages out of order, replay throws rather than silently emitting an inconsistent feed. Use `signal` to stop replay without reconnecting, and `resubscribeOnEnd` to control whether an ended live stream should be treated as reconnectable. The default is to resubscribe on end. ```ts const abort = new AbortController(); const replay = createTransactionReplay({ clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), startSlot: 1_000_000n, safetyMargin: 64n, signal: abort.signal, }); for await (const tx of replay) { console.log(tx.slot?.toString()); console.log(replay.getMetrics()); } ``` `getMetrics()` reports in-memory counters for buffered items, backfill/live emissions, reconnect emissions, and discarded duplicates. Persist durable progress in your own storage or use [`@thru/indexer`](/docs/sdks/web-packages/indexer/) when you want managed checkpoints. ## Factory Options Each replay factory is tuned to the source it reads: | Factory | Client shape | Notable options | | ----------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `createBlockReplay` | `client` | `filter`, `view`, `minConsensus`, `safetyMargin`, `pageSize`, `resubscribeOnEnd`, `signal` | | `createTransactionReplay` | `client` or `clientFactory` | `filter`, `minConsensus`, `returnEvents`, `safetyMargin`, `pageSize`, `resubscribeOnEnd`, `signal` | | `createEventReplay` | `client` or `clientFactory` | `filter`, `resumeAfter`, `safetyMargin`, `pageSize`, `resubscribeOnEnd`, `signal` | | `createAccountReplay` | `client` | `address`, `view`, `filter`, `pageAssemblerOptions`, `cleanupInterval` | | `createAccountsByOwnerReplay` | `client` or `clientFactory` | `owner`, `view`, `dataSizes`, `minUpdatedSlot`, `pageSize`, `maxRetries`, `retryConfig`, `onBackfillComplete`, `signal` | For long-running services, prefer `clientFactory` whenever the selected factory supports it. Reconnects can then create a fresh transport and dispose stale clients that expose `close()`. ## Account Replay Account replay is split into two shapes: * `createAccountReplay` for one account address. * `createAccountsByOwnerReplay` for owner-scoped indexing with backfill, live updates, and reconnect handling. `createAccountsByOwnerReplay` accepts either `client` or `clientFactory`. Prefer `clientFactory` for long-running workers so reconnects can create a fresh transport. Owner-scoped replay also supports `minUpdatedSlot`, `dataSizes`, `pageSize`, `maxRetries`, `pageAssemblerOptions`, `reconnectCleanupTimeoutMs`, `retryConfig`, `onBackfillComplete`, `logger`, and `signal`. Account replay yields `AccountReplayEvent` values: ```ts for await (const event of replay) { if (event.type === "account") { console.log(event.account.addressHex); console.log(event.account.slot); console.log(event.account.seq); console.log(event.account.isDelete); console.log(event.account.source); // "backfill" or "stream" } } ``` The replay package depends on generated protobuf types from `@thru/sdk/proto` internally. ## ChainClient Configuration `ChainClient` wraps Thru query and streaming services for replay workloads. | Option | What it does | | ----------------- | ------------------------------------------------------------------------- | | `baseUrl` | gRPC endpoint used when `ChainClient` creates its own transport. | | `apiKey` | Adds a bearer `Authorization` header to owned transports. | | `userAgent` | Adds a `User-Agent` header to owned transports. | | `transport` | Uses an application-owned Connect transport instead of creating one. | | `interceptors` | Adds Connect interceptors to an owned transport. | | `callOptions` | Passes Connect call options to every RPC made by the client. | | `useBinaryFormat` | Controls binary protobuf format for owned transports. Defaults to `true`. | When you do not pass `transport`, `baseUrl` is required and the client owns the underlying HTTP/2 session. When you do pass `transport`, your application owns that transport’s lifecycle. ```ts const client = new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL!, apiKey: process.env.CHAIN_API_KEY, userAgent: "my-indexer/1.0", }); ``` ## Client Lifecycle `ChainClient` owns an HTTP/2 session when it creates its own transport. Call `close()` when you are done with a standalone client. If you pass a custom transport, that transport remains owned by your application. Long-running replay factories that accept `clientFactory` close stale clients on reconnect when those clients expose `close()`. ```ts const client = new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }); try { const replay = createBlockReplay({ client, startSlot: 0n }); for await (const block of replay) { console.log(block.header?.slot?.toString()); } } finally { client.close(); } ``` Calling `close()` is idempotent. It aborts the owned HTTP/2 session, so make sure no in-flight RPCs or streams are still expected to complete on that client. ## Data Model Replay items are ordered by slot, deduplicated across the backfill/live overlap window, and exposed through an `AsyncIterable`. `ReplaySinkContext` tags each item with the replay phase, `backfill` or `live`, so downstream code can treat historical and realtime data differently if needed. ## Minimal Example ```ts import { ChainClient, createBlockReplay } from "@thru/replay"; const client = new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }); const replay = createBlockReplay({ client, startSlot: 1_000_000n }); for await (const block of replay) { console.log(block.header?.slot?.toString()); } ``` For transactions, events, and owner-scoped account replay, prefer `clientFactory` in services that must reconnect cleanly: ```ts const replay = createEventReplay({ clientFactory: () => new ChainClient({ baseUrl: process.env.CHAIN_RPC_URL! }), startSlot: checkpoint.slot, resumeAfter: checkpoint.eventId ? { slot: checkpoint.slot, eventId: checkpoint.eventId } : undefined, }); ``` ## Related Guides * [Indexing Overview](/docs/indexing/overview/) for package selection and the full indexing guide structure. * [Build an Indexer](/docs/indexing/build-an-indexer/) for a step-by-step guide using `@thru/indexer` on top of replay. * [`@thru/indexer`](/docs/sdks/web-packages/indexer/) for persistence and checkpoints built on replay. * [`@thru/sdk`](/docs/sdks/web-packages/sdk/) for the full app-facing RPC client. # @thru/sdk > Typed JavaScript and TypeScript SDK for Thru RPC, domain models, protobufs, helpers, crypto, and ABI reflection. Use `@thru/sdk` as the default TypeScript package for Thru applications and backend services. It contains the bound RPC client, domain models, transaction helpers, generated protobuf types, address/signature helpers, wallet crypto utilities, and ABI reflection tools. ## Install ```bash npm install @thru/sdk ``` For TypeScript, use modern module resolution: ```json { "compilerOptions": { "moduleResolution": "bundler", "module": "ESNext", "target": "ES2020" } } ``` Use `"moduleResolution": "nodenext"` if your app runs directly on Node ESM without a bundler. ## Entry Points | Import | What it provides | | ------------------- | --------------------------------------------------------------------------------------------------------------- | | `@thru/sdk` | Main SDK surface: `createThruClient`, domain models, modules, filters, transaction helpers, and stream helpers. | | `@thru/sdk/client` | Minimal bound-client setup: `createThruClient`, `Thru`, and `ThruClientConfig`. | | `@thru/sdk/proto` | Generated protobuf schemas, service definitions, view enums, and `create` from `@bufbuild/protobuf`. | | `@thru/sdk/helpers` | Address, signature, base64, hex, byte, and Web Crypto helpers. | | `@thru/sdk/crypto` | Mnemonic and HD wallet utilities for wallet onboarding. | | `@thru/sdk/abi` | WASM-backed ABI reflection, formatting, manifest handling, and import resolution. | ## Basic Usage ```ts import { AccountView, createThruClient } from "@thru/sdk"; const thru = createThruClient({ baseUrl: "https://rpc.alphanet.thru.org", }); const height = await thru.blocks.getBlockHeight(); const block = await thru.blocks.get({ slot: height.finalized }); const account = await thru.accounts.get("taExampleAddress...", { view: AccountView.FULL, }); console.log(block.header.blockHash, account.meta?.balance); ``` ## Domain Models The root package exports domain classes for the objects most apps pass around: * `Account`, `AccountMeta`, and `AccountData` * `Block`, `BlockHeader`, and `BlockFooter` * `Transaction`, `TransactionBuilder`, and `TransactionStatusSnapshot` * `ChainEvent` * `StateProof` * `HeightSnapshot` * `VersionInfo` * `Pubkey` and `Signature` * `Filter` and `FilterParamValue` These classes wrap protobuf data with validation, defensive byte copies, and conversion helpers. ## Client Modules The bound client groups common workflows under stable modules: * `thru.accounts` * `thru.blocks` * `thru.chain` * `thru.consensus` * `thru.events` * `thru.height` * `thru.keys` * `thru.node` * `thru.nonce` * `thru.proofs` * `thru.slots` * `thru.streaming` * `thru.transactions` * `thru.version` Use `@thru/sdk/client` when you only need the client constructor and its types: ```ts import { createThruClient, type Thru } from "@thru/sdk/client"; const thru: Thru = createThruClient({ baseUrl: "https://rpc.alphanet.thru.org", }); ``` ## Transaction Flow Use `transactions.build(...)` when another wallet or custody layer signs the payload. Use `transactions.buildAndSign(...)` when your app owns the fee-payer key material directly. ```ts import { decodeAddress } from "@thru/sdk/helpers"; const tx = await thru.transactions.build({ feePayer: { publicKey: decodeAddress(feePayerAddress) }, program: programAddress, accounts: { readWrite: readWriteAccounts, readOnly: readOnlyAccounts, }, instructionData, }); const signingPayload = tx.toWireForSigning(); const signedWire = await externalSigner.signTransaction(signingPayload); const result = await thru.transactions.sendAndTrack(signedWire); ``` ## Protobufs Use `@thru/sdk/proto` when you need generated message schemas or service definitions directly: ```ts import { create, GetBlockRequestSchema, QueryService } from "@thru/sdk/proto"; const request = create(GetBlockRequestSchema, { selector: { case: "slot", value: 100n }, }); ``` Most apps should prefer the domain SDK unless they are writing low-level RPC middleware, test harnesses, or protocol tooling. ## Helpers Use `@thru/sdk/helpers` for lightweight encoding and byte utilities: ```ts import { decodeAddress, encodeAddress, encodeSignature } from "@thru/sdk/helpers"; const bytes = decodeAddress("taExampleAddress..."); const address = encodeAddress(bytes); const signature = encodeSignature(signatureBytes); ``` ## Crypto Use `@thru/sdk/crypto` for mnemonic and HD wallet flows: ```ts import { MnemonicGenerator, ThruHDWallet } from "@thru/sdk/crypto"; const mnemonic = MnemonicGenerator.generate(); const wallet = await ThruHDWallet.fromMnemonic(mnemonic); ``` ## ABI Reflection Use `@thru/sdk/abi` when explorers, debuggers, or tests need to decode raw account, instruction, or event bytes from ABI YAML. ```ts import { ensureWasmLoaded, formatReflection, reflectInstruction } from "@thru/sdk/abi"; await ensureWasmLoaded(); const reflected = await reflectInstruction(abiYaml, instructionBytes); console.log(formatReflection(reflected)); ``` ## Related * [`@thru/programs`](/docs/sdks/web-packages/programs/) for token and passkey-manager program bindings. * [`@thru/wallet`](/docs/sdks/web-packages/wallet/) for embedded wallet integration. * [`@thru/replay`](/docs/sdks/web-packages/replay/) and [`@thru/indexer`](/docs/sdks/web-packages/indexer/) for chain data pipelines. # @thru/wallet > Embedded wallet SDK for browser, React, React UI, and wallet-managed transaction signing. `@thru/wallet` is the public wallet integration package. It combines the browser iframe SDK, shared wallet protocol/types, React hooks, and prebuilt React UI behind one package. ## Install ```bash npm install @thru/wallet @thru/sdk ``` ## Entry Points | Import | What it provides | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@thru/wallet` | Browser SDK, wallet protocol exports, wallet account types, connection helpers, transaction intent types, signing-context types, and signing-session types. | | `@thru/wallet/react` | `ThruProvider`, `useWallet`, `useAccounts`, `useThru`, and related React types. | | `@thru/wallet/react-ui` | `ThruAccountSwitcher` prebuilt React wallet control. | Native entrypoints also exist in the package, but this page focuses on the browser and React wallet surface. ## Browser SDK Use the root entrypoint when you want direct control of the embedded wallet iframe and wallet lifecycle. ```ts import { BrowserSDK } from "@thru/wallet"; const wallet = new BrowserSDK({ iframeUrl: "https://wallet.thru.org/embedded", rpcUrl: "https://rpc.alphanet.thru.org", }); await wallet.initialize(); wallet.on("connect", ({ accounts }) => { console.log("Connected accounts", accounts); }); const result = await wallet.connect({ metadata: { appId: window.location.origin, appName: "My Thru App", appUrl: window.location.origin, }, }); const thru = wallet.getThru(); const account = await thru.accounts.get(result.accounts[0].address); ``` The root package also exports wallet protocol types such as `WalletAccount`, `ConnectResult`, `ThruSigningContext`, `ThruTransactionIntent`, `ThruTransactionReviewPayload`, `ThruSigningSession`, and `ErrorCode`. ## React Use `@thru/wallet/react` for React apps that want provider state and hooks. ```tsx import { ThruProvider, useWallet } from "@thru/wallet/react"; export function App({ children }: { children: React.ReactNode }) { return ( {children} ); } export function ConnectButton() { const { connect, isConnected, isConnecting } = useWallet(); return ( ); } ``` Use `@thru/wallet/react-ui` when you want the prebuilt account switcher: ```tsx import { ThruProvider } from "@thru/wallet/react"; import { ThruAccountSwitcher } from "@thru/wallet/react-ui"; export function Header() { return ; } ``` ## Sign A Transaction Intent Use `signTransaction(...)` with a `ThruTransactionIntent`. The wallet owns fee payer selection, account ordering, headers, nonces, final wire transaction construction, and signing. ```ts function bytesToBase64(bytes: Uint8Array): string { let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } const signedBase64 = await wallet.signTransaction({ walletAddress, programAddress, instructionData: bytesToBase64(instructionData), readWriteAddresses, readOnlyAddresses, review: { appName: "My Thru App", programAddress, instruction: "Transfer", }, }); ``` `instructionData` is the base64-encoded program instruction payload. The returned value is the signed raw transaction encoded as base64. ## Signing Context Call `getSigningContext()` before building transactions that depend on signer or fee-payer identity. The selected managed account can differ from the public key that signs and pays for network submission. ```ts const context = await wallet.getSigningContext(); console.log(context.selectedAccountPublicKey); console.log(context.feePayerPublicKey); console.log(context.signerPublicKey); ``` ## Signing Sessions Use signing sessions when a connected app needs a temporary wallet-owned signer for repeated actions. This example reuses the `bytesToBase64` helper from the transaction intent example. ```ts const session = await wallet.createSigningSession({ walletAddress, durationSeconds: 60 * 30, }); const signedBase64 = await session.signTransaction({ programAddress, instructionData: bytesToBase64(instructionData), readWriteAddresses, readOnlyAddresses, }); await session.revoke(); ``` The browser SDK also exposes `createSigningSessionInstruction(...)`, `confirmSigningSession(...)`, `getSigningSession(...)`, `getSigningSessions(...)`, and `revokeSigningSession(...)`. ## Related * [Wallet Integration](/docs/wallet/overview/) * [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) * [Approval and Signing](/docs/wallet/approval-and-signing/) * [Signing Sessions](/docs/wallet/signing-sessions/) * [`@thru/sdk`](/docs/sdks/web-packages/sdk/) # Account Compression and State Proofs > How Thru compresses accounts off the active ledger and uses Merkle state proofs to validate operations against them. On the Thru network, data is stored within accounts. The validators of the network maintain the present state of some subset of all of the accounts, and the goal of the blockchain is to ensure that the accounts stored by each validator have the same up-to-date state. However, to process and validate modifications to accounts, the validators each would need to keep many accounts in their storage. Given the large number of accounts and ever growing state, the storage requirement becomes vast. To alleviate this issue, the Thru network supports account compression. Uncompressed accounts are active on the network, and can be modified. On the other hand, compressed accounts are in an archived state, where they cannot be accessed without first being uncompressed. Compressed accounts do not need to be stored by validators and can instead be stored off the chain. ## State Proofs If validators are no longer storing the state of compressed accounts, they no longer have all the information needed to validate every transaction, since the validity may depend on the state of compressed accounts. This is where state proofs come in; they provide cryptographic proof of the state of an account, which a validator can check, without having previously stored the state of that account. Specifically, we need to be able to give proofs for the following situations: * `TN_STATE_PROOF_TYPE_CREATION`: When we are creating a new account, we need to prove that it has never been created before. * `TN_STATE_PROOF_TYPE_EXISTING`: When we compress an account, we can get a proof that the account became compressed in its current state. Later, when we want to use the account again, we need to decompress the account. We can use the proof to show that it was previously compressed in the state that we claim it is. * `TN_STATE_PROOF_TYPE_UPDATING`: When we decompress an account, we can get a proof that the account was previously compressed and in its current state. After modifying an account, we may want to compress the account again. We need to provide the proof to show that the account was previously in a certain compressed state, and should now be updated to a new compressed state. We can provide cryptographic proofs for these situations by taking advantage of a data structure called a Merkle Tree. ### Usage Examples Here’s a simple smart contract that creates an account using a state proof: ```c #include #include /* Error codes */ #define TN_SIMPLE_CREATE_ERR_INVALID_INSTRUCTION_DATA 1 /* Instruction data structure */ struct __attribute__((packed)) simple_create_args { ushort account_index; /* Index of account to create */ uchar seed[32]; /* Seed for account derivation */ /* State proof data follows immediately after this struct */ }; typedef struct simple_create_args simple_create_args_t; TSDK_ENTRYPOINT_FN void start( void ) { tsdk_txn_t const * txn = tsdk_get_txn( ); uchar const * instruction_data = tsdk_txn_get_instr_data( txn ); ulong instruction_data_sz = tsdk_txn_get_instr_data_sz( txn ); /* Check minimum size for instruction data */ if( instruction_data_sz < sizeof(simple_create_args_t) ) { tsdk_revert( TN_SIMPLE_CREATE_ERR_INVALID_INSTRUCTION_DATA ); } simple_create_args_t const * args = (simple_create_args_t const *)instruction_data; /* Extract proof data that follows the instruction struct */ uchar const * proof_data = instruction_data + sizeof(simple_create_args_t); ulong remaining_bytes = instruction_data_sz - sizeof(simple_create_args_t); /* Validate proof header is present */ if( remaining_bytes < sizeof(tsdk_state_proof_hdr_t) ) { tsdk_revert( TN_SIMPLE_CREATE_ERR_INVALID_INSTRUCTION_DATA ); } /* Calculate proof size from header and verify against remaining data */ tsdk_state_proof_hdr_t const * proof_hdr = (tsdk_state_proof_hdr_t const *)proof_data; ulong proof_size = tsdk_state_proof_footprint_from_header( proof_hdr ); if( remaining_bytes < proof_size ) { tsdk_revert( TN_SIMPLE_CREATE_ERR_INVALID_INSTRUCTION_DATA ); } /* Create the account using seed and proof */ ulong result = tsys_account_create( args->account_index, args->seed, proof_data, proof_size ); if( result != TSDK_SUCCESS ) { tsdk_revert( result ); } /* Make account writable and resize to hold some data */ result = tsys_set_account_data_writable( args->account_index ); if( result != TSDK_SUCCESS ) { tsdk_revert( result ); } result = tsys_account_resize( args->account_index, 64 ); if( result != TSDK_SUCCESS ) { tsdk_revert( result ); } tsdk_return( TSDK_SUCCESS ); } ``` The state proof data is obtained from the query service `GenerateStateProof` RPC (or CLI: `thru txn make-state-proof creating `) and included in the instruction data when calling the smart contract. ### Merkle Tree Structure Thru organizes account data in a compressed binary Merkle tree based on account addresses. The tree is structured as a trie where each bit of an account’s address determines the path: ```plaintext Root Hash / \ Hash(A,B) Hash(C,D) / \ / \ Hash(A) Hash(B) Hash(C) Hash(D) | | | | A B C D ``` * All accounts are located at leaves at the bottom of the tree, at a maximum depth of 256. * To locate an account, follow the sequence of bits in its address. Bit 0 → go left, Bit 1 → go right * **Leaf nodes**: Store hashes of account data (`SHA-256(0x00 || pubkey || value_hash)`). * **Internal nodes**: Store hashes of their children (`SHA-256(0x01 || left_hash || right_hash)`). The tree is **compressed**, meaning that internal nodes only exist when account addresses actually diverge. Nodes with only one child are eliminated, keeping the tree compact. * **Root node**: Single hash representing the entire global account state. This is the only value that validators need to record. ### Proof Structure State proofs contain all information needed to cryptographically verify account operations against historical blockchain state. **Variable Size**: State proofs have variable size depending on the length of the path the proof takes through the tree. The `path_bitset` indicates which sibling hashes are included, allowing proofs to be compact for accounts in sparse areas of the tree. In the C SDK, use `tsdk_state_proof_footprint_from_header()` to determine the exact size of a proof before allocation. All state proofs share a common header: ```c struct { ulong type_slot; // High 2 bits: proof type, low 62 bits: slot number tn_hash_t path_bitset; // Bitmap indicating which tree levels have sibling hashes } hdr; ``` * **`type_slot`**: Encodes both the proof type (0=`EXISTING`, 1=`UPDATING`, 2=`CREATION`) and the historical slot number when the account state existed * **`path_bitset`**: A 256-bit bitmap where each bit indicates whether a sibling hash is provided for that tree level The following structure represents the maximum size a state proof can be. ```c struct tsdk_state_proof { struct { /* header as above */ } hdr; union { // Raw access to all proof data tn_hash_t proof_keys[258]; // CREATION proof (type=2): Proves an account didn't exist struct { tn_pubkey_t existing_leaf_pubkey; // Existing account that would be // sibling to new account tn_hash_t existing_leaf_hash; // Hash of that existing account tn_hash_t sibling_hashes[256]; // Path from existing account to root } creation; // EXISTING proof (type=0): Proves an account exists with specific data struct { tn_hash_t sibling_hashes[256]; // Path from account to root } existing; // UPDATING proof (type=1): Proves an account's previous state before // modification struct { tn_hash_t existing_leaf_hash; // Hash of the account's PREVIOUS state tn_hash_t sibling_hashes[256]; // Path proving the previous state // existed } updating; } proof_body; }; ``` ### How State Proofs Work All state proofs work by reconstructing paths through the Merkle tree and verifying the result matches a known state root. Each proof type targets a different aspect of account state. `CREATION` proofs demonstrate that a target address is available for a new account. The key insight is that the proof contains an existing account that shares the longest possible address prefix with the target account. * **Find the divergence point**: Follow both the target address and existing account address bit by bit until they diverge - one goes left, the other goes right * **Verify the existing path**: Use the sibling hashes to reconstruct the path from the existing account to the historical state root * **Confirm availability**: Since the existing account’s path is valid and the target address diverges from it, the target address must be unoccupied The existing account becomes the sibling to the new account when it’s created, which is why the proof must use the account with maximal prefix overlap. `EXISTING` proofs verify that an account exists with exactly the provided data at a historical point in time. * **Compute the leaf hash**: Hash the provided account metadata and data to get the leaf value * **Follow the address path**: Use each bit of the account address to navigate left or right through the tree * **Reconstruct to root**: At each level, combine the current hash with the appropriate sibling hash to compute the parent hash * **Verify the result**: The final computed hash must match the historical state root stored on-chain `UPDATING` proofs prove an account’s previous state before modification, enabling validated state transitions. * **Start with previous state**: Use the provided `existing_leaf_hash` as the leaf value (the account’s old state) * **Reconstruct the historical path**: Follow the account’s address using the sibling hashes to rebuild the path to root * **Validate the previous state**: The reconstructed path must reach the historical state root, proving the account genuinely had the claimed previous state This creates an audit trail where every state change can be cryptographically linked to a verified starting point. # Account Model > Complete specification of Thru's account structure, metadata, ownership model, and limitations Accounts are the fundamental units of state in the Thru network. They serve as containers for data, code, and value, enabling programs to store persistent information and users to hold tokens. All network state is organized around accounts, from user wallets and program code to application data and system configuration. Thru implements an account model that manages state, ownership, and data for all entities on the network. Each account is uniquely identified by a 32-byte public key and contains metadata and arbitrary data that programs can read and modify. See [Account Addresses](/docs/spec/accounts/addresses/) for more information about how account addresses are determined. ## Account Structure Each account consists of two main components: runtime metadata that defines the account’s properties and permissions, followed by variable-length data that can be up to 16MB in size. The metadata contains essential information like ownership, balance, and behavioral flags, while the data section stores arbitrary information that programs can read and modify. ### Account Metadata On chain, the runtime stores a 64-byte internal metadata header. The public C SDK exposes a packed `tsdk_account_meta_t` view without the runtime-only `magic` field. This page uses the SDK-facing names unless otherwise noted. **`version` · *uint8* · **required**** Account format version. Currently supports version 1 (`TN_ACCOUNT_V1 = 0x01`). **`flags` · *uint8* · **required**** Bitfield controlling account behavior and permissions. Account Flag Values **`TSDK_ACCOUNT_FLAG_PROGRAM` · *0x01*** Marks the account as an executable program. Program accounts contain bytecode that can be executed by the Thru VM. **`TSDK_ACCOUNT_FLAG_PRIVILEGED` · *0x02*** Indicates the account has special system privileges. Reserved for system contracts and core infrastructure. **`TSDK_ACCOUNT_FLAG_UNCOMPRESSABLE` · *0x04*** Prevents the account from being compressed in state trees. Used for frequently accessed accounts. **`TSDK_ACCOUNT_FLAG_EPHEMERAL` · *0x08*** Marks the account as temporary. Ephemeral accounts can be garbage collected when deleted. **`TSDK_ACCOUNT_FLAG_DELETED` · *0x10*** Indicates the account has been deleted but may still exist in compressed form. **`TSDK_ACCOUNT_FLAG_NEW` · *0x20*** Marks newly created accounts. Used during transaction processing to track state changes. **`TSDK_ACCOUNT_FLAG_COMPRESSED` · *0x40*** Indicates the account is stored in compressed state trees rather than active state. **`data_sz` · *uint32* · **required**** Size of the account’s data in bytes. Maximum value is 16MB (`TN_ACCOUNT_DATA_SZ_MAX = 16,777,216`). **`seq` · *uint64* · **required**** Account sequence number that is incremented each time the account is modified during transaction execution. The sequence is updated at the end of the transaction for accounts that were modified. **`owner` · *tn\_pubkey\_t* · **required**** 32-byte public key of the program that owns this account. Only the owner program can modify account data. **`balance` · *uint64* · **required**** Account balance in the network’s native token. Used for transaction fees and transfers. **`nonce` · *uint64* · **required**** Transaction nonce for replay protection. Must match the transaction nonce exactly for fee payer accounts. See [Transaction Execution](/docs/spec/runtime/transaction-execution/) for more details. ### Account Data Following the 64-byte metadata header, accounts can store up to 16MB of arbitrary data that programs can read and modify through direct memory access in the VM. Note For detailed information about how programs access account data in memory, see the [VM Memory Layout](/docs/spec/vm/memory-layout/#account-data) documentation. ```c struct __attribute__(( packed )) tsdk_account_meta { uchar version; /* Account format version */ uchar flags; /* Behavior flags bitfield */ uint data_sz; /* Data size in bytes */ ulong seq; /* Account sequence number */ tn_pubkey_t owner; /* Owner program public key */ ulong balance; /* Balance in native tokens */ ulong nonce; /* Transaction nonce */ }; ``` ## Ownership Model ### Program Ownership Every account is owned by exactly one program, identified by the `owner` field in the account metadata. Ownership is permanent and cannot be transferred. Only the owner program can: * Modify account data during transaction execution * Change account metadata (except balance and nonce) * Delete the account by setting the `TSDK_ACCOUNT_FLAG_DELETED` flag In almost all cases, the owner of an account is the program that created it. The exception is for accounts owned by the externally owned account (EOA) program, which may be created by any program. ### Externally Owned Accounts Externally owned accounts (EOAs), also known as user accounts, are owned by the externally owned account program, which is located at the address `0` (all zeros). They can be accessed by using the private key corresponding to their address. EOAs are created using [`tsys_account_create_eoa()`](/docs/spec/vm/syscalls/account_create_eoa/) with signature verification to prove ownership of the private key. ## Account Types Thru supports two fundamental account types that determine their lifecycle and storage characteristics: * Permanent Accounts **Purpose**: Long-lived accounts that persist in the network state indefinitely. **Characteristics**: * Default account type (no special flags required) * Require state proofs for creation to prevent conflicts * Persist in state trees even when deleted * Can be compressed to optimize storage * Support all account flags and operations **Lifecycle**: * Created with [`tsys_account_create()`](/docs/spec/vm/syscalls/account_create/) using state proofs * When deleted, account is completely removed but can be recreated without another state proof for a time before compression * Newly created accounts that have never existed before can be deleted like ephemeral accounts **Common Use**: * User wallets and token accounts * Program accounts containing executable code * Application data and state storage * System contracts and infrastructure * Ephemeral Accounts **Purpose**: Temporary accounts designed for short-term operations and automatic cleanup. **Characteristics**: * Marked with `TSDK_ACCOUNT_FLAG_EPHEMERAL` * No state proof required for creation * Cannot hold funds * Compressing an ephemeral account deletes it, and anyone can compress it **Lifecycle**: * Created with [`tsys_account_create_ephemeral()`](/docs/spec/vm/syscalls/account_create_ephemeral/) * Automatically cleaned up when both ephemeral and deleted flags are set **Common Use**: * Storing data temporarily to be used in a later transaction ### Account Purposes While there are only two fundamental types, accounts serve various purposes based on their flags and ownership: User Accounts (EOAs) **Purpose**: Store user funds and serve as transaction fee payers. **Also known as**: Externally Owned Accounts (EOAs) **Characteristics**: * Owned by the externally owned account program at address `0` (all zeros) * Created with [`tsys_account_create_eoa()`](/docs/spec/vm/syscalls/account_create_eoa/) using signature verification * Controlled by the holder of the private key corresponding to the account’s public key address * Typically have no custom data (data\_sz = 0) * Used for balance transfers and fee payments Program Accounts **Purpose**: Store executable bytecode for smart contracts. * Marked with `TSDK_ACCOUNT_FLAG_PROGRAM` * Contain compiled program code in account data * Upgradable by the configured program authority unless paused or finalized by policy Data Accounts **Purpose**: Store arbitrary application state and user data. * Owned by specific programs that can modify them * Most flexible storage option up to 16MB * Used for application state, user profiles, game data System Accounts **Purpose**: Core network infrastructure and privileged operations. * Marked with `TSDK_ACCOUNT_FLAG_PRIVILEGED` * Special permissions for network-level operations * Protected from normal program access ## Account Limitations ### Size Constraints **`Maximum Data Size` · *16MB*** Each account can store at most 16,777,216 bytes of data (`TN_ACCOUNT_DATA_SZ_MAX`). ### Access Permissions Read Access Any program can read account metadata and data from accounts included in the transaction. Write Access Only the owner program can modify account data. System operations can modify balance and nonce fields. Creation Rights New accounts can only be created by their designated owner program during transaction execution. ## Account Lifecycle ### Creation Programs create accounts using the system call interface: 1. **Create Account** Use `tsys_account_create()` or `tsys_account_create_ephemeral()` to create a new account: ```c // Create a new account with state proof ulong result = tsys_account_create(account_idx, seed, proof, proof_sz); // Create ephemeral account (no state proof required) ulong result = tsys_account_create_ephemeral(account_idx, seed); ``` Note For detailed syscall documentation, see the [System Calls reference](/docs/spec/vm/syscalls/overview/). Tip The account is immediately available for use within the same transaction. 2. **Set Initial Size** If the account needs data storage, resize it using `tsys_account_resize()`: ```c ulong result = tsys_account_resize(account_idx, new_size); ``` Note See [`tsys_account_resize()`](/docs/spec/vm/syscalls/account_resize/) for complete parameter details. ### Active State Active accounts exist in the current state and can be: * Read by any program in transactions that include them * Modified by their owner program through direct memory access * Transferred funds using [`tsys_account_transfer()`](/docs/spec/vm/syscalls/account_transfer/) * Resized using [`tsys_account_resize()`](/docs/spec/vm/syscalls/account_resize/) * Used as data storage for applications Note An account is considered active if it’s not compressed and not both ephemeral and deleted. ### Compression Programs can compress accounts to optimize state storage using the compression syscalls: ```c // Compress an account with state proof ulong result = tsys_account_compress(account_idx, proof, proof_sz); // Decompress an account with metadata, data, and proof ulong result = tsys_account_decompress(account_idx, meta, data, proof, proof_sz); ``` Note See [`tsys_account_compress()`](/docs/spec/vm/syscalls/account_compress/) and [`tsys_account_decompress()`](/docs/spec/vm/syscalls/account_decompress/) for detailed usage. **Ephemeral Account Compression** For ephemeral accounts, compression behaves differently: * Calling `tsys_account_compress()` on an ephemeral account immediately deletes it instead of compressing * **Relaxed permissions**: Any program can compress (delete) an ephemeral account if it’s writable in the transaction * **No ownership check**: Unlike `tsys_account_delete()`, compression doesn’t require the current program to own the account * No state proof required since the account is simply removed Caution Compressed accounts are not accessible by programs and cannot be directly modified. They must be decompressed by providing the account data and a state proof to re-upload them to active state. ### Deletion 1. **Delete Account** Programs delete accounts using the deletion syscall: ```c ulong result = tsys_account_delete(account_idx); ``` This sets the `TSDK_ACCOUNT_FLAG_DELETED` flag, making the account data inaccessible. Note See [`tsys_account_delete()`](/docs/spec/vm/syscalls/account_delete/) for complete documentation. 2. **Ephemeral Cleanup** Accounts marked both `TSDK_ACCOUNT_FLAG_EPHEMERAL` and `TSDK_ACCOUNT_FLAG_DELETED` can be garbage collected by the runtime. 3. **Permanent Deletion** Non-ephemeral deleted accounts are marked with the `TSDK_ACCOUNT_FLAG_DELETED` flag as a tombstone in the state tree. ## Account Operations ### Balance Transfers Programs can transfer funds between accounts using the transfer syscall: ```c // Transfer amount from one account to another ulong result = tsys_account_transfer(from_account_idx, to_account_idx, amount); ``` Note See [`tsys_account_transfer()`](/docs/spec/vm/syscalls/account_transfer/) for usage details and requirements. ### Flag Management Programs can modify account flags using the set flags syscall: ```c // Set account flags (owner program only) ulong result = tsys_account_set_flags(account_idx, flags); ``` Note See [`tsys_account_set_flags()`](/docs/spec/vm/syscalls/account_set_flags/) for available flags and restrictions. ### Making Accounts Writable Before modifying account data, programs must mark accounts as writable: ```c // Mark account as writable for current transaction ulong result = tsys_set_account_data_writable(account_idx); ``` Note See [`tsys_set_account_data_writable()`](/docs/spec/vm/syscalls/set_account_data_writable/) for detailed requirements. # Account Addresses > How account addresses are computed in Thru In Thru, all accounts are identified by 32-byte addresses. These addresses can be generated in two distinct ways: derived from keypairs using ED25519 cryptography, or computed deterministically by programs using SHA256 hashing. ## Keypair-Derived Addresses Externally owned accounts (EOAs), also known as user accounts, are accounts that are controlled by users with their private keys. They are owned by the externally owned account program, which is located at the address `0` (all zeros). The keypair is generated using ED25519 public key cryptography. Both the private and public keys are 32 bytes. ## Program-Derived Addresses When programs create accounts, their addresses are computed deterministically using SHA256 hashing. ### Address Computation Program-derived addresses (PDAs) are computed using the following formula: ```plaintext SHA256(program_pubkey || is_ephemeral || seed) ``` **Parameters:** * `program_pubkey` - The 32-byte public key of the program creating the account * `is_ephemeral` - A single byte (0 or 1) indicating whether the account is ephemeral * `seed` - A 32-byte seed provided by the program # Accounts > Complete specification of Thru's account system, including structure, addresses, ownership, and compression Accounts are the fundamental units of state in the Thru network. They serve as containers for data, code, and value, enabling programs to store persistent information and users to hold tokens. All network state is organized around accounts, from user wallets and program code to application data and system configuration. Thru implements an account model that manages state, ownership, and data for all entities on the network. Each account is uniquely identified by a 32-byte public key and contains metadata and arbitrary data that programs can read and modify. ## Key Components ### [Account Model](/docs/spec/accounts/account-model/) Complete specification of Thru’s account structure, metadata, ownership model, and limitations. Covers account metadata fields (version, flags, sequence number, owner, balance, nonce), account types (permanent and ephemeral), ownership model including externally owned accounts (EOAs), account lifecycle (creation, active state, compression, deletion), and account operations (balance transfers, flag management, resizing). ### [Account Addresses](/docs/spec/accounts/addresses/) How account addresses are computed in Thru. Explains two methods for generating 32-byte addresses: keypair-derived addresses using ED25519 cryptography for externally owned accounts (EOAs), and program-derived addresses (PDAs) computed deterministically using SHA256 hashing with program public keys, ephemeral flags, and seeds. ### [Account Compression and State Proofs](/docs/spec/accounts/account-compression/) Mechanism for archiving accounts to reduce validator storage requirements. Explains how validators compress cold accounts, the Merkle tree structure used for state proofs, and how transactions supply cryptographic evidence for the [`account_compress`](/docs/spec/vm/syscalls/account_compress/) and [`account_decompress`](/docs/spec/vm/syscalls/account_decompress/) syscalls. Covers the three types of state proofs: creation proofs, existing proofs, and updating proofs. # Core > Core blockchain primitives and data structures for the Thru blockchain The Thru Core specification defines the fundamental data structures and primitives that form the foundation of the Thru blockchain. These components handle the basic building blocks of blockchain operations. ## Key Components ### [Transactions](/docs/spec/core/transactions/) Atomic units of computation with detailed binary format specification. Includes transaction headers with fee payer and program information, account address lists (read-write and read-only), instruction data, and optional state proofs for fee payer accounts. Defines transaction validation criteria, signature verification, and account list ordering requirements. ### [Thru Format (thrufmt)](/docs/spec/core/thru-fmt/) Standard human-readable encoding format for Thru public addresses (`ta…`) and signatures (`ts…`). Documents the base64url encoding scheme, checksum algorithms, and CLI tools for converting between raw hex and the canonical string form. All clients must match the encoding behavior exactly, byte-for-byte. # Thru Format (thrufmt) `thrufmt` is the standard human-readable form for Thru public addresses (`ta…`) and signatures (`ts…`). It defines how raw binary keys and signatures are encoded, how checksums are added, and how clients should validate them. All clients are expected to match their behavior exactly, byte-for-byte. ## CLI Usage Use `thru util convert` to convert between `hex` and `thrufmt`. Convert a 32-byte hex public key to `ta…`: ```sh $ thru --json util convert pubkey hex-to-thrufmt \ 0000000000000000000000000000000000000000000000000000000000000001 { "hex_pubkey": "0000000000000000000000000000000000000000000000000000000000000001", "thru_pubkey": "taAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB" } ``` Convert a 64-byte hex signature to `ts…`: ```sh $ thru --json util convert signature hex-to-thrufmt \ 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef { "hex_signature": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "thru_signature": "tsASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7x4A" } ``` Drop `--json` for the plain-text output. Note that inverse conversions (`thrufmt-to-hex`) for both addresses and signatures are also supported. ## Base Encoding Thru streams raw bytes into a base64url encoder ([RFC 4648 §5](https://datatracker.ietf.org/doc/html/rfc4648#section-5)). The alphabet is: ```plaintext ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ ``` * The stream never uses padding (`=`). * Encoding starts with a two-character type prefix (`ta` or `ts`) so handles can be visually distinguished. * All checksums are computed on the binary payload before base64 encoding. ## Public Address Format (`ta…`) Encoding turns a 32-byte public key into 46 printable characters (`ta` + 44 base64url symbols). ### Encoding 1. All buffers begin with `ta` to flag the string as a Thru account. 2. Stream the 32 raw public-key bytes into the base64url accumulator, while updating a byte sum. 3. Take the sum modulo 256 (2^8) and append the single checksum byte, giving 33 total bytes. 4. Flush the accumulator to base64url characters without padding. Those 33 bytes always expand to 44 characters, so the finished string is 46 characters long (`ta` + 44 base64 symbols). ### Checksum ```plaintext checksum = (Σ pubkey[i]) & 0xFF ``` Decoding recomputes the sum from the decoded 32 bytes, masks it to 8 bits, and requires it to match the appended checksum byte. Any mismatch results in a decoding error, preventing truncated or mistyped addresses from being accepted. ## Signature Format (`ts…`) Encoding serializes a 64-byte Ed25519 signature into a 90-character string (`ts` + 88 base64url symbols). ### Encoding 1. All buffers begin with `ts` to flag the string as a Thru signature. 2. Stream all 64 signature bytes through the base64url accumulator, while updating a running checksum. 3. Take the sum modulo 65,536 (2^16) and append the two checksum bytes in big-endian order. That turns the payload into 66 bytes (64 data + 2 checksum). 4. Flush the accumulator to base64url characters. Sixty-six bytes always become 88 characters, resulting in a fixed 90-character `ts…` string. ### Checksum ```plaintext checksum = (Σ signature[i]) & 0xFFFF ``` Decoding expects a 90-character string starting with `ts`. After base64url decoding it verifies the checksum word from the last two bytes before returning the 64-byte signature. # Transactions Transactions on the Thru blockchain are the core atomic unit of computation that a user can submit for execution. Each transaction represents an indivisible set of operations that either all succeed or all fail together. ## Format A Thru transaction consists of the following components: ### Transaction Header (v1) The transaction header contains critical metadata and parameters: ```c struct { uint8_t transaction_version; // [0,1) bytes: Must be 0x01 uint8_t flags; // [1,2) bytes: Transaction flags uint16_t readwrite_accounts_cnt; // [2,4) bytes: Number of writable accounts uint16_t readonly_accounts_cnt; // [4,6) bytes: Number of readonly accounts uint16_t instr_data_sz; // [6,8) bytes: Size of instruction data uint32_t req_compute_units; // [8,12) bytes: Requested compute units uint16_t req_state_units; // [12,14) bytes: Requested state units uint16_t req_memory_units; // [14,16) bytes: Requested memory units uint64_t fee; // [16,24) bytes: Transaction fee uint64_t nonce; // [24,32) bytes: Transaction nonce uint64_t start_slot; // [32,40) bytes: Earliest valid slot uint32_t expiry_after; // [40,44) bytes: Slots until expiry uint16_t chain_id; // [44,46) bytes: Chain identifier uint16_t padding_0; // [46,48) bytes: Reserved padding pubkey_t fee_payer_pubkey; // [48,80) bytes: Fee payer's public key pubkey_t program_pubkey; // [80,112) bytes: Program's public key } ``` ### Account Addresses At the end of and after the header, the transaction contains arrays of account addresses that will be accessed during execution. The account addresses are organized in a specific order: 1. The fee payer account is always listed first in the transaction. This is also header field `fee_payer_pubkey`. 2. The program account is always listed second in the transaction. This is header field `program_pubkey`. 3. The writable accounts array follows, containing all accounts that the program may modify. 4. The read-only accounts array comes last, containing all accounts that the program may read but not modify. Note The account indices in a Thru transaction are always assigned as follows: * The fee payer account is at index **0** (corresponding to `fee_payer_pubkey` in the header). * The program account is at index **1** (corresponding to `program_pubkey` in the header). * Writable and read-only accounts follow at subsequent indices. This means that any time an account index is referenced (for example, in instruction data), index **0** always refers to the fee payer, and index **1** always refers to the program. These indices directly overlap with the order of the account addresses as listed after the header. ### Instruction Data Following the account addresses is the instruction data section. This contains the raw data that will be passed to the program during execution. The size of this data is specified in the header’s `instr_data_sz` field. ### Optional State Proof For fee payer accounts that are being created or decompressed from a compressed state, an optional state proof may be included. This proof validates the account’s existence or non-existence in the state tree. ### Transaction Signature The transaction concludes with a 64-byte Ed25519 signature from the fee payer. This signature is placed at the very end of the transaction data, after all other components including optional state proofs. The signature covers all transaction data preceding it (the “message”), ensuring the transaction’s integrity and authenticity. ## Complete Transaction Data Layout The following shows the complete binary layout of a Thru transaction: ```c struct thru_transaction_layout { // Transaction Header (112 bytes) struct { uint8_t transaction_version; // [0,1) bytes: Must be 0x01 uint8_t flags; // [1,2) bytes: Transaction flags uint16_t readwrite_accounts_cnt; // [2,4) bytes: Number of writable accounts uint16_t readonly_accounts_cnt; // [4,6) bytes: Number of readonly accounts uint16_t instr_data_sz; // [6,8) bytes: Size of instruction data uint32_t req_compute_units; // [8,12) bytes: Requested compute units uint16_t req_state_units; // [12,14) bytes: Requested state units uint16_t req_memory_units; // [14,16) bytes: Requested memory units uint64_t fee; // [16,24) bytes: Transaction fee uint64_t nonce; // [24,32) bytes: Transaction nonce uint64_t start_slot; // [32,40) bytes: Earliest valid slot uint32_t expiry_after; // [40,44) bytes: Slots until expiry uint16_t chain_id; // [44,46) bytes: Chain identifier uint16_t padding_0; // [46,48) bytes: Reserved padding uint8_t fee_payer_pubkey[32]; // [48,80) bytes: Fee payer's public key uint8_t program_pubkey[32]; // [80,112) bytes: Program's public key } header; // Account Addresses (variable size) uint8_t readwrite_accounts[readwrite_accounts_cnt][32]; // Writable account addresses uint8_t readonly_accounts[readonly_accounts_cnt][32]; // Read-only account addresses // Instruction Data (variable size) uint8_t instruction_data[instr_data_sz]; // Program instruction data // Optional State Proof (if TN_TXN_FLAG_HAS_FEE_PAYER_PROOF flag is set) struct { struct { uint64_t type_slot; // [high 2 bits: proof type, low 62 bits: slot] uint8_t path_bitset[32]; // Bitset indicating proof path indices } proof_header; // Variable proof body based on type: // For CREATION (type=2): existing_leaf_pubkey[32] + existing_leaf_hash[32] + sibling_hashes[] // For EXISTING (type=0): sibling_hashes[] // For UPDATING (type=1): existing_leaf_hash[32] + sibling_hashes[] uint8_t proof_body[]; // byte length = (proof_type + popcount(path_bitset)) * sizeof(fd_hash_t) } fee_payer_state_proof; // Optional Account Metadata (only for EXISTING state proof type) struct { uint16_t magic; // [0,2) bytes: Magic number (0xC7A3) uint8_t version; // [2,3) bytes: Account version (0x00) uint8_t flags; // [3,4) bytes: Account flags uint32_t data_sz; // [4,8) bytes: Account data size uint64_t seq; // [8,16) bytes: State counter uint8_t owner[32]; // [16,48) bytes: Account owner pubkey uint64_t balance; // [48,56) bytes: Account balance uint64_t nonce; // [56,64) bytes: Account nonce } fee_payer_account_meta; // Transaction Signature (always at the end, 64 bytes) uint8_t fee_payer_signature[64]; // Ed25519 signature covering all preceding data }; ``` ## Field Descriptions The following table describes each field in the transaction structure: | Field | Offset | Size | Type | Description | | ----------------------------- | ------------- | ---------- | ----------------- | ---------------------------------------------------------------------------------- | | **Transaction Header** | | | | | | `transaction_version` | 0 | 1 byte | uint8 | Transaction format version, must be `0x01` | | `flags` | 1 | 1 byte | uint8 | Transaction flags (bit 0: has fee payer proof, bits 1-7: reserved) | | `readwrite_accounts_cnt` | 2-3 | 2 bytes | uint16 | Number of accounts that can be modified by the program | | `readonly_accounts_cnt` | 4-5 | 2 bytes | uint16 | Number of accounts that can only be read by the program | | `instr_data_sz` | 6-7 | 2 bytes | uint16 | Size in bytes of the instruction data section | | `req_compute_units` | 8-11 | 4 bytes | uint32 | Maximum compute units the transaction may consume | | `req_state_units` | 12-13 | 2 bytes | uint16 | Maximum state units the transaction may consume | | `req_memory_units` | 14-15 | 2 bytes | uint16 | Maximum memory units the transaction may consume | | `fee` | 16-23 | 8 bytes | uint64 | Transaction fee in native tokens | | `nonce` | 24-31 | 8 bytes | uint64 | Transaction nonce, must match fee payer’s current nonce | | `start_slot` | 32-39 | 8 bytes | uint64 | Earliest slot when transaction becomes valid | | `expiry_after` | 40-43 | 4 bytes | uint32 | Number of slots after start\_slot when transaction expires | | `chain_id` | 44-45 | 2 bytes | uint16 | Chain identifier to prevent cross-chain replay attacks | | `padding_0` | 46-47 | 2 bytes | uint16 | Reserved padding, must be zero | | `fee_payer_pubkey` | 48-79 | 32 bytes | Ed25519 pubkey | Public key of the account paying transaction fees | | `program_pubkey` | 80-111 | 32 bytes | Ed25519 pubkey | Public key of the program to execute | | **Account Addresses** | | | | | | `readwrite_accounts` | 112+ | 32×N bytes | Ed25519 pubkey\[] | Array of writable account addresses (sorted ascending) | | `readonly_accounts` | Variable | 32×M bytes | Ed25519 pubkey\[] | Array of read-only account addresses (sorted ascending) | | **Instruction Data** | | | | | | `instruction_data` | Variable | Variable | uint8\[] | Raw data passed to the program during execution | | **Optional State Proof** | | | | | | `type_slot` | Variable | 8 bytes | uint64 | Proof type (bits 62-63) and slot number (bits 0-61) | | `path_bitset` | Variable | 32 bytes | uint8\[32] | Bitset indicating which indices have sibling hashes | | `proof_body` | Variable | Variable | uint8\[] | Variable proof data based on type and path\_bitset | | **Optional Account Metadata** | | | | | | `magic` | Variable | 2 bytes | uint16 | Magic number identifying account metadata (0xC7A3) | | `version` | Variable | 1 byte | uint8 | Account metadata version (0x00) | | `flags` | Variable | 1 byte | uint8 | Account flags (program, privileged, compressed, etc.) | | `data_sz` | Variable | 4 bytes | uint32 | Size of account data in bytes | | `seq` | Variable | 8 bytes | uint64 | Account state modification counter | | `owner` | Variable | 32 bytes | Ed25519 pubkey | Public key of the program that owns this account | | `balance` | Variable | 8 bytes | uint64 | Account balance in native tokens | | `nonce` | Variable | 8 bytes | uint64 | Account nonce for transaction ordering | | **Transaction Signature** | | | | | | `fee_payer_signature` | Last 64 bytes | 64 bytes | Ed25519 signature | Cryptographic signature from the fee payer covering all preceding transaction data | ### Transaction Flags The flags field at byte offset 1 controls optional transaction features. Currently, only one flag is defined: | Flag | Bit Position | Value | Description | | --------------------------------- | ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `TN_TXN_FLAG_HAS_FEE_PAYER_PROOF` | 0 | `0x01` | Indicates that the transaction includes a state proof for the fee payer account. When this flag is set, a state proof must be included at the end of the transaction after the instruction data. | All other bits in the flags field must be set to 0. The transaction parser will reject any transaction with unknown flags set to ensure forward compatibility. ## Limitations The Thru blockchain enforces several limits on transaction structure and size: * The maximum transaction size is 32KiB. * Each transaction can reference at most 1024 accounts. * All account addresses must be exactly 32 bytes in length. ## Validity Criteria A valid transaction is one that is includable in the chain. A block is itself unincludable if it contains an invalid transaction. A transaction must meet the following criteria to be considered valid. ### Format Validation * The transaction size must not exceed the maximum transmission unit (MTU) of 32KiB. * The transaction version must be exactly `0x01`. * All fields in the transaction header must be properly formatted and aligned. * The account arrays must contain the correct number of accounts as specified in the header. * The total number of accounts referenced must not exceed 1024. ### Signature Verification * The fee payer’s Ed25519 signature must be cryptographically valid. * The signature is located at the end of the transaction (last 64 bytes). * The signature must cover all transaction data preceding it (the “message”), which includes the header, account addresses, instruction data, and optional state proofs. ### Account List Verification * No duplicate accounts are allowed anywhere in the transaction. * Writable accounts must be sorted in ascending lexicographic order. * Read-only accounts must be sorted in ascending lexicographic order. # Technical Specifications > Comprehensive technical documentation for Thru's core protocols, runtime environment, and virtual machine # Thru Network Technical Specifications This section provides technical documentation for Thru Network’s architecture, covering the core blockchain protocols, runtime environment, and virtual machine implementation. ## Architecture Overview Thru Network is organized into three main specification areas: [Core ](/docs/spec/core/overview/)Fundamental blockchain data structures and protocols [Runtime Environment ](/docs/spec/runtime/overview/)Program execution environment and transaction processing [Virtual Machine ](/docs/spec/vm/overview/)VM architecture, instruction set, and system calls ## Core The core specifications define the fundamental data structures and rules that govern the Thru blockchain: ### Available Specifications * **[Core Overview](/docs/spec/core/overview/)** - Core blockchain primitives and data structures * **[Transactions](/docs/spec/core/transactions/)** - Transaction format and validation rules ## Runtime Environment The runtime environment manages program execution and provides the execution context for Thru programs: ### Available Specifications * **[Runtime Overview](/docs/spec/runtime/overview/)** - Core runtime architecture and principles * **[Transaction Execution](/docs/spec/runtime/transaction-execution/)** - How transactions are processed and executed * **[Resources](/docs/spec/runtime/resources/)** - Resource management and allocation * **[Error Handling](/docs/spec/runtime/errors/)** - Runtime error types and handling mechanisms ## Virtual Machine The Thru Virtual Machine provides the execution environment for programs running on the network: ### Available Specifications * **[VM Overview](/docs/spec/vm/overview/)** - Virtual machine architecture and design * **[Memory Layout](/docs/spec/vm/memory-layout/)** - VM memory organization and management * **[Instruction Set](/docs/spec/vm/instruction-set/)** - Complete VM instruction reference * **[Executable Format](/docs/spec/vm/executable-format/)** - Program binary format specification * **[VM Errors](/docs/spec/vm/errors/)** - Virtual machine error types and conditions ### System Calls * **[System Calls Overview](/docs/spec/vm/syscalls/overview/)** - Complete system call interface reference *** ## Getting Started To understand Thru’s technical architecture: 1. **Start with [Core](/docs/spec/core/overview/)** to understand the fundamental blockchain data structures 2. **Review the [Runtime Environment](/docs/spec/runtime/overview/)** to learn how programs execute 3. **Explore the [Virtual Machine](/docs/spec/vm/overview/)** specifications for detailed implementation guidance # Thru VM Debugging Support > Debugging support for the Thru VM via GDB remote server protocol ## Overview The Thru VM supports debugging via the GDB remote server protocol. Clients such as GDB and LLDB which support RISC-V can connect to a running VM instance, and can inspect and alter execution. ## Remote Server Protocol The GDB remote server protocol specifies how clients, such as GDB, interact with a running remote, which in this case is the Thru VM. When the VM is run in debug mode, it accepts a connection over TCP, by default on port 9001. Using `target remote localhost:9001`, GDB can connect to the VM and start debugging. Several important commands are supported, including: * `g`/`G`: Read and write registers * `m`/`M`: Read and write memory * `z`/`Z`: Set and clear breakpoints and watchpoints # Runtime Errors > Complete reference of Thru runtime transaction errors organized by error class The Thru runtime defines specific error codes that can occur during transaction processing. These errors are categorized into different classes based on when they occur in the transaction lifecycle. Tip For the authoritative, up-to-date error list, also check `src/thru/runtime/tn_runtime_errors.h`. ## Error Classes Runtime errors are organized into the following classes: Transaction Validation (0xFFFFFF00) Errors that occur while validating the transaction before any state is loaded (e.g., signature verification). Transaction Pre-Execute (0xFFFFFE00) Errors that occur while advancing the nonce or collecting fees, before actual program execution. Transaction Execute (0xFFFFFD00) Errors that occur during the course of program execution. Transaction Post-Execute (0xFFFFFC00) Fatal errors that should never happen during normal operation. These are not consensus enforceable and indicate system-level failures. Block Validation (0xFFFFE400) Errors that occur during block validation processes. ## Success Code **`TN_RUNTIME_TXN_EXECUTE_SUCCESS` · *0x00000000 (0)*** Indicates successful transaction execution with no errors. ## Transaction Validation Errors These errors occur during the initial validation phase before any state is loaded: **`TN_RUNTIME_TXN_ERR_INVALID_FORMAT` · *0xFFFFFF01 (-255)*** The transaction format is invalid or malformed. **`TN_RUNTIME_TXN_ERR_INVALID_VERSION` · *0xFFFFFF02 (-254)*** The transaction version is not supported or invalid. **`TN_RUNTIME_TXN_ERR_INVALID_FLAGS` · *0xFFFFFF03 (-253)*** The transaction contains invalid or unsupported flags. **`TN_RUNTIME_TXN_ERR_INVALID_SIGNATURE` · *0xFFFFFF04 (-252)*** The transaction signature is invalid or verification failed. **`TN_RUNTIME_TXN_ERR_DUPLICATE_ACCOUNT` · *0xFFFFFF05 (-251)*** The transaction contains duplicate account references. **`TN_RUNTIME_TXN_ERR_UNSORTED_ACCOUNTS` · *0xFFFFFF06 (-250)*** The account list in the transaction is not properly sorted. See [Transaction Account Lists](/docs/spec/core/transactions/#account-addresses) for sorting requirements. **`TN_RUNTIME_TXN_ERR_UNSORTED_READWRITE_ACCOUNTS` · *0xFFFFFF07 (-249)*** The read-write account list is not properly sorted. See [Transaction Account Lists](/docs/spec/core/transactions/#account-addresses) for sorting requirements. **`TN_RUNTIME_TXN_ERR_UNSORTED_READONLY_ACCOUNTS` · *0xFFFFFF08 (-248)*** The read-only account list is not properly sorted. See [Transaction Account Lists](/docs/spec/core/transactions/#account-addresses) for sorting requirements. **`TN_RUNTIME_TXN_ERR_ACCOUNT_COUNT_LIMIT_EXCEEDED` · *0xFFFFFF09 (-247)*** The transaction references more accounts than the maximum allowed limit. **`TN_RUNTIME_TXN_ERR_CHAIN_ID_ZERO` · *0xFFFFFF0A (-246)*** The transaction chain ID is zero, which is not allowed. ## Transaction Pre-Execute Errors These errors occur during nonce advancement and fee collection: **`TN_RUNTIME_TXN_ERR_NONCE_TOO_LOW` · *0xFFFFFE01 (-511)*** The transaction nonce is lower than the account’s current nonce. See [Transaction Format](/docs/spec/core/transactions/#field-descriptions) for nonce requirements. **`TN_RUNTIME_TXN_ERR_NONCE_TOO_HIGH` · *0xFFFFFE02 (-510)*** The transaction nonce is higher than the account’s expected next nonce. See [Transaction Format](/docs/spec/core/transactions/#field-descriptions) for nonce requirements. **`TN_RUNTIME_TXN_ERR_INSUFFICIENT_FEE_PAYER_BALANCE` · *0xFFFFFE03 (-509)*** The fee payer account does not have sufficient balance to cover transaction fees. **`TN_RUNTIME_TXN_ERR_FEE_PAYER_ACCOUNT_DOES_NOT_EXIST` · *0xFFFFFE04 (-508)*** The specified fee payer account does not exist in the ledger. **`TN_RUNTIME_TXN_ERR_NOT_LIVE_YET` · *0xFFFFFE05 (-507)*** The transaction is not yet valid according to its validity time constraints. **`TN_RUNTIME_TXN_ERR_EXPIRED` · *0xFFFFFE06 (-506)*** The transaction has expired and is no longer valid for execution. **`TN_RUNTIME_TXN_ERR_INVALID_FEE_PAYER_STATE_PROOF` · *0xFFFFFE07 (-505)*** The provided state proof for the fee payer account is invalid. **`TN_RUNTIME_TXN_ERR_INVALID_FEE_PAYER_STATE_PROOF_TYPE` · *0xFFFFFE08 (-504)*** The type of state proof provided for the fee payer is not valid or supported. **`TN_RUNTIME_TXN_ERR_INVALID_FEE_PAYER_STATE_PROOF_SLOT` · *0xFFFFFE09 (-503)*** The slot reference in the fee payer state proof is invalid or outdated. **`TN_RUNTIME_TXN_ERR_INVALID_FEE_PAYER_ACCOUNT_OWNER` · *0xFFFFFE0A (-502)*** The fee payer account owner is invalid. **`TN_RUNTIME_TXN_ERR_INVALID_FEE_PAYER_STATE_PROOF_ACCOUNT_OWNER` · *0xFFFFFE0B (-501)*** The fee payer state proof account owner does not match expected ownership. **`TN_RUNTIME_TXN_ERR_INVALID_FEE_PAYER_STATE_PROOF_ACCOUNT_META_DATA_SZ` · *0xFFFFFE0C (-500)*** The fee payer state proof account metadata size is invalid. **`TN_RUNTIME_TXN_ERR_CHAIN_ID_MISMATCH` · *0xFFFFFE0D (-499)*** The transaction chain ID does not match the runtime chain ID. **`TN_RUNTIME_TXN_ERR_FEE_PAYER_IN_COMPRESSION_TIMEOUT` · *0xFFFFFE0E (-498)*** The fee payer account is still within the compression cooldown window and cannot be uncompressed via a fee-payer state proof. Wait until the cooldown has elapsed before submitting the transaction. ## Transaction Execute Errors These errors occur during program execution: **`TN_RUNTIME_TXN_ERR_VM_FAILED` · *0xFFFFFD01 (-767)*** The virtual machine encountered a fatal error during program execution. **`TN_RUNTIME_TXN_ERR_INVALID_PROGRAM_ACCOUNT` · *0xFFFFFD02 (-766)*** The specified program account is invalid, not executable, or does not exist. **`TN_RUNTIME_TXN_ERR_VM_REVERT` · *0xFFFFFD03 (-765)*** The program explicitly reverted execution, rolling back all changes. See [exit syscall](/docs/spec/vm/syscalls/exit/) for revert behavior. Note This error may indicate the program set a user error code. Consult the program’s documentation or implementation for details. A negative user error code may indicate the error code is for a failed syscall - see [syscalls documentation](/docs/spec/vm/syscalls/overview/) for details. **`TN_RUNTIME_TXN_ERR_CU_EXHAUSTED` · *0xFFFFFD04 (-764)*** The transaction has exhausted its compute unit (CU) allowance. See [Compute Units](/docs/spec/runtime/resources/#compute-units) for resource management. **`TN_RUNTIME_TXN_ERR_SU_EXHAUSTED` · *0xFFFFFD05 (-763)*** The transaction has exhausted its storage unit (SU) allowance. See [Memory Units](/docs/spec/runtime/resources/#memory-units) for resource management. For VM-specific errors including syscall errors, fault codes, and virtual machine execution errors, see [VM Errors](/docs/spec/vm/errors/). ## Error Handling Tip When debugging transaction failures, first identify the error class to understand at which stage the transaction failed, then examine the specific error code for detailed troubleshooting information. # Runtime > Transaction execution environment for the Thru blockchain The Thru Runtime is the execution environment that processes transactions on the Thru blockchain. It manages the complete transaction lifecycle from validation through execution to state commitment. ## Key Components ### [Transaction Execution](/docs/spec/runtime/transaction-execution/) Covers the multi-phase transaction processing pipeline including pre-execution validation, VM execution, and post-execution rules. ### [Resource Management](/docs/spec/runtime/resources/) Defines the resource metering system with Compute Units (CUs) for instruction execution, Memory Units (MUs) for scratch space allocation, and resource consumption patterns. ### [Error Handling](/docs/spec/runtime/errors/) Comprehensive error codes organized by transaction lifecycle stage, from validation errors to execution failures and system-level issues. # Resources ## Compute Units Compute Units (CUs) are Thru’s mechanism for metering computational work and preventing infinite loops or resource exhaustion attacks. Every smart contract execution consumes compute units, and transactions have a limited budget that must be specified upfront. ### How Compute Units Work When you submit a transaction, you specify the maximum number of compute units your program is allowed to consume via the `req_compute_units` field. If your program exceeds this limit during execution, it will terminate with a `TN_VM_SYSCALL_ERR_COMPUTE_UNITS_EXCEEDED` error and all changes will be reverted. The CU charge model is simple: **1 CU per byte of instruction or data processed**. This means that the cost of an instruction is directly proportional to its size in bytes, and memory operations are charged based on the amount of data accessed. ### Instruction Costs Different types of operations consume different amounts of compute units: #### Basic Instructions The cost of an instruction is determined by its size: * **Regular (32-bit) Instructions: 4 CUs each** * **Compressed (16-bit) Instructions: 2 CUs each** This includes: * Arithmetic operations (`add`, `sub`, `mul`, `div`) * Logical operations (`and`, `or`, `xor`, `shl`, `shr`) * Branch and jump instructions (`beq`, `bne`, `jal`, `jalr`) **Examples:** ```assembly add x1, x2, x3 # 4 CUs (32-bit instruction) c.add x1, x2 # 2 CUs (16-bit compressed instruction) ``` #### System Calls **Base Syscall Cost: 512 CUs** Every system call has a base cost of 512 compute units, representing the overhead of saving and restoring VM state (32 registers × 8 bytes × 2 operations). Additional costs may apply based on the specific syscall operation: * **Memory allocation**: Variable cost based on pages allocated * **Account operations**: Additional costs for data processing * **Cross-program invocations**: Base cost + target program execution See the [syscalls documentation](/docs/spec/vm/syscalls/overview/) for detailed costs of each system call. #### Memory Operations **Memory Access: 1 CU per byte** The total cost for memory access instructions is the sum of the instruction’s base cost plus the cost for the bytes being accessed (1 CU per byte). * **Load/Store Data Costs:** * `lb` / `sb` (byte): 1 CU * `lh` / `sh` (half-word): 2 CUs * `lw` / `sw` (word): 4 CUs * `ld` / `sd` (double-word): 8 CUs **Total Cost Example:** A standard `lw` (load word) instruction is 32-bits, so its base cost is 4 CUs. It also loads a 4-byte word from memory, which costs an additional 4 CUs. * `lw` instruction (4 CUs) + data access (4 CUs) = **8 CUs total** Similarly, a standard `sd` (store double-word) instruction costs: * `sd` instruction (4 CUs) + data access (8 CUs) = **12 CUs total** **Page Faults: 4,096 CUs** When your program accesses memory that hasn’t been allocated or loaded, a page fault occurs. Each page fault costs exactly 4,096 compute units (equal to the page size). Tip Pre-allocate memory segments that you know you’ll need to avoid expensive page faults during critical execution paths. Note Compute unit costs are deterministic and consistent across all executions of the same program with the same inputs, making them suitable for predictable fee estimation. ## Memory Units Memory Units (MUs) represent the scratch space allocation budget for your transaction. Each memory unit corresponds to **4,096 bytes (4KB)** of memory that can be used for temporary storage during program execution. ### How Memory Units Work When you submit a transaction, you specify the maximum number of memory units your program is allowed to consume via the `req_memory_units` field. Unlike compute units, memory units can be **allocated and released** during execution as your program’s memory needs change. Note Memory units are charged for the **peak usage** during transaction execution, not the total allocated over time. If you allocate 10 MUs, release 5 MUs, then allocate 3 more MUs, you are charged for 10 MUs (the peak), not 18 MUs. ### Memory Unit Consumption Memory units are consumed through various operations that require scratch space: #### Anonymous Segment Operations **Growing Anonymous Segments** * **Stack allocation**: Growing the stack segment consumes memory units * **Heap allocation**: Growing heap segments for dynamic memory * Each page (4KB) of growth consumes 1 memory unit **Examples:** ```assembly # Allocating 8KB of stack space # Consumes 2 memory units (8KB ÷ 4KB = 2 MUs) ``` **Shrinking Anonymous Segments** * Shrinking segments **releases** memory units back to your budget * Released memory units can be reused for other allocations * This allows efficient memory management patterns #### Account Operations **Account Data Growth** * Resizing account data to a larger size consumes memory units. * Each additional 4KB page requires 1 memory unit * Growth is rounded up to the nearest page boundary **Account Data Shrinkage** * Reducing account data size releases memory units if the backing page of the account was dirty. * Released units become available for other operations * Helps optimize memory usage across account operations **Account Data Access and Copy-on-Write (CoW)** * **Read access**: Reading account data does not consume memory units * **Write access**: First write to an account page triggers Copy-on-Write (CoW) * **CoW allocation**: When writing to a page for the first time, a private copy is created * **Memory consumption**: Each CoW operation consumes 1 memory unit (4KB page) * **Subsequent writes**: Additional writes to the same page do not consume more memory units **CoW Memory Pattern:** ```rust // Example: Writing to account data account_data[0] = 1; // First write to page 0: Consumes 1 MU (CoW) account_data[100] = 2; // Same page 0: No additional MU account_data[4096] = 3; // First write to page 1: Consumes 1 MU (CoW) ``` Caution CoW allocation happens on the first write to each 4KB page of account data. Plan your memory budget to account for all pages you intend to modify, not just account growth. **Account Creation** * Creating new accounts does not consume memory units (yet). * Ephemeral accounts follow the same memory unit rules * Account deletion releases all associated memory units #### Event Emission **Event Buffer Growth** * Emitting events grows the event buffer segment * Each 4KB of event data consumes 1 memory unit * Events accumulate throughout transaction execution **Event Memory Pattern:** ```rust // Example: Emitting multiple events emit_event(small_event); // May not consume MU if fits in existing buffer emit_event(large_event); // Grows buffer, consumes additional MUs ``` ### Memory Management Best Practices 1. **Estimate peak memory usage** Calculate the maximum simultaneous memory allocation your program will need. Consider all active segments, account growth, and event buffers at their peak. ```rust // Conservative estimate for a complex operation let memory_units = base_stack_pages + max_account_growth_pages + event_buffer_pages; ``` 2. **Use memory efficiently** * **Release early**: Shrink segments when no longer needed * **Reuse space**: Take advantage of released memory units * **Batch operations**: Group memory allocations to minimize peak usage 3. **Handle allocation failures** Monitor available memory units before large allocations: ```rust // Check available memory before growing segments if available_memory_units() < required_pages { return Err(ProgramError::InsufficientMemory); } ``` ### Memory Unit Scenarios Simple Program Execution **\~2-4 MUs** * Base stack allocation: \~1-2 MUs * Small local variables: \~1 MU * Minimal event emission: \~1 MU Account Creation & Growth **Variable (depends on data size)** * New 8KB account: \~2 MUs * Growing existing account by 16KB: \~4 MUs * Multiple account operations: Sum of individual needs Heavy Event Emission **Variable (depends on event size)** * 100 small events (\~50 bytes each): \~2 MUs * Large structured events: \~1 MU per 4KB * Event-heavy programs: Plan accordingly Dynamic Memory Usage **Peak-based Charging** * Allocate 20KB (5 MUs), release 12KB (3 MUs), allocate 8KB (2 MUs) * **Charged: 5 MUs** (peak usage) * **Not: 10 MUs** (total allocated) Tip Unlike compute units, memory units encourage efficient memory management through the release mechanism. Design your programs to shrink segments when possible to maximize available memory for subsequent operations. ## State Units Caution This section of the specification is changing frequently. Check back soon for more details. # Transaction Execution Transaction execution in Thru follows a structured four-phase pipeline that ensures consistency, security, and resource management. Each phase has specific responsibilities and failure conditions. 1. **Transaction Validation** This phase occurs **before** a transaction can be included in a block. Validation failures result in invalid blocks and consensus rejection. **Signature Verification** * Transaction signature must be valid for the fee payer’s public key * Ed25519 signature verification on transaction payload **Account Structure Validation** * Account addresses must be properly sorted (read-write accounts in ascending order, then read-only accounts in ascending order - see [transaction format](/docs/spec/core/transactions/#account-list-verification)) * No duplicate account addresses across any account lists * Total account count must not exceed `TN_TXN_MAX_ACCOUNTS` limit * Fee payer and program accounts must be distinct **Transaction Format Validation** * Transaction version must be supported * Transaction flags must be valid * Transaction size must not exceed MTU limits Note **Critical**: These validation checks determine block validity. A block containing transactions that fail validation will be rejected by consensus. 2. **Pre-Execution** Pre-execution prepares the transaction context and performs economic checks. **If any pre-execution check fails, the transaction is rejected and VM execution does not occur.** Even so, some state changes (nonce advancement, fee collection) may occur during this phase that do persist. **Temporal Validation** * Current block slot must be ≥ transaction’s `start_slot` * Current block slot must be < transaction’s `expiry_slot` **Fee Payer Account Setup** * Fee payer account must exist or be created with a valid state proof * State proof validation (if present): * Cryptographic proof verification against current state root * Proof type must be `CREATION` or `EXISTING` * Proof path must represent valid Merkle tree path **Nonce Processing** * Transaction nonce must exactly match fee payer’s current nonce * **Nonce is advanced immediately** upon validation **Fee Processing** * Fee payer balance must be sufficient for transaction fee * Transaction fee is collected after nonce advancement Caution **Critical**: Nonce advancement occurs **before** fee collection. If fee collection fails due to insufficient balance, the nonce remains advanced but no fee is charged. This prevents replay attacks while allowing transactions to fail gracefully. 3. **VM Execution** The virtual machine executes the transaction’s program with strict resource limits and safety constraints. **Program Validation** * Program account must exist and be marked as executable * Program data must be valid bytecode **Resource Constraints** * **Compute Units**: Execution must not exceed `requested_compute_units` (see [compute units](/docs/spec/runtime/resources/#compute-units)) * **Memory**: VM memory access must stay within allocated segments **VM Execution Environment** * Program runs in isolated VM with segmented memory (`tn_vm_base.h:TN_VM_SEG_*`) * System calls provide controlled access to accounts and blockchain state * Execution can terminate with success or various fault conditions: * `TN_VM_SUCCESS`: Normal completion * `TN_VM_FAULT_REVERT`: Program-initiated revert * `TN_VM_FAULT_SIGCU`: Compute units exhausted * `TN_VM_FAULT_SIGSU`: State units exhausted **State Unit Calculation** State units are computed based on account data size changes: ```plaintext state_bytes_net = max(0, bytes_added - bytes_removed) state_units_consumed = ceil(state_bytes_net / TN_RUNTIME_PAGE_SZ) ``` 4. **Post-Execution** Post-execution finalizes state changes and handles compressed account proofs. **State Unit Validation** * **State Units**: After VM execution, state size changes are calculated and must not exceed `requested_state_units` (see [state units](/docs/spec/runtime/resources/#state-units)) * Transactions that exceed state unit limits are failed with `TN_VM_FAULT_SIGSU` **State Finalization** * Account state changes are committed to storage if execution succeeded * Failed executions preserve nonce advancement and fee collection only **Account Compression Processing** * Accounts that are compressed during execution are removed from the active state set ### Execution Results The execution result includes: * `execution_result`: VM exit code * `compute_units_consumed`: Actual compute units used * `state_units_consumed`: State units consumed by account changes * `user_error_code`: Program-specific error information * `error_program_acc_idx`: Account index of the program that caused the error, useful for identifying the faulting program in cross-program invocation (CPI) scenarios * Memory fault details (if applicable) Note **Resource Tracking**: All phases track resource consumption to ensure transactions stay within their declared limits and prevent denial-of-service attacks. ## Errors For detailed error codes and their descriptions, see [Runtime Errors](/docs/spec/runtime/errors/). # Virtual Machine Errors and Fault Codes > Virtual machine error codes, fault types, and syscall errors for the Thru VM This document contains VM-specific error codes, fault types, and syscall errors that can occur during program execution in the Thru virtual machine. ## VM Error Codes These errors occur at the virtual machine level during program execution: **`TN_VM_SUCCESS` · *0x00000000 (0)*** Virtual machine execution completed successfully. **`TN_VM_ERR_SIGTEXT` · *0xFFFFFFFFFFFFFFFF (-1)*** Invalid or corrupted program text/code segment. This indicates the executable format is malformed or the program code cannot be loaded properly. **`TN_VM_ERR_SIGILL` · *0xFFFFFFFFFFFFFFFE (-2)*** Illegal instruction encountered during execution. The program attempted to execute an invalid or unsupported instruction. See [Instruction Set](/docs/spec/vm/instruction-set/) for supported operations. **`TN_VM_ERR_SIGSEGV` · *0xFFFFFFFFFFFFFFFD (-3)*** Segmentation fault - invalid memory access. The program attempted to access memory outside its allocated segments or violated memory protection rules. See [Memory Layout](/docs/spec/vm/memory-layout/) for segment boundaries. **`TN_VM_ERR_SIGFAULT` · *0xFFFFFFFFFFFFFFFC (-4)*** General memory fault. A memory operation failed due to invalid addressing or protection violations. **`TN_VM_ERR_SIGCU` · *0xFFFFFFFFFFFFFFFB (-5)*** Compute units (CU) exhausted during execution. The program exceeded its allocated computational resources. See [Compute Units](/docs/spec/runtime/resources/#compute-units) for resource limits. **`TN_VM_ERR_SIGSU` · *0xFFFFFFFFFFFFFFFA (-6)*** Storage units (SU) exhausted during execution. The program exceeded its allocated memory/storage resources. See [Memory Units](/docs/spec/runtime/resources/#memory-units) for resource limits. ## VM Fault Types **`TN_VM_FAULT_REVERT` · *0x01*** Program execution was explicitly reverted. The program called the [exit syscall](/docs/spec/vm/syscalls/exit/) with a revert instruction. **`TN_VM_FAULT_SIGCU` · *0x02*** Compute unit exhaustion fault. Execution halted due to CU limit being reached. **`TN_VM_FAULT_SIGSU` · *0x03*** Storage unit exhaustion fault. Execution halted due to SU limit being reached. ## Syscall Errors These errors can occur during syscall execution and are returned by system calls to indicate specific failure conditions: ### Success Codes **`TN_VM_SYSCALL_SUCCESS` · *0x00000000 (0)*** Syscall completed successfully. **`TN_VM_SYSCALL_SUCCESS_EXIT` · *0x00000001 (1)*** Syscall completed successfully and program should exit normally. ### Account and Segment Errors **`TN_VM_ERR_SYSCALL_BAD_SEGMENT_TABLE_SIZE` · *0xFFFFFFFFFFFFFFF9 (-7)*** The segment table size is invalid or corrupted. This may occur when memory layout is incorrectly configured. **`TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` · *0xFFFFFFFFFFFFFFF8 (-8)*** The provided account index is out of bounds or invalid. Account indices must be within the transaction’s account list range. **`TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` · *0xFFFFFFFFFFFFFFF7 (-9)*** The referenced account does not exist in the ledger state. See [account\_create syscall](/docs/spec/vm/syscalls/account_create/) to create new accounts. **`TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` · *0xFFFFFFFFFFFFFFF6 (-10)*** Attempted to modify an account that is not marked as writable in the transaction. The account must be included in the read-write account list. See [set\_account\_data\_writable syscall](/docs/spec/vm/syscalls/set_account_data_writable/). **`TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS` · *0xFFFFFFFFFFFFFFF1 (-15)*** Attempted to create an account that already exists. Use [account\_create syscall](/docs/spec/vm/syscalls/account_create/) only for new accounts. **`TN_VM_ERR_SYSCALL_BAD_ACCOUNT_ADDRESS` · *0xFFFFFFFFFFFFFFF0 (-16)*** The provided account address is malformed or invalid format. **`TN_VM_ERR_SYSCALL_ACCOUNT_IS_NOT_PROGRAM` · *0xFFFFFFFFFFFFFFEF (-17)*** Attempted a program-specific operation on an account that is not executable/program account. **`TN_VM_ERR_SYSCALL_ACCOUNT_HAS_DATA` · *0xFFFFFFFFFFFFFFEE (-18)*** Attempted to perform an operation that requires an empty account, but the account contains data. See [account\_delete syscall](/docs/spec/vm/syscalls/account_delete/) to clear account data. **`TN_VM_ERR_SYSCALL_INVALID_ACCOUNT` · *0xFFFFFFFFFFFFFFE5 (-27)*** The account reference or structure is invalid or corrupted. **`TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_DATA_SIZE` · *0xFFFFFFFFFFFFFFDD (-35)*** The specified account data size is invalid or exceeds limits. See [account\_resize syscall](/docs/spec/vm/syscalls/account_resize/) for resizing constraints. **`TN_VM_ERR_SYSCALL_PROGRAM_IN_CALL_STACK` · *0xFFFFFFFFFFFFFFCF (-49)*** The attempted operation to mutate the specified account failed because the account is a program currently in the invocation call stack. ### Balance and Transfer Errors **`TN_VM_ERR_SYSCALL_BALANCE_OVERFLOW` · *0xFFFFFFFFFFFFFFF5 (-11)*** A balance operation would result in integer overflow. This prevents balance corruption. **`TN_VM_ERR_SYSCALL_INSUFFICIENT_BALANCE` · *0xFFFFFFFFFFFFFFDA (-38)*** Insufficient account balance for the requested transfer operation. See [account\_transfer syscall](/docs/spec/vm/syscalls/account_transfer/). ### Memory and Segment Errors **`TN_VM_ERR_SYSCALL_ACCOUNT_TOO_BIG` · *0xFFFFFFFFFFFFFFF4 (-12)*** The account size exceeds maximum allowed limits for account data. **`TN_VM_ERR_SYSCALL_SEGMENT_ALREADY_MAPPED` · *0xFFFFFFFFFFFFFFED (-19)*** Attempted to map a memory segment that is already mapped or in use. **`TN_VM_ERR_SYSCALL_INVALID_SEGMENT_ID` · *0xFFFFFFFFFFFFFFEB (-21)*** The provided segment ID is invalid or out of range. See [Memory Layout](/docs/spec/vm/memory-layout/) for valid segment types. **`TN_VM_ERR_SYSCALL_INVALID_SEGMENT_SIZE` · *0xFFFFFFFFFFFFFFE4 (-28)*** The requested segment size is invalid (not a multiple of 4096UL), zero, or exceeds limits. See [set\_anonymous\_segment\_sz syscall](/docs/spec/vm/syscalls/set_anonymous_segment_sz/). **`TN_VM_ERR_SYSCALL_INSUFFICIENT_PAGES` · *0xFFFFFFFFFFFFFFE6 (-26)*** Insufficient memory pages available for the requested allocation. **`TN_VM_ERR_SYSCALL_UNFREEABLE_PAGE` · *0xFFFFFFFFFFFFFFE3 (-29)*** Attempted to free a memory page that cannot be freed (e.g., system pages). **`TN_VM_ERR_SYSCALL_INVALID_ADDRESS` · *0xFFFFFFFFFFFFFFEA (-22)*** The provided memory address is invalid, out of bounds, or improperly aligned. **`TN_VM_ERR_SYSCALL_INVALID_OFFSET` · *0xFFFFFFFFFFFFFFD9 (-39)*** The provided offset is invalid or out of bounds for the target operation. ### Object and Reference Errors **`TN_VM_ERR_SYSCALL_INVALID_OBJ_REF_KIND` · *0xFFFFFFFFFFFFFFF3 (-13)*** The object reference type is invalid or not supported for the requested operation. **`TN_VM_ERR_SYSCALL_OBJ_NOT_WRITABLE` · *0xFFFFFFFFFFFFFFF2 (-14)*** Attempted to modify an object that is not writable or is read-only. ### System and Execution Errors **`TN_VM_ERR_SYSCALL_BAD_PARAMS` · *0xFFFFFFFFFFFFFFEC (-20)*** Invalid parameters provided to the syscall. Check parameter types, ranges, and requirements. **`TN_VM_ERR_SYSCALL_CALL_DEPTH_TOO_DEEP` · *0xFFFFFFFFFFFFFFE8 (-24)*** Maximum call depth exceeded. Prevents infinite recursion in [invoke syscall](/docs/spec/vm/syscalls/invoke/) chains. **`TN_VM_ERR_SYSCALL_REVERT` · *0xFFFFFFFFFFFFFFE7 (-25)*** Explicit revert requested. The program called [exit syscall](/docs/spec/vm/syscalls/exit/) with revert flag. **`TN_VM_SYSCALL_ERR_COMPUTE_UNITS_EXCEEDED` · *0xFFFFFFFFFFFFFFD8 (-40)*** Compute units exhausted during syscall execution. See [Compute Units](/docs/spec/runtime/resources/#compute-units). **`TN_VM_ERR_SYSCALL_INVALID_FLAGS` · *0xFFFFFFFFFFFFFFD7 (-41)*** Invalid flags provided to the syscall. See individual syscall documentation for valid flag values. **`TN_VM_ERR_SYSCALL_INVALID_SIGNATURE` · *0xFFFFFFFFFFFFFFD4 (-44)*** The provided signature is invalid or verification failed. This occurs when creating an EOA account with an invalid Ed25519 signature. See [account\_create\_eoa syscall](/docs/spec/vm/syscalls/account_create_eoa/) for signature requirements. ### State and Proof Errors **`TN_VM_ERR_SYSCALL_INVALID_STATE_PROOF` · *0xFFFFFFFFFFFFFFE9 (-23)*** The provided state proof is invalid, corrupted, or does not match the current state. **`TN_VM_ERR_SYSCALL_INVALID_PROOF_LEN` · *0xFFFFFFFFFFFFFFE0 (-32)*** The proof length is invalid or does not match expected size requirements. **`TN_VM_ERR_SYSCALL_INVALID_PROOF_SLOT` · *0xFFFFFFFFFFFFFFDF (-33)*** The slot reference in the state proof is invalid or outdated. ### Logging and Event Errors **`TN_VM_ERR_SYSCALL_LOG_DATA_TOO_LARGE` · *0xFFFFFFFFFFFFFFE2 (-30)*** The log data size exceeds maximum allowed length. See [log syscall](/docs/spec/vm/syscalls/log/) for size limits. **`TN_VM_ERR_SYSCALL_EVENT_TOO_LARGE` · *0xFFFFFFFFFFFFFFE1 (-31)*** The event data size exceeds maximum allowed length. See [emit\_event syscall](/docs/spec/vm/syscalls/emit_event/) for size limits. **`TN_VM_ERR_SYSCALL_EVENT_IS_ZERO_SIZE` · *0xFFFFFFFFFFFFFFD2 (-46)*** The event data size is zero. Events must contain at least one byte. See [emit\_event syscall](/docs/spec/vm/syscalls/emit_event/) for event requirements. ### Compression and Special Account Errors **`TN_VM_ERR_SYSCALL_ACCOUNT_IN_COMPRESSION_TIMEOUT` · *0xFFFFFFFFFFFFFFDE (-34)*** The account is in compression timeout and cannot be accessed. Wait for timeout period to expire. **`TN_VM_ERR_SYSCALL_INVALID_SEED_LENGTH` · *0xFFFFFFFFFFFFFFDC (-36)*** The seed length for account derivation is invalid. Seeds must meet specific length requirements. **`TN_VM_ERR_SYSCALL_TXN_HAS_COMPRESSED_ACCOUNT` · *0xFFFFFFFFFFFFFFDB (-37)*** The transaction contains compressed accounts that cannot be processed in this context. **`TN_VM_ERR_SYSCALL_EPHEMERAL_ACCOUNT_CANNOT_CREATE_PERSISTENT` · *0xFFFFFFFFFFFFFFD6 (-42)*** An ephemeral account attempted to create a persistent account, which is not allowed. Ephemeral accounts can only create other ephemeral accounts. See [account\_create\_ephemeral syscall](/docs/spec/vm/syscalls/account_create_ephemeral/). **`TN_VM_ERR_SYSCALL_ACCOUNT_COMPRESSION_NOT_ALLOWED` · *0xFFFFFFFFFFFFFFD5 (-43)*** Compression of this account is not currently allowed. See [account\_compress syscall](/docs/spec/vm/syscalls/account_compress/). **`TN_VM_ERR_SYSCALL_ACCOUNT_UNCOMPRESSABLE` · *0xFFFFFFFFFFFFFFD3 (-45)*** The account cannot be compressed. The account has flags or properties that prevent compression. See [account\_compress syscall](/docs/spec/vm/syscalls/account_compress/). ### State Counter Errors **`TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` · *0xFFFFFFFFFFFFFFD1 (-47)*** The state bytes counter would overflow when adding activated state bytes. This occurs during account creation or decompression operations when the global activated state counter (GASC) would exceed maximum bounds. See [account\_create](/docs/spec/vm/syscalls/account_create/), [account\_create\_ephemeral](/docs/spec/vm/syscalls/account_create_ephemeral/), [account\_create\_eoa](/docs/spec/vm/syscalls/account_create_eoa/), and [account\_decompress](/docs/spec/vm/syscalls/account_decompress/) syscalls. **`TN_VM_ERR_SYSCALL_STATE_BYTES_REMOVED_OVERFLOW` · *0xFFFFFFFFFFFFFFD0 (-48)*** The state bytes counter would overflow when adding deactivated state bytes. This occurs during account deletion or compression operations when the global deactivated state counter (GDSC) would exceed maximum bounds. See [account\_delete](/docs/spec/vm/syscalls/account_delete/) and [account\_compress](/docs/spec/vm/syscalls/account_compress/) syscalls. ## Error Handling Tip VM errors typically occur during program execution and may indicate issues with program logic, resource management, or invalid operations. Check the program implementation and ensure proper error handling for syscall operations. Note Virtual machine-level errors (fault codes and syscall errors) typically occur during program execution and may indicate issues with program logic, resource management, or invalid operations. Check the program implementation and ensure proper error handling for syscall operations. Note For runtime-level errors that occur outside of VM execution, see [Runtime Errors](/docs/spec/runtime/errors/). # Executable Format > Binary format specification for Thru executable programs Thru executable programs follow a specific binary format that includes a header, program bytecode, and trailer. ## Program Structure A Thru executable consists of three main components: | Component | Size | Description | | --------- | -------- | -------------------- | | Header | 8 bytes | Version and metadata | | Program | Variable | Executable bytecode | | Trailer | 8 bytes | Zero terminator | ## Header Format The header is exactly **8 bytes** long and contains version information. ### Header Layout | Offset | Size | Field | Description | | ------ | ---- | -------- | ----------------------------- | | 0 | 1 | Version | Program format version (0x01) | | 1-7 | 7 | Reserved | Reserved for future use | ### Version Field Currently, only version `0x01` is supported. ```hex 01 00 00 00 00 00 00 00 ^^ Version (0x01) ^^^^^^^^^^^^^^^ Reserved (7 bytes, typically zero) ``` ## Program Bytecode The program bytecode section contains the executable instructions: * Starts immediately after the 8-byte header * Variable length depending on the program size * Contains the actual program instructions * Minimum size is 0 bytes (empty programs are valid) ## Trailer Format The trailer is exactly **8 bytes** long and serves as a terminator. ### Trailer Layout | Offset | Size | Field | Description | | ------ | ---- | ---------- | --------------------------------- | | -8 | 8 | Terminator | Must be zero (0x0000000000000000) | The last 8 bytes of the executable must be set to zero. ```hex 00 00 00 00 00 00 00 00 ^^^^^^^^^^^^^^^^^^^^^^^ All bytes must be zero ``` ## Size Constraints * **Total minimum size**: 16 bytes (8-byte header + 8-byte trailer) * **Header size**: Exactly 8 bytes * **Trailer size**: Exactly 8 bytes * **Program size**: Variable (minimum 0 bytes) ## Complete Example Here’s a minimal valid Thru executable: ```hex 01 00 00 00 00 00 00 00 // Header: version 0x01 + 7 reserved bytes 95 00 00 00 00 00 00 00 // Program: 8 bytes of instructions 00 00 00 00 00 00 00 00 // Trailer: 8 zero bytes ``` In this example: * Total size: 24 bytes * Header size: 8 bytes * Program size: 8 bytes * Trailer size: 8 bytes Note The executable format provides extensibility through the version field for future format changes while maintaining a simple structure for current programs. # Instruction Set Architecture > Complete specification of ThruVM's RISC-V instruction set architecture and supported extensions ThruVM implements a RISC-V virtual machine that executes smart contract bytecode on the Thru blockchain. The VM is fully compliant with the RISC-V specification and supports multiple standard extensions for comprehensive computational capabilities. Execution on ThruVM is single-threaded and is entirely deterministic. There is no atomic access to data outside virtual machine execution, which implies sequential consistency for all memory operations. ## RISC-V Specification Compliance ThruVM is fully compliant with the [RISC-V Instruction Set Manual, Version 20250508](https://drive.google.com/file/d/1uviu1nH-tScFfgrovvFCrj7Omv8tFtkp/view). The implementation adheres to all architectural requirements and behavioral specifications defined in this version. Note All instructions are aligned to 16-bits (IALIGN=16) as per the RISC-V specification. Unaligned data access is not supported and will result in an exception. See [Programmer and Implementer Considerations](#Programmer-and-implementer-considerations) for more details. ## Supported Extensions ThruVM supports the following RISC-V instruction set extensions: | Extension ID | Name | Version | Purpose | | ------------ | ---------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------- | | **RV64I** | Base Integer Instruction Set | 2.1 | 64-bit base integer instructions including arithmetic, logical, memory, and control flow operations | | **M** | Standard Extension for Integer Multiplication and Division | 2.0 | Hardware multiplication, division, and remainder operations for both 32-bit and 64-bit integers | | **C** | Standard Extension for Compressed Instructions | 2.0 | 16-bit compressed instruction encodings to reduce code size and improve instruction fetch efficiency | | **B** | Standard Extension for Bit Manipulation | 1.0.0 | Bit manipulation instructions including count, rotate, permute, and logic operations | | **Zknh** | NIST Suite: Hash Function Instructions | 1.0.1 | Cryptographic hash function acceleration instructions for SHA-256 and SHA-512 | ## Instruction Set Architecture Details The following sections provide a brief description of the base ISA and supported extensions. ### Base ISA (RV64I) The RV64I base instruction set provides: * **64-bit integer registers**: 32 general-purpose registers (x0-x31) with x0 hardwired to zero * **Load/Store architecture**: Memory access only through explicit load and store instructions * **Arithmetic operations**: Addition, subtraction, logical operations (AND, OR, XOR) * **Shift operations**: Logical and arithmetic left/right shifts * **Comparison operations**: Set-less-than for signed and unsigned comparisons * **Branch instructions**: Conditional branches based on register comparisons * **Jump instructions**: Unconditional jumps with optional link register updates * **System instructions**: Environment calls and control/status register access ### Multiplication and Division (M Extension) The M extension adds support for: * **Integer multiplication**: `MUL`, `MULH`, `MULHSU`, `MULHU` * **Integer division**: `DIV`, `DIVU`, `REM`, `REMU` * **32-bit variants**: `MULW`, `DIVW`, `DIVUW`, `REMW`, `REMUW` for RV64 ### Compressed Instructions (C Extension) The C extension provides 16-bit instruction encodings for: * **Common operations**: Register-register operations, immediate operations * **Memory access**: Compressed load/store instructions * **Control flow**: Compressed branch and jump instructions * **Stack operations**: Stack pointer manipulation instructions Tip For optimal resource efficiency, use the compressed instructions to reduce [compute unit](/docs/spec/runtime/resources/#compute-units) consumption. ### Bit Manipulation (B Extension) The B extension includes instructions for: * **Bit counting**: Count leading/trailing zeros (`CLZ`, `CTZ`) * **Bit manipulation**: Single-bit operations, bit field operations * **Rotation**: Left and right rotations (`ROL`, `ROR`) * **Permutation**: Byte and nibble permutation operations * **Carry-less multiplication**: `CLMUL`, `CLMULH`, `CLMULR` ### Cryptographic Hash Functions (Zknh Extension) The Zknh extension provides acceleration for: * **SHA-256**: `SHA256SIG0`, `SHA256SIG1`, `SHA256SUM0`, `SHA256SUM1` * **SHA-512**: `SHA512SIG0`, `SHA512SIG1`, `SHA512SUM0`, `SHA512SUM1` ## Programmer and Implementer Considerations ### Illegal Instructions Using instructions that are currently unimplemented will cause the VM to exit with an illegal instruction exception (SIGILL), resulting in transaction revert. If an instruction becomes implemented in future versions, programs using those instruction encodings will automatically adopt the new behavior. To explicitly indicate illegal instructions, use encodings with all bits set to 0 or all bits set to 1. ### Memory Alignment ThruVM enforces strict alignment requirements: * **Instructions**: Must be aligned to 16-bit boundaries * **Data access**: Unaligned memory access is not supported and will trigger an exception ### System Calls For details on the syscall calling convention and available system calls, refer to the [Syscalls documentation](/docs/spec/vm/syscalls/overview/). ## Register Conventions ThruVM follows standard RISC-V register conventions: | Register | ABI Name | Purpose | | -------- | -------- | -------------------------------- | | x0 | zero | Hardwired to zero | | x1 | ra | Return address | | x2 | sp | Stack pointer | | x3 | gp | Global pointer | | x4 | tp | Thread pointer | | x5-x7 | t0-t2 | Temporary registers | | x8 | fp/s0 | Frame pointer/saved register | | x9 | s1 | Saved register | | x10-x17 | a0-a7 | Function arguments/return values | | x18-x27 | s2-s11 | Saved registers | | x28-x31 | t3-t6 | Temporary registers | # Virtual Machine Memory Layout ## Address Space Overview The Thru virtual machine uses a 48-bit segmented address space designed to provide memory isolation and efficient access to different types of data during program execution. The address format follows a three-component structure that enables both type safety and performance optimization. ## Address Format Each virtual address is composed of three fields packed into a 48-bit value: ```plaintext Bits: 47-40 | 39-24 | 23-0 seg_type| seg_idx | offset (8 bits) |(16 bits) |(24 bits) ``` Note For program code, the public C SDK exposes the address construction macro `TSDK_ADDR(seg_type, seg_idx, offset) = (seg_type << 40) | (seg_idx << 24) | offset`. ### Address Components * **Segment Type** (8 bits): Determines the memory region type and access permissions * **Segment Index** (16 bits): Identifies the specific segment within the type * **Offset** (24 bits): Byte offset within the segment (up to 16MB per segment) ## Memory Segment Types ### 0x00: Read-Only Data Segments Contains immutable data that programs can read but never modify. | Segment Index | Name | Purpose | Notes | | ------------- | ------------- | -------------------- | -------------------------------------------------------------------------------------------------------- | | 0x0000 | NULL | Invalid/null segment | Always causes access violation | | 0x0001 | TXN\_DATA | Transaction data | Contains serialized transaction | | 0x0002 | SHADOW\_STACK | VM shadow stack | Call frame metadata (public portion) - see [Shadow Stack Frame Structure](#shadow-stack-frame-structure) | | 0x0003 | PROGRAM | Program bytecode | Executable RISC-V code | | 0x0004 | BLOCK\_CTX | Block context | Block metadata and timing info | Caution Write operations to read-only segments cause access violations and will fail. Caution Note that programs should not pass pointers to their own program bytecode to other programs, since the bytecode will be changed upon invocation to the invoked program’s bytecode. ## Block Context History The block context segment exposes a rolling window of recent blocks, spaced evenly within the segment to allow direct pointer math from contracts. | Concept | Value | Details | | ---------------- | ------------------------------------------ | ---------------------------------------------- | | Addressing | `offset = blocks_ago * 0x1000` | `blocks_ago = 0` is the current block | | Per-block stride | `0x1000` bytes (`TN_VM_BLOCK_CTX_SPACING`) | Each context is aligned by 4096 bytes | | History window | 512 blocks (`TN_RUNTIME_CTX_BLOCK_SPAN`) | Accessing further back or before slot 0 faults | Context structure (offsets relative to the start of a block’s window): | Offset | Field | Type | Description | | ------ | ---------------- | ------------------- | ------------------------ | | `0x00` | `slot` | `ulong` | Slot number of the block | | `0x08` | `block_time` | `ulong` | Unix time in nanoseconds | | `0x10` | `block_price` | `ulong` | Block price | | `0x18` | `state_root` | `fd_hash_t` (32B) | State Merkle root | | `0x38` | `cur_block_hash` | `fd_hash_t` (32B) | Block hash | | `0x58` | `block_producer` | `fd_pubkey_t` (32B) | Producer public key | Any access beyond this layout or outside the history window triggers an access violation. ### Shadow Stack Frame Structure The shadow stack segment (0x0002) provides read-only access to call frame metadata, enabling cross-program communication and state inspection during execution. Each call frame stores information about the current program invocation context. #### Frame Organization The shadow stack maintains metadata for up to 17 frames: * **Frame 0**: Reserved frame (frame -1 in implementation) containing all zeros * **Frames 1-16**: Active call frames for program invocations (call depths 0-15) Programs access frame data using the shadow stack segment with the frame index encoded in the offset. #### Frame Structure Each shadow stack frame contains the following fields: | Offset | Field | Type | Size | Description | | ------ | ----------------- | ----------- | --------- | -------------------------------------------------- | | 0x00 | `program_acc_idx` | `ushort` | 2 bytes | Account index of the program for this frame | | 0x02 | `stack_pages` | `ushort` | 2 bytes | Total size of stack region in pages (4KB pages) | | 0x04 | `heap_pages` | `ushort` | `2 bytes` | Total size of heap region in pages (4KB pages) | | 0x06 | `padding` | - | 2 bytes | Alignment padding | | 0x08 | `saved_regs` | `ulong[32]` | 256 bytes | Saved register state (32 registers × 8 bytes each) | **Total frame size**: 264 bytes per frame #### Register State The `saved_regs` array stores all 32 RISC-V registers (x0-x31) at the time of program invocation. Note The register values stored in the public shadow stack represent the state at invoke time. When a program exits, the VM restores registers from this public shadow stack. Tip **Compute Cost**: Each invocation saves 256 bytes (registers to public shadow stack) and restores 256 bytes (from public shadow stack), for a total of 512 bytes of memory operations. The base syscall cost is 512 CUs (32 registers × 8 bytes × 2 operations). ### 0x02: Account Metadata Provides access to account metadata structures. | Field | Description | Access Notes | | -------- | ---------------------------- | --------------------------------------------------------------------- | | seg\_idx | Account index in transaction | Must be < transaction account count | | offset | Byte offset in metadata | Limited to `TSDK_ACCOUNT_META_FOOTPRINT` bytes in the public SDK view | * **Size**: Exposed to programs as `TSDK_ACCOUNT_META_FOOTPRINT` bytes through `tsdk_account_meta_t` * **Access**: Read-only from VM perspective * **Non-existent accounts**: Returns pointer to zeroed metadata structure * **Invalid access**: Access violations occur for out-of-bounds or invalid account indices ### 0x03: Account Data Page-based access to account data with copy-on-write semantics. **Address fields** | Field | Description | Constraints | | -------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | seg\_idx | Account index in transaction | Must be < transaction account count | | offset | Byte offset into account data | Single memory access must not span across a 4KB page boundary. For example, reading 8 bytes at offset 4093 would fault as it spans across 4096. | | Feature | Details | | ---------------- | -------------------------------------------------- | | **Page Size** | 4096 bytes | | **Alignment** | Enforced at page boundaries for cross-page access | | **Write Access** | Requires account to be writable by current program | | **COW Pages** | Automatic copy-on-write for modifications | Tip Account data accesses that span page boundaries cause access violations. Design your data structures to respect 4KB page alignment. ### 0x04: Event Data Reserved for event emission (implementation details in event subsystem). ### 0x05: Stack Stack memory segment that grows downward from high addresses to low addresses. | Characteristic | Value | | -------------------- | ------------------------------ | | **Growth Direction** | Downward (0xFFFFFF → 0x000000) | | **Page Size** | 4096 bytes | | **Max Segments** | 1 per VM instance | | **Size Limit** | 16MB per segment | | **Permission Model** | Frame-based access control | **Growth Behavior:** The stack segment grows downward, meaning as more memory is allocated, the segment size increases toward lower addresses. When you allocate new stack space, the valid address range expands from the bottom (higher addresses) toward the top (lower addresses), similar to traditional hardware stacks. **Address Layout:** * Virtual address range: `0xFFFFFF` down to current stack size * Stack pointer typically points to `0x000000` offset initially * As stack grows, valid addresses extend from `0xFFFFFF` downward ### 0x07: Heap Heap memory segment that grows upward from low addresses to high addresses. | Characteristic | Value | | -------------------- | ---------------------------- | | **Growth Direction** | Upward (0x000000 → 0xFFFFFF) | | **Page Size** | 4096 bytes | | **Max Segments** | 1 per VM instance | | **Size Limit** | 16MB per segment | | **Permission Model** | Frame-based access control | **Growth Behavior:** The heap segment grows upward, meaning as more memory is allocated, the segment size increases toward higher addresses. When you allocate new heap space, the valid address range expands from the base (0x000000) toward higher addresses, similar to traditional heap allocators. ## Address Translation Process The VM performs the following steps for each memory access: 1. **Address Validation** Check if the upper 16 bits of the 64-bit address are zero (ensuring 48-bit address space). 2. **Segment Extraction** Extract segment type, index, and offset from the address using bit manipulation. 3. **Alignment Check** Verify that the offset is properly aligned for the requested access size. 4. **Permission Validation** Check write permissions based on segment type and current execution context. 5. **Translation** Convert virtual address to host address based on segment type-specific logic. If any validation fails, an access violation occurs. ## Access Control and Permissions ### Write Access Control | Segment Type | Write Behavior | Access Violation Conditions | | ---------------- | -------------- | ------------------------------------------- | | Read-Only Data | Always fails | Any write attempt causes violation | | Account Metadata | Always fails | Runtime-managed, no direct writes allowed | | Account Data | Conditional | Violation if program doesn’t own account | | Anonymous Data | Conditional | Violation if frame permissions insufficient | ### Frame-Based Security Anonymous memory segments use a frame-based permission system: * Each page is tagged with the frame index (program invocation call depth) that allocated it * Pages can only be freed by program invocations at the same call depth or higher * Deeper called programs cannot free pages allocated by shallower called programs ## Memory Management ### Page Allocation * **Page Size**: 4096 bytes (TN\_RUNTIME\_PAGE\_SZ) * **Allocation**: Dynamic allocation from a global page pool * **Tracking**: Inverted page table for efficient management * **Limits**: Enforced at transaction level for resource control ### Copy-on-Write (COW) Account data uses COW semantics: 1. **Initial State**: Points to read-only account data 2. **First Write**: Allocates writable page and copies original data 3. **Subsequent Writes**: Operate on the writable copy 4. **Commit**: Modified pages are written back during transaction commit ## Access Violations Memory access can fail in several ways, resulting in access violations: | Violation Type | Cause | Details | | ----------------------- | ---------------------------------- | ------------------------------------------------------------- | | **Invalid Address** | Malformed address or out-of-bounds | Upper 16 bits non-zero, or address exceeds segment bounds | | **Permission Denied** | Unauthorized write access | Attempting to write to read-only segments or unowned accounts | | **Page Boundary Cross** | Access spans multiple pages | Single access cannot cross 4KB page boundaries | | **Resource Exhaustion** | No free pages available | Anonymous memory allocation when page pool is exhausted | | **Alignment Violation** | Misaligned access | Offset not aligned to required boundary for access size | | **Invalid Segment** | Unknown segment type/index | Segment type not implemented or segment index out of range | Caution **Access Violation Behavior**: When any of these violations occur the VM will fault and exit, reverting the transaction. ## Performance Considerations * **Page Alignment**: Design data structures to fit within 4KB pages * **Sequential Access**: Prefer sequential memory access patterns * **Segment Locality**: Group related data within the same segment type * **COW Overhead**: First write to account data incurs page allocation cost ## Example Address Calculations * Stack Address ```c // Stack segment at offset 0x1000 ulong stack_addr = TSDK_ADDR(TSDK_SEG_TYPE_STACK, 0x0000, 0x001000); // Result: 0x050000001000 ``` * Account Data ```c // Account index 5, offset 0x800 ulong account_addr = TSDK_ADDR(TSDK_SEG_TYPE_ACCOUNT_DATA, 5, 0x800); // Result: 0x030005000800 ``` * Transaction Data ```c // Transaction data at offset 0x40 ulong txn_addr = TSDK_ADDR(TSDK_SEG_TYPE_READONLY_DATA, TSDK_SEG_IDX_TXN_DATA, 0x40); // Result: 0x000001000040 ``` # Virtual Machine > RISC-V virtual machine for smart contract execution on the Thru blockchain ThruVM is a RISC-V-based virtual machine that executes smart contracts on the Thru blockchain. It provides deterministic program execution with segmented memory management and comprehensive system calls. ## Key Components ### [Instruction Set Architecture](/docs/spec/vm/instruction-set/) RISC-V base ISA (RV64I) with extensions for multiplication/division (M), compressed instructions (C), bit manipulation (B), and cryptographic hash functions (Zknh). ### [Memory Layout](/docs/spec/vm/memory-layout/) 48-bit segmented address space with different segment types for read-only data, account metadata, account data with copy-on-write semantics, and anonymous memory regions. ### [Executable Format](/docs/spec/vm/executable-format/) Binary format specification with 8-byte header, variable-length program bytecode, and 4-byte footer. ### [System Calls](/docs/spec/vm/syscalls/overview/) Comprehensive syscall interface for memory management, account operations, program control, state compression, and event emission. ### [Error Handling](/docs/spec/vm/errors/) Error codes for memory faults, resource exhaustion, invalid operations, and syscall failures. # account_compress > Compresses an account to save storage space with state proof ## Overview The `account_compress` syscall compresses an account by setting the COMPRESSED flag and providing a state proof. This allows the account data to be stored more efficiently while maintaining verifiability. ## Syscall Code **Code:** `0x08` (`TN_SYSCALL_CODE_ACCOUNT_COMPRESS`) ## C SDK Function ```c ulong tsys_account_compress(ulong account_idx, void const* proof, ulong proof_sz); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account to compress. Must be writable in the transaction. **`proof` · *void const** · **required***\* Pointer to the state proof data. **`proof_sz` · *ulong* · **required**** Size of the state proof data in bytes. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Account compressed successfully * `0` (special case) - Ephemeral account deleted instead of compressed **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Account does not exist * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable in transaction * `TN_VM_ERR_SYSCALL_TXN_HAS_COMPRESSED_ACCOUNT` (-37) - Transaction already has a compressed account * `TN_VM_ERR_SYSCALL_ACCOUNT_COMPRESSION_NOT_ALLOWED` (-43) - Account compression is not allowed * `TN_VM_ERR_SYSCALL_ACCOUNT_UNCOMPRESSABLE` (-45) - Account cannot be compressed * `TN_VM_ERR_SYSCALL_INVALID_PROOF_LEN` (-32) - Proof size mismatch * `TN_VM_ERR_SYSCALL_INVALID_PROOF_SLOT` (-33) - Proof references invalid block slot * `TN_VM_ERR_SYSCALL_INVALID_STATE_PROOF` (-23) - State proof verification failed * `TN_VM_ERR_SYSCALL_STATE_BYTES_REMOVED_OVERFLOW` (-48) - State bytes counter overflow during deactivation * `TN_VM_ERR_SYSCALL_PROGRAM_IN_CALL_STACK` (-49) - Cannot compress a program that is currently executing ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Proof cost**: Additional units equal to proof size in bytes * **Data cost**: Additional units equal to account data size in bytes * **Metadata cost**: Additional units equal to `sizeof(tsdk_account_meta_t)` * **Total formula**: `base_cost + proof_sz + account_data_sz + sizeof(tsdk_account_meta_t)` ### Memory Pages * **Page usage**: No immediate page deallocation (compression is logical) * **Proof storage**: Temporary memory for proof verification * **Metadata impact**: Account metadata becomes writable if not already ### State Counter Impact * **GDSC (Global Deactivated State Counter)**: Incremented by `TSDK_ACCOUNT_META_FOOTPRINT + account_data_sz` bytes * **Purpose**: Tracks deactivated state for the account compression in the global state counter * **Overflow protection**: Returns `TN_VM_ERR_SYSCALL_STATE_BYTES_REMOVED_OVERFLOW` if counter would overflow * **GASC/GDSC check**: Compression may be denied if state counter conditions are not met (unless owner/fee payer is compressing) ## Side Effects * **Account state**: Sets COMPRESSED flag and updates state counter * **Transaction state**: Records compression proof for transaction * **Special handling**: Ephemeral and deleted accounts are deleted instead ## Special Cases * Ephemeral Accounts Ephemeral accounts are simply deleted when “compressed” - they cannot be truly compressed since they don’t persist beyond the transaction. * Deleted Accounts Accounts that are marked as deleted are removed entirely rather than compressed. * New Accounts New accounts require a creation-type state proof, while existing accounts require an existing-type proof. * Program Accounts Program accounts that are currently in the call stack cannot be compressed. This includes the currently executing program and any program that has called into the current execution context. This prevents a program from compressing itself or its callers during execution. ## State Proof Requirements * **New accounts**: Must use `TN_STATE_PROOF_TYPE_CREATION` proof type * **Existing accounts**: Must use `TN_STATE_PROOF_TYPE_EXISTING` proof type * **Proof verification**: Must reference valid block slot and verify against state root * **Account hash**: Proof must match the computed hash of the account’s current state ## Transaction Limitations * Only one account can be compressed per transaction * The transaction records the compression proof and account details * Compression state is tracked to prevent multiple compressions ## Usage Notes * Account must be writable in the transaction (signer approval) * State proof ensures the account state is correctly recorded * Compressed accounts can later be decompressed with valid proofs * The account’s state counter is updated to the proof block’s counter * Program accounts cannot be compressed while they are in the call stack (current program or any caller) ## Example ```c #include // Compress an account with state proof ulong account_idx = 2; uchar proof_data[2048]; ulong proof_size = prepare_compression_proof(account_idx, proof_data); ulong result = tsys_account_compress(account_idx, proof_data, proof_size); if (result == TN_VM_SYSCALL_SUCCESS) { // Account compressed successfully // COMPRESSED flag set // State counter updated // Compression proof recorded in transaction } ``` # account_create > Creates a new permanent account with state proof verification ## Overview The `account_create` syscall creates a new permanent account using a program-defined address derived from a seed. The account creation requires a state proof to verify the address doesn’t already exist in the global state. ## Syscall Code **Code:** `0x04` (`TN_SYSCALL_CODE_ACCOUNT_CREATE`) ## C SDK Function ```c ulong tsys_account_create( ulong account_idx, uchar const seed[TN_SEED_SIZE], void const * proof, ulong proof_sz ); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account slot to create. Must be writable in the transaction and currently non-existent. **`seed` · *uchar const\[TN\_SEED\_SIZE]* · **required**** Fixed 32-byte seed used for address derivation. **`proof` · *void const** · **required***\* Pointer to the state proof data. **`proof_sz` · *ulong* · **required**** Size of the state proof data in bytes. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Account created successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable in transaction * `TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS` (-15) - Account already exists * `TN_VM_ERR_SYSCALL_INVALID_ADDRESS` (-22) - Invalid virtual address for seed or proof * `TN_VM_ERR_SYSCALL_BAD_ACCOUNT_ADDRESS` (-16) - Computed address doesn’t match expected * `TN_VM_ERR_SYSCALL_INVALID_PROOF_LEN` (-32) - Proof size mismatch * `TN_VM_ERR_SYSCALL_INVALID_PROOF_SLOT` (-33) - Proof references invalid block slot * `TN_VM_ERR_SYSCALL_INVALID_STATE_PROOF` (-23) - State proof verification failed * `TN_VM_ERR_SYSCALL_EPHEMERAL_ACCOUNT_CANNOT_CREATE_PERSISTENT` (-42) - Ephemeral account cannot create persistent account * `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` (-47) - State bytes counter overflow during activation ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Seed cost**: Additional units equal to `TN_SEED_SIZE` bytes * **Proof cost**: Additional units equal to proof size in bytes * **Metadata cost**: Additional units equal to `sizeof(tsdk_account_meta_t)` * **Total formula**: `base_cost + TN_SEED_SIZE + proof_sz + sizeof(tsdk_account_meta_t)` ### Memory Pages * **Page usage**: No direct page allocation (account data starts at size 0) * **Metadata pages**: Allocates pages for account metadata structure * **State proof validation**: Temporary memory usage for proof verification ### State Counter Impact * **GASC (Global Activated State Counter)**: Incremented by `TSDK_ACCOUNT_META_FOOTPRINT` bytes * **Purpose**: Tracks activated state for the account creation in the global state counter * **Overflow protection**: Returns `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` if counter would overflow ## Side Effects * **Account creation**: Creates a new account with NEW flag set * **Ownership**: Sets the current program as the account owner * **Account metadata**: Initializes writable metadata structure ## Address Derivation The account address is computed as: ```plaintext SHA256(current_program_pubkey || is_ephemeral(0) || seed) ``` ## State Proof Requirements * **New accounts**: Requires a creation-type state proof showing the address doesn’t exist * **Deleted accounts**: Can recreate without proof (removes DELETED flag) * **Proof verification**: Must reference a valid block slot and verify against state root ## Usage Notes * The computed account address must match the transaction’s expected address * Seed must be exactly `TN_SEED_SIZE` (32 bytes) * State proof ensures global uniqueness of the address * Created accounts are owned by the calling program * Account is implicitly marked as writable by the creating program ## Example ```c #include #include // Create account with seed "my_account" ulong account_idx = 5; uchar seed[TN_SEED_SIZE] = { 0 }; memcpy( seed, "my_account", 10 ); // Prepare state proof data uchar proof_data[1024]; ulong proof_size = prepare_creation_proof(proof_data); ulong result = tsys_account_create( account_idx, seed, proof_data, proof_size ); if (result == TN_VM_SYSCALL_SUCCESS) { // Account created successfully // Account is owned by current program // Account has NEW flag set } ``` # account_create_eoa > Creates a new Externally Owned Account (EOA) with signature verification ## Overview The `account_create_eoa` syscall creates a new Externally Owned Account (EOA) by verifying a cryptographic signature that proves ownership of the account’s private key. ## Syscall Code **Code:** `0x0F` (`TN_SYSCALL_CODE_ACCOUNT_CREATE_EOA`) ## C SDK Function ```c ulong tsys_account_create_eoa(ulong account_idx, tn_signature_t const* signature, void const* proof, ulong proof_sz); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account slot to create. The account’s public key address must be specified in the transaction at this index. **`signature` · *tn\_signature\_t const** · **required***\* Pointer to the Ed25519 signature (64 bytes). The signature must be created by signing the null owner address (32 bytes of zeros) with the private key corresponding to the account’s public key. **`proof` · *void const** · **required***\* Pointer to the state proof data verifying the account doesn’t exist. **`proof_sz` · *ulong* · **required**** Size of the state proof data in bytes. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - EOA account created successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS` (-15) - Account already exists * `TN_VM_ERR_SYSCALL_INVALID_ADDRESS` (-22) - Invalid virtual address for signature or proof * `TN_VM_ERR_SYSCALL_INVALID_SIGNATURE` (-44) - Ed25519 signature verification failed * `TN_VM_ERR_SYSCALL_INVALID_PROOF_LEN` (-32) - Proof size mismatch * `TN_VM_ERR_SYSCALL_INVALID_PROOF_SLOT` (-33) - Proof references invalid block slot * `TN_VM_ERR_SYSCALL_INVALID_STATE_PROOF` (-23) - State proof verification failed * `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` (-47) - State bytes counter overflow during activation ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Signature cost**: Additional 64 units for signature verification * **Proof cost**: Additional units equal to proof size in bytes * **Metadata cost**: Additional units equal to `sizeof(tsdk_account_meta_t)` * **Total formula**: `base_cost + 64 + proof_sz + sizeof(tsdk_account_meta_t)` ### Memory Pages * **Page usage**: No direct page allocation (account data starts at size 0) * **Metadata pages**: Allocates pages for account metadata structure * **State proof validation**: Temporary memory usage for proof verification ### State Counter Impact * **GASC (Global Activated State Counter)**: Incremented by `TSDK_ACCOUNT_META_FOOTPRINT` bytes * **Purpose**: Tracks activated state for the EOA account creation in the global state counter * **Overflow protection**: Returns `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` if counter would overflow ## Side Effects * **Account creation**: Creates a new account with `TSDK_ACCOUNT_FLAG_NEW` set * **Ownership**: Sets owner to null (00000…) for EOA accounts * **Account metadata**: Initializes writable metadata structure for an externally owned account ## Signature Verification The signature verification process: 1. Reads the Ed25519 signature (64 bytes) from the provided virtual address 2. Verifies the signature by checking that it was created by signing the null owner address (32 bytes of zeros) with the private key 3. Uses the account’s public key from the transaction to verify the signature 4. The signature proves ownership of the private key corresponding to the account address ### Signature Generation To create a valid signature for EOA account creation: ```plaintext message = null_owner_address // 32 bytes of zeros (0x00000...000) signature = ed25519_sign(private_key, message) ``` The verification confirms: ```plaintext ed25519_verify(message=null_owner, signature, public_key=account_address) == SUCCESS ``` ## State Proof Requirements * **New accounts**: Requires a creation-type state proof showing the address doesn’t exist * **Deleted accounts**: Can recreate with proof verification (removes `TSDK_ACCOUNT_FLAG_DELETED`, owner remains null) * **Proof verification**: Must reference a valid block slot and verify against state root ## EOA Properties * **Owner**: Always set to null address (00000…) * **NEW Flag**: `TSDK_ACCOUNT_FLAG_NEW` is set on first creation * **EOA identification**: Identified by null owner; there is no dedicated `TN_ACCOUNT_FLAG_EOA` * **External control**: Account is controlled by the holder of the private key, not by a program ## Usage Notes * The account address must correspond to the public key from which the signature is derived * Signature verification ensures only the private key holder can create the account * EOA accounts are distinguished from program-defined accounts by null owner * State proof ensures global uniqueness of the address * Account is created with zero balance and zero data size ## Example ```c #include #include // Create EOA for a specific public key address ulong account_idx = 3; // Ed25519 signature (64 bytes) - use tn_signature_t type // The signature MUST be generated by signing the null owner (32 zeros) tn_signature_t signature; uchar null_owner[32] = {0}; // 32 bytes of zeros // signature = ed25519_sign(private_key, null_owner, 32) // ... populate signature using your Ed25519 signing library ... // Prepare state proof data uchar proof_data[1024]; ulong proof_size = prepare_creation_proof(proof_data); ulong result = tsys_account_create_eoa(account_idx, &signature, proof_data, proof_size); if (result == TN_VM_SYSCALL_SUCCESS) { // EOA account created successfully // Account has NEW flag set // Owner is null (00000...) // Account is controlled by private key holder } ``` # account_create_ephemeral > Creates a temporary ephemeral account that exists only for the transaction duration ## Overview The `account_create_ephemeral` syscall creates a temporary account that exists only for the duration of the current transaction. Ephemeral accounts do not require state proofs and are automatically cleaned up at transaction end. ## Syscall Code **Code:** `0x05` (`TN_SYSCALL_CODE_ACCOUNT_CREATE_EPHEMERAL`) ## C SDK Function ```c ulong tsys_account_create_ephemeral( ulong account_idx, uchar const seed[TN_SEED_SIZE] ); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account slot to create. Must be writable in the transaction and currently non-existent. **`seed` · *uchar const\[TN\_SEED\_SIZE]* · **required**** Fixed 32-byte seed used for address derivation. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Ephemeral account created successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable in transaction * `TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS` (-15) - Account already exists * `TN_VM_ERR_SYSCALL_INVALID_ADDRESS` (-22) - Invalid virtual address for seed * `TN_VM_ERR_SYSCALL_BAD_ACCOUNT_ADDRESS` (-16) - Computed address doesn’t match expected * `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` (-47) - State bytes counter overflow during activation ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Seed cost**: Additional units equal to `TN_SEED_SIZE` bytes * **Metadata cost**: Additional units equal to `sizeof(tsdk_account_meta_t)` * **Total formula**: `base_cost + TN_SEED_SIZE + sizeof(tsdk_account_meta_t)` * **Note**: No proof cost since ephemeral accounts don’t require state proofs ### Memory Pages * **Page usage**: No direct page allocation (account data starts at size 0) * **Metadata pages**: Allocates pages for account metadata structure * **Cleanup**: All allocated pages are automatically freed at transaction end ### State Counter Impact * **GASC (Global Activated State Counter)**: Incremented by `TSDK_ACCOUNT_META_FOOTPRINT` bytes * **Purpose**: Tracks activated state for the ephemeral account creation in the global state counter * **Overflow protection**: Returns `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` if counter would overflow ## Side Effects * **Account creation**: Creates a new account with EPHEMERAL flag set * **Ownership**: Sets the current program as the account owner * **Automatic cleanup**: Account will be deleted at transaction end ## Address Derivation The ephemeral account address is computed as: ```plaintext SHA256(current_program_pubkey || is_ephemeral(1) || seed) ``` ## Usage Notes * No state proof required (faster creation) * Account exists only during transaction execution * Cannot be used in balance transfers (ephemeral accounts rejected) * Automatically deleted when transaction completes * Perfect for temporary storage and intermediate computations * The computed address must match the transaction’s expected address * Seed must be exactly `TN_SEED_SIZE` (32 bytes) ## Differences from Permanent Accounts | Feature | Permanent Account | Ephemeral Account | | ------------------ | -------------------------- | ------------------ | | State proof | Required | Not required | | Lifetime | Persists after transaction | Transaction only | | Balance transfers | Allowed | Forbidden | | Creation cost | Higher (includes proof) | Lower (no proof) | | Address derivation | `is_ephemeral = 0` | `is_ephemeral = 1` | ## Example ```c #include #include // Create ephemeral account for temporary storage ulong account_idx = 3; uchar seed[TN_SEED_SIZE] = { 0 }; memcpy( seed, "temp_storage", 12 ); ulong result = tsys_account_create_ephemeral( account_idx, seed ); if (result == TN_VM_SYSCALL_SUCCESS) { // Ephemeral account created successfully // Account will be automatically deleted at transaction end // Can be used for temporary data storage // Cannot participate in balance transfers } ``` # account_decompress > Decompresses a previously compressed account using account data and state proof ## Overview The `account_decompress` syscall restores a compressed account by providing the account’s data and a state proof verifying the data’s authenticity. This allows retrieval of previously compressed account state. ## Syscall Code **Code:** `0x09` (`TN_SYSCALL_CODE_ACCOUNT_DECOMPRESS`) ## C SDK Function ```c ulong tsys_account_decompress(ulong account_idx, void const* meta, void const* data, void const* proof, ulong proof_sz); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account to decompress. Must be writable in the transaction and currently compressed or non-existent. **`meta` · *void const** · **required***\* Pointer to the account metadata. **`data` · *void const** · **required***\* Pointer to the account data payload. **`proof` · *void const** · **required***\* Pointer to the state proof data. **`proof_sz` · *ulong* · **required**** Size of the state proof data in bytes. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Account decompressed successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable in transaction * `TN_VM_ERR_SYSCALL_ACCOUNT_ALREADY_EXISTS` (-15) - Account is already active (not compressed) * `TN_VM_ERR_SYSCALL_ACCOUNT_IN_COMPRESSION_TIMEOUT` (-34) - Account in compression cooldown period * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_DATA_SIZE` (-35) - Invalid account data size * `TN_VM_ERR_SYSCALL_INVALID_ADDRESS` (-22) - Invalid virtual address for data or proof * `TN_VM_ERR_SYSCALL_INVALID_PROOF_LEN` (-32) - Proof size mismatch * `TN_VM_ERR_SYSCALL_INVALID_PROOF_SLOT` (-33) - Proof references invalid block slot * `TN_VM_ERR_SYSCALL_INVALID_STATE_PROOF` (-23) - State proof verification failed * `TN_VM_ERR_SYSCALL_INSUFFICIENT_PAGES` (-26) - Not enough pages for account data * `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` (-47) - State bytes counter overflow during activation ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Data cost**: Additional units equal to account data size in bytes * **Proof cost**: Additional units equal to proof size in bytes * **Total formula**: `base_cost + data_sz + proof_sz` ### Memory Pages * **Page allocation**: Pages allocated for restored account data * **Data pages**: Number of pages based on account data size (page-aligned) * **Metadata pages**: Pages for account metadata structure * **Proof verification**: Temporary memory usage for proof validation ### State Counter Impact * **GASC (Global Activated State Counter)**: Incremented by `TSDK_ACCOUNT_META_FOOTPRINT` bytes, plus restored data bytes (up to original data size) * **Purpose**: Tracks activated state for the account decompression in the global state counter * **Overflow protection**: Returns `TN_VM_ERR_SYSCALL_STATE_BYTES_ADDED_OVERFLOW` if counter would overflow ## Side Effects * **Account restoration**: Restores account metadata and data from provided input * **Flag clearing**: Removes COMPRESSED flag from the account * **Memory allocation**: Allocates pages for the account data ## Data Format The account data must be structured as: ```c struct account_data { tsdk_account_meta_t metadata; // Public account metadata view uchar data[]; // Account data payload }; ``` ## State Proof Verification * **Proof type**: Must be `TN_STATE_PROOF_TYPE_EXISTING` * **Hash verification**: Computed account hash must match the proof * **Block validation**: Proof must reference a valid historical block * **State root**: Account hash must verify against the block’s state root ## Compression Timeout Accounts have a compression timeout period during which they cannot be decompressed. This prevents rapid compress/decompress cycles. ## Usage Notes * Account must be writable in the transaction (signer approval) * Account data size must match the metadata’s declared data size * The provided data is validated by computing and verifying its hash * Account becomes fully active after successful decompression * Sufficient pages must be available for the account data ## Example ```c #include // Decompress a previously compressed account ulong account_idx = 3; // Prepare account data (metadata + data) struct { tsdk_account_meta_t meta; uchar data[1024]; } account_data; // Load the account data from storage/network load_compressed_account_data(&account_data); // Prepare state proof uchar proof_data[2048]; ulong proof_size = load_decompression_proof(account_idx, proof_data); ulong result = tsys_account_decompress(account_idx, &account_data.meta, account_data.data, proof_data, proof_size); if (result == TN_VM_SYSCALL_SUCCESS) { // Account decompressed successfully // Account is now active and accessible // COMPRESSED flag cleared // Data is available for program use } ``` # account_delete > Marks an account as deleted, making it eligible for garbage collection ## Overview The `account_delete` syscall marks an account as deleted by setting the DELETED flag. The account must have zero balance and be writable by the current program. ## Syscall Code **Code:** `0x06` (`TN_SYSCALL_CODE_ACCOUNT_DELETE`) ## C SDK Function ```c ulong tsys_account_delete(ulong account_idx); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account to delete. Must be writable by the current program and have zero balance. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Account marked as deleted successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Account does not exist * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable by current program * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT` (-27) - Account has non-zero balance or non-zero nonce * `TN_VM_ERR_SYSCALL_STATE_BYTES_REMOVED_OVERFLOW` (-48) - State bytes counter overflow during deactivation ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional cost**: None * **Total cost**: Fixed at 512 units ### Memory Pages * **Page usage**: No immediate page deallocation (pages freed during garbage collection) * **Metadata impact**: May trigger metadata page allocation if not already writable * **Future cleanup**: Pages will be reclaimed after transaction completion ### State Counter Impact * **GDSC (Global Deactivated State Counter)**: Incremented by `TSDK_ACCOUNT_META_FOOTPRINT + account_data_sz` bytes * **Purpose**: Tracks deactivated state for the account deletion in the global state counter * **Overflow protection**: Returns `TN_VM_ERR_SYSCALL_STATE_BYTES_REMOVED_OVERFLOW` if counter would overflow ## Side Effects * **Account state**: Sets the DELETED flag on the account * **Metadata**: Makes account metadata writable if not already * **Data retention**: Account data and metadata remain accessible until garbage collection ## Usage Notes * Account must have exactly zero balance before deletion * Only the account owner (program) can delete the account * Deleted accounts can be recreated with `account_create` (removes DELETED flag) * Account data remains accessible during the transaction even after deletion * The account becomes eligible for garbage collection after transaction completion ## Deletion vs Compression | Operation | Purpose | Requirements | Effect | | --------- | --------------- | ----------------------- | -------------------- | | Delete | Remove account | Zero balance, ownership | Sets DELETED flag | | Compress | Archive account | State proof | Sets COMPRESSED flag | ## Example ```c #include // Delete an account (must have zero balance) ulong account_idx = 2; // First ensure the account has zero balance // (transfer out any remaining balance first) ulong result = tsys_account_delete(account_idx); if (result == TN_VM_SYSCALL_SUCCESS) { // Account marked as deleted // Will be garbage collected after transaction // Can still access data during this transaction // Can be recreated with account_create } ``` # account_resize > Changes the data size of an account ## Overview The `account_resize` syscall changes the data storage size of an account. This can be used to expand or shrink the account’s data capacity. ## Syscall Code **Code:** `0x07` (`TN_SYSCALL_CODE_ACCOUNT_RESIZE`) ## C SDK Function ```c ulong tsys_account_resize(ulong account_idx, ulong new_size); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account to resize. Must be writable by the current program. **`new_size` · *ulong* · **required**** New data size for the account in bytes. Must not exceed maximum account data size. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Account resized successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Account does not exist * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable by current program * `TN_VM_ERR_SYSCALL_ACCOUNT_TOO_BIG` (-12) - New size exceeds maximum allowed * `TN_VM_ERR_SYSCALL_INSUFFICIENT_PAGES` (-26) - Not enough pages available for expansion ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional cost**: None (size changes don’t directly affect compute cost) * **Total cost**: Fixed at 512 units regardless of new size ### Memory Pages * **Page allocation**: New pages allocated from transaction pool when expanding * **Page deallocation**: Excess pages returned to transaction pool when shrinking * **Page granularity**: Account data is managed in page-sized chunks * **Minimum allocation**: Account metadata always requires at least one page ## Side Effects * **Data size**: Changes the account’s data size to the specified value * **Memory allocation**: Allocates or frees pages as needed * **Metadata**: Makes account metadata and data writable if not already * **Data preservation**: Existing data is preserved when expanding ## Usage Notes * The new size must not exceed `TN_ACCOUNT_DATA_SZ_MAX` * When expanding: additional space is allocated and initialized * When shrinking: excess data is truncated and pages may be freed * If new size equals current size, the syscall succeeds immediately * Account must be writable by the current program * Sufficient pages must be available in the transaction’s page pool ## Memory Management * **Expansion**: Allocates additional pages from the transaction page pool * **Shrinking**: May free pages back to the pool (implementation dependent) * **Page alignment**: Account data is managed in page-sized chunks * **Data preservation**: Existing data within the new size is preserved ## Example ```c #include // Resize account data to 2048 bytes ulong account_idx = 1; ulong new_size = 2048; ulong result = tsys_account_resize(account_idx, new_size); if (result == TN_VM_SYSCALL_SUCCESS) { // Account data size changed to 2048 bytes // Existing data preserved (up to new size) // Additional space initialized if expanded } // Check current size without changing it result = tsys_account_resize(account_idx, current_size); // This will succeed immediately if current_size matches actual size ``` # account_set_flags > Modifies account flags such as PROGRAM ## Overview The `account_set_flags` syscall modifies specific flags on an account, such as marking it as a program account. Only certain flags can be modified. ## Syscall Code **Code:** `0x0E` (`TN_SYSCALL_CODE_ACCOUNT_SET_FLAGS`) ## C SDK Function ```c ulong tsys_account_set_flags(ushort account_idx, uchar flags); ``` ## Arguments **`account_idx` · *ushort* · **required**** Index of the account to modify. Must be writable by the current program. **`flags` · *uchar* · **required**** New flags value. Only modifiable flags will be changed. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Flags updated successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Account does not exist * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable by current program * `TN_VM_ERR_SYSCALL_INVALID_FLAGS` (-41) - Attempted to modify non-modifiable flags * `TN_VM_ERR_SYSCALL_ACCOUNT_IS_NOT_PROGRAM` (-17) - Account data is not valid program bytecode (when setting PROGRAM flag) * `TN_VM_ERR_SYSCALL_PROGRAM_IN_CALL_STACK` (-49) - Cannot unset PROGRAM flag while program is in the call stack ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional cost**: None * **Total cost**: Fixed at 512 units regardless of flag changes ### Memory Pages * **Page usage**: No direct page allocation * **Metadata impact**: May trigger metadata page allocation if not already writable * **Program validation**: Temporary memory usage when validating program bytecode ## Side Effects * **Account flags**: Updates the account’s flag bits * **Account metadata**: Makes account metadata writable if not already * **Program validation**: Validates bytecode when setting PROGRAM flag * **Write permissions**: May remove account writability when marking as program ## Modifiable Flags Only certain flags can be modified: **`TSDK_ACCOUNT_FLAG_PROGRAM` · *flag*** Marks the account as containing executable program bytecode. When setting this flag, the account data is validated as valid program bytecode. ## Flag Setting Behavior ### PROGRAM Flag **Setting the flag:** * Account data is validated as valid program bytecode * Account is automatically made non-writable (security measure) * Validation failure causes the syscall to fail **Clearing the flag:** * Cannot be cleared while the program is in the call stack (current program or any caller) * Returns `TN_VM_ERR_SYSCALL_PROGRAM_IN_CALL_STACK` if program is executing * Once cleared, account becomes a regular data account ## Usage Notes * Only the account owner (program) can modify flags * Attempts to modify non-modifiable flags result in an error * If no flags actually change, the syscall succeeds immediately * Program validation is performed when setting the PROGRAM flag * Account must exist and be writable by the current program ## Security Considerations * Setting the PROGRAM flag automatically removes account writability * This prevents modification of executable code after deployment * The PROGRAM flag cannot be cleared while the program is in the call stack * This prevents a program from invalidating itself or its callers during execution ## Example ```c #include // Mark account as a program (requires valid bytecode) ushort account_idx = 3; uchar current_flags = get_account_flags(account_idx); uchar new_flags = current_flags | TSDK_ACCOUNT_FLAG_PROGRAM; ulong result = tsys_account_set_flags(account_idx, new_flags); if (result == TN_VM_SYSCALL_SUCCESS) { // Account is now marked as a program // Account has been made non-writable // Bytecode has been validated } // Remove program flag (make it a data account again) new_flags = current_flags & ~TSDK_ACCOUNT_FLAG_PROGRAM; result = tsys_account_set_flags(account_idx, new_flags); // Attempt to modify non-modifiable flag (will fail) uchar invalid_flags = current_flags | TSDK_ACCOUNT_FLAG_DELETED; // Not modifiable result = tsys_account_set_flags(account_idx, invalid_flags); // Returns TN_VM_ERR_SYSCALL_INVALID_FLAGS ``` # account_transfer > Transfers balance between two accounts ## Overview The `account_transfer` syscall transfers a specified amount of balance from one account to another. Both accounts must exist and meet writability requirements. ## Syscall Code **Code:** `0x03` (`TN_SYSCALL_CODE_ACCOUNT_TRANSFER`) ## C SDK Function ```c ulong tsys_account_transfer(ulong from_account_idx, ulong to_account_idx, ulong amount); ``` ## Arguments **`from_account_idx` · *ulong* · **required**** Index of the source account to transfer balance from. Must be writable by the current program. **`to_account_idx` · *ulong* · **required**** Index of the destination account to transfer balance to. Must be writable in the transaction. **`amount` · *ulong* · **required**** Amount of balance to transfer from source to destination account. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Transfer completed successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Invalid from or to account index * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - From account not writable by program or to account not writable in transaction * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Source or destination account does not exist * `TN_VM_ERR_SYSCALL_INSUFFICIENT_BALANCE` (-38) - Source account has insufficient balance * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT` (-27) - Either account is ephemeral (not allowed for transfers) * `TN_VM_ERR_SYSCALL_BALANCE_OVERFLOW` (-11) - Destination balance would overflow ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional cost**: None * **Total cost**: Fixed at 512 units regardless of transfer amount ### Memory Pages * **Page usage**: No direct page allocation or deallocation * **Metadata impact**: May trigger metadata page allocation for both accounts if not already writable ## Side Effects * **Balance modification**: Decreases source account balance and increases destination balance * **Account metadata**: Makes account metadata writable if not already ## Usage Notes * Both accounts must be non-ephemeral accounts * Source account must be writable by the current program * Destination account must be writable in the transaction (signer approval) * Transfer amount cannot exceed source account balance * Destination balance cannot overflow (exceed maximum value) * Account metadata is automatically made writable for both accounts ## Example ```c #include // Transfer 1000 units from account 0 to account 1 ulong from_account = 0; ulong to_account = 1; ulong amount = 1000; ulong result = tsys_account_transfer(from_account, to_account, amount); if (result == TN_VM_SYSCALL_SUCCESS) { // Transfer completed successfully // from_account balance decreased by 1000 // to_account balance increased by 1000 } ``` # emit_event > Records an event in the transaction's event log ## Overview The `emit_event` syscall records an event in the transaction’s event log. Events are stored in anonymous memory and become part of the transaction’s output for external consumption. ## Syscall Code **Code:** `0x0D` (`TN_SYSCALL_CODE_EMIT_EVENT`) ## C SDK Function ```c ulong tsys_emit_event(void const* data, ulong data_sz); ``` ## Arguments **`data` · *void const** · **required***\* Pointer to the event data to be recorded. **`data_sz` · *ulong* · **required**** Size of the event data in bytes. Must not exceed the maximum event size. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Event recorded successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_EVENT_IS_ZERO_SIZE` (-46) - Event size must be greater than zero * `TN_VM_ERR_SYSCALL_EVENT_TOO_LARGE` (-31) - Event size exceeds maximum allowed * `TN_VM_ERR_SYSCALL_INVALID_ADDRESS` (-22) - Invalid virtual address for event data * `TN_VM_ERR_SYSCALL_INSUFFICIENT_PAGES` (-26) - Not enough pages to expand event storage ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Event cost**: Additional units equal to event data size in bytes * **Total formula**: `base_cost + data_sz` ### Memory Pages * **Event storage**: Pages allocated for event storage in anonymous memory segment * **Automatic expansion**: Event storage segment grows as needed (page-aligned) * **Metadata overhead**: Additional space for event metadata per event ## Side Effects * **Event storage**: Copies event data to transaction’s event log * **Memory allocation**: May expand event storage segment if needed * **Event counter**: Increments the transaction’s event count ## Event Metadata The runtime automatically adds internal metadata to each emitted event before exposing it in transaction output. That metadata is not part of the public C SDK surface, so programs should treat the event payload they pass to `tsys_emit_event()` as the only SDK-facing input. The metadata attached by the runtime includes: * **Event size**: Size of the event data * **Call index**: Unique identifier for the program invocation * **Program account index**: Index of the program that emitted the event ## Storage Management * Events are stored in a grow-up anonymous memory segment * Storage automatically expands as needed (page-aligned) * Events are stored sequentially in the order emitted * Multiple events can be emitted by the same program ## Size Limitations * Maximum event size: `UINT_MAX` bytes (4GB) * Practical limits imposed by available memory pages * Event storage competes with other memory allocations ## Usage Notes * Events are included in transaction output for external consumption * Event order is preserved within the transaction * Events from different programs are distinguished by metadata * Event data format is application-defined ## Example ```c #include #include #include // Emit a simple text event char event_msg[] = "Account created successfully"; ulong result = tsys_emit_event(event_msg, strlen(event_msg)); // Emit structured event data struct account_created_event { uint64_t account_id; uint64_t initial_balance; char account_type[16]; } event = { .account_id = 12345, .initial_balance = 1000000, .account_type = "savings" }; result = tsys_emit_event(&event, sizeof(event)); // Emit multiple related events for (int i = 0; i < transfer_count; i++) { struct transfer_event { uint64_t from_account; uint64_t to_account; uint64_t amount; } transfer = { .from_account = transfers[i].from, .to_account = transfers[i].to, .amount = transfers[i].amount }; result = tsys_emit_event(&transfer, sizeof(transfer)); if (result != TN_VM_SYSCALL_SUCCESS) break; } ``` # exit > Exits the current program execution with an optional error code ## Overview The `exit` syscall terminates the current program execution, either returning to the calling program or ending transaction execution. It can optionally revert the transaction or return with an error code. ## Syscall Code **Code:** `0x0B` (`TN_SYSCALL_CODE_EXIT`) ## C SDK Function ```c ulong __attribute__((noreturn)) tsys_exit(ulong exit_code, ulong revert); ``` ## Arguments **`exit_code` · *ulong* · **required**** User-defined error code to return. This value is passed to the calling program or transaction result. **`revert` · *ulong* · **required**** Revert flag. If non-zero, causes transaction revert; if zero, normal exit. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Normal exit (when returning to caller) * `TN_VM_SYSCALL_SUCCESS_EXIT` (1) - Transaction exit (when ending transaction) **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_REVERT` (-25) - Transaction revert requested ## Resource Consumption ### Compute Units * **Base cost**: None (exit syscalls don’t charge compute units) * **Total cost**: 0 units * **Rationale**: Exit operations are not charged since they terminate execution ### Memory Pages * **Page usage**: No page allocation or deallocation * **Shadow stack**: Uses existing shadow stack to restore previous execution state * **Memory cleanup**: No explicit cleanup (handled by transaction end) ## Side Effects * **Register a0**: Set to the user error code * **Program execution**: Terminates current program * **Call stack**: Pops current frame (if not root) or ends transaction * **Transaction state**: May trigger revert if revert flag is set ## Exit Behavior * Normal Exit (revert = 0) **Root Program**: Ends transaction execution with success * Sets transaction result to user error code * Transaction commits successfully **Called Program**: Returns to calling program * Restores previous execution frame * Calling program continues execution * User error code available in a0 * Revert Exit (revert ≠ 0) **Any Program**: Causes transaction revert * Sets VM fault to `TN_VM_FAULT_REVERT` * Transaction state is rolled back * Returns `TN_VM_ERR_SYSCALL_REVERT` ## Call Stack Behavior When exiting from a called program: 1. Current frame index decremented 2. Previous execution state restored from shadow stack 3. Program context switched back to caller 4. User error code placed in a0 ## Usage Notes * The user error code is application-defined and can convey success/failure information * Revert flag provides transaction-level error handling * Normal exits from root programs complete the transaction successfully * Multiple nested calls can be unwound with appropriate exit calls ## Error Code Conventions While error codes are user-defined, common conventions include: * `0`: Success * `1-999`: Application-specific success codes * `1000+`: Application-specific error codes ## Example ```c #include // Normal successful exit ulong success_code = 0; tsys_exit(success_code, 0); // Returns to caller or ends transaction successfully // Exit with application error ulong error_code = 1001; // Application-specific error tsys_exit(error_code, 0); // Returns error code to caller // Revert transaction due to critical error ulong critical_error = 9999; tsys_exit(critical_error, 1); // Causes transaction revert, returns TN_VM_ERR_SYSCALL_REVERT // In calling program after normal return: // a0 now contains the error code from the called program ``` # increment_anonymous_segment_sz > Increments or decrements the size of an anonymous memory segment by a delta value ## Overview The `increment_anonymous_segment_sz` syscall modifies the size of an anonymous memory segment by adding or subtracting a delta value. This is useful for dynamic memory allocation and deallocation. ## Syscall Code **Code:** `0x01` (`TN_SYSCALL_CODE_INCREMENT_ANONYMOUS_SEGMENT_SZ`) ## C SDK Function ```c ulong tsys_increment_anonymous_segment_sz(void* segment_addr, ulong delta, void** addr); ``` ## Arguments **`segment_addr` · *void** · **required***\* Virtual address that identifies the anonymous segment to modify. The segment type and index are extracted from this address. **`delta` · *ulong* · **required**** Delta value to add to the current segment size. Can be positive (expand) or negative (shrink). If zero, returns the current segment boundary address without modification. **`addr` · *void*** · **required**\*\* Pointer to store the resulting segment boundary address. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Operation completed successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_SEGMENT_ID` (-21) - Invalid segment type or index * `TN_VM_ERR_SYSCALL_INVALID_SEGMENT_SIZE` (-28) - Size overflow/underflow detected * `TN_VM_ERR_SYSCALL_INSUFFICIENT_PAGES` (-26) - Not enough free pages for expansion * `TN_VM_ERR_SYSCALL_UNFREEABLE_PAGE` (-29) - Cannot free pages when shrinking ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Delta cost**: Additional units based on delta value * **Positive delta**: Additional units equal to the delta value * **Negative delta**: No additional cost beyond base * **Zero delta**: No additional cost (query operation) * **Total formula**: `base_cost + max(0, delta)` ### Memory Pages * **Page allocation**: New pages allocated from transaction pool when expanding * **Page deallocation**: Freed pages returned to transaction pool when shrinking * **Page constraints**: Pages can only be freed if allocated in current or later call frames ## Side Effects * **Memory allocation/deallocation**: Pages allocated when expanding or freed when shrinking * **Output parameter**: Sets the addr parameter to the segment boundary address * **Segment state**: Updates segment size and page mappings ## Usage Notes * When delta is 0, returns current segment size without modification * For positive delta: allocates new pages and returns old boundary * For negative delta: frees pages and returns new boundary * Overflow/underflow protection prevents invalid size calculations * Only works with anonymous data segments (heap and stack types) ## Example ```c #include // Increase heap segment by 4096 bytes void* segment_addr = TSDK_ADDR(TSDK_SEG_TYPE_HEAP, 0, 0); void* boundary_addr; ulong result = tsys_increment_anonymous_segment_sz(segment_addr, 4096, &boundary_addr); if (result == TN_VM_SYSCALL_SUCCESS) { // boundary_addr now contains the address of the old segment boundary // Segment has been expanded by 4096 bytes } // Check current size without modification result = tsys_increment_anonymous_segment_sz(segment_addr, 0, &boundary_addr); // boundary_addr contains current segment boundary address ``` # invoke > Invokes another program with instruction data ## Overview The `invoke` syscall calls another program, creating a new execution frame. This enables cross-program invocation and modularity in the ThruVM runtime. ## Syscall Code **Code:** `0x0A` (`TN_SYSCALL_CODE_INVOKE`) ## C SDK Function ```c ulong tsys_invoke(void const* instr_data, ulong instr_data_sz, ushort program_account_idx, tsdk_invoke_auth_t const* auth, ulong* invoke_err_code); ``` ## Arguments **`instr_data` · *void const** · **required***\* Pointer to the instruction data to pass to the invoked program. **`instr_data_sz` · *ulong* · **required**** Length of the instruction data in bytes. **`program_account_idx` · *ushort* · **required**** Index of the program account to invoke. Must be a valid program account. **`auth` · *tsdk\_invoke\_auth\_t const** · **required***\* Optional invoke authorization descriptor. Pass `NULL` to use default authorization behavior. When provided, the descriptor is validated by the SDK before the syscall is issued. **`invoke_err_code` · *ulong** · **required***\* Pointer to store the error code returned by the invoked program. This will be set to the exit code of the invoked program. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Program invocation initiated successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Program account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Program account does not exist * `TN_VM_ERR_SYSCALL_ACCOUNT_IS_NOT_PROGRAM` (-17) - Account is not a valid program * `TN_VM_ERR_SYSCALL_CALL_DEPTH_TOO_DEEP` (-24) - Maximum call depth exceeded ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * Represents the overhead of saving and restoring VM register state * **Implementation details**: * **Save operation**: All 32 registers (256 bytes) are saved to the public shadow stack * **Restore operation**: When returning from the invoked program (via exit syscall), the registers (256 bytes) are restored from the public shadow stack back to the VM * **Total memory operations per invoke/exit cycle**: 512 bytes (256 saved + 256 restored) * **Cost calculation**: 32 registers × 8 bytes × 2 operations (save + restore) = 512 CUs * **Additional cost**: None for the invoke syscall itself * **Note**: The invoked program will consume additional compute units during its execution ### Memory Pages * **Page usage**: No direct page allocation * **Shadow stack**: Uses existing shadow stack pages to save execution state * **Call overhead**: Minimal memory overhead for call frame management ## Side Effects * **Execution context**: Creates new execution frame and updates call stack * **Register setup**: Sets a0 to instruction data address, a1 to data length * **Program counter**: Resets PC to 0 for the new program * **Call tracking**: Increments frame and call indices * **Shadow stack**: Saves current execution state on shadow stack * Stores all 32 registers (256 bytes) in the public shadow stack * Public shadow stack is read-only memory accessible to programs via memory segment 0x0002 * Preserves program context including stack/heap page counts * Creates accessible snapshot for cross-program inspection and state restoration ## Call Stack Management The syscall manages a call stack with the following updates: * **Frame index**: Incremented to create new frame * **Call depth**: Updated to reflect nesting level * **Shadow stack**: Current state saved for restoration on return * **Program context**: Switches to the invoked program’s bytecode ## Program Validation * The target account must have the PROGRAM flag set * Program bytecode is validated before invocation * Account must exist and be accessible in the transaction ## Depth Limiting * Maximum call depth is enforced (`TN_VM_CALL_DEPTH_MAX`) * Prevents infinite recursion and stack overflow * Call depth is tracked per execution frame ## Register State Upon successful invocation: * **a0**: Set to instruction data virtual address * **a1**: Set to instruction data length * **PC**: Reset to 0 (start of invoked program) * Other registers: Preserved from calling context ## Usage Notes * Instruction data can be any format understood by the target program * The invoked program executes with the same transaction context * Account permissions and writability are preserved * Return values are passed through program exit mechanisms * If `auth` is non-`NULL`, SDK-side validation runs before the syscall: * bad auth magic: `0xBAD0A170` * auth account not owned by current program: `0xBAD0A171` * invalid account index in auth/deauth lists: `0xBAD0A173` ## Example ```c #include #include // Invoke a calculator program with operation data ushort calculator_program_idx = 5; // Prepare instruction data struct calc_instruction { uint32_t operation; // 1 = add, 2 = multiply uint64_t operand_a; uint64_t operand_b; } instruction = { .operation = 1, // Addition .operand_a = 100, .operand_b = 200 }; ulong invoke_err = (ulong)-1; ulong result = tsys_invoke( &instruction, sizeof(instruction), calculator_program_idx, NULL, &invoke_err ); if (result == TN_VM_SYSCALL_SUCCESS && invoke_err == TN_VM_SYSCALL_SUCCESS) { // Calculator program is now executing // It will receive instruction data in a0, a1 // Current program state saved on shadow stack // Execution will continue in calculator program } ``` # log > Outputs debug/diagnostic information to the console ## Overview The `log` syscall outputs data to the console for debugging and diagnostic purposes. It prints the specified data directly to stdout. ## Syscall Code **Code:** `0x0C` (`TN_SYSCALL_CODE_LOG`) ## C SDK Function ```c ulong tsys_log(void const* data, ulong data_len); ``` ## Arguments **`data` · *void const** · **required***\* Pointer to the data to be logged. **`data_len` · *ulong* · **required**** Length of the data to log in bytes. Must not exceed the maximum log data size. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Data logged successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_LOG_DATA_TOO_LARGE` (-30) - Data exceeds maximum log size * `TN_VM_ERR_SYSCALL_INVALID_ADDRESS` (-22) - Invalid virtual address for data ## Resource Consumption ### Compute Units * **Base cost**: None (log syscalls don’t charge compute units) * **Total cost**: 0 units * **Rationale**: Logging is considered a debugging aid and is not charged ### Memory Pages * **Page usage**: No page allocation * **I/O overhead**: Minimal system overhead for stdout operations * **Buffer management**: Uses system stdout buffer ## Side Effects * **Console output**: Writes data directly to stdout * **Buffer flushing**: Flushes stdout buffer after writing ## Size Limitations * Maximum log data size: `TN_VM_LOG_DATA_MAX_SIZE` (1024 bytes) * Attempts to log more data will result in an error * No truncation is performed - the entire operation fails ## Output Behavior * Data is output byte-for-byte to stdout * No formatting, newlines, or prefixes are added * Output is immediately flushed to ensure visibility * Binary data is output as-is (may include non-printable characters) ## Usage Notes * Intended for debugging and diagnostic purposes only * Should not be used for production data exchange * Performance impact is minimal but output is synchronous * Consider the security implications of logging sensitive data Caution The log syscall outputs raw data to stdout. Be careful not to log sensitive information such as private keys, authentication tokens, or user data in production environments. ## Example ```c #include #include #include // Log a simple debug message char debug_msg[] = "Debug: Account balance updated\n"; ulong result = tsys_log(debug_msg, strlen(debug_msg)); // Log binary data (e.g., account hash) uchar account_hash[32]; get_account_hash(account_hash); result = tsys_log(account_hash, 32); // Log formatted data char buffer[256]; int len = snprintf(buffer, sizeof(buffer), "Account %lu: balance = %llu\n", account_id, balance); if (len > 0 && len < sizeof(buffer)) { result = tsys_log(buffer, len); } ``` # Syscalls Overview > Listing of all of the available system calls (syscalls) in the ThruVM Syscalls in the ThruVM provide programs with access to runtime services and system resources. They enable programs to manage memory, manipulate accounts, transfer funds, and interact with the broader system. Tip Syscall error-code numeric values may change over time. Verify the authoritative values in `src/thru/runtime/vm/tn_vm_base.h` when implementing or auditing error handling. ## Syscall Listing Memory Management tsys\_set\_anonymous\_segment\_sz [Code 0x00](/docs/spec/vm/syscalls/set_anonymous_segment_sz/) - Sets anonymous segment size to specific value tsys\_increment\_anonymous\_segment\_sz [Code 0x01](/docs/spec/vm/syscalls/increment_anonymous_segment_sz/) - Increments/decrements segment size by delta Account Management tsys\_set\_account\_data\_writable [Code 0x02](/docs/spec/vm/syscalls/set_account_data_writable/) - Marks account data writable by program tsys\_account\_transfer [Code 0x03](/docs/spec/vm/syscalls/account_transfer/) - Transfers balance between accounts tsys\_account\_create [Code 0x04](/docs/spec/vm/syscalls/account_create/) - Creates permanent account with proof tsys\_account\_create\_ephemeral [Code 0x05](/docs/spec/vm/syscalls/account_create_ephemeral/) - Creates temporary account tsys\_account\_delete [Code 0x06](/docs/spec/vm/syscalls/account_delete/) - Marks account as deleted tsys\_account\_resize [Code 0x07](/docs/spec/vm/syscalls/account_resize/) - Changes account data size tsys\_account\_set\_flags [Code 0x0E](/docs/spec/vm/syscalls/account_set_flags/) - Modifies account flags Compression & Storage tsys\_account\_compress [Code 0x08](/docs/spec/vm/syscalls/account_compress/) - Compresses account with state proof tsys\_account\_decompress [Code 0x09](/docs/spec/vm/syscalls/account_decompress/) - Decompresses account with data and proof Program Control tsys\_invoke [Code 0x0A](/docs/spec/vm/syscalls/invoke/) - Invokes another program tsys\_exit [Code 0x0B](/docs/spec/vm/syscalls/exit/) - Exits program execution Logging & Events tsys\_log [Code 0x0C](/docs/spec/vm/syscalls/log/) - Outputs debug information tsys\_emit\_event [Code 0x0D](/docs/spec/vm/syscalls/emit_event/) - Records transaction event ## Calling Convention ThruVM syscalls follow a standardized calling convention using RISC-V registers: * **Syscall number**: Placed in register `a7` * **Arguments**: Passed in registers `a0` through `a6` (up to 7 arguments) * **Return value**: Placed in register `a0` * **Invocation**: Use the `ecall` instruction to trigger the syscall ### C SDK Interface The C SDK provides convenient wrapper functions for all syscalls. Include `` to access these functions: ```c #include // Example: Transfer funds between accounts ulong result = tsys_account_transfer(from_account, to_account, amount); if (result == TN_VM_SYSCALL_SUCCESS) { // Transfer successful } ``` ### Raw Assembly Template For direct assembly usage: ```asm # Set arguments in a0-a6 li a0, account_idx li a1, amount # Set syscall number in a7 li a7, TN_SYSCALL_CODE_ACCOUNT_TRANSFER # Invoke syscall ecall # Check return value in a0 ``` ## Compute Units Most syscalls consume compute units: * **Typical base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional costs**: Vary by syscall complexity and data size * **Current exceptions**: `exit` and `log` currently apply zero CU charge in implementation ## Error Handling Syscalls return standardized error codes: * `TN_VM_SYSCALL_SUCCESS` (0) - Operation successful * Negative values indicate specific error conditions * Programs should always check return values ## Usage Patterns ### Memory Management Use memory syscalls to allocate and manage anonymous data segments for program scratch space and dynamic data structures. ### Account Operations Account syscalls enable creating, modifying, and managing accounts within transactions. Always verify account ownership and permissions. ### Cross-Program Calls Use `tsys_invoke` and `tsys_exit` syscalls to build modular programs that can call each other while maintaining security boundaries. ### State Management Compression syscalls allow efficient storage of large account states with cryptographic proofs for verification. ### Debugging & Monitoring Logging and event syscalls provide visibility into program execution and enable external monitoring. # set_account_data_writable > Marks an account's data as writable by the current program ## Overview The `set_account_data_writable` syscall marks an account’s data as writable by the current executing program. This grants the program permission to modify the account’s data and metadata. ## Syscall Code **Code:** `0x02` (`TN_SYSCALL_CODE_SET_ACCOUNT_DATA_WRITABLE`) ## C SDK Function ```c ulong tsys_set_account_data_writable(ulong account_idx); ``` ## Arguments **`account_idx` · *ulong* · **required**** Index of the account to mark as writable. Must be a valid account index within the transaction. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Account successfully marked as writable **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_ACCOUNT_INDEX` (-8) - Account index out of bounds * `TN_VM_ERR_SYSCALL_ACCOUNT_NOT_WRITABLE` (-10) - Account not writable in transaction or ownership check failed * `TN_VM_ERR_SYSCALL_ACCOUNT_DOES_NOT_EXIST` (-9) - Account does not exist ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional cost**: None * **Total cost**: Fixed at 512 units regardless of account state ### Memory Pages * **Page usage**: No direct page allocation or deallocation * **Metadata impact**: May trigger metadata page allocation if account metadata becomes writable ## Side Effects * **Account state**: Marks the account as writable by the current program * **Write permissions**: Grants the program ability to modify account data ## Usage Notes * The account must be marked as writable in the transaction (signer approval) * The account must be owned by the current program * Program accounts cannot be made writable * If the account is already writable by the current program, the syscall succeeds immediately * Once writable, the program can modify account data and metadata ## Ownership Rules The account can be made writable only if: * The account is not a program account * The account is owned by the current executing program ## Example ```c #include // Mark account at index 1 as writable ulong account_idx = 1; ulong result = tsys_set_account_data_writable(account_idx); if (result == TN_VM_SYSCALL_SUCCESS) { // Account is now writable by this program // Can now modify account data and metadata } ``` # set_anonymous_segment_sz > Sets the size of an anonymous memory segment to a specific value ## Overview The `set_anonymous_segment_sz` syscall sets the size of an anonymous memory segment to a specific value. This syscall is used for precise memory management when you need to set an exact segment size. ## Syscall Code **Code:** `0x00` (`TN_SYSCALL_CODE_SET_ANONYMOUS_SEGMENT_SZ`) ## C SDK Function ```c ulong tsys_set_anonymous_segment_sz(void* addr); ``` ## Arguments **`addr` · *void** · **required***\* Virtual address that encodes the segment type, index, and desired size offset. The size is determined by the offset in the address. ## Return Value Returns a syscall result code: **`Success` · *ulong*** * `TN_VM_SYSCALL_SUCCESS` (0) - Operation completed successfully **`Error Codes` · *ulong*** * `TN_VM_ERR_SYSCALL_INVALID_SEGMENT_ID` (-21) - Invalid segment type or index * `TN_VM_ERR_SYSCALL_INVALID_SEGMENT_SIZE` (-28) - Invalid segment size (not page-aligned) * `TN_VM_ERR_SYSCALL_INSUFFICIENT_PAGES` (-26) - Not enough free pages for expansion * `TN_VM_ERR_SYSCALL_UNFREEABLE_PAGE` (-29) - Cannot free pages when shrinking ## Resource Consumption ### Compute Units * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional cost**: Variable based on size change * **Expansion**: Additional units equal to the number of bytes increased * **Shrinking**: No additional cost beyond base * **Total formula**: `base_cost + max(0, size_increase)` ### Memory Pages * **Page allocation**: Required pages are allocated from the transaction’s page pool * **Page deallocation**: Freed pages are returned to the transaction’s page pool * **Page size**: Operations work in `TN_COW_TABLE_PAGE_SZ` chunks ## Side Effects * **Memory allocation/deallocation**: Pages are allocated when expanding or freed when shrinking the segment * **Segment state**: Updates the segment size and page mappings ## Usage Notes * The segment size must be page-aligned (multiple of page size) * For stack segments, the size is calculated as `(0xFFFFFF - offset + 1)` * For heap segments, the size is equal to the offset * Only works with anonymous data segments (heap and stack types) * Pages are allocated from the transaction’s page pool ## Example ```c #include // Set the heap segment (index 0) to 8192 bytes void* segment_addr = TSDK_ADDR(TSDK_SEG_TYPE_HEAP, 0, 8192); ulong result = tsys_set_anonymous_segment_sz(segment_addr); if (result == TN_VM_SYSCALL_SUCCESS) { // Segment size set successfully // Segment now has exactly 8192 bytes allocated } ``` # Syscalls > Listing of all of the available system calls (syscalls) in the ThruVM Syscalls in the ThruVM provide programs with access to runtime services and system resources. They enable programs to manage memory, manipulate accounts, transfer funds, and interact with the broader system. ## Calling Convention ThruVM syscalls follow a standardized calling convention using RISC-V registers: * **Syscall number**: Placed in register `a7` * **Arguments**: Passed in registers `a0` through `a6` (up to 7 arguments) * **Return value**: Placed in register `a0` * **Invocation**: Use the `ecall` instruction to trigger the syscall ### C SDK Interface The C SDK provides convenient wrapper functions for all syscalls. Include `` to access these functions: ```c #include // Example: Transfer funds between accounts ulong result = tsys_account_transfer(from_account, to_account, amount); if (result == TN_VM_SYSCALL_SUCCESS) { // Transfer successful } ``` ### Raw Assembly Template For direct assembly usage: ```asm # Set arguments in a0-a6 li a0, account_idx li a1, amount # Set syscall number in a7 li a7, TN_SYSCALL_CODE_ACCOUNT_TRANSFER # Invoke syscall ecall # Check return value in a0 ``` ## Compute Units All syscalls consume compute units: * **Base cost**: `TN_VM_SYSCALL_BASE_COST` (512 units) * **Additional costs**: Vary by syscall complexity and data size * **Total consumption**: Base cost + operation-specific costs ## Error Handling Syscalls return standardized error codes: * `TN_VM_SYSCALL_SUCCESS` (0) - Operation successful * Negative values indicate specific error conditions * Programs should always check return values ## Syscall Listing Memory Management tsys\_set\_anonymous\_segment\_sz [Code 0x00](/docs/spec/vm/syscalls/set_anonymous_segment_sz/) - Sets anonymous segment size to specific value tsys\_increment\_anonymous\_segment\_sz [Code 0x01](/docs/spec/vm/syscalls/increment_anonymous_segment_sz/) - Increments/decrements segment size by delta Account Management tsys\_set\_account\_data\_writable [Code 0x02](/docs/spec/vm/syscalls/set_account_data_writable/) - Marks account data writable by program tsys\_account\_transfer [Code 0x03](/docs/spec/vm/syscalls/account_transfer/) - Transfers balance between accounts tsys\_account\_create [Code 0x04](/docs/spec/vm/syscalls/account_create/) - Creates permanent account with proof tsys\_account\_create\_ephemeral [Code 0x05](/docs/spec/vm/syscalls/account_create_ephemeral/) - Creates temporary account tsys\_account\_delete [Code 0x06](/docs/spec/vm/syscalls/account_delete/) - Marks account as deleted tsys\_account\_resize [Code 0x07](/docs/spec/vm/syscalls/account_resize/) - Changes account data size tsys\_account\_set\_flags [Code 0x0E](/docs/spec/vm/syscalls/account_set_flags/) - Modifies account flags tsys\_account\_create\_eoa [Code 0x0F](/docs/spec/vm/syscalls/account_create_eoa/) - Creates Externally Owned Account with signature verification Compression & Storage tsys\_account\_compress [Code 0x08](/docs/spec/vm/syscalls/account_compress/) - Compresses account with state proof tsys\_account\_decompress [Code 0x09](/docs/spec/vm/syscalls/account_decompress/) - Decompresses account with data and proof Program Control tsys\_invoke [Code 0x0A](/docs/spec/vm/syscalls/invoke/) - Invokes another program tsys\_exit [Code 0x0B](/docs/spec/vm/syscalls/exit/) - Exits program execution Logging & Events tsys\_log [Code 0x0C](/docs/spec/vm/syscalls/log/) - Outputs debug information tsys\_emit\_event [Code 0x0D](/docs/spec/vm/syscalls/emit_event/) - Records transaction event ## Usage Patterns ### Memory Management Use memory syscalls to allocate and manage anonymous data segments for program scratch space and dynamic data structures. ### Account Operations Account syscalls enable creating, modifying, and managing accounts within transactions. Always verify account ownership and permissions. ### Cross-Program Calls Use `tsys_invoke` and `tsys_exit` syscalls to build modular programs that can call each other while maintaining security boundaries. ### State Management Compression syscalls allow efficient storage of large account states with cryptographic proofs for verification. ### Debugging & Monitoring Logging and event syscalls provide visibility into program execution and enable external monitoring. # Approval and Signing > Understand the embedded wallet request lifecycle for `connect()`, `getSigningContext()`, wallet-managed `signTransaction()` intents, and user-facing approval states. Use this page when you need the exact high-level lifecycle behind `connect()`, `getSigningContext()`, and `signTransaction()`. ## Connect Lifecycle From a dApp perspective, `connect()` is a request for wallet approval plus account discovery. 1. The dApp calls `connect()`. 2. `BrowserSDK` auto-initializes the iframe if needed and forwards the request to the embedded provider. 3. The provider posts a `connect` request to the wallet iframe with the dApp origin and optional app metadata. 4. The wallet decides whether it can reuse a cached connection for the same origin, auto-approve a previously connected app, or show the connect UI. 5. If approval succeeds, the wallet returns the connected accounts and resolved app metadata. 6. The provider caches the connected accounts locally and emits `connect`. ## What Changes Connect Behavior | Condition | What happens | | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | | Same origin is already connected in the current iframe session | The wallet can return the cached connection result immediately. | | The dApp supplies app metadata and the app is already known for the selected account | The wallet can auto-approve if it is already unlocked. | | The wallet is locked | The wallet shows connect UI and may require passkey unlock before approval completes. | | No metadata is supplied | The wallet still works, but the connection flow goes through the visible approval UI. | ## Signing Lifecycle `signTransaction()` is a separate approval flow. A connected wallet does not imply automatic transaction approval. 1. The dApp builds the program instruction payload for the action. 2. The dApp calls `wallet.signTransaction(intent)` with a `ThruTransactionIntent`. 3. The embedded provider shows the wallet iframe and posts a `signTransaction` request. 4. The wallet stores the pending request and opens the transaction approval modal. 5. If the wallet is locked, passkey authentication runs before signing continues. 6. The wallet chooses the fee payer, orders accounts, fills headers and nonces, builds the final transaction, signs it, and returns canonical raw transaction bytes encoded as base64. 7. The dApp submits those returned bytes separately. ## Managed Account Vs Network Signer The selected account and the network signer can differ. * the selected account is the managed wallet account the user is acting as * the fee payer and signer can be the embedded manager profile instead * `getSigningContext()` exposes that split for display and diagnostics The dApp should not assume the selected managed account is the transaction fee payer. For wallet-managed signing, the wallet owns fee payer choice and final transaction layout. ## `getSigningContext()` Lifecycle Use `getSigningContext()` when your UI or diagnostics need to show the managed account, fee payer, or signer identity. The request returns: * `selectedAccountPublicKey` * `feePayerPublicKey` * `signerPublicKey` * accepted encodings reported by the current wallet context * the output encoding that the wallet will return ## dApp UX Expectations | Phase | What the wallet is doing | What the dApp should show | | ----------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | Before `connect()` resolves | Waiting for connect approval or unlock | A persistent “waiting for wallet approval” state | | Before `signTransaction()` resolves | Waiting for transaction approval and possibly passkey unlock | A persistent “confirm in wallet” state | | After `signTransaction()` resolves | The wallet has returned canonical raw bytes, but has not submitted them | A “submitting transaction” state until the dApp sends and tracks the transaction | | On rejection | Returning a user-rejected error | A recoverable error state with retry | ## Important Distinction The wallet signs. The dApp submits. If `signTransaction()` succeeds but the transaction never appears in explorer, the first thing to check is whether the dApp actually sent the returned raw bytes to the network. ## Account Selection The sign flow uses the currently selected wallet account. If the dApp wants to sign with a different account, it should change selection before signing. That selection affects the managed account context shown to the user, but it does not necessarily mean the same public key is the network fee payer. ## Signing Sessions A signing session is a wallet-owned temporary signer for repeated app actions. Session-backed signing still uses `ThruTransactionIntent`, but the session handle signs without opening the normal transaction approval UI for each action. Use [Signing Sessions](/docs/wallet/signing-sessions/) when an app needs a short-lived signer that the wallet created or confirmed for that app. ## Related References * [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) * [Signing Sessions](/docs/wallet/signing-sessions/) * [`@thru/wallet`](/docs/sdks/web-packages/wallet/) * [`@thru/sdk`](/docs/sdks/web-packages/sdk/) # Embedded Wallet Integration > Integrate the hosted embedded wallet into a Thru web app and use `connect()`, `getSigningContext()`, and wallet-managed `signTransaction()` intents correctly. Use this page when you want one reference page for wiring the hosted embedded wallet into a web app. ## Use This When * you want the recommended React integration path * you need to understand the smallest public wallet contract a dApp uses * you want to connect a dApp, send a wallet-managed transaction intent for approval, and then submit it with `@thru/sdk` ## Choose The Right Package Layer | Entry point | Use it when | Avoid it when | | -------------------- | ------------------------------------------------------------- | --------------------------------------- | | `@thru/wallet/react` | Your app already uses React and you want provider plus hooks. | You are not using React. | | `@thru/wallet` | You want a browser-side SDK without React. | You want React provider state or hooks. | ## Install For the recommended React path: ```bash npm install @thru/wallet @thru/sdk ``` For a non-React integration: ```bash npm install @thru/wallet @thru/sdk ``` ## Minimal React Setup Wrap the app with `ThruProvider` and point it at the hosted wallet iframe. ```tsx import { ThruProvider } from "@thru/wallet/react"; export function App({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ## Minimal Connect Flow `connect()` is the dApp entrypoint. The wallet resolves the request against the iframe, origin, and app metadata. ```tsx import { useWallet } from "@thru/wallet/react"; export function ConnectButton() { const { connect, isConnected, isConnecting } = useWallet(); if (isConnected) { return ; } return ( ); } ``` ## Minimal Sign-And-Submit Flow Use `signTransaction()` with a transaction intent. The dApp supplies the program, instruction bytes, account addresses, and optional review metadata. The wallet approves the action, chooses the fee payer, orders accounts, fills transaction headers and nonces, signs the final transaction, and returns canonical raw transaction bytes encoded as base64. `instructionData` is the base64-encoded program instruction payload. ```tsx import { useThru, useWallet } from "@thru/wallet/react"; function bytesToBase64(bytes: Uint8Array): string { let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } function base64ToBytes(value: string): Uint8Array { const binary = atob(value); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } export function SubmitSignedTransaction({ programAddress, instructionData, readWriteAddresses, readOnlyAddresses, }: { programAddress: string; instructionData: Uint8Array; readWriteAddresses?: string[]; readOnlyAddresses?: string[]; }) { const { thru } = useThru(); const { wallet } = useWallet(); return ( ); } ``` ## Signing Context Call `wallet.getSigningContext()` when your UI needs to display the selected managed account or the current network signer. You do not need to use this response to build the final transaction wire payload; the wallet owns that step for `signTransaction(intent)`. The current embedded wallet contract returns a managed-fee-payer shape: ```ts type ThruSigningContext = { mode: "managed_fee_payer"; selectedAccountPublicKey: string | null; feePayerPublicKey: string; signerPublicKey: string; acceptedInputEncodings: [ "signing_payload_base64", "raw_transaction_base64", ]; outputEncoding: "raw_transaction_base64"; }; ``` Use it to answer two questions before signing: * which managed account the user thinks they are acting as * which public key actually signs and pays for network submission ## What The dApp Owns The dApp is responsible for: * deciding when to call `connect()` * building the program instruction payload * passing a `ThruTransactionIntent` to `signTransaction()` * submitting the returned raw transaction bytes directly * showing the right status while the wallet UI is open The wallet is responsible for: * presenting connection and approval UI * unlocking with passkey if required * selecting the current wallet account * returning the current signing contract for the embedded environment * choosing the fee payer and network signer * ordering accounts, filling headers and nonces, and constructing the final wire transaction * returning canonical raw transaction bytes after signing ## Important Assumptions * the iframe URL must be a trusted wallet origin: `https://wallet.thru.org` or localhost during development * `signTransaction()` expects a transaction intent with `programAddress` and base64 `instructionData` * the wallet, not the dApp, owns fee payer selection and final transaction wire layout * the browser wallet contract is intentionally narrow: connect, disconnect, account selection, transaction signing, and signing sessions ## Open Next * [Approval and Signing](/docs/wallet/approval-and-signing/) to understand what happens after a dApp calls `connect()` or `signTransaction()` * [Signing Sessions](/docs/wallet/signing-sessions/) when repeated actions should use a temporary wallet-owned signer * [Troubleshooting](/docs/wallet/troubleshooting/) if the request flow stalls or the transaction never appears on-chain # Overview > Choose the right wallet docs page for embedded wallet integration, approval flow behavior, signing sessions, and troubleshooting. Use this section when you are integrating a Thru web app with the hosted embedded wallet at `wallet.thru.org`. ## Start Here [Embedded Wallet Integration ](/docs/wallet/embedded-wallet-integration/)Set up the wallet in a web app, choose the right package layer, and wire \`connect()\` plus \`signTransaction()\` correctly. [Approval and Signing ](/docs/wallet/approval-and-signing/)Understand the request lifecycle for connect and transaction signing, including cached reconnects, approval modals, and dApp UX expectations. [Signing Sessions ](/docs/wallet/signing-sessions/)Create, use, list, confirm, and revoke temporary wallet-owned signing sessions for repeated actions. [Troubleshooting ](/docs/wallet/troubleshooting/)Debug wallet integration failures such as origin issues, stalled approval flows, malformed transaction payloads, and transactions that never reach explorer. ## Choose The Right Page | If your task is… | Open this page | Then open… | | ----------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------- | | Add wallet connect to a React dApp | [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) | [`@thru/wallet`](/docs/sdks/web-packages/wallet/) with `@thru/wallet/react` | | Add wallet connect to a non-React dApp | [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) | [`@thru/wallet`](/docs/sdks/web-packages/wallet/) | | Explain what the user sees during approval | [Approval and Signing](/docs/wallet/approval-and-signing/) | [`@thru/wallet`](/docs/sdks/web-packages/wallet/) protocol exports | | Add a temporary signer for repeated app actions | [Signing Sessions](/docs/wallet/signing-sessions/) | [`@thru/wallet`](/docs/sdks/web-packages/wallet/) signing session APIs | | Debug why a request or transaction flow failed | [Troubleshooting](/docs/wallet/troubleshooting/) | [Explorer MCP](/docs/api-ref/explorer-mcp/overview/) | ## What This Section Assumes * you are integrating the hosted wallet iframe rather than building a custom wallet * your web app can reach `https://wallet.thru.org/embedded` * you will send wallet-managed transaction intents to the wallet for approval and signing * you will use [`@thru/sdk`](/docs/sdks/web-packages/sdk/) or another client to actually submit signed transactions ## Open Next * [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) for the recommended integration path * [Approval and Signing](/docs/wallet/approval-and-signing/) before you design wallet status UI * [Signing Sessions](/docs/wallet/signing-sessions/) when an app needs temporary approval for repeated actions * [Troubleshooting](/docs/wallet/troubleshooting/) when the wallet flow is present but not behaving as expected # Signing Sessions > Create, use, confirm, list, and revoke temporary wallet-owned signing sessions from the browser wallet SDK. Use signing sessions when a connected app needs a temporary wallet-owned signer for repeated actions. A session can sign the same `ThruTransactionIntent` shape as `wallet.signTransaction(...)`, but the session handle signs through the wallet’s session key instead of opening the normal approval UI for every action. ## When To Use A Signing Session Use a signing session when: * the user has already approved a narrow app flow * the app needs to submit repeated transactions for the same wallet account * the app can keep the session short-lived and revocable Do not use a signing session as a replacement for the initial wallet connection or for actions that should always show the full wallet approval modal. ## Create A Session `createSigningSession(...)` opens the wallet, asks the user to approve the session, stores the session descriptor in app-local SDK storage, and returns a `ThruSigningSession` handle. ```ts const session = await wallet.createSigningSession({ walletAddress, durationSeconds: 60 * 30, review: { appName: "My Thru App", instruction: "Repeated transfer approval", }, }); console.log(session.id); console.log(session.walletAddress); console.log(session.publicKey); console.log(session.expiresAt); ``` The wallet stores the private key. The SDK stores only the descriptor needed by this app to find and use the session later. ## Sign With A Session Use the returned session handle to sign a wallet-managed transaction intent. The returned string is the signed raw transaction encoded as base64, ready for submission. ```ts function bytesToBase64(bytes: Uint8Array): string { let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } function base64ToBytes(value: string): Uint8Array { const binary = atob(value); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } const signedBase64 = await session.signTransaction({ programAddress, instructionData: bytesToBase64(instructionData), readWriteAddresses, readOnlyAddresses, review: { appName: "My Thru App", programAddress, instruction: "Transfer", }, }); const signature = await thru.transactions.send(base64ToBytes(signedBase64)); ``` Session-backed signing still uses wallet-managed transaction construction. The app supplies the program instruction intent, and the wallet/session path owns the final transaction wire layout. ## List And Reuse Sessions Signing sessions are app-scoped. Use `getSigningSessions()` to find sessions known to the current SDK storage scope, or `getSigningSession(id)` when you already stored an id in app state. ```ts const sessions = await wallet.getSigningSessions(); const activeSession = sessions.find( (session) => session.walletAddress === walletAddress && session.expiresAt > Math.floor(Date.now() / 1000), ); if (activeSession) { const signedBase64 = await activeSession.signTransaction({ programAddress, instructionData: bytesToBase64(instructionData), readWriteAddresses, readOnlyAddresses, }); } ``` If no session is available, fall back to `wallet.signTransaction(intent)` or ask the user to create a new session. ## Create A Session Instruction Use `createSigningSessionInstruction(...)` when the app needs to prepare the session authority instruction first, include it in a later user-approved transaction, and confirm the session only after that transaction lands. ```ts const prepared = await wallet.createSigningSessionInstruction({ walletAddress, walletAccountIdx: 2, durationSeconds: 60 * 30, }); console.log(prepared.session.id); console.log(prepared.programAddress); console.log(prepared.instructionData); ``` Include `prepared.instructionData` in the transaction that adds the session authority. After that transaction is accepted on-chain, confirm the session: ```ts const session = await wallet.confirmSigningSession(prepared.session.id); ``` `confirmSigningSession(id)` asks the wallet to verify the session and then stores the descriptor in the SDK’s app-local storage. ## Revoke A Session Revoke a session when the user disconnects, when the app no longer needs the temporary signer, or when the app detects that the session is expired. ```ts await session.revoke(); // Or revoke by id. await wallet.revokeSigningSession(session.id); ``` Revocation removes the local SDK descriptor and asks the wallet to delete its session key. ## API Shape ```ts type ThruTransactionIntent = { walletAddress?: string; programAddress: string; instructionData: string; readWriteAddresses?: string[]; readOnlyAddresses?: string[]; review?: ThruTransactionReviewPayload; }; type ThruTransactionReviewPayload = { appName?: string; programAddress?: string; abiName?: string; instruction?: string; simulation?: { before?: string; after?: string; }; abiReflection?: { label?: string; kind?: string | null; typeName?: string; value?: unknown; rawHex?: string; source?: string; error?: string; }; }; type ThruSigningSessionDescriptor = { id: string; walletAddress: string; publicKey: string; authIdx: number; expiresAt: number; createdAt: number; }; type ThruSigningSession = ThruSigningSessionDescriptor & { signTransaction(transaction: ThruTransactionIntent): Promise; revoke(): Promise; toJSON(): ThruSigningSessionDescriptor; }; ``` ## Related References * [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) * [Approval and Signing](/docs/wallet/approval-and-signing/) * [`@thru/wallet`](/docs/sdks/web-packages/wallet/) # Troubleshooting > Debug common embedded wallet integration failures such as iframe origin errors, stalled approval flow, malformed transaction intents, signing session issues, and transactions that never appear in explorer. Use this page when the wallet integration exists but the dApp flow is failing or stalling. ## Iframe Or Origin Errors ### Symptom The wallet fails before any UI appears, or initialization throws an origin error. ### Likely Cause The embedded provider only accepts trusted iframe origins: * `https://wallet.thru.org` * `http://localhost` during development ### What To Check * the `iframeUrl` is an absolute URL * the origin is `wallet.thru.org` in production * you did not point the SDK at an arbitrary site or an app page that is not the wallet iframe ## `connect()` Never Resolves ### Symptom The dApp waits forever or users say the wallet never finishes connecting. ### What To Check * keep a visible “waiting for wallet approval” state while `connect()` is in flight * make sure the iframe is actually being shown * if you supplied metadata, make sure `appId` and `appUrl` match the dApp origin you expect * if the wallet is locked, expect passkey unlock before approval completes ## `signTransaction()` Fails Immediately ### Symptom The wallet rejects the request before the approval modal is useful. ### What To Check * the wallet is already connected before you call `signTransaction()` * the intent includes a valid `programAddress` * `instructionData` is a non-empty base64 string containing program instruction bytes * `readWriteAddresses` and `readOnlyAddresses` contain account addresses, not raw bytes * if you pass `walletAddress`, it matches the wallet account the user is acting as ## Approval Flow Stalls ### Symptom The wallet iframe opens, but the request seems stuck. ### What To Check * do not hide the dApp loading state as soon as the wallet iframe appears * if the wallet is locked, the request may be waiting for passkey authentication * a request can also stall from the user perspective if the dApp immediately tears down the component that started the flow ## The Transaction Never Appears In Explorer ### Symptom `signTransaction()` succeeds, but there is no explorer entry. ### Likely Cause The dApp never submitted the signed payload, or it submitted it against a different network than the explorer view you checked. ### What To Check * the dApp called `thru.transactions.send(...)` or an equivalent submit path after signing * the signed payload, not the unsigned payload, is what got submitted * the explorer network matches the RPC endpoint your dApp is using * if needed, inspect the result with [Explorer MCP](/docs/api-ref/explorer-mcp/overview/) ## Built With The Wrong Fee Payer ### Symptom The transaction signs cleanly, but the app expected a different network fee payer or signer. ### What To Check * do not assume the selected managed account is also the network signer * use `getSigningContext()` for display or diagnostics when the UI needs to show the current signer split * do not prebuild the final transaction wire payload for the browser wallet flow * let `signTransaction(intent)` choose the fee payer, order accounts, fill headers and nonces, and return the final signed bytes ## Reordered Bytes After Signing ### Symptom The app signs successfully but then fails after trying to reshuffle signature bytes and transaction payload bytes before submission. ### What To Check * `signTransaction()` now returns canonical raw transaction bytes ready for direct submission * do not prepend, append, or reorder signature bytes yourself after the wallet returns the result * send the returned bytes directly through `thru.transactions.send(...)` ## Parse Or Signing Errors ### Symptom The wallet says the transaction is invalid or signing fails unexpectedly. ### What To Check * the program instruction bytes came from a correct instruction builder * the instruction bytes are base64 encoded before they are assigned to `instructionData` * the account selection and fee payer assumptions match the wallet-managed signing model * the app is not treating the selected managed account as the network signer without checking `getSigningContext()` ## Signing Session Is Missing ### Symptom `session.signTransaction(...)` fails with a missing or unknown session error. ### What To Check * the session was created or confirmed in the same app storage scope * `getSigningSessions()` returns the session before you try to use it * the session has not expired * the user has not revoked it in the wallet * if you used `createSigningSessionInstruction(...)`, the transaction that added the session authority landed before you called `confirmSigningSession(id)` ## Useful Next Reads * [Embedded Wallet Integration](/docs/wallet/embedded-wallet-integration/) * [Approval and Signing](/docs/wallet/approval-and-signing/) * [Signing Sessions](/docs/wallet/signing-sessions/) * [Explorer MCP Overview](/docs/api-ref/explorer-mcp/overview/) # Duration > gRPC message google.protobuf.Duration **Package:** `google.protobuf` ## Fields | Field | Type | # | Description | | --------- | ------- | ------------ | ----------- | | `seconds` | `int64` | 1 · optional | | | `nanos` | `int32` | 2 · optional | | # Timestamp > gRPC message google.protobuf.Timestamp **Package:** `google.protobuf` ## Fields | Field | Type | # | Description | | --------- | ------- | ------------ | ----------- | | `seconds` | `int64` | 1 · optional | | | `nanos` | `int32` | 2 · optional | | # BytesList > BytesList holds a list of byte arrays for multi-value CEL filtering. BytesList holds a list of byte arrays for multi-value CEL filtering. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | -------- | ---------------- | ------------ | ----------- | | `values` | repeated `bytes` | 1 · optional | | # CelFilterValidation > CelFilterValidation describes the validation configuration returned to CelFilterValidation describes the validation configuration returned to clients to help them build safe filter expressions. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------------------- | ----------------- | ------------ | --------------------------------------------------------------- | | `allowed_functions` | repeated `string` | 1 · optional | List of allowed CEL function names for a specific request type. | | `allowed_fields` | repeated `string` | 2 · optional | List of allowed field names accessible to the CEL expression. | | `max_complexity` | `uint32` | 3 · optional | Maximum AST node count permitted for a CEL expression. | # ConsensusStatus > ConsensusStatus represents the minimum consensus level a resource has ConsensusStatus represents the minimum consensus level a resource has achieved when returned by the service. **Package:** `thru.common.v1` ## Values | Name | # | Description | | ----------------------------------- | - | -------------------------------------------------------------------------------------------------------------------------------------------- | | `CONSENSUS_STATUS_UNSPECIFIED` | 0 | CONSENSUS\_STATUS\_UNSPECIFIED indicates the consensus status is unknown. | | `CONSENSUS_STATUS_OBSERVED` | 1 | CONSENSUS\_STATUS\_OBSERVED indicates the resource has been observed but not yet confirmed in a finalized block. | | `CONSENSUS_STATUS_INCLUDED` | 2 | CONSENSUS\_STATUS\_INCLUDED indicates the resource has been included in the ledger but may not be finalized. | | `CONSENSUS_STATUS_FINALIZED` | 3 | CONSENSUS\_STATUS\_FINALIZED indicates the resource is finalized for a slot. | | `CONSENSUS_STATUS_LOCALLY_EXECUTED` | 4 | CONSENSUS\_STATUS\_LOCALLY\_EXECUTED indicates the local node executed the resource but broader cluster finality may not have been achieved. | | `CONSENSUS_STATUS_CLUSTER_EXECUTED` | 5 | CONSENSUS\_STATUS\_CLUSTER\_EXECUTED indicates the entire cluster has executed and agreed on the resource. | # CurrentOrHistoricalVersion > CurrentOrHistoricalVersion is an empty marker message for VersionContext.current. CurrentOrHistoricalVersion is an empty marker message for VersionContext.current. **Package:** `thru.common.v1` ## Fields *No fields.* # CurrentVersion > CurrentVersion is an empty marker message for VersionContext.current. CurrentVersion is an empty marker message for VersionContext.current. **Package:** `thru.common.v1` ## Fields *No fields.* # ErrorDetail > ErrorDetail provides structured error metadata attached to gRPC errors. ErrorDetail provides structured error metadata attached to gRPC errors. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------------------------ | ------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------- | | `error_type` | [`ErrorType`](/docs/api-ref/grpc/messages/thru/common/v1/error-type/) | 1 · optional | High-level error category. | | `message` | `string` | 2 · optional | Human-readable error summary. | | `retention_window_slots` | `uint64` | 3 · optional | Optional retention window expressed in slots when applicable. | | `retention_cutoff` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 4 · optional | Optional retention cutoff timestamp when applicable. | | `field_name` | `string` | 5 · optional | Optional name of the field associated with the error. | # ErrorType > ErrorType captures structured error categories returned by the services. ErrorType captures structured error categories returned by the services. **Package:** `thru.common.v1` ## Values | Name | # | Description | | -------------------------------------- | - | ---------------------------------------------------------------------------------------- | | `ERROR_TYPE_UNSPECIFIED` | 0 | ERROR\_TYPE\_UNSPECIFIED is an unknown error condition. | | `ERROR_TYPE_NOT_FOUND` | 1 | ERROR\_TYPE\_NOT\_FOUND indicates requested data does not exist. | | `ERROR_TYPE_NOT_RETAINED` | 2 | ERROR\_TYPE\_NOT\_RETAINED indicates requested data was not retained under TTL. | | `ERROR_TYPE_NOT_FOUND_OR_NOT_RETAINED` | 3 | ERROR\_TYPE\_NOT\_FOUND\_OR\_NOT\_RETAINED indicates an ambiguous not found vs TTL case. | | `ERROR_TYPE_INVALID_REQUEST` | 4 | ERROR\_TYPE\_INVALID\_REQUEST indicates the request failed validation. | | `ERROR_TYPE_CEL_VALIDATION_FAILED` | 5 | ERROR\_TYPE\_CEL\_VALIDATION\_FAILED indicates a CEL filter failed validation. | | `ERROR_TYPE_INTERNAL_ERROR` | 6 | ERROR\_TYPE\_INTERNAL\_ERROR indicates an internal server error occurred. | # Filter > Filter represents a CEL-based expression applied to query or stream results. Filter represents a CEL-based expression applied to query or stream results. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------------ | -------------------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------- | | `expression` | `string` | 1 · optional | CEL expression applied server-side. Empty expressions are treated as no-op. | | `params` | map\ | 2 · optional | Named parameter bindings for expression parameterization. | # FilterParamValue > FilterParamValue carries strongly-typed CEL parameter bindings. FilterParamValue carries strongly-typed CEL parameter bindings. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | -------------------- | ------------------------------------------------------------------------- | ------------- | ----------- | | `string_value` | `string` | 1 · optional | | | `bytes_value` | `bytes` | 2 · optional | | | `bool_value` | `bool` | 3 · optional | | | `int_value` | `sint64` | 4 · optional | | | `uint_value` | `uint64` | 5 · optional | | | `double_value` | `double` | 6 · optional | | | `pubkey_value` | [`Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 7 · optional | | | `signature_value` | [`Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 8 · optional | | | `ta_pubkey_value` | [`TaPubkey`](/docs/api-ref/grpc/messages/thru/common/v1/ta-pubkey/) | 9 · optional | | | `ts_signature_value` | [`TsSignature`](/docs/api-ref/grpc/messages/thru/common/v1/ts-signature/) | 10 · optional | | | `bytes_list_value` | [`BytesList`](/docs/api-ref/grpc/messages/thru/common/v1/bytes-list/) | 11 · optional | | | `pubkey_list_value` | [`PubkeyList`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey-list/) | 12 · optional | | # PageRequest > PageRequest contains pagination parameters for listing RPCs. PageRequest contains pagination parameters for listing RPCs. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------------ | -------- | ------------ | ------------------------------------------------------------- | | `page_size` | `uint32` | 1 · optional | Maximum number of items to return in a single response. | | `page_token` | `string` | 2 · optional | Token identifying the position to resume from. | | `order_by` | `string` | 3 · optional | Optional ordering specification in “field \[asc\|desc]” form. | # PageResponse > PageResponse captures pagination metadata returned with list results. PageResponse captures pagination metadata returned with list results. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ----------------- | -------- | ------------ | --------------------------------------------------- | | `next_page_token` | `string` | 1 · optional | Token to retrieve the next page of results, if any. | | `total_size` | `uint64` | 2 · optional | Total number of items available when known. | # Pubkey > Pubkey represents a 32-byte public key value. Pubkey represents a 32-byte public key value. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------- | ------- | ------------ | -------------------------- | | `value` | `bytes` | 1 · optional | 32-byte public key buffer. | # PubkeyList > PubkeyList holds a list of pubkeys for multi-value CEL filtering. PubkeyList holds a list of pubkeys for multi-value CEL filtering. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | -------- | ----------------------------------------------------------------------- | ------------ | ----------- | | `values` | repeated [`Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | # Signature > Signature represents a 64-byte signature value. Signature represents a 64-byte signature value. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------- | ------- | ------------ | ------------------------- | | `value` | `bytes` | 1 · optional | 64-byte signature buffer. | # TaPubkey > TaPubkey represents a ta-encoded public key string (46 characters). TaPubkey represents a ta-encoded public key string (46 characters). **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------- | -------- | ------------ | ------------------------------------------ | | `value` | `string` | 1 · optional | 46-character ta-encoded public key string. | # TsSignature > TsSignature represents a ts-encoded signature string (90 characters). TsSignature represents a ts-encoded signature string (90 characters). **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | ------- | -------- | ------------ | ----------------------------------------- | | `value` | `string` | 1 · optional | 90-character ts-encoded signature string. | # VersionContext > VersionContext selects which logical version of a resource should be VersionContext selects which logical version of a resource should be returned. At least one field must be set. **Package:** `thru.common.v1` ## Fields | Field | Type | # | Description | | --------------------- | --------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------- | | `current` | [`CurrentVersion`](/docs/api-ref/grpc/messages/thru/common/v1/current-version/) | 1 · optional | Request the latest version available at request time. | | `currentOrHistorical` | [`CurrentOrHistoricalVersion`](/docs/api-ref/grpc/messages/thru/common/v1/current-or-historical-version/) | 2 · optional | Request the latest version, or historical on. | | `slot` | `uint64` | 3 · optional | Request the version for a specific slot number. | | `timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 4 · optional | Request the version nearest to the provided block timestamp. | | `seq` | `uint64` | 5 · optional | Request the version for a specific seq number. Relevant only for GetAccount and GetRawAccount requests. | # Account > Account models a fully decoded account resource. Account models a fully decoded account resource. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------ | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `meta` | [`AccountMeta`](/docs/api-ref/grpc/messages/thru/core/v1/account-meta/) | 2 · optional | | | `data` | [`AccountData`](/docs/api-ref/grpc/messages/thru/core/v1/account-data/) | 3 · optional | | | `version_context` | [`VersionContextMetadata`](/docs/api-ref/grpc/messages/thru/core/v1/version-context-metadata/) | 4 · optional | | | `consensus_status` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 5 · optional | | # AccountData > AccountData contains account data payloads. AccountData contains account data payloads. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------------------- | -------- | ------------ | ----------- | | `data` | `bytes` | 1 · optional | | | `compressed` | `bool` | 2 · optional | | | `compression_algorithm` | `string` | 3 · optional | | # AccountFlags > AccountFlags enumerates boolean account capability flags. AccountFlags enumerates boolean account capability flags. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------- | ------ | ------------ | ----------- | | `is_program` | `bool` | 1 · optional | | | `is_privileged` | `bool` | 2 · optional | | | `is_uncompressable` | `bool` | 3 · optional | | | `is_ephemeral` | `bool` | 4 · optional | | | `is_deleted` | `bool` | 5 · optional | | | `is_new` | `bool` | 6 · optional | | | `is_compressed` | `bool` | 7 · optional | | # AccountMeta > AccountMeta captures metadata associated with an account. AccountMeta captures metadata associated with an account. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------- | ----------------------------------------------------------------------------- | ------------ | ------------------------------------------------ | | `version` | `uint32` | 1 · optional | | | `flags` | [`AccountFlags`](/docs/api-ref/grpc/messages/thru/core/v1/account-flags/) | 2 · optional | | | `data_size` | `uint32` | 3 · optional | | | `seq` | `uint64` | 4 · optional | Account sequence number | | `owner` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 5 · optional | | | `balance` | `uint64` | 6 · optional | | | `nonce` | `uint64` | 7 · optional | | | `last_updated_slot` | `uint64` | 8 · optional | The slot in which this account was last updated. | # AccountPage > AccountPage represents a 4KB chunk for streaming account data. AccountPage represents a 4KB chunk for streaming account data. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------------------- | -------- | ------------ | ----------- | | `page_idx` | `uint32` | 1 · optional | | | `page_size` | `uint32` | 2 · optional | | | `page_data` | `bytes` | 3 · optional | | | `compressed` | `bool` | 4 · optional | | | `compression_algorithm` | `string` | 5 · optional | | # AccountView > AccountView controls which sections of account resources are returned. AccountView controls which sections of account resources are returned. **Package:** `thru.core.v1` ## Values | Name | # | Description | | -------------------------- | - | ------------------------------------------------------------- | | `ACCOUNT_VIEW_UNSPECIFIED` | 0 | ACCOUNT\_VIEW\_UNSPECIFIED uses service defaults. | | `ACCOUNT_VIEW_PUBKEY_ONLY` | 1 | ACCOUNT\_VIEW\_PUBKEY\_ONLY returns only the account address. | | `ACCOUNT_VIEW_META_ONLY` | 2 | ACCOUNT\_VIEW\_META\_ONLY returns only account metadata. | | `ACCOUNT_VIEW_DATA_ONLY` | 3 | ACCOUNT\_VIEW\_DATA\_ONLY returns only account data. | | `ACCOUNT_VIEW_FULL` | 4 | ACCOUNT\_VIEW\_FULL returns address, metadata, and data. | # Block > Block represents a fully decoded block resource. Block represents a fully decoded block resource. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------ | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `header` | [`BlockHeader`](/docs/api-ref/grpc/messages/thru/core/v1/block-header/) | 1 · optional | | | `footer` | [`BlockFooter`](/docs/api-ref/grpc/messages/thru/core/v1/block-footer/) | 2 · optional | | | `body` | `bytes` | 3 · optional | | | `consensus_status` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # BlockFooter > BlockFooter captures execution result metadata for a block. BlockFooter captures execution result metadata for a block. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------------ | ----------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `status` | [`ExecutionStatus`](/docs/api-ref/grpc/messages/thru/core/v1/execution-status/) | 2 · optional | | | `consumed_compute_units` | `uint64` | 3 · optional | | | `consumed_state_units` | `uint32` | 4 · optional | | | `attestor_payment` | `uint64` | 5 · optional | | # BlockHash > BlockHash represents a 32-byte hash for block identifiers. BlockHash represents a 32-byte hash for block identifiers. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------- | ------- | ------------ | -------------------------- | | `value` | `bytes` | 1 · optional | 32-byte block hash buffer. | # BlockHeader > BlockHeader describes metadata about a block. BlockHeader describes metadata about a block. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | --------------------- | ------------------------------------------------------------------------------------- | ------------- | ----------- | | `slot` | `uint64` | 1 · optional | | | `block_hash` | [`BlockHash`](/docs/api-ref/grpc/messages/thru/core/v1/block-hash/) | 2 · optional | | | `header_signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 3 · optional | | | `version` | `uint32` | 4 · optional | | | `producer` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 5 · optional | | | `expiry_timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 6 · optional | | | `start_slot` | `uint64` | 7 · optional | | | `expiry_after` | `uint32` | 8 · optional | | | `max_block_size` | `uint32` | 9 · optional | | | `max_compute_units` | `uint64` | 10 · optional | | | `max_state_units` | `uint32` | 11 · optional | | | `bond_amount_lock_up` | `uint64` | 12 · optional | | | `block_time` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 13 · optional | | | `weight_slot` | `uint64` | 14 · optional | | | `chain_id` | `uint32` | 15 · optional | | # BlockView > BlockView controls how much of a block resource is returned. BlockView controls how much of a block resource is returned. **Package:** `thru.core.v1` ## Values | Name | # | Description | | ------------------------------ | - | ----------- | | `BLOCK_VIEW_UNSPECIFIED` | 0 | | | `BLOCK_VIEW_HEADER_ONLY` | 1 | | | `BLOCK_VIEW_HEADER_AND_FOOTER` | 2 | | | `BLOCK_VIEW_BODY_ONLY` | 3 | | | `BLOCK_VIEW_FULL` | 4 | | # DataSlice > DataSlice describes a contiguous range of account data to return. DataSlice describes a contiguous range of account data to return. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | -------- | -------- | ------------ | ----------- | | `offset` | `uint32` | 1 · optional | | | `length` | `uint32` | 2 · optional | | # ExecutionStatus > ExecutionStatus enumerates block execution results. ExecutionStatus enumerates block execution results. **Package:** `thru.core.v1` ## Values | Name | # | Description | | ------------------------------ | - | ----------- | | `EXECUTION_STATUS_UNSPECIFIED` | 0 | | | `EXECUTION_STATUS_PENDING` | 1 | | | `EXECUTION_STATUS_EXECUTED` | 2 | | | `EXECUTION_STATUS_FAILED` | 3 | | # Hash > Hash represents a 32-byte hash value. Hash represents a 32-byte hash value. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------- | ------- | ------------ | -------------------- | | `value` | `bytes` | 1 · optional | 32-byte hash buffer. | # NodeContact > NodeContact represents a network contact address for a node. NodeContact represents a network contact address for a node. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ---------- | -------- | ------------ | ---------------------------------------------------- | | `ip4_addr` | `uint32` | 1 · optional | IPv4 address as 32-bit integer (network byte order). | | `port` | `uint32` | 2 · optional | Port number. | # NodeProtocol > NodeProtocol represents a protocol supported by a node contact. NodeProtocol represents a protocol supported by a node contact. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------- | | `contact_idx` | `uint32` | 1 · optional | Index into the contacts array. | | `protocol_id` | `uint32` | 2 · optional | Protocol identifier (discovery=1, block\_dissem=2, consensus=3, snapshot=4, repair=5, transaction=6). | # NodeRecord > NodeRecord represents a node's signed identity and network information. NodeRecord represents a node’s signed identity and network information. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------- | ---------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------- | | `version` | `uint32` | 1 · optional | Record version. | | `chain_id` | `uint32` | 2 · optional | Chain identifier. | | `seqnum` | `uint64` | 3 · optional | Sequence number (incremented on each record update). | | `pubkey` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 4 · optional | Node’s ED25519 public key. | | `contacts` | repeated [`NodeContact`](/docs/api-ref/grpc/messages/thru/core/v1/node-contact/) | 5 · optional | Network contact addresses. | | `protocols` | repeated [`NodeProtocol`](/docs/api-ref/grpc/messages/thru/core/v1/node-protocol/) | 6 · optional | Protocols supported at each contact. | | `signature` | `bytes` | 7 · optional | ED25519 signature over the record. | | `is_own` | `bool` | 8 · optional | True if this is the responding node’s own record. | # RawAccount > RawAccount captures raw serialized account bytes. RawAccount captures raw serialized account bytes. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------------- | ---------------------------------------------------------------------------------------------- | ------------ | ----------- | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `raw_meta` | `bytes` | 2 · optional | | | `raw_data` | `bytes` | 3 · optional | | | `version_context` | [`VersionContextMetadata`](/docs/api-ref/grpc/messages/thru/core/v1/version-context-metadata/) | 4 · optional | | # RawBlock > RawBlock captures raw block bytes for direct access. RawBlock captures raw block bytes for direct access. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------- | -------- | ------------ | ----------- | | `slot` | `uint64` | 1 · optional | | | `raw_block` | `bytes` | 2 · optional | | # RawTransaction > RawTransaction provides direct access to serialized transaction bytes. RawTransaction provides direct access to serialized transaction bytes. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------------- | ----------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `raw_transaction` | `bytes` | 2 · optional | | # StateProof > StateProof returns binary proof data along with context. StateProof returns binary proof data along with context. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------- | -------- | ------------ | ----------- | | `proof` | `bytes` | 1 · optional | | | `slot` | `uint64` | 2 · optional | | # StateProofRequest > StateProofRequest describes a request to generate an account state proof. StateProofRequest describes a request to generate an account state proof. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------- | ------------------------------------------------------------------------------ | ------------ | ----------- | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `proof_type` | [`StateProofType`](/docs/api-ref/grpc/messages/thru/core/v1/state-proof-type/) | 2 · optional | | | `target_slot` | `uint64` | 3 · optional | | # StateProofType > StateProofType selects the type of state proof to generate. StateProofType selects the type of state proof to generate. **Package:** `thru.core.v1` ## Values | Name | # | Description | | ------------------------------ | - | ----------- | | `STATE_PROOF_TYPE_UNSPECIFIED` | 0 | | | `STATE_PROOF_TYPE_CREATING` | 1 | | | `STATE_PROOF_TYPE_UPDATING` | 2 | | | `STATE_PROOF_TYPE_EXISTING` | 3 | | # Transaction > Transaction describes a fully decoded transaction resource. Transaction describes a fully decoded transaction resource. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------ | ------------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `header` | [`TransactionHeader`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-header/) | 2 · optional | | | `body` | `bytes` | 3 · optional | | | `execution_result` | [`TransactionExecutionResult`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-execution-result/) | 4 · optional | | | `slot` | `uint64` | 5 · optional | | | `block_offset` | `uint32` | 6 · optional | | # TransactionEvent > TransactionEvent describes an event emitted during transaction execution. TransactionEvent describes an event emitted during transaction execution. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------- | ----------------------------------------------------------------------------- | ------------ | ----------- | | `event_id` | `string` | 1 · optional | | | `call_idx` | `uint32` | 2 · optional | | | `program_idx` | `uint32` | 3 · optional | | | `program` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 4 · optional | | | `payload` | `bytes` | 5 · optional | | # TransactionExecutionResult > TransactionExecutionResult captures execution outcomes. TransactionExecutionResult captures execution outcomes. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ------------------------ | ------------------------------------------------------------------------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `consumed_compute_units` | `uint32` | 1 · optional | | | `consumed_memory_units` | `uint32` | 2 · optional | | | `consumed_state_units` | `uint32` | 3 · optional | | | `user_error_code` | `uint64` | 4 · optional | | | `vm_error` | [`TransactionVmError`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-vm-error/) | 5 · optional | | | `execution_result` | `uint64` | 6 · optional | | | `pages_used` | `uint32` | 7 · optional | | | `events_count` | `uint32` | 8 · optional | | | `events_size` | `uint32` | 9 · optional | | | `readwrite_accounts` | repeated [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 10 · optional | | | `readonly_accounts` | repeated [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 11 · optional | | | `events` | repeated [`TransactionEvent`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-event/) | 12 · optional | | | `error_program_acc_idx` | `uint32` | 13 · optional | Account index of the program that caused the error (revert, fault, etc.). Only meaningful when vm\_error indicates a failure. | # TransactionHeader > TransactionHeader carries structured metadata for a transaction. TransactionHeader carries structured metadata for a transaction. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | -------------------------- | ----------------------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------- | | `fee_payer_signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `version` | `uint32` | 2 · optional | | | `flags` | `uint32` | 3 · optional | | | `readwrite_accounts_count` | `uint32` | 4 · optional | | | `readonly_accounts_count` | `uint32` | 5 · optional | | | `instruction_data_size` | `uint32` | 6 · optional | | | `requested_compute_units` | `uint32` | 7 · optional | | | `requested_state_units` | `uint32` | 8 · optional | | | `requested_memory_units` | `uint32` | 9 · optional | | | `expiry_after` | `uint32` | 10 · optional | | | `fee` | `uint64` | 11 · optional | | | `nonce` | `uint64` | 12 · optional | | | `start_slot` | `uint64` | 13 · optional | | | `fee_payer_pubkey` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 14 · optional | | | `program_pubkey` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 15 · optional | | | `chain_id` | `uint32` | 16 · optional | Chain identifier to prevent transaction replay across different chains. | # TransactionView > TransactionView controls how transactions are materialized in responses. TransactionView controls how transactions are materialized in responses. **Package:** `thru.core.v1` ## Values | Name | # | Description | | ---------------------------------- | - | ----------- | | `TRANSACTION_VIEW_UNSPECIFIED` | 0 | | | `TRANSACTION_VIEW_SIGNATURE_ONLY` | 1 | | | `TRANSACTION_VIEW_HEADER_ONLY` | 2 | | | `TRANSACTION_VIEW_HEADER_AND_BODY` | 3 | | | `TRANSACTION_VIEW_FULL` | 4 | | # TransactionVmError > TransactionVmError enumerates runtime error codes returned by the executor. TransactionVmError enumerates runtime error codes returned by the executor. **Package:** `thru.core.v1` ## Values | Name | # | Description | | ------------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------------- | | `TRANSACTION_VM_EXECUTE_SUCCESS` | 0 | TN\_RUNTIME\_TXN\_EXECUTE\_SUCCESS | | `TRANSACTION_VM_ERROR_INVALID_FORMAT` | -255 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FORMAT | | `TRANSACTION_VM_ERROR_INVALID_VERSION` | -254 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_VERSION | | `TRANSACTION_VM_ERROR_INVALID_FLAGS` | -253 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FLAGS | | `TRANSACTION_VM_ERROR_INVALID_SIGNATURE` | -252 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_SIGNATURE | | `TRANSACTION_VM_ERROR_DUPLICATE_ACCOUNT` | -251 | TN\_RUNTIME\_TXN\_ERR\_DUPLICATE\_ACCOUNT | | `TRANSACTION_VM_ERROR_UNSORTED_ACCOUNTS` | -250 | TN\_RUNTIME\_TXN\_ERR\_UNSORTED\_ACCOUNTS | | `TRANSACTION_VM_ERROR_UNSORTED_READWRITE_ACCOUNTS` | -249 | TN\_RUNTIME\_TXN\_ERR\_UNSORTED\_READWRITE\_ACCOUNTS | | `TRANSACTION_VM_ERROR_UNSORTED_READONLY_ACCOUNTS` | -248 | TN\_RUNTIME\_TXN\_ERR\_UNSORTED\_READONLY\_ACCOUNTS | | `TRANSACTION_VM_ERROR_ACCOUNT_COUNT_LIMIT_EXCEEDED` | -247 | TN\_RUNTIME\_TXN\_ERR\_ACCOUNT\_COUNT\_LIMIT\_EXCEEDED | | `TRANSACTION_VM_ERROR_NONCE_TOO_LOW` | -511 | TN\_RUNTIME\_TXN\_ERR\_NONCE\_TOO\_LOW | | `TRANSACTION_VM_ERROR_NONCE_TOO_HIGH` | -510 | TN\_RUNTIME\_TXN\_ERR\_NONCE\_TOO\_HIGH | | `TRANSACTION_VM_ERROR_INSUFFICIENT_FEE_PAYER_BALANCE` | -509 | TN\_RUNTIME\_TXN\_ERR\_INSUFFICIENT\_FEE\_PAYER\_BALANCE | | `TRANSACTION_VM_ERROR_FEE_PAYER_ACCOUNT_DOES_NOT_EXIST` | -508 | TN\_RUNTIME\_TXN\_ERR\_FEE\_PAYER\_ACCOUNT\_DOES\_NOT\_EXIST | | `TRANSACTION_VM_ERROR_NOT_LIVE_YET` | -507 | TN\_RUNTIME\_TXN\_ERR\_NOT\_LIVE\_YET | | `TRANSACTION_VM_ERROR_EXPIRED` | -506 | TN\_RUNTIME\_TXN\_ERR\_EXPIRED | | `TRANSACTION_VM_ERROR_INVALID_FEE_PAYER_STATE_PROOF` | -505 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FEE\_PAYER\_STATE\_PROOF | | `TRANSACTION_VM_ERROR_INVALID_FEE_PAYER_STATE_PROOF_TYPE` | -504 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FEE\_PAYER\_STATE\_PROOF\_TYPE | | `TRANSACTION_VM_ERROR_INVALID_FEE_PAYER_STATE_PROOF_SLOT` | -503 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FEE\_PAYER\_STATE\_PROOF\_SLOT | | `TRANSACTION_VM_ERROR_INVALID_FEE_PAYER_ACCOUNT_OWNER` | -502 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FEE\_PAYER\_ACCOUNT\_OWNER | | `TRANSACTION_VM_ERROR_INVALID_FEE_PAYER_STATE_PROOF_ACCOUNT_OWNER` | -501 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FEE\_PAYER\_STATE\_PROOF\_ACCOUNT\_OWNER | | `TRANSACTION_VM_ERROR_INVALID_FEE_PAYER_STATE_PROOF_ACCOUNT_META_DATA_SZ` | -500 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_FEE\_PAYER\_STATE\_PROOF\_ACCOUNT\_META\_DATA\_SZ | | `TRANSACTION_VM_ERROR_VM_FAILED` | -767 | TN\_RUNTIME\_TXN\_ERR\_VM\_FAILED | | `TRANSACTION_VM_ERROR_INVALID_PROGRAM_ACCOUNT` | -766 | TN\_RUNTIME\_TXN\_ERR\_INVALID\_PROGRAM\_ACCOUNT | | `TRANSACTION_VM_ERROR_VM_REVERT` | -765 | TN\_RUNTIME\_TXN\_ERR\_VM\_REVERT | | `TRANSACTION_VM_ERROR_CU_EXHAUSTED` | -764 | TN\_RUNTIME\_TXN\_ERR\_CU\_EXHAUSTED | | `TRANSACTION_VM_ERROR_SU_EXHAUSTED` | -763 | TN\_RUNTIME\_TXN\_ERR\_SU\_EXHAUSTED | # VersionContextMetadata > VersionContextMetadata captures context for a returned account state. VersionContextMetadata captures context for a returned account state. **Package:** `thru.core.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------- | ------------ | ----------- | | `slot` | `uint64` | 1 · optional | | | `block_timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 2 · optional | | # AccountSnapshot > AccountSnapshot represents the state of an account at a point in time. AccountSnapshot represents the state of an account at a point in time. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------- | ------------------------------------------------------------------------------------ | ------------ | ----------- | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `meta` | [`thru.core.v1.AccountMeta`](/docs/api-ref/grpc/messages/thru/core/v1/account-meta/) | 2 · optional | | | `data` | `bytes` | 3 · optional | | | `exists` | `bool` | 4 · optional | | # AccountUpdate > AccountUpdate describes a delta for an account. AccountUpdate describes a delta for an account. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------- | ------------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------- | | `slot` | `uint64` | 1 · optional | | | `page` | [`thru.core.v1.AccountPage`](/docs/api-ref/grpc/messages/thru/core/v1/account-page/) | 2 · optional | | | `meta` | [`thru.core.v1.AccountMeta`](/docs/api-ref/grpc/messages/thru/core/v1/account-meta/) | 3 · optional | | | `delete` | `bool` | 4 · optional | | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 5 · optional | Account address for identifying the account in multi-account streams. | # ActiveStateHashEntry > ActiveStateHashEntry represents an active state hash for a specific slot. ActiveStateHashEntry represents an active state hash for a specific slot. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------- | -------- | ------------ | ----------- | | `slot` | `uint64` | 1 · optional | | | `active_state_hash` | `bytes` | 2 · optional | | # BatchSendTransactionsRequest > BatchSendTransactionsRequest submits multiple transactions to the cluster. BatchSendTransactionsRequest submits multiple transactions to the cluster. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------ | ---------------- | ------------ | ------------------------------------------------------------------------------ | | `raw_transactions` | repeated `bytes` | 1 · optional | List of raw transaction bytes encoded according to chain specification. | | `num_retries` | `int32` | 2 · optional | Number of retries for each transaction if not accepted by UDS (defaults to 0). | # BatchSendTransactionsResponse > BatchSendTransactionsResponse returns submission results for each transaction. BatchSendTransactionsResponse returns submission results for each transaction. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------ | -------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------ | | `signatures` | repeated [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | Signatures for each transaction (in same order as request). | | `accepted` | repeated `bool` | 2 · optional | Acceptance status for each transaction (true if accepted, false if not). | # BlockFinished > BlockFinished is sent when block's execution is complete. BlockFinished is sent when block’s execution is complete. Contains block accumulator metrics finalized after execution. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ | | `slot` | `uint64` | 1 · optional | | | `global_activated_state_counter` | `uint64` | 2 · optional | Global counter tracking total activated state across all accounts. Increments when accounts are created or modified. | | `global_deactivated_state_counter` | `uint64` | 3 · optional | Global counter tracking total deactivated state across all accounts. Increments when accounts are deleted or compressed. | | `collected_fees` | `uint64` | 4 · optional | Total fees collected from all transactions in this block. | # CallFrame > CallFrame represents a single shadow stack frame from the VM. CallFrame represents a single shadow stack frame from the VM. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------- | ----------------- | ------------ | ------------------------------------------------- | | `program_acc_idx` | `uint32` | 1 · optional | | | `program_counter` | `uint64` | 2 · optional | | | `stack_pointer` | `uint64` | 3 · optional | | | `saved_registers` | repeated `uint64` | 4 · optional | | | `stack_window` | `bytes` | 5 · optional | Up to 1024 bytes above sp | | `stack_window_base` | `uint64` | 6 · optional | Virtual address of first byte (== stack\_pointer) | # DebugReExecuteRequest > DebugReExecuteRequest is a request to re-execute a transaction in simulated environment. DebugReExecuteRequest is a request to re-execute a transaction in simulated environment. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------------------- | ----------------------------------------------------------------------------------- | ------------ | ------------------------------------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `include_state_before` | `bool` | 2 · optional | | | `include_state_after` | `bool` | 3 · optional | | | `include_account_data` | `bool` | 4 · optional | | | `include_memory_dump` | `bool` | 5 · optional | Return all allocated stack+heap pages | # DebugReExecuteResponse > DebugReExecuteResponse is a response from the debug service. DebugReExecuteResponse is a response from the debug service. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------- | -------------------------------------------------------------------------------------------- | ------------ | ----------- | | `transaction` | [`thru.core.v1.Transaction`](/docs/api-ref/grpc/messages/thru/core/v1/transaction/) | 1 · optional | | | `stdout` | `string` | 2 · optional | | | `log` | `string` | 3 · optional | | | `trace` | `string` | 4 · optional | | | `execution_details` | [`VmExecutionDetails`](/docs/api-ref/grpc/messages/thru/services/v1/vm-execution-details/) | 5 · optional | | | `state_before` | repeated [`AccountSnapshot`](/docs/api-ref/grpc/messages/thru/services/v1/account-snapshot/) | 6 · optional | | | `state_after` | repeated [`AccountSnapshot`](/docs/api-ref/grpc/messages/thru/services/v1/account-snapshot/) | 7 · optional | | | `memory_segments` | repeated [`MemorySegment`](/docs/api-ref/grpc/messages/thru/services/v1/memory-segment/) | 8 · optional | | # Event > Event represents a transaction event emitted by the chain. Event represents a transaction event emitted by the chain. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------------- | ------------------------------------------------------------------------------------- | ------------- | ----------- | | `event_id` | `string` | 1 · optional | | | `transaction_signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 2 · optional | | | `program` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 3 · optional | | | `payload` | `bytes` | 4 · optional | | | `slot` | `uint64` | 5 · optional | | | `call_idx` | `uint32` | 6 · optional | | | `program_idx` | `uint32` | 7 · optional | | | `payload_size` | `uint32` | 8 · optional | | | `block_offset` | `uint32` | 9 · optional | | | `timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 10 · optional | | | `fee_payer` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 11 · optional | | # GenerateStateProofRequest > GenerateStateProofRequest requests an account state proof. GenerateStateProofRequest requests an account state proof. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------- | ------------------------------------------------------------------------------------------------- | ------------ | ----------- | | `request` | [`thru.core.v1.StateProofRequest`](/docs/api-ref/grpc/messages/thru/core/v1/state-proof-request/) | 1 · optional | | # GenerateStateProofResponse > GenerateStateProofResponse contains the generated proof. GenerateStateProofResponse contains the generated proof. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------- | ---------------------------------------------------------------------------------- | ------------ | ----------- | | `proof` | [`thru.core.v1.StateProof`](/docs/api-ref/grpc/messages/thru/core/v1/state-proof/) | 1 · optional | | # GetAccountRequest > GetAccountRequest retrieves a decoded account by public key. GetAccountRequest retrieves a decoded account by public key. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `view` | [`thru.core.v1.AccountView`](/docs/api-ref/grpc/messages/thru/core/v1/account-view/) | 2 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | | `data_slice` | [`thru.core.v1.DataSlice`](/docs/api-ref/grpc/messages/thru/core/v1/data-slice/) | 5 · optional | | # GetActiveStateHashesRequest > GetActiveStateHashesRequest retrieves active state hashes for a range of slots. GetActiveStateHashesRequest retrieves active state hashes for a range of slots. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------ | -------- | ------------ | ------------------------------------------------------------------------------------------ | | `end_slot` | `uint64` | 1 · optional | The upper bound slot (inclusive). If not specified, defaults to the latest finalized slot. | | `start_slot` | `uint64` | 2 · optional | The lower bound slot (inclusive). If not specified, defaults to max(0, end\_slot - 127). | # GetActiveStateHashesResponse > GetActiveStateHashesResponse contains active state hashes ending at the requested slot. GetActiveStateHashesResponse contains active state hashes ending at the requested slot. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------------- | -------------------------------------------------------------------------------------------------------- | ------------ | ----------- | | `active_state_hashes` | repeated [`ActiveStateHashEntry`](/docs/api-ref/grpc/messages/thru/services/v1/active-state-hash-entry/) | 1 · optional | | # GetBlockRequest > GetBlockRequest retrieves decoded block information by slot or hash. GetBlockRequest retrieves decoded block information by slot or hash. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `slot` | `uint64` | 1 · optional | | | `block_hash` | [`thru.core.v1.BlockHash`](/docs/api-ref/grpc/messages/thru/core/v1/block-hash/) | 2 · optional | | | `view` | [`thru.core.v1.BlockView`](/docs/api-ref/grpc/messages/thru/core/v1/block-view/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # GetChainInfoRequest > GetChainInfoRequest retrieves chain-level information. GetChainInfoRequest retrieves chain-level information. **Package:** `thru.services.v1` ## Fields *No fields.* # GetChainInfoResponse > GetChainInfoResponse returns chain-level information. GetChainInfoResponse returns chain-level information. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------- | | `chain_id` | `uint32` | 1 · optional | Chain identifier, unique per network (similar to Ethereum chain ID). Used to prevent transaction replay across different chains. | | `min_lock_time` | `uint64` | 2 · optional | Minimum timestamp (nanoseconds since epoch) at which 4f+1 validator weight has reported advancement. Zero if not yet available. | # GetEventRequest > GetEventRequest fetches an event by identifier. GetEventRequest fetches an event by identifier. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ---------------------------------------------------------------------------------------------- | ------------ | ----------- | | `event_id` | `string` | 1 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 2 · optional | | # GetHeightRequest > gRPC message thru.services.v1.GetHeightRequest **Package:** `thru.services.v1` ## Fields *No fields.* # GetHeightResponse > GetHeightResponse GetHeightResponse **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------ | -------- | ------------ | ----------- | | `finalized` | `uint64` | 1 · optional | | | `locally_executed` | `uint64` | 2 · optional | | | `cluster_executed` | `uint64` | 3 · optional | | # GetNodePubkeyRequest > GetNodePubkeyRequest requests the node's own public key. GetNodePubkeyRequest requests the node’s own public key. **Package:** `thru.services.v1` ## Fields *No fields.* # GetNodePubkeyResponse > GetNodePubkeyResponse returns the node's ED25519 public key. GetNodePubkeyResponse returns the node’s ED25519 public key. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------- | ----------------------------------------------------------------------------- | ------------ | ----------- | | `pubkey` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | # GetNodeRecordsRequest > GetNodeRecordsRequest requests all known node records. GetNodeRecordsRequest requests all known node records. **Package:** `thru.services.v1` ## Fields *No fields.* # GetNodeRecordsResponse > GetNodeRecordsResponse returns all known node records from the gossip network. GetNodeRecordsResponse returns all known node records from the gossip network. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------- | ------------------------------------------------------------------------------------------- | ------------ | ----------- | | `records` | repeated [`thru.core.v1.NodeRecord`](/docs/api-ref/grpc/messages/thru/core/v1/node-record/) | 1 · optional | | # GetNodeStatusRequest > GetNodeStatusRequest requests the node's operational status. GetNodeStatusRequest requests the node’s operational status. **Package:** `thru.services.v1` ## Fields *No fields.* # GetNodeStatusResponse > GetNodeStatusResponse returns the node's operational status including GetNodeStatusResponse returns the node’s operational status including consensus readiness, repair state, and current heights. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------------- | -------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------ | | `ready` | `bool` | 1 · optional | High-level readiness flag. True when consensus is active and the node is ready to accept and process transactions. | | `consensus` | [`NodeConsensusStatus`](/docs/api-ref/grpc/messages/thru/services/v1/node-consensus-status/) | 2 · optional | Consensus subsystem state. | | `repair` | [`NodeRepairStatus`](/docs/api-ref/grpc/messages/thru/services/v1/node-repair-status/) | 3 · optional | Repair subsystem state. | | `finalized_slot` | `uint64` | 4 · optional | Latest finalized slot. | | `locally_executed_slot` | `uint64` | 5 · optional | Latest locally executed slot. | # GetRawAccountRequest > GetRawAccountRequest retrieves raw account bytes by public key. GetRawAccountRequest retrieves raw account bytes by public key. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `address` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `view` | [`thru.core.v1.AccountView`](/docs/api-ref/grpc/messages/thru/core/v1/account-view/) | 2 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # GetRawBlockRequest > GetRawBlockRequest retrieves raw block bytes by slot or hash. GetRawBlockRequest retrieves raw block bytes by slot or hash. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `slot` | `uint64` | 1 · optional | | | `block_hash` | [`thru.core.v1.BlockHash`](/docs/api-ref/grpc/messages/thru/core/v1/block-hash/) | 2 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 3 · optional | | # GetRawTransactionRequest > GetRawTransactionRequest retrieves raw transaction bytes by signature. GetRawTransactionRequest retrieves raw transaction bytes by signature. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 2 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 3 · optional | | # GetSlotMetricsRequest > GetSlotMetricsRequest retrieves metrics for a specific slot. GetSlotMetricsRequest retrieves metrics for a specific slot. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------ | -------- | ------------ | ------------------------------------------ | | `slot` | `uint64` | 1 · optional | Block slot number to retrieve metrics for. | # GetSlotMetricsResponse > GetSlotMetricsResponse contains slot-level metrics. GetSlotMetricsResponse contains slot-level metrics. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------------------------------- | ------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------- | | `slot` | `uint64` | 1 · optional | Block slot number. | | `global_activated_state_counter` | `uint64` | 2 · optional | Global counter tracking total activated state across all accounts. | | `global_deactivated_state_counter` | `uint64` | 3 · optional | Global counter tracking total deactivated state across all accounts. | | `collected_fees` | `uint64` | 4 · optional | Total fees collected from all transactions in this block. | | `block_timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 5 · optional | Block timestamp when available. | # GetStateRootsRequest > GetStateRootsRequest retrieves up to 257 state roots ending at a specified slot. GetStateRootsRequest retrieves up to 257 state roots ending at a specified slot. Used for transaction replay to verify state proofs against historical roots. Returns state roots in ascending slot order, from (slot - 256) to slot (inclusive). **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------ | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `slot` | `uint64` | 1 · optional | The slot to retrieve state roots up to (inclusive). Returns up to 257 state roots ending at this slot. If not specified, returns state roots ending at the latest available slot. | # GetStateRootsResponse > GetStateRootsResponse contains up to 257 state roots ending at the requested slot. GetStateRootsResponse contains up to 257 state roots ending at the requested slot. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------- | ------------------------------------------------------------------------------------------- | ------------ | ----------- | | `state_roots` | repeated [`StateRootEntry`](/docs/api-ref/grpc/messages/thru/services/v1/state-root-entry/) | 1 · optional | | # GetTransactionRequest > GetTransactionRequest retrieves a decoded transaction by signature. GetTransactionRequest retrieves a decoded transaction by signature. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `view` | [`thru.core.v1.TransactionView`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-view/) | 2 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # GetTransactionStatusRequest > GetTransactionStatusRequest fetches execution status for a transaction. GetTransactionStatusRequest fetches execution status for a transaction. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------- | ----------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | # GetVersionRequest > GetVersionRequest fetches component version strings. GetVersionRequest fetches component version strings. **Package:** `thru.services.v1` ## Fields *No fields.* # GetVersionResponse > GetVersionResponse returns version information per component. GetVersionResponse returns version information per component. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------- | ---------------------- | ------------ | ----------- | | `versions` | map\ | 1 · optional | | # ListAccountsRequest > ListAccountsRequest lists accounts using CEL filters. ListAccountsRequest lists accounts using CEL filters. The filter expression supports filtering on account metadata fields using CEL (Common Expression Language). A filter expression is REQUIRED for all ListAccounts requests. Available fields for filtering: * account.address.value (bytes): The account’s public key address * account.meta.owner.value (bytes): The account owner’s public key * account.meta.balance (uint64): Account balance in native units * account.meta.seq (uint64): Account sequence number / state counter * account.meta.nonce (uint64): Account transaction nonce * account.meta.data\_size (uint32): Size of account data in bytes * account.meta.version (uint32): Account version number * account.meta.flags (AccountFlags): Account capability flags (message type) * account.meta.flags.is\_program (bool): Flag indicating if account is a program * account.meta.flags.is\_privileged (bool): Flag indicating if account is privileged * account.meta.flags.is\_uncompressable (bool): Flag indicating if account data cannot be compressed * account.meta.flags.is\_ephemeral (bool): Flag indicating if account is ephemeral * account.meta.flags.is\_deleted (bool): Flag indicating if account is deleted * account.meta.flags.is\_new (bool): Flag indicating if account is new * account.meta.flags.is\_compressed (bool): Flag indicating if account data is compressed * account.meta.last\_updated\_slot (uint64): Slot when account was last modified Available CEL functions: * has(field): Check if optional field exists * uint(value): Convert to uint type * int(value): Convert to int type * string(value): Convert to string type * bytes(value): Convert to bytes type Available filter parameters (accessible via params.\* in expressions): * params.owner\_bytes (bytes): Owner public key for owner filtering (REQUIRED when filtering by owner) * params.prefix (bytes): Byte prefix for range-based filtering * params.tag (any): Custom tag parameter * params.min\_slot (uint64): Minimum slot parameter * params.min\_updated\_slot (uint64): Minimum last\_updated\_slot for filtering Filter examples: 1. Filter by balance: filter.expression = “account.meta.balance > uint(1000000)” 2. Filter by balance range: filter.expression = “account.meta.balance >= uint(100) && account.meta.balance <= uint(10000)” 3. Filter by owner (requires params.owner\_bytes): filter.expression = “account.meta.owner.value == params.owner\_bytes” filter.params\[“owner\_bytes”].bytes\_value = <32-byte owner pubkey> 4. Filter by data size: filter.expression = “account.meta.data\_size > uint(0)” filter.expression = “account.meta.data\_size >= uint(100) && account.meta.data\_size <= uint(1000)” 5. Filter by nonce: filter.expression = “account.meta.nonce == uint(0)” 6. Filter by sequence number: filter.expression = “account.meta.seq >= uint(100)” 7. Filter by version: filter.expression = “account.meta.version == uint(1)” 8. Filter by specific address (using inline bytes literal with octal escaping): filter.expression = “account.address.value == b’\001\002\003…’” Note: Binary bytes must be properly escaped using octal notation (\NNN) for non-printable bytes 9. Filter by address prefix (using range comparison): filter.expression = “account.address.value >= b’\001\002\003\004’ && account.address.value <= b’\001\002\003\004\377\377…’” Note: Create upper bound by appending 0xff bytes after prefix 10. Combine multiple conditions with AND: filter.expression = “account.meta.balance > uint(0) && account.meta.data\_size == uint(0)” 11. Combine multiple conditions with OR: filter.expression = “account.meta.balance == uint(0) || account.meta.balance > uint(50000)” 12. Complex combined filters: filter.expression = “(account.meta.balance > uint(100) && account.meta.balance < uint(10000)) || account.meta.data\_size > uint(1000)” 13. Check for optional field existence: filter.expression = “has(account.address.value)” filter.expression = “has(account.meta.owner.value)” // Requires params.owner\_bytes 14. Use inequality operators: filter.expression = “account.meta.balance < uint(5000)” filter.expression = “account.meta.balance <= uint(5000)” filter.expression = “account.meta.balance >= uint(100)” filter.expression = “account.meta.balance != uint(999)” 15. Type conversions: filter.expression = “account.meta.balance == uint(100)” filter.expression = “uint(account.meta.data\_size) > uint(0)” 16. Filter by account flags (individual flag fields): filter.expression = “account.meta.flags.is\_program == true” filter.expression = “account.meta.flags.is\_privileged == true” filter.expression = “account.meta.flags.is\_uncompressable == true” filter.expression = “account.meta.flags.is\_ephemeral == true” filter.expression = “account.meta.flags.is\_deleted == true” filter.expression = “account.meta.flags.is\_new == true” filter.expression = “account.meta.flags.is\_compressed == true” 17. Combine flag filters with other conditions: filter.expression = “account.meta.flags.is\_program == true && account.meta.balance > uint(0)” 18. Filter by last\_updated\_slot: filter.expression = “account.meta.last\_updated\_slot >= uint(1000)” 19. Filter by last\_updated\_slot with params: filter.expression = “account.meta.last\_updated\_slot >= params.min\_updated\_slot” filter.params\[“min\_updated\_slot”].uint\_value = 1000 Limitations: * The startsWith() function only works with strings, not bytes * The bytesPrefix() function is NOT available for ListAccounts (use range comparison instead) * Parameter names other than owner\_bytes, prefix, tag, min\_slot, min\_updated\_slot are NOT permitted * All filters are pushed down to SQL for optimal performance where possible **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | | `view` | [`thru.core.v1.AccountView`](/docs/api-ref/grpc/messages/thru/core/v1/account-view/) | 1 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 2 · optional | | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 3 · optional | CEL filter expression (REQUIRED). See message documentation for examples. | | `page` | [`thru.common.v1.PageRequest`](/docs/api-ref/grpc/messages/thru/common/v1/page-request/) | 4 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 5 · optional | | # ListAccountsResponse > ListAccountsResponse contains paginated accounts. ListAccountsResponse contains paginated accounts. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------- | ------------------------------------------------------------------------------------------ | ------------ | ----------- | | `accounts` | repeated [`thru.core.v1.Account`](/docs/api-ref/grpc/messages/thru/core/v1/account/) | 1 · optional | | | `page` | [`thru.common.v1.PageResponse`](/docs/api-ref/grpc/messages/thru/common/v1/page-response/) | 2 · optional | | # ListBlocksRequest > ListBlocksRequest lists blocks with pagination and filtering. ListBlocksRequest lists blocks with pagination and filtering. Returns blocks ordered from latest slot to earliest (slot DESC) by default. Supports filtering on block header and footer fields using CEL expressions. Available fields for filtering: Header fields: * block.header.slot (uint64): Block slot number * block.header.version (uint32): Block version number * block.header.start\_slot (uint64): Start slot for block production * block.header.producer.value (bytes): Block producer’s public key * block.header.expiry\_after (uint32): Expiry duration in slots * block.header.expiry\_timestamp (google.protobuf.Timestamp): Expiry timestamp * block.header.max\_block\_size (uint32): Maximum block size in bytes * block.header.max\_compute\_units (uint64): Maximum compute units allowed * block.header.max\_state\_units (uint32): Maximum state units allowed * block.header.bond\_amount\_lock\_up (uint64): Bond amount lock-up for block production * block.header.price (uint64): Block production price * block.header.block\_hash.value (bytes): Block hash * block.header.header\_signature.value (bytes): Header signature * block.header.block\_time (google.protobuf.Timestamp): Block timestamp Footer fields: * block.footer.signature.value (bytes): Block signature * block.footer.status (int32): Block execution status (2 = EXECUTION\_STATUS\_EXECUTED) * block.footer.consumed\_compute\_units (uint64): Total compute units consumed by all transactions * block.footer.consumed\_state\_units (uint32): Total state units consumed by all transactions * block.footer.attestor\_payment (uint64): Payment to attestors for block validation Consensus status: * block.consensus\_status (int32): Consensus status (always CONSENSUS\_STATUS\_INCLUDED for persisted blocks) Available CEL functions: * has(field): Check if optional field exists * uint(value): Convert to uint type * int(value): Convert to int type * bytes(value): Convert to bytes type * timestamp(value): Convert to timestamp type * duration(value): Convert to duration type Available filter parameters (accessible via params.\* in expressions): * params.slot (uint64): Slot number for filtering * params.u64 (uint64): Generic uint64 parameter * params.producer (bytes): Producer public key for filtering Filter examples: 1. Filter by specific slot: filter.expression = “block.header.slot == uint(1234)” 2. Filter by slot range: filter.expression = “block.header.slot >= uint(1000) && block.header.slot <= uint(2000)” 3. Filter by slot using parameter: filter.expression = “block.header.slot == params.slot” filter.params\[“slot”].uint\_value = 1234 4. Filter by block version: filter.expression = “block.header.version == uint(1)” 5. Filter by producer (using parameter): filter.expression = “block.header.producer.value == params.producer” filter.params\[“producer”].bytes\_value = <32-byte producer pubkey> 6. Filter by max compute units: filter.expression = “block.header.max\_compute\_units > uint(1000000)” filter.expression = “block.header.max\_compute\_units >= uint(0) && block.header.max\_compute\_units <= uint(10000000)” 7. Filter by max state units: filter.expression = “block.header.max\_state\_units > uint(0)” 8. Filter by price: filter.expression = “block.header.price >= uint(0)” filter.expression = “block.header.price > uint(1000)” 9. Filter by start\_slot: filter.expression = “block.header.start\_slot <= uint(5000)” 10. Filter by expiry\_after: filter.expression = “block.header.expiry\_after > uint(0)” 11. Filter by max\_block\_size: filter.expression = “block.header.max\_block\_size >= uint(1000000)” 12. Filter by execution status: filter.expression = “block.footer.status == int(2)” // EXECUTION\_STATUS\_EXECUTED 13. Filter by consumed compute units: filter.expression = “block.footer.consumed\_compute\_units > uint(0)” filter.expression = “block.footer.consumed\_compute\_units >= uint(100) && block.footer.consumed\_compute\_units <= uint(1000000)” 14. Filter by consumed state units: filter.expression = “block.footer.consumed\_state\_units >= uint(0)” filter.expression = “block.footer.consumed\_state\_units > uint(10)” 15. Check for footer signature existence: filter.expression = “has(block.footer.signature)” 16. Check for footer existence: filter.expression = “has(block.footer)” 17. Check for producer existence: filter.expression = “has(block.header.producer)” 18. Check for block hash existence: filter.expression = “has(block.header.block\_hash)” 19. Check for header signature existence: filter.expression = “has(block.header.header\_signature)” 20. Check for expiry timestamp existence: filter.expression = “has(block.header.expiry\_timestamp)” 21. Check for block time existence: filter.expression = “has(block.header.block\_time)” 22. Combine multiple header conditions with AND: filter.expression = “block.header.slot >= uint(1000) && block.header.max\_compute\_units > uint(1000000)” 23. Combine multiple footer conditions with AND: filter.expression = “block.footer.consumed\_compute\_units > uint(0) && block.footer.consumed\_state\_units > uint(0)” 24. Combine header and footer conditions: filter.expression = “block.header.slot >= uint(1000) && block.footer.consumed\_compute\_units > uint(100000)” 25. Combine multiple conditions with OR: filter.expression = “block.header.slot == uint(100) || block.header.slot == uint(200)” filter.expression = “block.footer.consumed\_compute\_units > uint(1000000) || block.footer.consumed\_state\_units > uint(10000)” 26. Complex combined filters: filter.expression = “(block.header.slot >= uint(1000) && block.header.slot <= uint(2000)) || block.footer.consumed\_compute\_units > uint(5000000)” 27. Use inequality operators: filter.expression = “block.header.max\_compute\_units < uint(10000000)” filter.expression = “block.header.max\_compute\_units <= uint(10000000)” filter.expression = “block.header.price >= uint(100)” filter.expression = “block.footer.consumed\_compute\_units != uint(0)” 28. Combine has() with value checks: filter.expression = “has(block.header.producer) && block.header.producer.value == params.producer” filter.params\[“producer”].bytes\_value = <32-byte producer pubkey> 29. Filter by multiple resource limits: filter.expression = “block.header.max\_compute\_units > uint(1000000) && block.header.max\_state\_units > uint(1000) && block.header.max\_block\_size > uint(1000000)” 30. Filter blocks with high resource consumption: filter.expression = “block.footer.consumed\_compute\_units > uint(block.header.max\_compute\_units / 2)” Note: All filters are pushed down to SQL for optimal performance where possible. When SQL pushdown is not possible, filters are evaluated in-memory on fetched results. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------- | ------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 1 · optional | CEL filter expression (OPTIONAL). See message documentation for examples. | | `page` | [`thru.common.v1.PageRequest`](/docs/api-ref/grpc/messages/thru/common/v1/page-request/) | 2 · optional | | | `view` | [`thru.core.v1.BlockView`](/docs/api-ref/grpc/messages/thru/core/v1/block-view/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # ListBlocksResponse > ListBlocksResponse returns a page of blocks. ListBlocksResponse returns a page of blocks. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------- | ------------------------------------------------------------------------------------------ | ------------ | ----------- | | `blocks` | repeated [`thru.core.v1.Block`](/docs/api-ref/grpc/messages/thru/core/v1/block/) | 1 · optional | | | `page` | [`thru.common.v1.PageResponse`](/docs/api-ref/grpc/messages/thru/common/v1/page-response/) | 2 · optional | | # ListEventsRequest > ListEventsRequest lists events with CEL filtering and pagination. ListEventsRequest lists events with CEL filtering and pagination. Returns events ordered from most recent to older (slot DESC, block\_offset DESC, call\_idx DESC). Supports filtering on event metadata and payload using CEL expressions with specialized byte functions. Available fields for filtering: * event.event\_id (string): Unique event identifier (format: “ts{slot}*{block\_offset}*{call\_idx}”) * event.transaction\_signature.value (bytes): Transaction signature that emitted the event * event.slot (uint64): Block slot number where event was emitted * event.call\_idx (uint32): Instruction call index within transaction * event.block\_offset (uint32): Transaction’s position within the block * event.timestamp (google.protobuf.Timestamp): Event emission timestamp * event.program.value (bytes): Program public key that emitted the event * event.program\_idx (uint32): Program index within transaction * event.payload (bytes): Event payload data * event.payload\_size (uint32): Size of event payload in bytes * event.fee\_payer.value (bytes): Fee payer’s public key for the transaction Available CEL functions: * has(field): Check if optional field exists * startsWith(string, prefix): Check if string starts with prefix (string fields only) * first1Byte(bytes): Extract first byte as uint8 * first4Bytes(bytes): Extract first 4 bytes as uint32 (little-endian) * first8Bytes(bytes): Extract first 8 bytes as uint64 (little-endian) * bytesPrefix(bytes, prefix): Check if bytes start with prefix * uint(value): Convert to uint type * int(value): Convert to int type * string(value): Convert to string type * bytes(value): Convert to bytes type * double(value): Convert to double type * timestamp(value): Convert to timestamp type * duration(value): Convert to duration type Available filter parameters (accessible via params.\* in expressions): * params.slot (uint64): Slot number for filtering * params.u64 (uint64): Generic uint64 parameter for payload matching * params.signature (Signature): Transaction signature for filtering * params.signature.value (bytes): Transaction signature bytes for filtering * params.address (Pubkey): Program address for filtering * params.address.value (bytes): Program address bytes for filtering * params.prefix (bytes): Byte prefix for payload filtering * params.timestamp (Timestamp): Timestamp parameter for filtering Filter examples: 1. Filter by slot: filter.expression = “event.slot > uint(1000)” filter.expression = “event.slot >= uint(100) && event.slot <= uint(200)” 2. Filter by slot using parameter: filter.expression = “event.slot >= params.slot” filter.params\[“slot”].int\_value = 1234 3. Filter by call index: filter.expression = “event.call\_idx == uint(0)” // First instruction filter.expression = “event.call\_idx > uint(0)” // Nested instructions 4. Filter by block offset: filter.expression = “event.block\_offset >= uint(0)” filter.expression = “event.block\_offset == uint(5)” 5. Filter by transaction signature (using parameter): filter.expression = “event.transaction\_signature.value == params.signature” filter.params\[“signature”].signature\_value.value = <64-byte signature> 6. Filter by program address (using parameter): filter.expression = “has(event.program) && event.program.value == params.address” filter.params\[“address”].pubkey\_value.value = <32-byte program pubkey> 7. Filter by event ID prefix using startsWith: filter.expression = “event.event\_id.startsWith(“ts”)” filter.expression = “event.event\_id.startsWith(“ts1000\_”)” 8. Check for payload existence: filter.expression = “has(event.payload)” 9. Check for program existence: filter.expression = “has(event.program)” 10. Filter by payload first byte (event type): filter.expression = “has(event.payload) && first1Byte(event.payload) == uint(1)” // MESSAGE events filter.expression = “has(event.payload) && first1Byte(event.payload) == uint(2)” // Other type 11. Filter by payload first 4 bytes (uint32 event type): filter.expression = “has(event.payload) && first4Bytes(event.payload) == uint(2)” // COUNTER events filter.expression = “has(event.payload) && first4Bytes(event.payload) == params.u64” filter.params\[“u64”].int\_value = 2 12. Filter by payload first 8 bytes (uint64 event type): filter.expression = “has(event.payload) && first8Bytes(event.payload) == uint(6)” // PATTERN events filter.expression = “has(event.payload) && first8Bytes(event.payload) == params.u64” filter.params\[“u64”].int\_value = 6 13. Filter by payload byte prefix: filter.expression = “bytesPrefix(event.payload, params.prefix)” filter.params\[“prefix”].bytes\_value = \ 14. Combine slot and call\_idx filters: filter.expression = “event.slot > uint(1000) && event.call\_idx == uint(0)” 15. Combine slot, call\_idx, and payload existence: filter.expression = “event.slot > uint(1000) && event.call\_idx == uint(0) && has(event.payload)” 16. Filter by program and payload type: filter.expression = “has(event.program) && event.program.value == params.address && first1Byte(event.payload) == uint(1)” filter.params\[“address”].pubkey\_value.value = <32-byte program pubkey> 17. Filter MESSAGE events (type 1) with specific program: filter.expression = “has(event.payload) && first1Byte(event.payload) == uint(1) && event.program.value == params.address” filter.params\[“address”].pubkey\_value.value = <32-byte program pubkey> 18. Filter COUNTER events (type 2) in slot range: filter.expression = “has(event.payload) && first4Bytes(event.payload) == uint(2) && event.slot >= uint(100) && event.slot <= uint(200)” 19. Filter PATTERN events (type 6) with payload prefix: filter.expression = “has(event.payload) && first8Bytes(event.payload) == uint(6) && bytesPrefix(event.payload, params.prefix)” filter.params\[“prefix”].bytes\_value = \ 20. Complex combined filter: filter.expression = “(event.slot > uint(1000) && event.call\_idx == uint(0)) || first1Byte(event.payload) == uint(1)” 21. Filter events from specific transaction: filter.expression = “event.transaction\_signature.value == params.signature && has(event.payload)” filter.params\[“signature”].signature\_value.value = <64-byte signature> 22. Filter by multiple payload type options: filter.expression = “has(event.payload) && (first1Byte(event.payload) == uint(1) || first1Byte(event.payload) == uint(2))” 23. Filter nested instruction events: filter.expression = “event.call\_idx > uint(0) && has(event.program)” 24. Filter first instruction events only: filter.expression = “event.call\_idx == uint(0)” 25. Filter events with payload longer than specific size (using bytesPrefix with empty prefix): filter.expression = “has(event.payload)” 26. Combine has() checks: filter.expression = “has(event.program) && has(event.payload) && has(event.timestamp)” 27. Use inequality operators: filter.expression = “event.slot < uint(10000)” filter.expression = “event.slot <= uint(10000)” filter.expression = “event.call\_idx >= uint(0)” filter.expression = “event.block\_offset != uint(0)” 28. Filter by timestamp (if available): filter.expression = “has(event.timestamp)” 29. Match specific event ID pattern: filter.expression = “event.event\_id.startsWith(“ts1234\_0\_”)” 30. Complex payload and metadata filter: filter.expression = “event.slot >= params.slot && has(event.payload) && first8Bytes(event.payload) == params.u64 && event.program.value == params.address” filter.params\[“slot”].int\_value = 1000 filter.params\[“u64”].int\_value = 6 filter.params\[“address”].pubkey\_value.value = <32-byte program pubkey> Note: Filters on slot, call\_idx, block\_offset, transaction\_signature, and program are pushed down to SQL for optimal performance. Payload filters (first1Byte, first4Bytes, first8Bytes, bytesPrefix) are evaluated in-memory on fetched results. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 1 · optional | CEL filter expression (OPTIONAL). See message documentation for examples. | | `page` | [`thru.common.v1.PageRequest`](/docs/api-ref/grpc/messages/thru/common/v1/page-request/) | 2 · optional | | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # ListEventsResponse > ListEventsResponse returns paginated events. ListEventsResponse returns paginated events. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------- | ------------------------------------------------------------------------------------------ | ------------ | ----------- | | `events` | repeated [`Event`](/docs/api-ref/grpc/messages/thru/services/v1/event/) | 1 · optional | | | `page` | [`thru.common.v1.PageResponse`](/docs/api-ref/grpc/messages/thru/common/v1/page-response/) | 2 · optional | | # ListSlotMetricsRequest > ListSlotMetricsRequest lists slot metrics for a range of slots. ListSlotMetricsRequest lists slot metrics for a range of slots. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------ | -------- | ------------ | ----------------------------------------------------------------------------- | | `start_slot` | `uint64` | 1 · optional | Start slot for the range (inclusive). | | `end_slot` | `uint64` | 2 · optional | End slot for the range (inclusive). Defaults to start\_slot if not specified. | | `limit` | `uint32` | 3 · optional | Maximum number of results to return. Defaults to 100. | # ListSlotMetricsResponse > ListSlotMetricsResponse contains a list of slot metrics. ListSlotMetricsResponse contains a list of slot metrics. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------- | ------------------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `metrics` | repeated [`GetSlotMetricsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-slot-metrics-response/) | 1 · optional | | # ListTransactionsForAccountRequest > ListTransactionsForAccountRequest lists transactions involving a specific account. ListTransactionsForAccountRequest lists transactions involving a specific account. This RPC returns all transactions where the specified account appears in any capacity (fee payer, signer, or affected account). Results can be filtered using CEL expressions on transaction properties. ## Available Filter Fields * `transaction.slot` (uint64): Block slot number * `transaction.block_offset` (uint32): Position within block * `transaction.signature.value` (bytes): Transaction signature Header fields (in-memory evaluation only, not SQL pushdown): * `transaction.header.version` (uint32): Transaction format version * `transaction.header.fee` (uint64): Transaction fee * `transaction.header.nonce` (uint64): Sender account nonce * `transaction.header.start_slot` (uint64): Earliest slot for execution * `transaction.header.expiry_after` (uint32): Expiry duration in slots * `transaction.header.requested_compute_units` (uint64): Requested compute units * `transaction.header.requested_memory_units` (uint32): Requested memory units * `transaction.header.requested_state_units` (uint32): Requested state units * `transaction.header.fee_payer_pubkey.value` (bytes): Fee payer public key * `transaction.header.program_pubkey.value` (bytes): Program public key * `transaction.header.fee_payer_signature.value` (bytes): Fee payer signature Execution result fields: * `transaction.execution_result.vm_error` (TransactionVmError enum): VM execution status (0 = success) * `transaction.execution_result.user_error_code` (uint64): User-defined error code * `transaction.execution_result.execution_result` (uint64): Alias for user\_error\_code * `transaction.execution_result.consumed_compute_units` (uint64): Compute units used * `transaction.execution_result.consumed_memory_units` (uint32): Memory units used * `transaction.execution_result.consumed_state_units` (uint32): State units used * `transaction.execution_result.events_count` (uint32): Number of events emitted * `transaction.execution_result.events_size` (uint32): Total size of event data in bytes ## Filter Examples ### Filter by slot ```plaintext filter \{ expression: "transaction.slot == params.slot" params \{ key: "slot" value \{ int_value: 12345 } } } ``` ### Filter by block offset ```plaintext filter \{ expression: "transaction.block_offset == uint(5)" } ``` ### Filter by compute units (high usage) ```plaintext filter \{ expression: "transaction.execution_result.consumed_compute_units >= uint(1000000)" } ``` ### Filter by memory units ```plaintext filter \{ expression: "transaction.execution_result.consumed_memory_units > uint(0)" } ``` ### Filter by state units ```plaintext filter \{ expression: "transaction.execution_result.consumed_state_units > uint(0)" } ``` ### Filter successful transactions (by error code) ```plaintext filter \{ expression: "transaction.execution_result.user_error_code == uint(0)" } ``` ### Filter by VM execution status ```plaintext filter \{ expression: "transaction.execution_result.vm_error == int(0)" } ``` ### Filter transactions with events ```plaintext filter \{ expression: "transaction.execution_result.events_count > uint(0)" } ``` ### Filter by event data size ```plaintext filter \{ expression: "transaction.execution_result.events_size > uint(0)" } ``` ### Check for optional fields using has() ```plaintext filter \{ expression: "has(transaction.execution_result)" } ``` ### Combined filters with AND/OR ```plaintext filter \{ expression: "transaction.execution_result.consumed_compute_units >= uint(1000000) && transaction.execution_result.user_error_code == uint(0)" } ``` ### Using params for dynamic values ```plaintext filter \{ expression: "transaction.execution_result.consumed_compute_units >= params.u64" params \{ key: "u64" value \{ int_value: 500000 } } } ``` ## Available Functions * `has(field)`: Check if optional field is present * `uint(value)`: Convert to unsigned integer * `int(value)`: Convert to signed integer * `string(value)`: Convert to string type * `bytes(value)`: Convert to bytes type ## Available Filter Parameters * `params.slot` (uint64): Slot number for filtering * `params.u64` (uint64): Generic uint64 parameter * `params.pubkey` (Pubkey): Public key parameter for filtering ## Performance Notes Filters on `transaction.slot`, `transaction.block_offset`, and `transaction.execution_result.*` fields are optimized with SQL pushdown for better performance. Complex expressions may fall back to in-memory evaluation. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------- | ---------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------- | | `account` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 1 · optional | | | `page` | [`thru.common.v1.PageRequest`](/docs/api-ref/grpc/messages/thru/common/v1/page-request/) | 2 · optional | | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 3 · optional | Optional CEL filter applied after the account constraint. | # ListTransactionsForAccountResponse > ListTransactionsForAccountResponse contains transaction data. ListTransactionsForAccountResponse contains transaction data. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------------- | -------------------------------------------------------------------------------------------- | ------------ | ----------- | | `page` | [`thru.common.v1.PageResponse`](/docs/api-ref/grpc/messages/thru/common/v1/page-response/) | 1 · optional | | | `transactions` | repeated [`thru.core.v1.Transaction`](/docs/api-ref/grpc/messages/thru/core/v1/transaction/) | 2 · optional | | # ListTransactionsRequest > ListTransactionsRequest lists executed transactions with CEL filtering and pagination. ListTransactionsRequest lists executed transactions with CEL filtering and pagination. Returns transactions ordered from most recent to older (slot DESC, block\_offset DESC). Supports filtering on transaction metadata and execution results using CEL expressions. Available fields for filtering: * transaction.slot (uint64): Block slot number where transaction was executed * transaction.block\_offset (uint32): Transaction’s position within the block * transaction.signature.value (bytes): Transaction signature Header fields (in-memory evaluation only, not SQL pushdown): * transaction.header.version (uint32): Transaction format version * transaction.header.fee (uint64): Transaction fee * transaction.header.nonce (uint64): Sender account nonce * transaction.header.start\_slot (uint64): Earliest slot for execution * transaction.header.expiry\_after (uint32): Expiry duration in slots * transaction.header.requested\_compute\_units (uint64): Requested compute units * transaction.header.requested\_memory\_units (uint32): Requested memory units * transaction.header.requested\_state\_units (uint32): Requested state units * transaction.header.fee\_payer\_pubkey.value (bytes): Fee payer public key * transaction.header.program\_pubkey.value (bytes): Program public key * transaction.header.fee\_payer\_signature.value (bytes): Fee payer signature Execution result fields: * transaction.execution\_result.vm\_error (TransactionVmError enum): VM error code (0 = success) * transaction.execution\_result.user\_error\_code (uint64): User-defined error code * transaction.execution\_result.execution\_result (uint64): Alias for user\_error\_code * transaction.execution\_result.consumed\_compute\_units (uint64): Compute units consumed * transaction.execution\_result.consumed\_memory\_units (uint32): Memory units consumed * transaction.execution\_result.consumed\_state\_units (uint32): State units consumed * transaction.execution\_result.events\_count (uint32): Number of events emitted * transaction.execution\_result.events\_size (uint32): Total size of events in bytes Available CEL functions: * has(field): Check if optional field exists * uint(value): Convert to uint type * int(value): Convert to int type * string(value): Convert to string type * bytes(value): Convert to bytes type Available filter parameters (accessible via params.\* in expressions): * params.slot (uint64): Slot number for filtering * params.u64 (uint64): Generic uint64 parameter * params.pubkey (Pubkey): Public key parameter for filtering Filter examples: 1. Filter by slot range: filter.expression = “transaction.slot >= uint(1000) && transaction.slot <= uint(2000)” 2. Filter by successful transactions (no error): filter.expression = “transaction.execution\_result.user\_error\_code == uint(0)” filter.expression = “transaction.execution\_result.vm\_error == int(0)” 3. Filter by specific VM error: filter.expression = “transaction.execution\_result.vm\_error == int(2)” // VM\_REVERT filter.expression = “transaction.execution\_result.vm\_error == int(4)” // NONCE\_TOO\_LOW filter.expression = “transaction.execution\_result.vm\_error == int(5)” // NONCE\_TOO\_HIGH 4. Filter by resource usage: filter.expression = “transaction.execution\_result.consumed\_compute\_units > uint(1000)” filter.expression = “transaction.execution\_result.consumed\_memory\_units > uint(0)” filter.expression = “transaction.execution\_result.consumed\_state\_units >= uint(0)” 5. Filter by compute units range: filter.expression = “transaction.execution\_result.consumed\_compute\_units >= uint(0) && transaction.execution\_result.consumed\_compute\_units < uint(1000000)” 6. Filter by events count: filter.expression = “transaction.execution\_result.events\_count == uint(0)” // No events (transfers) filter.expression = “transaction.execution\_result.events\_count > uint(0)” // Has events filter.expression = “transaction.execution\_result.events\_count == uint(1)” // Exactly 1 event 7. Filter by events size: filter.expression = “transaction.execution\_result.events\_size == uint(0)” // No events filter.expression = “transaction.execution\_result.events\_size > uint(0)” // Has events filter.expression = “transaction.execution\_result.events\_size >= uint(100)” // Large events filter.expression = “transaction.execution\_result.events\_size >= uint(50) && transaction.execution\_result.events\_size <= uint(200)” 8. Filter by block offset: filter.expression = “transaction.block\_offset >= uint(0)” filter.expression = “transaction.block\_offset == uint(5)” 9. Combine multiple conditions with AND: filter.expression = “transaction.execution\_result.user\_error\_code == uint(0) && transaction.execution\_result.consumed\_compute\_units > uint(0)” 10. Combine multiple conditions with OR: filter.expression = “transaction.execution\_result.user\_error\_code == uint(0) || transaction.execution\_result.user\_error\_code != uint(0)” filter.expression = “transaction.execution\_result.vm\_error == int(2) || transaction.execution\_result.vm\_error == int(4)” 11. Complex combined filters: filter.expression = “(transaction.slot >= uint(0) && transaction.execution\_result.user\_error\_code == uint(0)) || transaction.execution\_result.consumed\_compute\_units > uint(100000)” 12. Check for field existence: filter.expression = “has(transaction.execution\_result)” 13. Use inequality operators: filter.expression = “transaction.execution\_result.consumed\_compute\_units < uint(1000000)” filter.expression = “transaction.execution\_result.consumed\_compute\_units <= uint(1000000)” filter.expression = “transaction.execution\_result.consumed\_compute\_units >= uint(0)” filter.expression = “transaction.execution\_result.user\_error\_code != uint(999)” 14. Use params.slot parameter: filter.expression = “transaction.slot == params.slot” filter.params\[“slot”].uint\_value = 1234 15. Use params.u64 with type conversion: filter.expression = “transaction.execution\_result.consumed\_compute\_units < uint(params.u64)” filter.params\[“u64”].uint\_value = 1000000 The return\_events flag controls whether event data is included in the response: * return\_events = false (default): Only event counts/sizes are returned, not actual event data * return\_events = true: Full event data is included in execution results Note: All filters are pushed down to SQL for optimal performance where possible. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------------------------------------------------------------------------------------------------ | ------------ | ------------------------------------------------------------------------- | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 1 · optional | CEL filter expression (OPTIONAL). See message documentation for examples. | | `page` | [`thru.common.v1.PageRequest`](/docs/api-ref/grpc/messages/thru/common/v1/page-request/) | 2 · optional | | | `return_events` | `bool` | 3 · optional | Whether to include event data in results (default: false) | | `version_context` | [`thru.common.v1.VersionContext`](/docs/api-ref/grpc/messages/thru/common/v1/version-context/) | 4 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 5 · optional | | # ListTransactionsResponse > ListTransactionsResponse returns paginated executed transactions. ListTransactionsResponse returns paginated executed transactions. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------------- | -------------------------------------------------------------------------------------------- | ------------ | ----------- | | `transactions` | repeated [`thru.core.v1.Transaction`](/docs/api-ref/grpc/messages/thru/core/v1/transaction/) | 1 · optional | | | `page` | [`thru.common.v1.PageResponse`](/docs/api-ref/grpc/messages/thru/common/v1/page-response/) | 2 · optional | | # MemoryPage > MemoryPage represents a single allocated page of VM memory. MemoryPage represents a single allocated page of VM memory. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------ | -------- | ------------ | ------------------------------------- | | `page_index` | `uint32` | 1 · optional | Index within the segment’s page array | | `data` | `bytes` | 2 · optional | Page contents (4096 bytes) | # MemorySegment > MemorySegment represents all allocated pages of a VM memory segment. MemorySegment represents all allocated pages of a VM memory segment. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------------- | ---------------------------------------------------------------------------------- | ------------ | -------------------------------------- | | `segment_type` | `uint32` | 1 · optional | VM segment type (5=stack, 7=heap) | | `segment_size` | `uint64` | 2 · optional | Total virtual size of segment in bytes | | `pages` | repeated [`MemoryPage`](/docs/api-ref/grpc/messages/thru/services/v1/memory-page/) | 3 · optional | | # NodeConsensusStatus > NodeConsensusStatus describes the consensus driver state. NodeConsensusStatus describes the consensus driver state. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------- | --------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------- | | `active` | `bool` | 1 · optional | Whether post-repair consensus is active and voting. | | `mode` | [`NodeMode`](/docs/api-ref/grpc/messages/thru/services/v1/node-mode/) | 2 · optional | How consensus was entered: regular startup or catastrophic recovery. | | `frontier` | `uint64` | 3 · optional | Current consensus frontier slot (first slot without EXECUTION\_FINISHED). | # NodeMode > NodeMode represents the consensus/repair operating mode. NodeMode represents the consensus/repair operating mode. **Package:** `thru.services.v1` ## Values | Name | # | Description | | ------------------------ | - | ----------- | | `NODE_MODE_UNKNOWN` | 0 | | | `NODE_MODE_REGULAR` | 1 | | | `NODE_MODE_CATASTROPHIC` | 2 | | # NodeRepairStatus > NodeRepairStatus describes the repair subsystem state. NodeRepairStatus describes the repair subsystem state. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------- | ------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `active` | `bool` | 1 · optional | Whether catastrophic repair gating is active. When true, the node is suppressing consensus output and rejecting transactions. Note: during normal gap repair (non-catastrophic), this is false but consensus.active is also false, so ready is correctly false. | # SendAndTrackTxnRequest > SendAndTrackTxnRequest submits a transaction and tracks its execution. SendAndTrackTxnRequest submits a transaction and tracks its execution. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------- | ----------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transaction` | `bytes` | 1 · optional | Raw transaction bytes encoded according to chain specification. | | `timeout` | [`google.protobuf.Duration`](/docs/api-ref/grpc/messages/google/protobuf/duration/) | 2 · optional | Optional timeout for tracking the transaction execution. If not specified, the stream will remain open until the transaction is executed or the client cancels. | # SendAndTrackTxnResponse > SendAndTrackTxnResponse streams transaction status updates. SendAndTrackTxnResponse streams transaction status updates. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------- | | `status` | [`SubmissionStatus`](/docs/api-ref/grpc/messages/thru/services/v1/submission-status/) | 1 · optional | Current submission status of the transaction. | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 2 · optional | Transaction signature (populated for tracking messages). | | `consensus_status` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 3 · optional | Consensus status (populated for tracking messages). | | `execution_result` | [`thru.core.v1.TransactionExecutionResult`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-execution-result/) | 4 · optional | Execution result (populated for tracking messages when execution completes). | # SendTransactionRequest > SendTransactionRequest submits a transaction to the cluster. SendTransactionRequest submits a transaction to the cluster. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------------- | ------- | ------------ | --------------------------------------------------------------- | | `raw_transaction` | `bytes` | 1 · optional | Raw transaction bytes encoded according to chain specification. | # SendTransactionResponse > SendTransactionResponse echoes submission metadata. SendTransactionResponse echoes submission metadata. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------- | ----------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | # StateRootEntry > StateRootEntry represents a state root for a specific slot. StateRootEntry represents a state root for a specific slot. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------ | -------- | ------------ | ----------- | | `slot` | `uint64` | 1 · optional | | | `state_root` | `bytes` | 2 · optional | | # StreamAccountUpdatesRequest > StreamAccountUpdatesRequest subscribes to account delta events. StreamAccountUpdatesRequest subscribes to account delta events. Filter expressions support the following params: * params.min\_slot (uint64): Minimum slot for filtering updates Example: account\_update.slot >= params.min\_slot * params.min\_balance (uint64): Minimum balance for filtering Example: snapshot.meta.balance >= params.min\_balance * params.owner (bytes|Pubkey): Owner pubkey for filtering Examples: snapshot.meta.owner.value == params.owner account\_update.meta.owner.value == params.owner * params.address (bytes|Pubkey): Account address for filtering Example: account\_address.value == params.address Note: Use account\_address for unified filtering across both snapshot and update messages * params.addresses (BytesList): List of addresses for multi-account filtering Example: account\_address.value in params.addresses Note: Use the ‘in’ operator to filter by multiple addresses in a single subscription * params.min\_nonce (uint64): Minimum nonce for filtering Example: snapshot.meta.nonce >= params.min\_nonce * params.min\_seq (uint64): Minimum sequence number for filtering Example: snapshot.meta.seq >= params.min\_seq * params.min\_data\_size (uint32): Minimum data size for filtering Example: snapshot.meta.data\_size >= params.min\_data\_size Available snapshot fields (thru.core.v1.Account): snapshot.address, snapshot.address.value (bytes), snapshot.meta.balance (uint64), snapshot.meta.seq (uint64), snapshot.meta.nonce (uint64), snapshot.meta.data\_size (uint32), snapshot.meta.version (uint32), snapshot.meta.flags (AccountFlags), snapshot.meta.flags.is\_program (bool), snapshot.meta.flags.is\_privileged (bool), snapshot.meta.flags.is\_uncompressable (bool), snapshot.meta.flags.is\_ephemeral (bool), snapshot.meta.flags.is\_deleted (bool), snapshot.meta.flags.is\_new (bool), snapshot.meta.flags.is\_compressed (bool), snapshot.meta.owner, snapshot.meta.owner.value (bytes) Available account\_update fields (AccountUpdate): account\_update.slot (uint64), account\_update.delete (bool), account\_update.meta.balance (uint64), account\_update.meta.seq (uint64), account\_update.meta.nonce (uint64), account\_update.meta.data\_size (uint32), account\_update.meta.version (uint32), account\_update.meta.flags (AccountFlags), account\_update.meta.flags.is\_program (bool), account\_update.meta.flags.is\_privileged (bool), account\_update.meta.flags.is\_uncompressable (bool), account\_update.meta.flags.is\_ephemeral (bool), account\_update.meta.flags.is\_deleted (bool), account\_update.meta.flags.is\_new (bool), account\_update.meta.flags.is\_compressed (bool), account\_update.meta.owner, account\_update.meta.owner.value (bytes) Available unified fields (work for both snapshot and update messages): account\_address, account\_address.value (bytes) - extracted from whichever message type is present Filter expression examples: 1. Filter by minimum balance (snapshot or update): Expression: “(has(snapshot.meta) && snapshot.meta.balance >= uint(1000000)) || (has(account\_update.meta) && account\_update.meta.balance >= uint(1000000))” 2. Filter by account owner using params: Expression: “(has(snapshot.meta) && has(snapshot.meta.owner) && snapshot.meta.owner.value == params.owner) || (has(account\_update.meta) && has(account\_update.meta.owner) && account\_update.meta.owner.value == params.owner)” Params: {“owner”: <32-byte pubkey>} 3. Filter by specific account address (works for both snapshot and update messages): Expression: “account\_address.value == params.address” Params: {“address”: <32-byte pubkey>} 4. Filter by minimum slot for updates: Expression: “has(account\_update.meta) && account\_update.slot >= params.min\_slot” Params: {“min\_slot”: 1000} 5. Filter by nonce greater than value: Expression: “(has(snapshot.meta) && snapshot.meta.nonce >= uint(5)) || (has(account\_update.meta) && account\_update.meta.nonce >= uint(5))” 6. Filter by sequence number: Expression: “(has(snapshot.meta) && snapshot.meta.seq >= uint(100)) || (has(account\_update.meta) && account\_update.meta.seq >= uint(100))” 7. Filter by data size: Expression: “(has(snapshot.meta) && snapshot.meta.data\_size >= uint(1024)) || (has(account\_update.meta) && account\_update.meta.data\_size >= uint(1024))” 8. Filter by account version: Expression: “(has(snapshot.meta) && snapshot.meta.version >= uint(1)) || (has(account\_update.meta) && account\_update.meta.version >= uint(1))” 9. Filter by account flags (check if account is a program): Expression: “(has(snapshot.meta) && has(snapshot.meta.flags) && snapshot.meta.flags.is\_program) || (has(account\_update.meta) && has(account\_update.meta.flags) && account\_update.meta.flags.is\_program)” 10. Filter by privileged flag: Expression: “(has(snapshot.meta) && has(snapshot.meta.flags) && snapshot.meta.flags.is\_privileged) || (has(account\_update.meta) && has(account\_update.meta.flags) && account\_update.meta.flags.is\_privileged)” 11. Filter non-delete updates: Expression: “has(snapshot.meta) || (has(account\_update.meta) && (!has(account\_update.delete) || !account\_update.delete))” 12. Check for snapshot or update presence: Expression: “has(snapshot.meta) || has(account\_update.meta)” 13. Combined filters (multiple conditions): Expression: “has(snapshot.meta) || (has(account\_update.meta) && account\_update.slot >= params.min\_slot && account\_update.meta.balance >= params.min\_balance)” Params: {“min\_slot”: 1000, “min\_balance”: 1000000} 14. Filter by minimum balance using params: Expression: “(has(snapshot.meta) && snapshot.meta.balance >= params.min\_balance) || (has(account\_update.meta) && account\_update.meta.balance >= params.min\_balance)” Params: {“min\_balance”: 5000000000} Note: The response contains either a snapshot (initial state), an update (delta), or a BlockFinished message. Filters should handle both snapshot and update cases using OR logic to match either message type. To filter by specific account address (recommended - works for both message types): Expression: “account\_address.value == params.address” Params: {“address”: <32-byte pubkey>} To filter by owner (program): Expression: “(has(snapshot.meta.owner) && snapshot.meta.owner.value == params.owner) || (has(account\_update.meta.owner) && account\_update.meta.owner.value == params.owner)” Params: {“owner”: <32-byte program pubkey>} To filter by multiple account addresses (recommended for multi-account subscriptions): Expression: “account\_address.value in params.addresses” Params: {“addresses”: BytesList{values: \[\, \, …]}} Note: Use BytesList parameter type with the ‘in’ operator for efficient multi-address filtering **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------- | ------------------------------------------------------------------------------------ | ------------ | ----------- | | `view` | [`thru.core.v1.AccountView`](/docs/api-ref/grpc/messages/thru/core/v1/account-view/) | 2 · optional | | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 4 · optional | | # StreamAccountUpdatesResponse > StreamAccountUpdatesResponse contains either an initial snapshot or a delta. StreamAccountUpdatesResponse contains either an initial snapshot or a delta. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------- | ------------------------------------------------------------------------------- | ------------ | ----------- | | `snapshot` | [`thru.core.v1.Account`](/docs/api-ref/grpc/messages/thru/core/v1/account/) | 1 · optional | | | `update` | [`AccountUpdate`](/docs/api-ref/grpc/messages/thru/services/v1/account-update/) | 2 · optional | | | `finished` | [`BlockFinished`](/docs/api-ref/grpc/messages/thru/services/v1/block-finished/) | 3 · optional | | # StreamBlocksRequest > StreamBlocksRequest subscribes to real-time block updates. StreamBlocksRequest subscribes to real-time block updates. Filter expressions support the following params: * params.slot (int64): Slot number for comparison Example: block.block.header.slot == params.slot * params.min\_slot (int64): Minimum slot for range filtering Example: block.block.header.slot >= params.min\_slot * params.start\_slot (int64): Starting slot for filtering Example: block.block.header.slot >= params.start\_slot * params.u64 (int64): Generic 64-bit value for numeric comparisons Examples: block.block.header.max\_compute\_units > params.u64 block.block.header.bond\_amount\_lock\_up >= params.u64 * params.producer (bytes|Pubkey): Producer pubkey for filtering Examples: block.block.header.producer.value == params.producer has(block.block.header.producer) && block.block.header.producer.value == params.producer Available block header fields: block.block.header.slot, block.block.header.version, block.block.header.start\_slot, block.block.header.expiry\_after, block.block.header.max\_block\_size, block.block.header.max\_compute\_units, block.block.header.max\_state\_units, block.block.header.bond\_amount\_lock\_up, block.block.header.producer, block.block.header.producer.value Available block footer fields: block.block.footer.status, block.block.footer.consumed\_compute\_units, block.block.footer.consumed\_state\_units, block.block.footer.attestor\_payment Available consensus status field: block.block.consensus\_status Filter expression examples: 1. Filter by specific slot: Expression: “block.block.header.slot == params.slot” Params: {“slot”: 12345} 2. Filter by slot range: Expression: “block.block.header.slot >= params.min\_slot” Params: {“min\_slot”: 1000} 3. Filter by block version: Expression: “block.block.header.version >= uint(0)” 4. Filter by specific producer: Expression: “has(block.block.header.producer) && block.block.header.producer.value == params.producer” Params: {“producer”: <32-byte pubkey>} 5. Filter by max compute units: Expression: “block.block.header.max\_compute\_units > uint(0)” 6. Filter by max state units: Expression: “block.block.header.max\_state\_units > uint(0)” 7. Filter by bond amount lock-up: Expression: “block.block.header.bond\_amount\_lock\_up >= uint(0)” 8. Filter by footer status: Expression: “has(block.block.footer) && block.block.footer.status == int(1)” Note: EXECUTION\_STATUS\_PENDING = 1, EXECUTION\_STATUS\_EXECUTED = 2 9. Filter by consumed compute units: Expression: “has(block.block.footer) && block.block.footer.consumed\_compute\_units > uint(0)” 10. Filter by consumed state units: Expression: “has(block.block.footer) && block.block.footer.consumed\_state\_units > uint(0)” 11. Filter by consensus status: Expression: “block.block.consensus\_status == int(2)” Note: CONSENSUS\_STATUS\_UNSPECIFIED = 0, CONSENSUS\_STATUS\_OBSERVED = 1, CONSENSUS\_STATUS\_INCLUDED = 2 12. Check for optional fields presence: Expression: “has(block.block.header) && has(block.block.footer)” 13. Combined filters (multiple conditions): Expression: “block.block.header.slot >= params.min\_slot && has(block.block.footer) && block.block.footer.consumed\_compute\_units > uint(0)” Params: {“min\_slot”: 1000} 14. Numeric comparison with params: Expression: “block.block.header.max\_compute\_units > params.u64” Params: {“u64”: 1000000} **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `start_slot` | `uint64` | 1 · optional | | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 2 · optional | | | `view` | [`thru.core.v1.BlockView`](/docs/api-ref/grpc/messages/thru/core/v1/block-view/) | 3 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 4 · optional | | # StreamBlocksResponse > StreamBlocksResponse delivers block updates. StreamBlocksResponse delivers block updates. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------- | ----------------------------------------------------------------------- | ------------ | ----------- | | `block` | [`thru.core.v1.Block`](/docs/api-ref/grpc/messages/thru/core/v1/block/) | 1 · optional | | # StreamEventsRequest > StreamEventsRequest subscribes to chain events. StreamEventsRequest subscribes to chain events. Filter expressions support the following params: * params.prefix (bytes): Byte prefix for payload filtering Example: bytesPrefix(event.payload, params.prefix) * params.slot (int64): Slot number for comparison Example: event.slot >= params.slot * params.u64 (int64): Generic 64-bit value for payload extraction Examples: first8Bytes(event.payload) == params.u64 first4Bytes(event.payload) == params.u64 first1Byte(event.payload) == params.u64 * params.signature (bytes|Signature|TsSignature): Signature for comparison Examples: event.signature.value == params.signature // bytes type bytesPrefix(event.signature.value, params.signature) Note: Signature and TsSignature types are auto-converted to bytes * params.address (bytes|Pubkey|TaPubkey): Address/pubkey for comparison Examples: event.program.value == params.address // bytes type bytesPrefix(event.program.value, params.address) Note: Pubkey and TaPubkey types are auto-converted to bytes * params.timestamp (int64): Timestamp in seconds for comparison Example: int(event.timestamp) > params.timestamp Available event fields: event.event\_id (string), event.slot (uint64), event.payload (bytes), event.call\_idx (uint32), event.signature (Signature), event.signature.value (bytes), event.program (Pubkey), event.program.value (bytes), event.timestamp (Timestamp) Note: Unlike ListEventsRequest in QueryService, StreamEventsRequest does NOT support: event.block\_offset, event.program\_idx, event.payload\_size, event.transaction\_signature Available filter functions: * has(field): Check if optional field exists Example: has(event.program) && has(event.signature) * startsWith(string, prefix): Check if string starts with prefix Example: event.event\_id.startsWith(“ts”) * bytesPrefix(bytes, prefix): Check if bytes start with prefix Examples: bytesPrefix(event.payload, b”\x01\x00\x00\x00\x00\x00\x00\x00”) bytesPrefix(event.payload, params.prefix) bytesPrefix(event.program.value, params.address) bytesPrefix(event.signature.value, params.signature) * first1Byte(bytes): Extract first byte as uint Example: first1Byte(event.payload) == uint(1) * first4Bytes(bytes): Extract first 4 bytes as little-endian uint32 Example: first4Bytes(event.payload) == uint(2) * first8Bytes(bytes): Extract first 8 bytes as little-endian uint64 Example: first8Bytes(event.payload) == uint(6) * uint(value): Convert value to uint for comparison * int(value): Convert value to int (used for timestamps) Filter expression examples: 1. Filter by payload type using first1Byte (MESSAGE event type = 1): Expression: “first1Byte(event.payload) == uint(1)” 2. Filter by payload type using first4Bytes (COUNTER event type = 2): Expression: “first4Bytes(event.payload) == uint(2)” 3. Filter by payload type using first8Bytes (PATTERN event type = 6): Expression: “first8Bytes(event.payload) == uint(6)” 4. Filter by payload prefix with params: Expression: “bytesPrefix(event.payload, params.prefix)” Params: {“prefix”: \} 5. Filter by slot using params: Expression: “event.slot >= params.slot” Params: {“slot”: 1000} 6. Filter by program address (exact match): Expression: “event.program.value == params.address” Params: {“address”: <32-byte pubkey as bytes, Pubkey, or TaPubkey>} 7. Filter by program address (prefix match): Expression: “bytesPrefix(event.program.value, params.address)” Params: {“address”: \} 8. Filter by transaction signature (exact match): Expression: “event.signature.value == params.signature” Params: {“signature”: <64-byte signature as bytes, Signature, or TsSignature>} 9. Filter by transaction signature (prefix match): Expression: “bytesPrefix(event.signature.value, params.signature)” Params: {“signature”: \} 10. Filter by call\_idx (0 = main program, 1+ = CPI calls): Expression: “event.call\_idx == uint(0)” Expression: “event.call\_idx == uint(1)” 11. Filter by event\_id prefix: Expression: “event.event\_id.startsWith(“ts”)” 12. Filter by timestamp (events in last hour): Expression: “has(event.timestamp) && int(event.timestamp) > int(1700000000)” 13. Filter by timestamp using params: Expression: “has(event.timestamp) && int(event.timestamp) > params.timestamp” Params: {“timestamp”: 1700000000} 14. Check field existence: Expression: “has(event.program) && has(event.signature)” 15. Filter using params.u64 with first8Bytes: Expression: “first8Bytes(event.payload) == params.u64” Params: {“u64”: 6} 16. Filter using params.u64 with first4Bytes: Expression: “first4Bytes(event.payload) == params.u64” Params: {“u64”: 2} 17. Filter using params.u64 with first1Byte: Expression: “first1Byte(event.payload) == params.u64” Params: {“u64”: 1} 18. Combined filter (slot + payload type + call\_idx): Expression: “event.slot > uint(1000) && first1Byte(event.payload) == uint(1) && event.call\_idx == uint(0)” 19. Combined filter (call\_idx + program address): Expression: “event.call\_idx == uint(1) && event.program.value == params.address” Params: {“address”: <32-byte CPI target program pubkey>} 20. Timestamp range filter: Expression: “has(event.timestamp) && int(event.timestamp) > int(1700000000) && int(event.timestamp) < int(1700100000)” **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | -------- | ----------------------------------------------------------------------------- | ------------ | ----------- | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 1 · optional | | # StreamEventsResponse > StreamEventsResponse delivers event payloads. StreamEventsResponse delivers event payloads. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------- | ------------------------------------------------------------------------------------- | ------------ | ----------- | | `event_id` | `string` | 1 · optional | | | `payload` | `bytes` | 2 · optional | | | `timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 3 · optional | | | `program` | [`thru.common.v1.Pubkey`](/docs/api-ref/grpc/messages/thru/common/v1/pubkey/) | 4 · optional | | | `call_idx` | `uint32` | 5 · optional | | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 6 · optional | | | `slot` | `uint64` | 7 · optional | | # StreamHeightRequest > StreamHeightRequest subscribes to real-time height updates. StreamHeightRequest subscribes to real-time height updates. **Package:** `thru.services.v1` ## Fields *No fields.* # StreamHeightResponse > StreamHeightResponse delivers height update events. StreamHeightResponse delivers height update events. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------ | -------- | ------------ | ----------- | | `finalized` | `uint64` | 1 · optional | | | `locally_executed` | `uint64` | 2 · optional | | | `cluster_executed` | `uint64` | 3 · optional | | # StreamNodeRecordsRequest > StreamNodeRecordsRequest subscribes to node record updates from the gossip network. StreamNodeRecordsRequest subscribes to node record updates from the gossip network. **Package:** `thru.services.v1` ## Fields *No fields.* # StreamNodeRecordsResponse > StreamNodeRecordsResponse delivers node record updates. StreamNodeRecordsResponse delivers node record updates. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------- | ---------------------------------------------------------------------------------- | ------------ | ------------------------------------------------- | | `record` | [`thru.core.v1.NodeRecord`](/docs/api-ref/grpc/messages/thru/core/v1/node-record/) | 1 · optional | A node record (initial snapshot or update). | | `finished` | `bool` | 2 · optional | Signals the initial batch of records is complete. | # StreamSlotMetricsRequest > StreamSlotMetricsRequest subscribes to per-slot metrics updates. StreamSlotMetricsRequest subscribes to per-slot metrics updates. Metrics are emitted after each block’s execution completes. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------ | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `start_slot` | `uint64` | 1 · optional | Optional starting slot. If specified, starts streaming from this slot. If not specified, starts from the next executed slot. | # StreamSlotMetricsResponse > StreamSlotMetricsResponse delivers slot metrics after execution completes. StreamSlotMetricsResponse delivers slot metrics after execution completes. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ---------------------------------- | ------------------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------------- | | `slot` | `uint64` | 1 · optional | Block slot number. | | `global_activated_state_counter` | `uint64` | 2 · optional | Global counter tracking total activated state across all accounts. | | `global_deactivated_state_counter` | `uint64` | 3 · optional | Global counter tracking total deactivated state across all accounts. | | `collected_fees` | `uint64` | 4 · optional | Total fees collected from all transactions in this block. | | `block_timestamp` | [`google.protobuf.Timestamp`](/docs/api-ref/grpc/messages/google/protobuf/timestamp/) | 5 · optional | Block timestamp. | # StreamTransactionsRequest > StreamTransactionsRequest subscribes to transaction confirmations. StreamTransactionsRequest subscribes to transaction confirmations. Filter expressions support the following params: * params.min\_slot (uint64): Minimum slot for filtering transactions Example: transaction.slot >= params.min\_slot * params.max\_slot (uint64): Maximum slot for filtering transactions Example: transaction.slot <= params.max\_slot * params.slot (uint64): Specific slot for exact match filtering Example: transaction.slot == params.slot * params.min\_fee (uint64): Minimum fee for filtering transactions Example: transaction.header.fee >= params.min\_fee * params.fee\_payer (bytes|Pubkey): Fee payer pubkey for filtering Example: transaction.header.fee\_payer\_pubkey.value == params.fee\_payer * params.signature (bytes|Signature): Transaction signature for filtering Example: transaction.signature.value == params.signature Available transaction fields (thru.core.v1.Transaction): transaction.signature, transaction.signature.value (bytes), transaction.slot (uint64), transaction.block\_offset (uint32), transaction.header, transaction.header.version (uint32), transaction.header.fee (uint64), transaction.header.fee\_payer\_pubkey, transaction.header.fee\_payer\_pubkey.value (bytes), transaction.execution\_result, transaction.execution\_result.user\_error\_code (uint32), transaction.execution\_result.vm\_error (int32) Available consensus\_status field: consensus\_status (int32) - Current consensus status of the transaction Values: CONSENSUS\_STATUS\_UNSPECIFIED = 0, CONSENSUS\_STATUS\_OBSERVED = 1, CONSENSUS\_STATUS\_INCLUDED = 2 Filter expression examples: 1. Filter by minimum slot: Expression: “has(transaction.slot) && transaction.slot >= params.min\_slot” Params: {“min\_slot”: 1000} 2. Filter by slot range: Expression: “has(transaction.slot) && transaction.slot >= params.min\_slot && transaction.slot <= params.max\_slot” Params: {“min\_slot”: 1000, “max\_slot”: 2000} 3. Filter by specific slot: Expression: “has(transaction.slot) && transaction.slot == params.slot” Params: {“slot”: 12345} 4. Filter by minimum fee: Expression: “has(transaction.header) && transaction.header.fee >= params.min\_fee” Params: {“min\_fee”: 5000} 5. Filter by fee payer: Expression: “has(transaction.header.fee\_payer\_pubkey) && transaction.header.fee\_payer\_pubkey.value == params.fee\_payer” Params: {“fee\_payer”: <32-byte pubkey>} 6. Filter by transaction signature: Expression: “has(transaction.signature) && transaction.signature.value == params.signature” Params: {“signature”: <64-byte signature>} 7. Filter by header version: Expression: “has(transaction.header) && transaction.header.version >= uint(0)” 8. Filter by successful execution: Expression: “has(transaction.execution\_result) && transaction.execution\_result.vm\_error == int(0)” 9. Filter by user error code: Expression: “has(transaction.execution\_result) && transaction.execution\_result.user\_error\_code == uint(0)” 10. Filter by consensus status: Expression: “consensus\_status >= int(2)” Note: Use >= int(2) for CONSENSUS\_STATUS\_INCLUDED and above 11. Check for execution result presence: Expression: “has(transaction.execution\_result)” 12. Filter by transaction with header and slot: Expression: “has(transaction.slot) && transaction.slot >= uint(0) && has(transaction.header)” 13. Combined filters (slot, fee, and status): Expression: “has(transaction.slot) && transaction.slot >= params.min\_slot && has(transaction.header) && transaction.header.fee >= params.min\_fee && consensus\_status >= int(2)” Params: {“min\_slot”: 1000, “min\_fee”: 5000} 14. Filter successful transactions with minimum fee: Expression: “has(transaction.execution\_result) && transaction.execution\_result.vm\_error == int(0) && has(transaction.header) && transaction.header.fee >= params.min\_fee” Params: {“min\_fee”: 10000} Note: The min\_consensus field in the request provides built-in consensus filtering without requiring a CEL expression. Use it in combination with filter expressions for more complex filtering logic. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | --------------- | ------------------------------------------------------------------------------------------------ | ------------ | ----------- | | `filter` | [`thru.common.v1.Filter`](/docs/api-ref/grpc/messages/thru/common/v1/filter/) | 1 · optional | | | `min_consensus` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 2 · optional | | # StreamTransactionsResponse > StreamTransactionsResponse delivers transaction events. StreamTransactionsResponse delivers transaction events. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------- | ----------------------------------------------------------------------------------- | ------------ | ----------- | | `transaction` | [`thru.core.v1.Transaction`](/docs/api-ref/grpc/messages/thru/core/v1/transaction/) | 1 · optional | | # SubmissionStatus > SubmissionStatus represents the status of a transaction in the submission pipeline. SubmissionStatus represents the status of a transaction in the submission pipeline. **Package:** `thru.services.v1` ## Values | Name | # | Description | | ------------------------------- | - | ------------------------------------------------------- | | `SUBMISSION_STATUS_UNSPECIFIED` | 0 | Submission status is unspecified (default value). | | `SUBMISSION_STATUS_RECEIVED` | 1 | Transaction has been received by the gRPC server. | | `SUBMISSION_STATUS_ACCEPTED` | 2 | Transaction has been accepted by the forwarder via UDS. | # TrackTransactionRequest > TrackTransactionRequest subscribes to status updates for a transaction. TrackTransactionRequest subscribes to status updates for a transaction. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ----------- | ----------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `timeout` | [`google.protobuf.Duration`](/docs/api-ref/grpc/messages/google/protobuf/duration/) | 2 · optional | | # TrackTransactionResponse > TrackTransactionResponse reports status transitions for a transaction. TrackTransactionResponse reports status transitions for a transaction. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `consensus_status` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 2 · optional | | | `execution_result` | [`thru.core.v1.TransactionExecutionResult`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-execution-result/) | 3 · optional | | # TransactionStatus > TransactionStatus captures status metadata for a transaction. TransactionStatus captures status metadata for a transaction. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | | `signature` | [`thru.common.v1.Signature`](/docs/api-ref/grpc/messages/thru/common/v1/signature/) | 1 · optional | | | `consensus_status` | [`thru.common.v1.ConsensusStatus`](/docs/api-ref/grpc/messages/thru/common/v1/consensus-status/) | 2 · optional | | | `execution_result` | [`thru.core.v1.TransactionExecutionResult`](/docs/api-ref/grpc/messages/thru/core/v1/transaction-execution-result/) | 3 · optional | | # VmExecutionDetails > VmExecutionDetails contains detailed VM state after execution. VmExecutionDetails contains detailed VM state after execution. **Package:** `thru.services.v1` ## Fields | Field | Type | # | Description | | ------------------------ | -------------------------------------------------------------------------------- | ------------- | ----------- | | `execution_code` | `uint64` | 1 · optional | | | `user_error_code` | `uint64` | 2 · optional | | | `compute_units_consumed` | `uint64` | 3 · optional | | | `state_units_consumed` | `uint64` | 4 · optional | | | `pages_used` | `uint64` | 5 · optional | | | `program_counter` | `uint64` | 6 · optional | | | `instruction_counter` | `uint64` | 7 · optional | | | `fault_code` | [`VmFaultCode`](/docs/api-ref/grpc/messages/thru/services/v1/vm-fault-code/) | 8 · optional | | | `segv_vaddr` | `uint64` | 9 · optional | | | `segv_size` | `uint64` | 10 · optional | | | `segv_write` | `bool` | 11 · optional | | | `registers` | repeated `uint64` | 12 · optional | | | `call_depth` | `uint64` | 13 · optional | | | `max_call_depth` | `uint64` | 14 · optional | | | `call_frames` | repeated [`CallFrame`](/docs/api-ref/grpc/messages/thru/services/v1/call-frame/) | 15 · optional | | # VmFaultCode > VmFaultCode enumerates VM halt conditions. VmFaultCode enumerates VM halt conditions. **Package:** `thru.services.v1` ## Values | Name | # | Description | | ----------------- | - | -------------------------------------------------- | | `VM_FAULT_NONE` | 0 | No fault | | `VM_FAULT_REVERT` | 1 | TN\_VM\_FAULT\_REVERT — explicit revert by program | | `VM_FAULT_SIGCU` | 2 | TN\_VM\_FAULT\_SIGCU — compute units exhausted | | `VM_FAULT_SIGSU` | 3 | TN\_VM\_FAULT\_SIGSU — state units exhausted | # gRPC API Overview > High-performance gRPC API for real-time blockchain interactions using Protocol Buffers The Thru gRPC API provides high-performance, strongly-typed remote procedure calls for interacting with the blockchain. Built on Protocol Buffers and HTTP/2, gRPC offers efficient binary serialization and bidirectional streaming. For browser-based applications, gRPC-Web provides a JavaScript-compatible version of the gRPC protocol that works in web browsers without requiring plugins or proxies. ## API Endpoints | Network | Endpoint | Protocol | Description | | -------- | ------------------------------- | ----------------- | --------------------------------------------------------------------- | | Alphanet | `https://rpc.alphanet.thru.org` | gRPC and gRPC-Web | Unified public RPC endpoint for native and browser-compatible clients | ## Available Services [QueryService ](/docs/api-ref/grpc/services/query-service/)Read-only operations for querying blockchain state, accounts, blocks, and transactions [CommandService ](/docs/api-ref/grpc/services/command-service/)Write operations for submitting transactions and executing state-changing operations [StreamingService ](/docs/api-ref/grpc/services/streaming-service/)Real-time streaming for account updates, block notifications, and transaction monitoring ## Common Features ### Protocol Buffers All gRPC services use Protocol Buffers (proto3) for serialization. Proto definitions are available in the [GitHub repository](https://github.com/Unto-Labs/thru-net/tree/main/proto). ### Binary Serialization gRPC uses Protocol Buffers for efficient binary serialization, resulting in payloads 2-10x smaller than JSON. ### Streaming Support The StreamingService supports bidirectional streaming for real-time updates: ```python # Subscribe to account updates request = SubscribeAccountRequest(address=account_address) for update in client.SubscribeAccount(request): print(f"Account updated: {update}") ``` ### Error Handling gRPC uses status codes for error handling: * `OK` (0) - Success * `NOT_FOUND` (5) - Resource not found * `INVALID_ARGUMENT` (3) - Invalid request parameters * `INTERNAL` (13) - Internal server error * `UNAVAILABLE` (14) - Service unavailable ## Performance Typical performance characteristics: * Request latency: <50ms * Throughput: 1000+ requests/second per connection * Payload size: 2-10x smaller than REST/JSON ## Best Practices Connection Pooling Reuse gRPC channels and connections instead of creating new ones for each request Timeouts Set appropriate deadlines for requests to prevent hanging operations Retry Logic Implement exponential backoff for retries on transient failures Health Checks Monitor service health using gRPC health check protocol # CommandService > CommandService defines transactional RPCs that mutate state or perform CommandService defines transactional RPCs that mutate state or perform expensive computations. **Package:** `thru.services.v1` ## RPCs ### `SendTransaction` Submit a new transaction to the cluster. * **Request:** [`SendTransactionRequest`](/docs/api-ref/grpc/messages/thru/services/v1/send-transaction-request/) * **Response:** [`SendTransactionResponse`](/docs/api-ref/grpc/messages/thru/services/v1/send-transaction-response/) Try it live `thru.services.v1.CommandService/SendTransaction` Request JSON { "transaction": "\" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `BatchSendTransactions` Submit multiple transactions to the cluster in batch. * **Request:** [`BatchSendTransactionsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/batch-send-transactions-request/) * **Response:** [`BatchSendTransactionsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/batch-send-transactions-response/) Try it live `thru.services.v1.CommandService/BatchSendTransactions` Request JSON { "transactions": \[ "\" ] } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `SendAndTrackTxn` *(server-stream)* Submit a transaction and track its execution status. Returns a stream of status updates starting with RECEIVED, then ACCEPTED, followed by consensus and execution updates, closing after the transaction is executed. * **Request:** [`SendAndTrackTxnRequest`](/docs/api-ref/grpc/messages/thru/services/v1/send-and-track-txn-request/) * **Response:** [`SendAndTrackTxnResponse`](/docs/api-ref/grpc/messages/thru/services/v1/send-and-track-txn-response/) Try it live `thru.services.v1.CommandService/SendAndTrackTxn` Request JSON { "transaction": "\" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` # DebugService > DebugService defines unary RPCs helpful for debugging. DebugService defines unary RPCs helpful for debugging. **Package:** `thru.services.v1` ## RPCs ### `DebugReExecute` DebugReExecute executes a transaction in simulated environment. * **Request:** [`DebugReExecuteRequest`](/docs/api-ref/grpc/messages/thru/services/v1/debug-re-execute-request/) * **Response:** [`DebugReExecuteResponse`](/docs/api-ref/grpc/messages/thru/services/v1/debug-re-execute-response/) Try it live `thru.services.v1.DebugService/DebugReExecute` Request JSON { "signature": { "value": "2Jqjbuct1yuDSIsJJlol3NG1MvvBtsiETcSXopHFOPC7QaD0DnqBbUceMnCxd8ItFrQFOK1iLXDFl0w2GYZlDw==" }, "includeStateBefore": true, "includeStateAfter": true } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` # QueryService > QueryService defines unary RPCs for accessing blockchain data. QueryService defines unary RPCs for accessing blockchain data. **Package:** `thru.services.v1` ## RPCs ### `GetHeight` Get block heights * **Request:** [`GetHeightRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-height-request/) * **Response:** [`GetHeightResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-height-response/) Try it live `thru.services.v1.QueryService/GetHeight` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetChainInfo` Get chain-level information including chain ID. * **Request:** [`GetChainInfoRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-chain-info-request/) * **Response:** [`GetChainInfoResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-chain-info-response/) Try it live `thru.services.v1.QueryService/GetChainInfo` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetAccount` Get account information. * **Request:** [`GetAccountRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-account-request/) * **Response:** [`thru.core.v1.Account`](/docs/api-ref/grpc/messages/thru/core/v1/account/) Try it live `thru.services.v1.QueryService/GetAccount` Request JSON { "address": { "value": "tz1X8LkZq4SVPGH0Jrb9B8yeOPKfTOMJ/rSbs70D5XU=" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetRawAccount` Get account raw bytes. * **Request:** [`GetRawAccountRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-raw-account-request/) * **Response:** [`thru.core.v1.RawAccount`](/docs/api-ref/grpc/messages/thru/core/v1/raw-account/) Try it live `thru.services.v1.QueryService/GetRawAccount` Request JSON { "address": { "value": "tz1X8LkZq4SVPGH0Jrb9B8yeOPKfTOMJ/rSbs70D5XU=" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetTransaction` Get transaction by signature. * **Request:** [`GetTransactionRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-transaction-request/) * **Response:** [`thru.core.v1.Transaction`](/docs/api-ref/grpc/messages/thru/core/v1/transaction/) Try it live `thru.services.v1.QueryService/GetTransaction` Request JSON { "signature": { "value": "2Jqjbuct1yuDSIsJJlol3NG1MvvBtsiETcSXopHFOPC7QaD0DnqBbUceMnCxd8ItFrQFOK1iLXDFl0w2GYZlDw==" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetRawTransaction` Get raw transaction by signature. * **Request:** [`GetRawTransactionRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-raw-transaction-request/) * **Response:** [`thru.core.v1.RawTransaction`](/docs/api-ref/grpc/messages/thru/core/v1/raw-transaction/) Try it live `thru.services.v1.QueryService/GetRawTransaction` Request JSON { "signature": { "value": "2Jqjbuct1yuDSIsJJlol3NG1MvvBtsiETcSXopHFOPC7QaD0DnqBbUceMnCxd8ItFrQFOK1iLXDFl0w2GYZlDw==" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetBlock` Get block by slot or hash. * **Request:** [`GetBlockRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-block-request/) * **Response:** [`thru.core.v1.Block`](/docs/api-ref/grpc/messages/thru/core/v1/block/) Try it live `thru.services.v1.QueryService/GetBlock` Request JSON { "slot": "80200" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetRawBlock` Get raw block bytes. * **Request:** [`GetRawBlockRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-raw-block-request/) * **Response:** [`thru.core.v1.RawBlock`](/docs/api-ref/grpc/messages/thru/core/v1/raw-block/) Try it live `thru.services.v1.QueryService/GetRawBlock` Request JSON { "slot": "80200" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `ListAccounts` List accounts using CEL-based filtering. * **Request:** [`ListAccountsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/list-accounts-request/) * **Response:** [`ListAccountsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/list-accounts-response/) Try it live `thru.services.v1.QueryService/ListAccounts` Request JSON { "filter": { "expression": "account.meta.balance > uint(0)" }, "page": { "pageSize": 5 } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `ListBlocks` List blocks using pagination and filtering. By default returns blocks ordered from latest slot to the first one. * **Request:** [`ListBlocksRequest`](/docs/api-ref/grpc/messages/thru/services/v1/list-blocks-request/) * **Response:** [`ListBlocksResponse`](/docs/api-ref/grpc/messages/thru/services/v1/list-blocks-response/) Try it live `thru.services.v1.QueryService/ListBlocks` Request JSON { "page": { "pageSize": 5 } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `ListTransactionsForAccount` List executed transaction signatures involving an account. * **Request:** [`ListTransactionsForAccountRequest`](/docs/api-ref/grpc/messages/thru/services/v1/list-transactions-for-account-request/) * **Response:** [`ListTransactionsForAccountResponse`](/docs/api-ref/grpc/messages/thru/services/v1/list-transactions-for-account-response/) Try it live `thru.services.v1.QueryService/ListTransactionsForAccount` Request JSON { "account": { "value": "tz1X8LkZq4SVPGH0Jrb9B8yeOPKfTOMJ/rSbs70D5XU=" }, "page": { "pageSize": 5 } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetEvent` Get a specific event by ID. * **Request:** [`GetEventRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-event-request/) * **Response:** [`Event`](/docs/api-ref/grpc/messages/thru/services/v1/event/) Try it live `thru.services.v1.QueryService/GetEvent` Request JSON { "eventId": "tse\_4tl2wZSKaw5boRQe\_JaZuEyNvXKbC5FAOxeUaG3VT3Bo0n1jQQn30TuZMphjB3NDN7jkUaFDSMcAzvNE99CByN:80255:4:0" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `ListEvents` List events with CEL filtering and pagination. Returns events ordered from most recent to older. * **Request:** [`ListEventsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/list-events-request/) * **Response:** [`ListEventsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/list-events-response/) Try it live `thru.services.v1.QueryService/ListEvents` Request JSON { "page": { "pageSize": 5 } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `ListTransactions` List executed transactions with CEL filtering and pagination. Returns transactions ordered from most recent to older. * **Request:** [`ListTransactionsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/list-transactions-request/) * **Response:** [`ListTransactionsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/list-transactions-response/) Try it live `thru.services.v1.QueryService/ListTransactions` Request JSON { "page": { "pageSize": 5 } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetTransactionStatus` Get derived transaction status metadata. * **Request:** [`GetTransactionStatusRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-transaction-status-request/) * **Response:** [`TransactionStatus`](/docs/api-ref/grpc/messages/thru/services/v1/transaction-status/) Try it live `thru.services.v1.QueryService/GetTransactionStatus` Request JSON { "signature": { "value": "2Jqjbuct1yuDSIsJJlol3NG1MvvBtsiETcSXopHFOPC7QaD0DnqBbUceMnCxd8ItFrQFOK1iLXDFl0w2GYZlDw==" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GenerateStateProof` Generate an account state proof snapshot. * **Request:** [`GenerateStateProofRequest`](/docs/api-ref/grpc/messages/thru/services/v1/generate-state-proof-request/) * **Response:** [`GenerateStateProofResponse`](/docs/api-ref/grpc/messages/thru/services/v1/generate-state-proof-response/) Try it live `thru.services.v1.QueryService/GenerateStateProof` Request JSON { "request": { "address": { "value": "tz1X8LkZq4SVPGH0Jrb9B8yeOPKfTOMJ/rSbs70D5XU=" }, "targetSlot": "80200" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetVersion` Get component version strings. * **Request:** [`GetVersionRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-version-request/) * **Response:** [`GetVersionResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-version-response/) Try it live `thru.services.v1.QueryService/GetVersion` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetStateRoots` Get state roots for a range of slots. Used for transaction replay to verify state proofs against historical roots. * **Request:** [`GetStateRootsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-state-roots-request/) * **Response:** [`GetStateRootsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-state-roots-response/) Try it live `thru.services.v1.QueryService/GetStateRoots` Request JSON { "slot": "80200" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetActiveStateHashes` Get active state hashes for a range of slots. * **Request:** [`GetActiveStateHashesRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-active-state-hashes-request/) * **Response:** [`GetActiveStateHashesResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-active-state-hashes-response/) Try it live `thru.services.v1.QueryService/GetActiveStateHashes` Request JSON { "endSlot": "80200", "startSlot": "80190" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetSlotMetrics` Get slot-level metrics including state counters and collected fees. * **Request:** [`GetSlotMetricsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-slot-metrics-request/) * **Response:** [`GetSlotMetricsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-slot-metrics-response/) Try it live `thru.services.v1.QueryService/GetSlotMetrics` Request JSON { "slot": "80200" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `ListSlotMetrics` List slot metrics for a range of slots. * **Request:** [`ListSlotMetricsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/list-slot-metrics-request/) * **Response:** [`ListSlotMetricsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/list-slot-metrics-response/) Try it live `thru.services.v1.QueryService/ListSlotMetrics` Request JSON { "startSlot": "80190", "endSlot": "80200" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetNodePubkey` Get the node’s own ED25519 public key. * **Request:** [`GetNodePubkeyRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-node-pubkey-request/) * **Response:** [`GetNodePubkeyResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-node-pubkey-response/) Try it live `thru.services.v1.QueryService/GetNodePubkey` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetNodeRecords` Get all known node records from the gossip network. * **Request:** [`GetNodeRecordsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-node-records-request/) * **Response:** [`GetNodeRecordsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-node-records-response/) Try it live `thru.services.v1.QueryService/GetNodeRecords` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `GetNodeStatus` Get the node’s operational status including consensus readiness. * **Request:** [`GetNodeStatusRequest`](/docs/api-ref/grpc/messages/thru/services/v1/get-node-status-request/) * **Response:** [`GetNodeStatusResponse`](/docs/api-ref/grpc/messages/thru/services/v1/get-node-status-response/) Try it live `thru.services.v1.QueryService/GetNodeStatus` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` # StreamingService > StreamingService serves server-streaming gRPC APIs for real-time data. StreamingService serves server-streaming gRPC APIs for real-time data. **Package:** `thru.services.v1` ## RPCs ### `StreamBlocks` *(server-stream)* * **Request:** [`StreamBlocksRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-blocks-request/) * **Response:** [`StreamBlocksResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-blocks-response/) Try it live `thru.services.v1.StreamingService/StreamBlocks` Request JSON { "startSlot": "80220" } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `StreamAccountUpdates` *(server-stream)* * **Request:** [`StreamAccountUpdatesRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-account-updates-request/) * **Response:** [`StreamAccountUpdatesResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-account-updates-response/) Try it live `thru.services.v1.StreamingService/StreamAccountUpdates` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `StreamTransactions` *(server-stream)* * **Request:** [`StreamTransactionsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-transactions-request/) * **Response:** [`StreamTransactionsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-transactions-response/) Try it live `thru.services.v1.StreamingService/StreamTransactions` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `StreamEvents` *(server-stream)* * **Request:** [`StreamEventsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-events-request/) * **Response:** [`StreamEventsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-events-response/) Try it live `thru.services.v1.StreamingService/StreamEvents` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `TrackTransaction` *(server-stream)* * **Request:** [`TrackTransactionRequest`](/docs/api-ref/grpc/messages/thru/services/v1/track-transaction-request/) * **Response:** [`TrackTransactionResponse`](/docs/api-ref/grpc/messages/thru/services/v1/track-transaction-response/) Try it live `thru.services.v1.StreamingService/TrackTransaction` Request JSON { "signature": { "value": "2Jqjbuct1yuDSIsJJlol3NG1MvvBtsiETcSXopHFOPC7QaD0DnqBbUceMnCxd8ItFrQFOK1iLXDFl0w2GYZlDw==" } } Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `StreamHeight` *(server-stream)* * **Request:** [`StreamHeightRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-height-request/) * **Response:** [`StreamHeightResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-height-response/) Try it live `thru.services.v1.StreamingService/StreamHeight` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `StreamSlotMetrics` *(server-stream)* Stream per-slot metrics including state counters and collected fees. Metrics are emitted after each block’s execution completes. * **Request:** [`StreamSlotMetricsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-slot-metrics-request/) * **Response:** [`StreamSlotMetricsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-slot-metrics-response/) Try it live `thru.services.v1.StreamingService/StreamSlotMetrics` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ``` ### `StreamNodeRecords` *(server-stream)* Stream node records from the gossip network. Returns an initial snapshot of all known records, followed by live updates. * **Request:** [`StreamNodeRecordsRequest`](/docs/api-ref/grpc/messages/thru/services/v1/stream-node-records-request/) * **Response:** [`StreamNodeRecordsResponse`](/docs/api-ref/grpc/messages/thru/services/v1/stream-node-records-response/) Try it live `thru.services.v1.StreamingService/StreamNodeRecords` Request JSON {} Send POST `https://rpc.alphanet.thru.org` Response ``` — ```