---
title: "Quickstart: Build a C Program"
description: Learn how to develop and build programs for the Thru network using the C SDK
source_url:
  html: https://thru.org/docs/program-development/building-a-c-program/
  md: https://thru.org/docs/program-development/building-a-c-program.md
---

# Quickstart: Build a C Program

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](https://thru.org/docs/program-development/setting-up-thru-devkit.md) 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](https://thru.org/docs/sdks/c-reference/program-structure.md) and [Headers and Entry Points](https://thru.org/docs/sdks/c-reference/headers-and-entry-points.md) 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](https://thru.org/docs/sdks/c-reference/accounts-and-transaction-context.md) 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](https://thru.org/docs/sdks/c-reference/program-structure.md) and [Error Handling and Return Codes](https://thru.org/docs/sdks/c-reference/error-handling-and-return-codes.md).

**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](https://thru.org/docs/sdks/c-reference/common-patterns.md) and [Common Gotchas](https://thru.org/docs/sdks/c-reference/common-gotchas.md).

**SDK Imports**

When developing programs using the SDK, include these essential headers:

- `<thru-sdk/c/tn_sdk.h>` - Core SDK functions and macros
- `<thru-sdk/c/tn_sdk_syscall.h>` - System call functions (optional, for advanced operations)

The [Headers and Entry Points](https://thru.org/docs/sdks/c-reference/headers-and-entry-points.md) 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](https://thru.org/docs/sdks/c-reference/build-integration.md).

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](https://thru.org/docs/sdks/c-reference/program-structure.md).

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 <thru-sdk/c/tn_sdk.h>

/* 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](https://thru.org/docs/sdks/c-reference/headers-and-entry-points.md), [State Proofs](https://thru.org/docs/sdks/c-reference/state-proofs.md), and [Common Gotchas](https://thru.org/docs/sdks/c-reference/common-gotchas.md) 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 <stddef.h>
#include <thru-sdk/c/tn_sdk.h>
#include <thru-sdk/c/tn_sdk_syscall.h>
#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](https://thru.org/docs/sdks/c-reference/accounts-and-transaction-context.md) for `tsdk_get_txn()` and `tsdk_get_account_data_ptr(...)`
- [Syscalls](https://thru.org/docs/sdks/c-reference/syscalls.md) for `tsys_account_create(...)`, `tsys_account_resize(...)`, `tsys_set_account_data_writable(...)`, and `tsys_emit_event(...)`
- [Common Patterns](https://thru.org/docs/sdks/c-reference/common-patterns.md) 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](https://thru.org/docs/sdks/c-reference/build-integration.md).

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](https://thru.org/docs/program-development/program-development-lifecycle.md). For exact CLI flags and related subcommands, see the [Program](https://thru.org/docs/cli-reference/program-commands.md) 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…<program id>
   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…<program id>
   ```

2. **Note Your Program Accounts**

   Save the program account addresses from the output:

   - **Program account**: `ta…<program id>`

   > **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…<program id>
   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](https://thru.org/docs/cli-reference/program-commands.md) 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> <INSTRUCTIONS>`:

- `<PROGRAM>` - Program account address (becomes index 1 in the account array)
- `<INSTRUCTIONS>` - 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](https://thru.org/docs/sdks/c-reference/accounts-and-transaction-context.md).

## 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](https://thru.org/docs/sdks/c-reference/state-proofs.md), [Headers and Entry Points](https://thru.org/docs/sdks/c-reference/headers-and-entry-points.md), and [Common Gotchas](https://thru.org/docs/sdks/c-reference/common-gotchas.md).

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 <YOUR_SEED>`

   ```bash
   thru program derive-address ta…<program id> count_acc
   ```

   Output:

   ```plaintext
   Program ID: ta…<program id>
   Seed: count_acc
   Ephemeral: false
   Derived Address: ta…<PDA account>
   ```

   > **Note:**
   >
   > The derived address is deterministic - the same program and seed will always generate the same account address.
   >
   > The [Program](https://thru.org/docs/cli-reference/program-commands.md) 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…<PDA account>
   ```

   Output:

   ```plaintext
   Success: State proof created successfully
   Account: ta…<PDA account>
   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](https://thru.org/docs/sdks/c-reference/state-proofs.md) 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](https://thru.org/docs/sdks/c-reference/common-gotchas.md) 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](https://thru.org/docs/cli-reference/program-commands.md) 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 <derived_account>`

   Concatenate in order:

   ```text
   00000000 + 0200 + <seed_hex_32_bytes> + 68000000 + <proof_bytes>
   ```

   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](https://thru.org/docs/program-development/program-development-lifecycle.md). For exact transaction flags and output fields, see [Transaction](https://thru.org/docs/cli-reference/transaction-commands.md).

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 <account>` - The program-derived address where the counter will be created/stored
   - `<program_address>` - Your deployed program account
   - `<instruction_data>` - The hex-encoded instruction data you constructed

   Send the create counter transaction using the CLI:

   ```bash
   thru txn execute \
     --fee 0 \
     --readwrite-accounts ta…<PDA account> \
     ta…<program id> \
     000000000200636F756E745F6163630000000000000000000000000000000000000000000000680000003502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
   ```

   > **Caution:**
   >
   > **Important**: The `--readwrite-accounts` parameter uses `ta…<PDA account>` - 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…<PDA account> \
     ta…<program id> \
     010000000200
   ```

   > **Note:**
   >
   > **Reminder**: We use the same `—readwrite-accounts ta…<PDA account>` 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.
