Skip to main content
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

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

ArgumentMeaning
instr_dataPointer to the callee instruction payload.
instr_data_szPayload size in bytes.
program_account_idxTransaction account index of the program being invoked.
authOptional authorization descriptor for CPI account auth and deauth rules.
invoke_err_codeOptional output for the callee’s own error code.

tsdk_invoke_auth_t layout

The public auth descriptor is a header plus a flexible array:
FieldMeaning
magicMust equal TSDK_INVOKE_AUTH_MAGIC.
auth_cntNumber of authorized account indices at the start of acc_idxs.
deauth_cntNumber 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

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

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.