Skip to content

Quickstart: Build a C Program

View as Markdown

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.

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

Before you begin, complete the Thru RPC Developer Kit setup guide so the CLI, C SDK, and toolchain are installed. You should be comfortable with basic C programming.

Before diving into program structure, it’s important to understand the fundamental concepts of Thru program development.

Entry Point Function

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 and 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 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

Use tsdk_return() for successful completion:

tsdk_return(TSDK_SUCCESS);

Use tsdk_revert() to terminate with an error:

tsdk_revert(error_code);

For the full control-flow and error model, including how tsdk_return() and tsdk_revert() terminate execution, see Program Structure and Error Handling and Return Codes.

Input Validation

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 and Common Gotchas.

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 reference explains when to include each header and which surfaces are safe to treat as your default entrypoint.

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:

    Terminal window
    mkdir -p my-thru-project/examples
    cd my-thru-project
  2. Create Build Configuration

    Create a GNUmakefile in your project root:

    GNUmakefile
    # 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
  3. Create Program Build Configuration

    Create a Local.mk file in your examples directory:

    examples/Local.mk
    # My Thru C SDK Counter Program
    $(call make-bin,tn_counter_program_c,tn_counter_program,,-ltn_sdk)

Your project structure should now look like this:

my-thru-project/
├── GNUmakefile
└── examples/
└── Local.mk

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

If you want the runtime-level model behind this split, see Program Structure.

We’ll create both files in the examples/ directory.

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
#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, State Proofs, and Common Gotchas pages go deeper on packed structs, header boundaries, and dynamic trailing payloads like proof_data.

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
#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 for tsdk_get_txn() and tsdk_get_account_data_ptr(...)
  • Syscalls for tsys_account_create(...), tsys_account_resize(...), tsys_set_account_data_writable(...), and tsys_emit_event(...)
  • Common Patterns for safe size checks before casting instruction buffers

Your project structure is now complete:

my-thru-project/
├── GNUmakefile
└── examples/
├── Local.mk
├── tn_counter_program.h
└── tn_counter_program.c

With your program files in place, build the program:

  1. Build the Program

    Run the build from your project root:

    Terminal window
    make
  2. Verify Build Output

    Confirm the binary exists:

    Terminal window
    ls build/thruvm/bin/

    You should see tn_counter_program_c.bin in the output directory.

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. For exact CLI flags and related subcommands, see the Program reference page.

  1. Create Program on Network

    Upload and create your program using a unique seed name:

    Terminal window
    thru program create $SEED $PATH_TO_PROGRAM_BINARY

    For our counter example:

    Terminal window
    thru program create thru_program ./build/thruvm/bin/tn_counter_program_c.bin

    You’ll see output similar to this:

    Terminal window
    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>

When you need to modify and redeploy your program:

  1. Make Code Changes and Rebuild

    After making changes to your program code, rebuild it:

    Terminal window
    make
  2. Upgrade the Deployed Program

    Use the same seed to upgrade your existing program:

    Terminal window
    thru program upgrade $SEED $PATH_TO_PROGRAM_BINARY

    For our counter example:

    Terminal window
    thru program upgrade thru_program ./build/thruvm/bin/tn_counter_program_c.bin

    You’ll see output similar to this:

    Terminal window
    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

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.

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.

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.

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:

/* 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 */

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.

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

This section intentionally stays tutorial-focused. For deeper reference material on proof bytes, buffer layout, and packed struct handling, see State Proofs, Headers and Entry Points, and 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 <YOUR_SEED>

    Terminal window
    thru program derive-address ta…<program id> count_acc

    Output:

    Program ID: ta…<program id>
    Seed: count_acc
    Ephemeral: false
    Derived Address: ta…<PDA account>
  2. Create State Proof

    Generate a state proof for the account you want to create:

    Terminal window
    thru txn make-state-proof creating ta…<PDA account>

    Output:

    Success: State proof created successfully
    Account: ta…<PDA account>
    Proof Type: creating
    Proof Size: 104 bytes
    Proof Data (hex): 3502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
  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:

    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;

    See 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:

    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 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:

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

    Example final hex string:

    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:

    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

    Value: 1 → 01000000

    2. Account Index (2): ushort in little endian

    Value: 2 → 0200

    Complete Instruction Data:

    010000000200

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. For exact transaction flags and output fields, see Transaction.

  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:

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

    Output:

    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
  2. Execute Increment Counter Transaction

    After successfully creating a counter, you can increment it:

    Terminal window
    thru txn execute \
    --fee 0 \
    --readwrite-accounts ta…<PDA account> \
    ta…<program id> \
    010000000200

    Output:

    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

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