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
Section titled “Main entrypoint”ulongtsys_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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “CPI checklist”Before calling tsys_invoke, verify:
program_account_idxis 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_resultandinvoke_err
- 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_invokeinto one check. - The easiest CPI bugs come from mismatched account indices, not from the syscall callsite itself.