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
- Create: Initialize a new counter account with value 0
- Increment: Increase an existing counter’s value by 1
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
Before diving into program structure, it’s important to understand the fundamental concepts of Thru program development. Entry Point Functionstart(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(...)
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.tsdk_return() for successful completion:
tsdk_revert() to terminate with an error:
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)
Setting Up Your Project
Before writing any code, let’s set up the project structure and build configuration.Create Build Configuration
Create a
GNUmakefile in your project root:GNUmakefile
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.Create Program Build Configuration
Create a
Local.mk file in your examples directory:examples/Local.mk
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.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
examples/ directory.
Create the Header File
Createexamples/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
- 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
proof_data.
Create the Implementation File
Createexamples/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
- 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
- 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
Building Your Program
With your program files in place, build the program:Build the Program
Run the build from your project root:
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.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. 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:For our counter example:You’ll see output similar to this:
Note Your Program Accounts
Save the program account addresses from the output:
- Program account:
ta…<program id>
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
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:Make Code Changes and Rebuild
After making changes to your program code, rebuild it:
Always rebuild your program after making code changes to ensure the binary file contains your latest updates.
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:- 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)
thru-cli 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)
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
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: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
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:- 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
The process below shows how to create a new counter. Once created, incrementing is much simpler (just instruction type + account index).
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: Output:
thru-cli program derive-program-account <YOUR_SEED>The derived address is deterministic - the same program and seed will always generate the same account address.The Program reference documents the related derivation helpers in more detail.
Create State Proof
Generate a state proof for the account you want to create:Output:
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 reference explains what the proof bytes represent and how to reason about them in C programs.
Construct Create Counter Instruction Data
Now construct the instruction data by encoding each field according to the struct definition. Recall our create instruction structure: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:Encode the fields in this order:Example final hex string:
-
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-cli program seed-to-hex count_acc - Result:
636f756e745f6163630000000000000000000000000000000000000000000000
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-cli 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-cli txn make-state-proof creating <derived_account>
- Value: output of
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:Output:
thru-cli 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
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.🎉 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
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
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.