Skip to content

Cross Program Invocation

View as Markdown

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.

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.

programs/c/examples/tn_cpi_stub_program.h
#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 */

This entrypoint validates indices, builds a tiny buffer, and forwards it. Swap the buffer layout for your real instruction body.

programs/c/examples/tn_cpi_stub_program.c
#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 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 );
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 );
}
  • 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_invoke receives the buffer pointer, its length (offset), the target program account index, and an optional invoke-auth descriptor (NULL in this minimal example). Both syscall errors (invoke_result) and callee errors (invoke_err) are bubbled up with tsdk_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.