Cross-Program Invocation (CPI)

The mechanism letting one program call another mid-transaction, like a DEX invoking the token program to move funds, which powers the deep composability that lets Solana apps snap together like Lego bricks.

What is Cross-Program Invocation (CPI)?

If you look at a Solana transaction, a Cross-Program Invocation (CPI) occurs when the program executing within that transaction chooses to make calls to other programs. Rather than implementing token transfers or pool math from scratch, the program invokes a third-party program for specific operations. For example, an escrow program will CPI into the SPL Token program to transfer tokens between accounts. Or, Jupiter’s router program will CPI into Raydium and Orca liquidity pool programs to execute a multi-leg swap route. This is Solana’s way of doing composability; just as Ethereum contract-to-contract calls do for Ethereum.

Think of it like a General Contractor. You hire one firm to do a home remodel, and none of the contractors work as an individual on any part of the project (for the whole project, the general contractor is the entity working). The GC can hire a plumber who, in turn, might hire an inspector to ensure the work is done correctly. In one, unified contract at the top level, each specialized sub-contractor works on their portion to build your new home. If that plumber doesn't pass inspection, you aren’t left with a partially finished home; the work stops and the contract doesn’t succeed. A CPI chain works exactly the same way, with transaction atomicity ensuring that any failure causes a complete rollback.

How CPIs work, technically speaking

A program uses an invoke function to make a call into another program, passing the target program, a list of accounts, and some instruction data. This data is structurally the same as any instruction you’d submit in the transaction.

When a call is made, a specific rule applies: a callee can only touch accounts that are already explicitly defined in the initiating transaction’s account list. A malicious or buggy program called as a CPI won’t be able to reach out and manipulate arbitrary program state. The CPI runtime simply won’t give the program anything its caller isn’t already giving them in the transaction.

This chain is at most 4 deep, or 3 levels deep. A program can call another program. The second program can call a third program, which can call a fourth program. The chain then must end. (For comparison, the EVM allows call stacks that are at most 1024 deep.)

When making the call, if you need to transfer tokens out of an escrow vault (a Program-Derived Address, or PDA), you would use invoke_signed. When a program is used to sign a transaction on behalf of a PDA, which is an account owned by a program and for which there’s no private key (a PDA is always derived by passing its seeds and the owner program ID), it needs to pass its seed(s) and derive its PDAs to sign on their own behalf. Every time an Orca pool withdraws funds or a Magic Eden escrow releases an NFT, it goes through invoke_signed.

All of these instructions also share a compute budget, of which you get at most 200,000 compute units for your transaction by default, with a maximum cap of 1.4 million. Furthermore, all the CPIs share a single transaction atomicity. One program can call another, which can call another, which can call yet another. If one of those 4th level programs returns an error, the whole chain rolls back.

How do they compare to Ethereum?

Just as Solana is able to make CPIs, Ethereum also uses contract-to-contract calls for composability. In Ethereum, the popular Uniswap router calls into pool contracts.

However, these calls have guardrails.

As mentioned previously, with Solana and a program calling another via CPI, the called program can only work on accounts that are part of the transaction. You, as the initiator of the transaction, have pre-declared every account that can be accessed and manipulated.

In Ethereum, this isn’t possible (without extra precautions). In an external call in Ethereum, an invoked contract can, if permitted, call back the original contract. This is the re-entrancy pattern, the security vulnerability that was the primary vector in the famous DAO hack in 2016. Solana’s runtime doesn’t support re-entrant CPIs to already active programs, making it a much simpler system in that way.

Now, that doesn’t mean programs and PDAs are completely secure; the account validation issues that we saw with Wormhole are just that—bugs in the program that’s supposed to be calling them. So you can exploit a Solana program and its CPI in a similar way. However, the vulnerabilities will be different, and thus auditors will look for different bugs.

Why are these important?

Let’s say you want to deposit SOL. You could deposit it with Marinade Finance. Marinade Finance will then make its own CPI to a stake program, then call SPL Token to mint your mSOL. Or you could deposit into a Kamino vault, which will CPI into some lending program that will handle collateral deposits on your behalf. Or you could route your tokens through Jupiter’s router to execute a complex DEX swap strategy (Jupiter’s router CPIs into two separate DEX programs, atomically, all within a single 400ms transaction slot). The entire concept is built on Ethereum, popularized by DeFi money legos, with a single transaction and one final all-or-nothing success/failure.

Of course, there are restrictions. Since the CPI chain is at most 4 deep, you’ll see that if you make a deposit that triggers multiple levels, the whole thing might fail at some point. In practice, we don’t see it often, because protocol designers flatten their architectures enough to avoid that limit (which does get hit sometimes). This also touches on another restriction—the limited compute budget, since deep chains of CPIs can exhaust their compute units and revert the transaction.

Is a CPI a transaction?

No, a CPI isn’t a transaction. It’s a call made during the execution of another transaction. So, your one transaction can make many internal calls within another transaction. These internal calls share the same compute units, fee payer, and success/failure logic. If you look at the transaction in your favorite block explorer, you’ll see these as inner-instructions within the instruction that triggered them.

Contents

Writen By

Stay Updated - Follow us on X

Project review threads, dApp insights, announcements, news, and more.