Passing Remaining Accounts
Forward remaining_accounts to CPIs when the callee expects variable account lists (routing, oracles, multi-hop swaps).
Recipe
let mut cpi_ctx = CpiContext::new(program, accounts);
cpi_ctx = cpi_ctx.with_remaining_accounts(
ctx.remaining_accounts.iter().map(|a| a.to_account_info()).collect(),
);When to reach for this: Callee program documents accounts beyond its fixed IDL layout.
Working Example
pub fn relay(ctx: Context<Relay>) -> Result<()> {
for acc in ctx.remaining_accounts.iter() {
require!(acc.owner == &expected_owner, RelayError::BadAccount);
}
let mut cpi_ctx = CpiContext::new(
ctx.accounts.target_program.to_account_info(),
target::cpi::accounts::Handle { admin: ctx.accounts.admin.to_account_info() },
);
cpi_ctx = cpi_ctx.with_remaining_accounts(ctx.remaining_accounts.to_vec());
target::cpi::handle(cpi_ctx)?;
Ok(())
}What this demonstrates:
- Validate every remaining account before CPI
- Order must match callee expectations
- Combine with declare_program CPI structs
- Document routing format for clients
Deep Dive
Client Responsibility
Clients append accounts after fixed IDL accounts in transaction metas. Mismatch causes callee failure or security bugs.
CU Impact
Iterating large remaining sets costs CU; cap count with require!(len <= MAX).
Gotchas
- Unvalidated remaining accounts - Critical vulnerability.. Fix: Check owner, signer, discriminator.
- Wrong order forwarded - Callee reads wrong account as oracle.. Fix: Publish strict ordering spec.
- Unbounded length - CU exhaustion.. Fix: Enforce max remaining count.
- Mixing remaining with init - Anchor ordering confusion.. Fix: Keep fixed accounts in struct only.
- Assuming callee validates same as you - Defense in depth missing.. Fix: Validate on both sides.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Fixed account struct | Known layout | Dynamic routers |
| Account metas in instruction data | Rare patterns | Standard remaining_accounts |
FAQs
Anchor 0.32.1 error format?
Custom codes in IDL plus #[msg] strings.
emit! vs emit_cpi!?
emit_cpi! costs more CU but improves capture reliability.
Should I use msg! on mainnet?
Avoid except behind feature flags.
How do clients get error codes?
From IDL via Anchor or Codama codegen.
Do events appear in IDL?
Yes, #[event] types are listed.
Can constraints return custom errors?
Yes with @ MyError::Variant.
What uses CU in logging?
Each log line and formatted string.
How to test errors?
anchor test expecting error codes.
Are logs consensus data?
Yes, but not an API contract; use events.
Next read?
See Related for remaining cpi.
Related
- remaining_accounts - program side
- Accessing Accounts - ctx.remaining_accounts
- CPI Safety - validation
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.