A cross-program invocation lets a running Solana program invoke an instruction in another program. The caller passes accounts and permitted privileges; the callee applies its own rules. PDA signer privilege can be added through invoke_signed, and the calls share the transaction’s compute budget.
A program can reuse another program’s operation
A wallet normally submits top-level instructions in a transaction. While executing one of them, a program can invoke another program's instruction. That nested call is a CPI.
For a vault withdrawal, an application can validate the withdrawal request and then invoke the token program to transfer tokens. It reuses the token program's transfer rules rather than directly rewriting token-account data.
A DeFi route can compose several such operations. The programs, accounts and instruction data determine what happens. A single wallet approval can therefore authorize a transaction with substantial nested behaviour.
Read the transaction as a call tree. A top-level application label alone does not describe every asset movement or authority used beneath it.
Pass the accounts and privileges the callee needs
The caller supplies the target instruction and required account information. A writable account permits the relevant modification under runtime and program rules. Signer privilege proves an authority condition where the callee requires it.
A callee cannot simply promote an ordinary readonly account into a writable one. Privileges come from the calling context, with the supported PDA signer mechanism as a specific addition.
In the vault example, the source and recipient token accounts must be valid for the intended transfer. The application should validate the expected token program, mint and authority relationship before invoking it.
Checking a known program ID matters when the caller expects particular code semantics. Letting a user replace the intended callee with arbitrary code can break assumptions about what the nested instruction does.
Authorize a vault through invoke_signed
Suppose the vault's stored token authority is a PDA derived by the application. No wallet holds a private key for that address. The application supplies matching seeds to invoke_signed.
The runtime checks the derivation using the calling program's ID and adds the matching signer privilege for the call. The token program then checks whether that authority may perform the requested transfer.
This mechanism does not decide whether Alice deserves the withdrawal. The application must verify her claim, the allowed amount and recipient before requesting the transfer.
Keep data ownership separate: the token program owns the vault account data, while the application's PDA supplies authority for a supported token operation.
Nested work uses the shared compute budget
Nested work consumes the shared execution budget
The callee's computation consumes the shared transaction budget; CPI does not create a free second budget. Nested calls also have invocation and data-handling costs.
For a chosen teaching budget of 200,000 CU, if earlier work and invocation overhead have consumed 30,000, only 170,000 remain for subsequent execution under that simplified accounting. These are illustrative allocations, not a measured cost of the example vault.
Call depth and resource limits depend on the active runtime features. Avoid copying one old depth limit as a permanent property across clusters. Use current limits when constructing and testing a specific program.
Simulation logs and consumed-unit fields help identify where a composed transaction runs out of compute. Optimize the actual measured path rather than assuming that each program's isolated cost simply sums without overhead.
An earlier successful call can still be rolled back
A nested call can succeed locally and print a success log. If a later instruction makes the transaction fail, the transaction's ordinary application-state changes do not commit.
For example, a composed action transfers tokens through CPI and then fails its final account check. The earlier transfer's success log does not establish that the recipient kept those tokens. Inspect the final execution error and resulting state.
Fees and special protocol handling such as durable nonce advancement need their own rules. Transaction atomicity is not a promise that a failed attempt has no cost or bookkeeping effect.
Some programs may handle an error returned from a nested call rather than propagating it. Trace the actual caller behaviour and final transaction result instead of treating every inner error or log as the whole outcome.
Read a call tree during debugging
Start with the transaction's final error and the top-level instruction index. Read logs in execution order, pairing invoke and return messages to understand nesting. Resolve account addresses using the actual transaction format.
Inspect inner instructions where available. Compare expected source, destination, amount and authority with what the program actually invoked. Program logs are diagnostic output, so validate important financial outcomes against accepted state.
Test incorrect authority, wrong mint, readonly accounts and a failure after an earlier successful CPI. Those cases exercise the boundary between your application checks and the callee's checks.
The simulation guide shows how to inspect execution before broadcasting without treating a preview as a settlement guarantee.
Common questions
Is a CPI another transaction?
It is a nested instruction invocation during the current transaction’s execution. It shares that transaction’s resource and commit context.
Can the callee make an account writable by itself?
It cannot arbitrarily escalate readonly privileges. The runtime checks the permissions passed through the calling context.
Does an inner success log prove a completed transfer?
No. Check the final transaction result and accepted state; a later failure can roll back earlier application changes.
Sources and documentation
- Solana cross-program invocations
- Solana program derived addresses
- Solana instructions
- Account modification rules
- Solana transactions
- simulateTransaction
- getTransaction
Found an error or an outdated detail? Send a correction.
