Skip to content

Limits ​

Every maximum on a template and on a run, in one place. The values come from common/src/template/wire.rs. Terms such as CPI, row and account group are in the Glossary, and the rules behind each limit in the language reference.

Program limits ​

The Ballista program enforces these, whichever SDK built the template.

Accounts ​

LimitMaximum
Runtime accounts per run (fixed + batch rows + group members)120
Accounts per batch row8 (minimum 1)
Account groups per template8
Members per account group255
Accounts per CPI, including a forwarded account group64
Programs per group filter2
Matches per group filter4 (minimum 1)
Except keys per group filter4
Group filter match offset65,535

One transaction holds fewer: about 61 runtime accounts. See accounts per transaction.

CPIs ​

LimitMaximum
CPIs per run, counting every time a loop body runs and 3 for each registry open64
Instruction data per CPI4,096 bytes
Readable CPI return data1,024 bytes

This limit counts only Ballista's own calls. Solana's instruction trace, which also counts the run itself and every nested call, binds first.

Inputs ​

LimitMaximum
Inputs, fixed plus row32
Row inputs per batch row8
Input values per run, fixed plus every row256
Run data (group lengths plus input values)1,024 bytes
One bytes input or literal1,024 bytes

Loops ​

LimitMaximum
Loops per template (forEach and repeat together), not nested8
Count-loop maximum (max)255 (minimum 1)

Output and byte reads ​

LimitMaximum
One log (emit) or return data (setReturnData), worst case1,024 bytes
Return-data steps per template1
One byte read from account or instruction data1,024 bytes (minimum 1)

An emit tag is at least 4 bytes. The tag rule is under Output.

Registries ​

LimitMaximum
Registries per template8
Entries a template opens8
Field bytes per registry512 (minimum 1)
Entry account: 72-byte header plus fields584 bytes
CPIs each open counts toward the 64 per run3

Fields take bool 1 byte, u64 and i64 8, u128 16 and pubkey 32. An entry's rent on mainnet is (128 + the entry's bytes) × 5,080 lamports: 1,097,280 for 16 bytes of fields, 88 bytes in all. See what it costs. The rules are under Registries.

Sizes and bytecode ​

LimitMaximum
Compiled template10,240 bytes
Registers64
VM instructions128 (minimum 1)
PDA seeds per derivation, not counting the bump15
Bytes per PDA seed32

Registers ​

A template has 64 registers, each holding one value during a run. Both SDK compilers give each value its own register:

  • each fixed input the steps read, and each distinct constant, loaded once before the first step;
  • each value an expression reads or computes: an account read, the clock, a row input, a sum, a comparison, a cast.

A let takes none of its own: it names its value's register. Writing the same read or sum twice computes it twice, so give a value used more than once a let. A loop body compiles once, so its registers count once, however many passes it makes.

Past 64, the compiler reuses a register once its value has been read for the last time. Inputs and constants hold theirs from the start of the run, and a value read inside a loop holds its register for the whole loop. Compilation fails only when more than 64 values are in use at once: Template uses more than 64 registers: N values are in use at once at steps[k] (label).

compileTemplate(template).stats.registers (compiled.stats.registers in Rust) gives the count after reuse, and stats.instructions the VM instructions, at most 128: each value above takes one, and so do most other steps, such as a require or a call.

SDK limits ​

Both SDK compilers add their own maximums. A template built by hand or with other tools is bound only by the program limits above.

LimitMaximum
Batch rows (maxIterations)60
Steps in a loop body64
Data parts per CPI64
Data parts per emit or setReturnData64
Step label length64 characters

Without the SDK's 60-row cap, the number of rows is still bounded: fixed accounts plus the row width times the maximum rows must fit in 120 runtime accounts, and the worst-case CPI count, with every loop at its maximum, must fit in 64.

Transaction limits ​

Solana's own limits often bind before Ballista's.

Transaction size ​

A legacy or version 0 transaction holds at most 1,232 bytes, and each upload instruction goes in a transaction of its own. With the creator as fee payer, these are the largest that fit:

TransactionLargest upload chunkLargest template uploaded in one shot
Legacy1,023 bytes960 bytes
Version 01,021 bytes958 bytes

One byte more makes 1,233. Larger templates upload in chunks. In one shot, the version 0 transaction would be 1,288 bytes for the daily cap, 1,636 for the signed quote and 1,866 for the oracle swap. In TypeScript, give buildKitTemplateUploadPlan the transactionMessage you send, and it fits each instruction to it (Lifecycle).

Accounts per transaction ​

A transaction uses at most 64 accounts, whatever its version. Accounts loaded from address lookup tables count too: a table makes a transaction smaller, not wider. Solana's feature to raise the limit to 128, increase_tx_account_lock_limit, is not active on mainnet.

So a run alone in its transaction fits about 61 runtime accounts:

  • the Ballista program and the template account take two of the 64;
  • the fee payer takes a third, unless it is also one of the run's accounts;
  • every other instruction takes the accounts it adds, such as the Compute Budget program.

Ballista's limit of 120 runtime accounts can't be reached in one transaction today. To carry 61 accounts in bytes, use a version 1 transaction, up to 4,096 bytes, or a version 0 transaction, up to 1,232 bytes, with an address lookup table.

Instruction trace ​

A transaction runs at most 64 instructions in total: its own instructions plus every CPI at every depth, including the calls a called program makes itself. The run is one of them, so even alone in its transaction a run can make at most 63 CPIs. Ballista's count can't see nested calls, so size a loop to the trace, not only to its max.

In a local test against copies of the mainnet programs, a template sells SOL through Jupiter in slices, one per pass of a count loop, then pays out rows. The transaction spends 15 entries before the first slice (compute budget, Jupiter's setup and its CPIs, and the run), 7 per slice (Jupiter, the pool, their event calls and two token transfers) and 1 per payout. Seven slices fill the trace exactly, 15 + 7 × 7 = 64, so the first payout would be entry 65 and fails with MaxInstructionTraceLengthExceeded, although the loop allows 8.

Call depth ​

Solana nests calls at most 5 frames deep. The transaction's own instruction is frame 1, and each CPI runs one frame below its caller. SIMD-0268 would raise this to 9; it is not active on mainnet.

  • A run called by the transaction is frame 1, so the programs a template calls run at frame 2, and their own calls at frames 3 to 5.
  • A template that runs another template puts the inner run at frame 2, and the inner run's calls at frame 3.
  • A call into frame 6 fails the transaction with Solana's CallDepth error, not a Ballista code.

In a local test, a run that runs a Jupiter sale reaches exactly frame 5: the outer run, the inner run, Jupiter, Meteora DLMM, then the Token program. A deeper venue, a transfer hook or one more wrapper doesn't fit.

Compute ​

Compute cost depends on the template: PDA bump searches, account reads, loop passes, logs, CPIs and the programs they call all add to it. So does creating a registry entry, which derives the entry's address and calls the System program. Solana's log call alone charges an emit 200compute units plus 1 per byte. Simulate the exact transaction and set the compute-unit limit from the measurement plus a margin. The TypeScript SDK's createComputeUnitProvider does this.

When each limit is checked ​

Before any run, the compiler and the program's verifier (at create or finalize) reject a template that could exceed a limit in its worst case: register, instruction, and loop counts, inputs and account groups, runtime accounts at the maximum rows, CPIs with every loop at its maximum, accounts listed per CPI, PDA seeds, the largest instruction data each CPI could build, the largest log or return data each output could build, byte-read lengths, fixed-offset reads past an account's declared minimum length, and registry indexes, sizes, opens, and field ranges.

On every run, Run checks what the caller supplies: the run data and each input value, the number of runtime accounts, the row count (it must divide evenly and fall between the minimum and maximum), account group sizes, each fixed and row account against its declaration, each count loop's count against its maximum, each CPI's accounts plus its forwarded group against 64, and each registry entry against its template, registry, and key.

Heap ​

A run allocates its inputs, its registers and one set of CPI buffers, plus, once needed, one register snapshot shared by its loops and one 1,024-byte buffer shared by its logs and return data. It reuses each of them, so heap use does not grow with the number of CPIs, loops, or outputs, and stays within Solana's default 32 KiB even for 64 CPIs of 4 KiB each.

If a template nears several limits at once, split it. Smaller templates are easier to review.

BALLISTA / A SMALL MACHINE FOR COMPLEX TRANSACTIONS