---
title: Cross-Program Invocation
description: Use tsys_invoke and tsdk_invoke_auth_t safely for CPI flows in C.
source_url:
  html: https://thru.org/docs/sdks/c-reference/cross-program-invocation/
  md: https://thru.org/docs/sdks/c-reference/cross-program-invocation.md
---

# Cross-Program Invocation

Use this page when you need to call another program from a C program and want the smallest correct model for account auth, deauth, and error handling.

## Main entrypoint

```c
ulong
tsys_invoke( void const * instr_data,
             ulong        instr_data_sz,
             ushort       program_account_idx,
             tsdk_invoke_auth_t const * auth,
             ulong *      invoke_err_code );
```

## What each argument means

| Argument | Meaning |
| - | - |
| `instr_data` | Pointer to the callee instruction payload. |
| `instr_data_sz` | Payload size in bytes. |
| `program_account_idx` | Transaction account index of the program being invoked. |
| `auth` | Optional authorization descriptor for CPI account auth and deauth rules. |
| `invoke_err_code` | Optional output for the callee’s own error code. |

## `tsdk_invoke_auth_t` layout

The public auth descriptor is a header plus a flexible array:

| Field | Meaning |
| - | - |
| `magic` | Must equal `TSDK_INVOKE_AUTH_MAGIC`. |
| `auth_cnt` | Number of authorized account indices at the start of `acc_idxs`. |
| `deauth_cnt` | Number of deauthorized account indices after the auth entries. |
| `acc_idxs[]` | First `auth_cnt` authorized indices, then `deauth_cnt` deauthorized indices. |

## Validation the wrapper performs before CPI

If `auth` is non-null, the wrapper checks:

- the magic value is correct
- every auth entry references a valid account index
- every auth entry is owned by the current program
- every deauth entry references a valid account index

If any of those checks fail, the wrapper reverts before issuing the syscall.

## Minimal no-auth pattern

```c
ulong invoke_err = 0UL;
ulong invoke_result = tsys_invoke( payload,
                                   payload_sz,
                                   target_program_idx,
                                   NULL,
                                   &invoke_err );

if( invoke_result != TSDK_SUCCESS ) tsdk_revert( invoke_result );
if( invoke_err    != TSDK_SUCCESS ) tsdk_revert( invoke_err );
```

## Minimal auth descriptor pattern

```c
struct my_invoke_auth {
  ulong  magic;
  ushort auth_cnt;
  ushort deauth_cnt;
  ushort acc_idxs[1];
};

struct my_invoke_auth auth = {
  .magic = TSDK_INVOKE_AUTH_MAGIC,
  .auth_cnt = 1U,
  .deauth_cnt = 0U,
  .acc_idxs = { writable_account_idx },
};
```

Pass `&auth` as the `auth` argument to `tsys_invoke`.

## How authorization actually resolves

The current SDK’s authorization helpers walk the shadow stack from the most recent parent frame downward:

- deeper frames override shallower ones
- deauth entries are checked before auth entries
- auth entries are only accepted if the invoking frame’s program owns the account
- fee payer and current program remain implicitly authorized

## CPI checklist

Before calling `tsys_invoke`, verify:

- `program_account_idx` is valid
- the callee payload layout matches what the target program expects
- every account index embedded in the payload matches the transaction ordering
- any auth entries refer only to accounts owned by the current program
- you handle both `invoke_result` and `invoke_err`

## Notes

- For most first-pass CPI work, start with `auth = NULL`; only add explicit auth when the callee needs it.
- Do not collapse the two error channels from `tsys_invoke` into one check.
- The easiest CPI bugs come from mismatched account indices, not from the syscall callsite itself.
