Cross Program Invocation
This tutorial will give you a tiny, working scaffold that shows how to package instruction data and forward it to another program with tsys_invoke. The example compiles but leaves business logic as placeholders so you can extend it for your real payloads.
Prerequisites
Section titled “Prerequisites”- Thru C SDK installed (see Quickstart: Build a C Program)
- Basic familiarity with transaction account indexing (
0= fee payer,1= current program,2+= user-supplied accounts)
Minimal instruction shape (CPI stub)
Section titled “Minimal instruction shape (CPI stub)”The header defines a pared-down instruction struct: only indices plus a small payload that you can repurpose. Add or replace fields to match your program.
#ifndef HEADER_tn_programs_c_examples_tn_cpi_stub_program_h#define HEADER_tn_programs_c_examples_tn_cpi_stub_program_h
#include <thru-sdk/c/tn_sdk.h>
#define TN_CPI_STUB_INSTRUCTION (0U) /* Tag understood by the callee */
#define TN_CPI_STUB_ERR_INVALID_INPUT (1UL)#define TN_CPI_STUB_ERR_UNSUPPORTED (2UL)
/* Placeholder-friendly instruction forwarded to another program. */typedef struct __attribute__((packed)) { uint instruction_type; ushort target_program_account_idx; ushort mint_account_idx; ushort authority_account_idx; uchar payload[8]; /* Replace with your real data */} tn_cpi_stub_args_t;
#endif /* HEADER_tn_programs_c_examples_tn_cpi_stub_program_h */Minimal entrypoint with tsys_invoke
Section titled “Minimal entrypoint with tsys_invoke”This entrypoint validates indices, builds a tiny buffer, and forwards it. Swap the buffer layout for your real instruction body.
#include <stddef.h>#include <string.h>
#include <thru-sdk/c/tn_sdk.h>#include <thru-sdk/c/tn_sdk_syscall.h>
#include "tn_cpi_stub_program.h"
TSDK_ENTRYPOINT_FN voidstart( 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 );
if( instruction_data_sz < sizeof( tn_cpi_stub_args_t ) ) { tsdk_revert( TN_CPI_STUB_ERR_INVALID_INPUT ); }
tn_cpi_stub_args_t const * args = (tn_cpi_stub_args_t const *)instruction_data;
if( !tsdk_is_account_idx_valid( args->target_program_account_idx ) || !tsdk_is_account_idx_valid( args->mint_account_idx ) || !tsdk_is_account_idx_valid( args->authority_account_idx ) ) { tsdk_revert( TN_CPI_STUB_ERR_INVALID_INPUT ); }
/* Minimal payload: tag + mint index + authority index + one byte */ uchar token_instr[1 + sizeof( ushort ) + sizeof( ushort ) + sizeof( uchar )]; ulong offset = 0UL;
token_instr[offset] = TN_CPI_STUB_INSTRUCTION; offset++; memcpy( token_instr + offset, &args->mint_account_idx, sizeof( ushort ) ); offset += sizeof( ushort ); memcpy( token_instr + offset, &args->authority_account_idx, sizeof( ushort ) ); offset += sizeof( ushort ); token_instr[offset] = args->payload[0]; offset++;
ulong invoke_err; ulong invoke_result = tsys_invoke( token_instr, offset, args->target_program_account_idx, NULL, &invoke_err );
if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result ); if( invoke_err != TSDK_SUCCESS ) tsdk_revert( invoke_err );
tsdk_return( TSDK_SUCCESS );}How the pieces fit
Section titled “How the pieces fit”- Payload layout: The buffer starts with a single-byte tag (
TN_CPI_STUB_INSTRUCTION), followed by the two indices and the first payload byte. Replace this layout with your own wire format. - Invocation:
tsys_invokereceives the buffer pointer, its length (offset), the target program account index, and an optional invoke-auth descriptor (NULLin this minimal example). Both syscall errors (invoke_result) and callee errors (invoke_err) are bubbled up withtsdk_revert. - Account ordering reminder: idx0 is the fee payer, idx1 is the current program; the rest are whatever you supply in the transaction. Make sure the indices you place in the struct match the order in the transaction header.