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
- Thru RPC Developer Kit - Follow the setup guide to install the RPC client and CLI tools
- Thru C SDK - Download
thru-program-sdk-c-{version}.tar.gzfrom GitHub releases - RISC-V Toolchain - Required for compiling programs (installed via the C SDK’s
deps.shscript) - Basic understanding of C programming
Core Concepts
Before diving into program structure, it’s important to understand the fundamental concepts of Thru program development. Entry Point FunctionEvery 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.instruction_data: Raw instruction data passed to your Thru programinstruction_data_sz: Size of the instruction data in bytes
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:
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.
<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.1
Create Project Directory
Create a directory for your program project:
2
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.3
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
While two files is the minimum requirement, larger programs may include additional source files, utility modules, or other dependencies as needed.
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
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
Building Your Program
With your program files in place, build the program:1
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.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:1
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:
2
Note Your Program Accounts
Save the program account addresses from the output:
- Program account:
taj9LZ-hhRGi3iBWxaOsXQpO_Lj-tWioGxY_nleuwbpga4
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:1
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.
2
Upgrade the Deployed Program
Use the same seed to upgrade your existing program:For our counter example:You’ll see output similar to this:
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.
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 creation
- Create Counter:
instruction_type = 0+account_index+counter_program_seed[32]+proof_size+proof_data[proof_size] - Increment Counter:
instruction_type = 1+account_index
The create instruction is more complex because it includes a variable-length proof that follows the fixed-size struct. The proof data is not part of the struct definition but follows it dynamically based on the
proof_size field.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).
1
Derive Account Address
First, derive the account address where your counter will be stored using the program address and a seed:Output:
The derived address is deterministic - the same program and seed will always generate the same account address.
2
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.
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:1. Instruction Type (CREATE = 0): 2. Account Index (2): 3. Counter Program Seed: 32-byte array, zero-padded4. Proof Size (104 bytes): 5. Proof Data: 104 bytes from state proofComplete Instruction Data: Concatenate all componentsFinal hex string:
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.For Production Use: While we show manual hex construction here for educational purposes, you’d typically use a client library or SDK to handle this encoding automatically. This detailed breakdown helps you understand what happens under the hood.
uint in little endianushort in little endianAccount index 2 refers to the first writable account in the transaction (index 0 = fee payer, index 1 = program, index 2+ = other accounts).
uint in little endianThe
proof_size field indicates the size of the cryptographic proof needed to verify the account in the state tree. If proof_size = 0, no proof is needed.4
Increment Counter Instruction
For incrementing an existing counter, the instruction is much simpler as it only needs the instruction type and account index:1. Instruction Type (INCREMENT = 1): 2. Account Index (2): Complete Instruction Data:
uint in little endianushort in little endianSending Transactions
Once you have constructed your instruction data, you can send transactions to interact with your deployed counter program.1
Execute Create Counter Transaction
Send the create counter transaction using the CLI:Output:Command Structure:
Important: The
--readwrite-accounts parameter uses tagEPj9hCW8Z9ehMoyJ4UR7faJggQhXb56_8kGxLeitJ1R - 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.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’s account address<instruction_data>- The hex-encoded instruction data we 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.2
Execute Increment Counter Transaction
After successfully creating a counter, you can increment it:Output:
Reminder: We use the same
--readwrite-accounts tagEPj9hCW8Z9ehMoyJ4UR7faJggQhXb56_8kGxLeitJ1R because we’re modifying the same counter account that was created in the previous step.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.🎉 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.