Beta. On-chain CPI composability is newly available. The
POST /api/v1/cpi/route-accounts
endpoint and the vulcx-aggregator on-chain helpers may change before general availability. If you
hit a rough edge, reach out on Telegram.route instruction via Cross-Program Invocation (CPI),
invoke_signed with your PDA seeds. Because it happens inside your transaction, the swap and the logic
around it (repay a loan, rebalance a vault, credit a merchant) either all succeed or all revert.
Why call route on-chain
| Property | What it gives you |
|---|---|
| Atomicity | The swap and your surrounding logic succeed or revert together — no half-done state, no price window between steps. |
| PDA as authority | Your program owns the funds and signs the swap. No user-wallet round-trip — works for autonomous keepers, vaults, and strategies. |
| Composability | The swap is a middle step, not the whole transaction. Stack it with anything your program does. |
When to use CPI vs the HTTP paths
| Path | Endpoint | Signer | Use it when |
|---|---|---|---|
| Transaction | POST /api/v1/swap | User wallet | You want a ready-to-sign transaction. Simplest integration. |
| Instructions | POST /api/v1/instructions | User wallet | You compose the swap with other instructions in an off-chain-built transaction. |
| CPI accounts | POST /api/v1/cpi/route-accounts | Your program’s PDA | Another on-chain program calls route inside its own instruction. |
Flow
Step 1 — Get the account list (off-chain)
Your backend callsPOST /api/v1/cpi/route-accounts with your program’s PDA as the authority.
The response contains the route instruction (with the PDA already wired in as the swap authority)
plus the token accounts that PDA must own.
data payload:
Step 2 — Call route from your program (on-chain)
Add the Vulcx aggregator as a library dependency and use itsroute_cpi helper to assemble the
account list and invoke route, signing with your PDA seeds.
The on-chain helpers ship in the
vulcx-aggregator crate. Pull it in with the cpi feature so it
builds as a library (no duplicate entrypoint):Cargo.toml
num_steps = 1 plus a populated token_accounts/hops.
lib.rs
&[&[SEED, &[bump]]]) is the whole point: route accepts your program’s
PDA as user_transfer_authority, so the swap is authorized by your program, not a wallet.
Account layout reference
route reads a fixed set of accounts followed by remaining_accounts (your token accounts, then one
group per hop). RouteAccounts::account_metas() produces this exact order and flags for you — you
rarely assemble it by hand — but here it is for reference and debugging.
Fixed accounts (always first, in this order):
| # | Account | Flags |
|---|---|---|
| 0 | token_program | readonly |
| 1 | authority (user_transfer_authority) | signer — writable iff any hop is Moonit |
| 2 | protocol_config | readonly |
| 3 | vortex_program | readonly |
| 4 | fluxbeam_program | readonly |
| 5 | program_signer | readonly |
remaining_accounts: all your token_accounts (writable), then each hop’s account group
in route order, then optional protocol_fee_ata / referrer_ata (writable).
Per-hop account groups (count and flags depend on the DEX):
- Vortex (7)
- Vortex V2 (10)
- Fluxbeam (14)
- Moonit (14)
| # | Account | Flags |
|---|---|---|
| 0 | pool | writable |
| 1 | vault_a | writable |
| 2 | vault_b | writable |
| 3 | tick_0 | writable |
| 4 | tick_1 | writable |
| 5 | tick_2 | writable |
| 6 | oracle | readonly |
Gotchas
simulate: falsefor real execution.simulate: truemakesroutebail early — it’s only for probing the account wiring without touching liquidity.- Moonit hops need a writable authority. If your route includes a Moonit hop, the authority must be
a writable signer.
RouteAccountsdetects this and sets the flag; make sure your PDA account is markedmutin the context (harmless for non-Moonit routes). - Split routes aren’t supported yet. A split route returns
400fromPOST /api/v1/cpi/route-accounts. Retry with a pair that routes through a single path. - Keep the layout in sync. If you hand-assemble accounts instead of forwarding what the endpoint
returns, a single wrong writable/signer flag surfaces as an opaque privilege-escalation or
not-writable failure deep inside the CPI. Prefer forwarding
routeInstruction.accountsverbatim.
Reference program
vulcx-cpi-consumer
The worked, end-to-end example program that CPIs into
route with its own PDA — the tested template
this guide is built from. Ask on Telegram for access.