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.
What We’re Building
Section titled “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:
- Create: Initialize a new counter account with value 0
- Increment: Increase an existing counter’s value by 1
Prerequisites
Section titled “Prerequisites”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.
Core Concepts
Section titled “Core Concepts”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.
Setting Up Your Project
Section titled “Setting Up Your Project”Before writing any code, let’s set up the project structure and build configuration.
-
Create Project Directory
Create a directory for your program project:
Terminal window mkdir -p my-thru-project/examplescd my-thru-project -
Create Build Configuration
Create a
GNUmakefilein your project root:GNUmakefile # Simple makefile for my Thru projectBASEDIR:=$(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-sdkinclude $(THRU_C_SDK_DIR)/thru_c_program.mk -
Create Program Build Configuration
Create a
Local.mkfile 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.mkProgram Structure
Section titled “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
If you want the runtime-level model behind this split, see Program Structure.
We’ll create both files in the examples/ directory.
Create the Header File
Section titled “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:
#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;
#endifKey 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_sizefield: 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 the Implementation File
Section titled “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:
#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
startfunction 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()andtsdk_get_account_data_ptr(...) - Syscalls for
tsys_account_create(...),tsys_account_resize(...),tsys_set_account_data_writable(...), andtsys_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.cBuilding Your Program
Section titled “Building Your Program”With your program files in place, build the program:
-
Build the Program
Run the build from your project root:
Terminal window make -
Verify Build Output
Confirm the binary exists:
Terminal window ls build/thruvm/bin/You should see
tn_counter_program_c.binin the output directory.
Deploying Your Program
Section titled “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. For exact CLI flags and related subcommands, see the Program reference page.
-
Create Program on Network
Upload and create your program using a unique seed name:
Terminal window thru program create $SEED $PATH_TO_PROGRAM_BINARYFor our counter example:
Terminal window thru program create thru_program ./build/thruvm/bin/tn_counter_program_c.binYou’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_programInfo: Step 1: Uploading program to temporary buffer (seed: thru_program_temporary)Info: Checking for existing upload state...Info: No existing upload foundInfo: Starting fresh uploadInfo: Creating meta and buffer accounts...Success: Transaction completed: tsBBvaXmtyKDn95pAeo03DEiHlobsAc5t80NoFKqFzEwd_5q2PoGTdXNl9gvNU_PmFsSWy168mFcmRMmhL2VsICB8hInfo: Writing chunk 1/1 (778 bytes) at offset 0Success: Transaction completed: ts5ubIqevoNhioFKswmUolG-7-umnXZwNhTFLD00XQFu05xhspnZK4A6JpgwHr80kW4-fMHHbG44skZzCzZSb1CCBgInfo: Finalizing upload...Success: Transaction completed: tsXzyg8MIISD4keoNHaTsxEruagay7Q9vcDs69FyDKpjOgiUgrpr5IZLdfesX9n-si-CoFn0bLMJara156AmsADR0jSuccess: Program uploaded to temporary buffer successfullyInfo: Temporary meta account: taOxQq4ms4bF2lxs4gM1tkZS90q2ACVo9xvuw-UTzAOO3XInfo: Temporary buffer account: taqrAKYvM6KxZMxotJzaEUhkNQxum-AGr4OKrgN6uCvMMpInfo: Step 2: Creating managed program from temporary bufferInfo: Fee payer: tazLZyk2wT3WO1-_vgH2iqNi0nayD5z2jfFcK10hgrSpanInfo: Manager program meta account: ta8GX2vn4xeY-hGHgrnARdDhz9q3EtcKMnQYOCAtMOza9oInfo: Manager program account: ta…<program id>Info: Creating state proofs for permanent program...Success: Transaction completed: tsq6iZBJwRmShKrVZXl0TrzdVam6LCQ1GdyXXpMuOmfvfpq9BhhHkqwx1jwj3ud4l1V4hxxxoZR1fd94z2UTlYCSEFSuccess: Managed program created successfullyInfo: Step 3: Cleaning up temporary buffer accountInfo: Cleaning up program accounts...Info: Executing cleanup transaction...Success: Transaction completed: ts12OV8Y2p6c-UVaqctw2exA2GOdro7CuXSr5fqLEKMlEWrrilxj-1YuaGsRbtdMpyln5wMuy456OcLjvZRrxLDCK0Success: Temporary buffer account cleaned up successfullySuccess: Permanent managed program created successfullyInfo: Meta account: ta8GX2vn4xeY-hGHgrnARdDhz9q3EtcKMnQYOCAtMOza9oInfo: Program account: ta…<program id> -
Note Your Program Accounts
Save the program account addresses from the output:
- Program account:
ta…<program id>
- Program account:
Updating Your Program
Section titled “Updating Your Program”When you need to modify and redeploy your program:
-
Make Code Changes and Rebuild
After making changes to your program code, rebuild it:
Terminal window make -
Upgrade the Deployed Program
Use the same seed to upgrade your existing program:
Terminal window thru program upgrade $SEED $PATH_TO_PROGRAM_BINARYFor our counter example:
Terminal window thru program upgrade thru_program ./build/thruvm/bin/tn_counter_program_c.binYou’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_programInfo: Step 1: Uploading program to temporary buffer (seed: thru_program_upgrade_temporary)Info: Checking for existing upload state...Info: No existing upload foundInfo: Starting fresh uploadInfo: Creating meta and buffer accounts...Success: Transaction completed: tsVAIY50dLC3vjkBC0y08yeoXcoa3M6Mzb9jG6pscofprvMs6SEvYUNOZuXJb0Xx1_1kPh_wVMpRMnlukmvgduCiATInfo: Writing chunk 1/1 (778 bytes) at offset 0Success: Transaction completed: tsN2k0h3HS4G8LW_LYYzAumXLYVOfRA4cXiQHFVVjb__kXKjxAM4BF_dMQasIvXKHT8PlGE673B3nS_os_VnObCR9rInfo: Finalizing upload...Success: Transaction completed: tso9ORuCIqpr2hZBgtBPC1aKZrFHP8mdbWrCr849R4LcSOLcQmJyR7foDbsgoM6lsrCK8jM_5JtQ5DOrw-t0_CCh5wSuccess: Program uploaded to temporary buffer successfullyInfo: Temporary meta account: taiMvWgn2SAj5ZHmMCYaja5VzOu-8MmZBTujAVDLdcXYL3Info: Temporary buffer account: taLsLf6_wdbPXMklS2Ss3VqJNCHnqRSy1CXzb0ZdNwtTH_Info: Step 2: Upgrading managed program from temporary bufferInfo: Fee payer: tazLZyk2wT3WO1-_vgH2iqNi0nayD5z2jfFcK10hgrSpanInfo: Manager program meta account: ta8GX2vn4xeY-hGHgrnARdDhz9q3EtcKMnQYOCAtMOza9oInfo: Manager program account: ta…<program id>Success: Transaction completed: tse-0ldYMRrs45XaDbDTfSnFIXtuDQdY_pd5kDUyrf0iJkZE1u-l8KQntTgWlNdx-QsAPInqINLfjru7UtDHMQBR1QSuccess: Managed program upgraded successfullyInfo: Step 3: Cleaning up temporary buffer accountInfo: Cleaning up program accounts...Info: Executing cleanup transaction...Success: Transaction completed: tsfQJTncHpt-_lFf0aH6Wefparw7_59NQEv6dQ3kDLJuLNW9-_6nOC0El1NOgNr7854zGE9yCM4Kw7hbTwvC-OASSHSuccess: Temporary buffer account cleaned up successfullySuccess: Program upgrade completed! New program size: 778 bytes
Interacting with Your Deployed Program
Section titled “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
Section titled “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:
- Fee Payer Account - The account paying transaction fees (always at index 0)
- Program Account - Your deployed program (always at index 1)
- Writable Accounts - Accounts the program can modify (sorted in ascending order by hex key)
- 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:
- Use a unique seed to create a counter account
- Calculate the resulting account address from the seed
- 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.
Interacting with Your Program
Section titled “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
Section titled “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:
/* 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 *//* 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 creationproof_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
Section titled “Constructing Instruction Data”To interact with your deployed counter program, you need to construct properly formatted instruction data. This involves three main steps:
- Derive the account address where your counter will be stored
- Generate a state proof to verify account creation
- 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.
-
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_accOutput:
Program ID: ta…<program id>Seed: count_accEphemeral: falseDerived Address: ta…<PDA account> -
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 successfullyAccount: ta…<PDA account>Proof Type: creatingProof Size: 104 bytesProof Data (hex): 3502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -
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 bytesushort account_index; // 2 bytesuchar counter_program_seed[TN_SEED_SIZE]; // 32 bytesuint 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_bytesEncode the fields in this order:
-
instruction_type, create-counter instruction identifier. This is explicitly set by the example counter program.- Value:
0(for creation) - Format:
uintlittle-endian:00000000
- Value:
-
account_index, first account in the read/write account list- Value:
2(idx 0 is fee-payer, idx 1 = program address) - Format:
ushortlittle-endian:0200
- Value:
-
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-hexand related derivation helpers. - Value:
-
proof_size, number of proof bytes appended after the fixed-size fields. This is part of the output ofthru txn make-state-proof creating.- Value:
104 - Format:
uintlittle-endian:68000000
- Value:
-
proof_bytes, appended exactly as returned by the CLI- Value: output of
thru txn make-state-proof creating <derived_account>
- Value: output of
Concatenate in order:
00000000 + 0200 + <seed_hex_32_bytes> + 68000000 + <proof_bytes>Example final hex string:
000000000200636f756e745f6163630000000000000000000000000000000000000000000000680000003502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -
-
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 bytesushort account_index; // 2 bytes} tn_counter_increment_args_t;1. Instruction Type (INCREMENT = 1):
uintin little endianValue: 1 → 010000002. Account Index (2):
ushortin little endianValue: 2 → 0200Complete Instruction Data:
010000000200
Sending Transactions
Section titled “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. For exact transaction flags and output fields, see Transaction.
-
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> \000000000200636F756E745F6163630000000000000000000000000000000000000000000000680000003502000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000Output:
Success: Transaction executed successfullySignature: tsWCCCBvTAx4pMkYbeW8hMrf6cPax6ujQ7o_uGV378qS_lm25sN2tbMXvfrL-5ouYObwCMYpgvi1AEumc_fFeRAh-6Slot: 567Compute Units Consumed: 7524State Units Consumed: 1Execution Result: 0VM Error: 0User Error Code: 0Events Count: 0Events Size: 0Pages Used: 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> \010000000200Output:
Success: Transaction executed successfullySignature: ts0dxkQCkCr2pGwhchAD5ub_OfYll1gjCemMkxLl2xHWD0QvkrzQpKFzdBRSI28855HvMoptHBIExh3_37xKE0ABzcSlot: 568Compute Units Consumed: 5980State Units Consumed: 0Execution Result: 0VM Error: 0User Error Code: 0Events Count: 1Events Size: 18Pages Used: 3Events:Event 1: call_idx=0, program_idx=1Data (hex): 0100000000000000
Next Steps
Section titled “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