---
title: Cross Program Invocation
description: Use a minimal, compile-ready skeleton to wrap an instruction and
  forward it with tsys_invoke.
source_url:
  html: https://thru.org/docs/program-development/cross-program-invocation/
  md: https://thru.org/docs/program-development/cross-program-invocation.md
---

# 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

- Thru C SDK installed (see [Quickstart: Build a C Program](https://thru.org/docs/program-development/building-a-c-program.md))
- Basic familiarity with transaction [account indexing](https://thru.org/docs/core-concepts/accounts.md) (`0` = fee payer, `1` = current program, `2+` = user-supplied accounts)

## 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.

**programs/c/examples/tn\_cpi\_stub\_program.h**

```c
#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

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**

```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 );
}
```

## 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_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.

> **Caution:**
>
> This example is deliberately minimal and does not perform real work. Always align your payload and account indices with the program you are calling, and validate any variable-length data before forwarding it.
