Compute cost and program size of Solana programs compiled from TypeScript, measured against Anchor, native Rust and Pinocchio, and verified on mainnet.
TypeSOL compiles a subset of TypeScript straight to Solana bytecode, without Rust in between. On identical
programs it uses less compute than Anchor on every instruction measured, less than hand-tuned Pinocchio on 9 of 9,
and produces the smallest binaries of the four.
17.9%
of Anchor's compute units, as the geometric mean over 9 instructions
of a counter, a vault and an escrow. Every instruction falls between 4.7% and 31.9%. Pinocchio reaches 22.2%, native Rust 55.4%.
10.7×
smaller binaries than Anchor: 46.4 KB for the three programs against
494.8 KB, and 35% smaller than Pinocchio's 71.9 KB.
26 / 26
official example instructions cheaper than both Anchor and native Rust.
Programs from solana-developers/program-examples, built unmodified, against TypeSOL ports. Lowest of all four on 13.
61% less
compute on mainnet than Anchor across the same 4 escrow
transactions (56,067 against 142,707 CU). All 16 transactions are public and listed with their Solscan links in section 5.
Table 1. Summary of results. Best value in each row underlined (lowest cost, or most instructions won); TypeSOL in bold.
Measure
TypeSOL
Pinocchio
Native Rust
Anchor
Compute units, share of Anchorgeometric mean, 9 instructions
17.9%
22.2%
55.4%
100.0%
Lowest compute units of the fourof 9 instructions
9
0
0
0
Binary size, three programsKB
46.4
71.9
109.4
494.8
Official examples, lowest of the fourof 26 instructions
13
13
0
0
Official examples, binary sizeKB, 17 programs
228.3
270.9
1,434.0
2,590.5
Mainnet escrow, total compute units4 transactions each
56,067
77,883
112,239
142,707
1Scope and method
The report answers one question: what does a Solana program cost to run and to deploy when it is written in TypeSOL
instead of Anchor, native Rust or Pinocchio? It measures compute units per instruction and the size of the deployed
program. It does not measure developer time, tooling or ecosystem.
1.1 Programs
Three programs (counter, vault, escrow) are written four times with the same instruction encoding, account layout
and checks, so one client drives all four. Seventeen further programs come from
solana-developers/program-examples at commit de09019; their Anchor, native and
Pinocchio versions are built unmodified and compared with TypeSOL ports of the Anchor versions (section 4).
1.2 Equal outcome before any number
Every implementation receives identical transactions in LiteSVM. A number is reported only when the final account
state is identical across all four and the same hostile calls are refused: a stranger signing, an overflow, a withdrawal
below rent, a fake maker. Where implementations knowingly differ, the difference is recorded (Appendix A).
1.3 Equal conditions
All four run at the same program address with the same deterministic wallets, so program-derived addresses and bump
searches cost the same. Compute units are means over 20 rounds; sizes are those of the deployed .so file.
1.4 Pinned and public
The compiler is installed from npm at a fixed version, with its sources from that version's tag. Every result records
its toolchain (Appendix B), and the continuous-integration run of typesol/benchmarks reproduces the figures exactly.
2Compute units
Each instruction runs alone in its transaction. Compute units are what Solana meters and what priority fees are
priced against; a lower figure means a cheaper and more schedulable transaction.
TypeSOLPinocchioNative RustAnchor
Figure 1. Mean compute units per call over 20 rounds in LiteSVM. Bars share one scale. Lower is better.
Table 2. Compute units per instruction. The last two columns give TypeSOL's figure as a share of Anchor's and its difference from Pinocchio's.
Instruction
TypeSOL
Pinocchio
Native Rust
Anchor
TypeSOL vs Anchor
TypeSOL vs Pinocchio
counterinitialize
1,866
3,186
4,601
6,573
28.4%
−41.4%
counterincrement
110
113
857
2,323
4.7%
−2.7%
vaultopen
1,885
3,185
4,613
6,627
28.4%
−40.8%
vaultdeposit
1,314
1,377
2,832
4,355
30.2%
−4.6%
vaultwithdraw
315
370
1,176
3,628
8.7%
−14.9%
vaultshut
161
167
950
2,024
8.0%
−3.6%
escrowmake
3,605
6,179
9,240
13,304
27.1%
−41.7%
escrowtake
4,019
4,051
8,500
14,365
28.0%
−0.8%
escrowrefund
2,696
2,769
5,695
8,449
31.9%
−2.6%
Geometric mean, share of Anchor
17.9%
22.2%
55.4%
100.0%
TypeSOL is the lowest of the four on all 9 instructions. The gap to Anchor is widest on small
instructions (counter increment at 4.7%), where Anchor's fixed per-instruction work dominates, and narrowest on
instructions that create accounts or call other programs, where shared runtime costs dominate.
3Program size
Deploying a program locks rent in proportion to its size, and larger programs take longer to load. TypeSOL emits
C through one shared runtime header, so only what a program uses is linked.
TypeSOLPinocchioNative RustAnchor
Figure 2. Size of the built .so in KB (1,024 bytes). Rust programs built with cargo-build-sbf in release mode.
Table 3. Binary size per program, in KB.
Program (KB)
TypeSOL
Pinocchio
Native Rust
Anchor
Anchor ÷ TypeSOL
counter
11.0
19.6
27.9
145.2
13.2×
vault
14.1
22.9
35.0
158.9
11.3×
escrow
21.3
29.4
46.5
190.7
9.0×
Total
46.4
71.9
109.4
494.8
10.7×
4Independent programs
To rule out benchmarks written in TypeSOL's favour, the same comparison runs on programs TypeSOL did not write:
17 examples from solana-developers/program-examples (de09019, MIT), 26 instructions in total.
Each example's observable outcome must match across all four implementations before any of its numbers is reported.
Reading the Pinocchio column. These native and Pinocchio examples are teaching code. Many omit
owner, type, PDA or signer checks that Anchor and TypeSOL make, or take a bump from the client instead of searching for
it. Their figures include that difference. Appendix A lists, per example, what each version checks and how it handled
the hostile calls.
Table 4. Compute units per instruction, official examples. Labels ending in (tx) cover the whole transaction where the versions split one job differently between client and program. Example names link to Appendix A.
The official escrow was deployed to Solana mainnet-beta in all four versions and called with the same mints, amounts
and accounts, one instruction per transaction: make an offer and take it, then make a second offer and refund it. All
16 transactions succeeded between 13:13:35 and 13:13:54 UTC on 2026-10-27, in slots
462,083,319 to 462,083,407. Every figure below was read back from the chain and can be checked on Solscan.
TypeSOLPinocchioNative RustAnchorPrograms called (ATA, SPL Token)
Figure 3. Compute units of each mainnet transaction. The solid part is the escrow program itself; the hatched part is the programs it called, as their own log lines report.
Table 5. Transaction ledger, 2026-10-27. Each row is one confirmed mainnet transaction; the line beneath it is its Solscan page. ATA: Associated Token Account program; Token: SPL Token program.
¹ Total compute units minus those the called programs log for themselves. It includes the cost of making each CPI and of
System program calls, which do not log their own consumption.
Most of make_offer is the Associated Token Account program creating the vault, about 13,500 CU in every version. It
searches for the vault address's bump itself at 1,500 CU a try, so its cost moves between offers: TypeSOL's second
make_offer cost 1,500 more than the first while the escrow program's own part stayed at 6,719 CU both times.
Anchor's and native Rust's own parts grew between the two offers, by 6,000 and 4,500 CU.
The Pinocchio version takes the offer's bump from the client instead of searching for it, and its take_offer also
calls the Associated Token Account program.
The escrow program's own part, summed over the four transactions: TypeSOL 26,846 CU, Pinocchio 35,456,
native Rust 84,257, Anchor 111,926.
These programs were built with an earlier TypeSOL than the one measured in sections 2 to 4, so their figures differ from
LiteSVM's. The deployed programs match their builds byte for byte (typesol verify for TypeSOL).
Lower cost is worth nothing if the program is wrong. The figures above sit on the following test base, all in the
public typesol/typesol repository.
556 tests
Programs compiled and run in LiteSVM, compile-time diagnostics, generated clients against real programs, and on-chain tooling.
Differential testing
The same transactions go to TypeSOL and to Anchor twins of each example. Outcomes, account contents and balances must match after every step.
Fuzzing
Arithmetic at every integer width is checked against BigInt. Random honest and hostile actions are checked against a reference model, and token supply and SOL are conserved.
46 ported programs
Every program in solana-developers/program-examples is ported: 30 run the original tests' flows unchanged, and 16 have documented differences.
Anchor compatibility
Anchor's IDL format, discriminators and error codes, so existing Anchor and Codama clients work with TypeSOL programs.
Verifiable builds
typesol verify rebuilds a program and compares it byte for byte with what is deployed.
7Limitations and disclosures
Authorship
This report is prepared by the TypeSOL project, which has an interest in its results. For that reason every input, script and result is public and reproducible (Appendix B).
Security audit
TypeSOL has had internal security reviews but no external audit yet. Programs holding significant value should wait for one.
What is measured
Compute units and binary size measure cost. They do not measure developer time, documentation or ecosystem maturity, where Anchor has a lead of years.
Environment
LiteSVM uses the mainnet cost model, but programs reached through CPI (SPL Token, Token Metadata) are whichever version is loaded. Token Metadata is taken from mainnet-beta and its hash recorded.
Teaching code
The official native and Pinocchio examples do less checking than Anchor and TypeSOL in several places (section 4, Appendix A).
APer-example detail
For each official example: compute units and binary size per version, the hostile or edge calls the benchmark makes
and whether each version refused or accepted them, and how the versions differ. Accepted calls are in italics; not every
acceptance is a flaw (a second set_favorites is an update in Anchor's version).
Measured as the whole creating transaction; every variant creates the keypair account with a CPI inside the instruction. Account layouts differ (Anchor and TypeSOL: Borsh Strings in 171 bytes; native: Borsh sized to the data; Pinocchio: fixed 51-byte record with 16-byte fields); the decoded fields are compared. The native version does not check the system program account; Pinocchio caps each string at 16 bytes.
The native and Pinocchio versions also log both account addresses and require the account to create to hold no lamports; Anchor (and the TypeSOL port) leave that account unchecked. The native version does not check that the payer signed.
The native and Pinocchio versions store only the name (Pinocchio: a fixed 16-byte field, with the bump passed by the client), not the bump and user key Anchor stores; on close they keep the rent of an empty account in it (0 bytes, owned by the System Program) where Anchor and TypeSOL close it entirely. Pinocchio's create_user signs with the bump the client passes and does not search for it (the others derive the canonical bump on chain, a find_program_address of several thousand CU); its close_user does the search. Compared: the stored name, and that the closed account holds no data and is no longer owned by the program.
Only increment is compared: the native and Pinocchio versions have no initialize (the client creates a zeroed 8-byte account), while Anchor's initialize_counter creates the account itself. The native and Pinocchio increment do not check the account's owner or type.
The native and Pinocchio versions size the account by the instruction data length and do not decode it. Anchor, native and TypeSOL log the new address as base58 (Anchor and native via to_string).
Binary sizes are hand + lever. initialize (tx) is the whole transaction that creates and initializes the power account: every variant creates it inside lever's initialize (CreateAccount CPI). pull_lever includes the CPI into lever. Anchor and TypeSOL hand check that power is a lever PowerStatus account before the CPI; the native and Pinocchio hand forward any account (lever's own data parsing and the runtime's owner rule still stop a foreign account).
set_favorites by another signer on this user's PDA
refused
refused
refused
refused
Only the first set_favorites, which creates the user's PDA, is compared: the native and Pinocchio versions refuse to write an existing account (Anchor and TypeSOL overwrite it), and they have no update. The variants store different layouts: Anchor and TypeSOL 344 bytes with Borsh strings, native exactly the Borsh size, Pinocchio 80 bytes of fixed fields (color 8 bytes, 4 hobbies of 16) with the bump passed by the client (no on-chain bump search); the PDA seed is "favorites" in Anchor/TypeSOL and "favorite" in native/Pinocchio. Pinocchio does not check that the instruction data fits its layout beyond the copy.
create_new_account without the new account's signature
refused
accepted
accepted
refused
The same observable result by different means: Anchor and TypeSOL fund a System-owned vault with a System Transfer and pay for each new account with a signed System CreateAccount CPI; native and Pinocchio create the vault as a program-owned account (CreateAccount CPI) and move lamports out of it directly (no CPI) in create_new_account. The vault's owner is therefore not compared (its balance is). Native searches the bump on chain in both instructions; Pinocchio takes it from the client and checks it with create_program_address; Anchor and TypeSOL find the canonical bump on chain while checking the vault. The native and Pinocchio create_new_account do not require the new account's signature.
Anchor and TypeSOL find the canonical bump on chain at creation, store it, and check the PDA against the user and the stored bump on every increment; the native and Pinocchio versions sign with the bump the client sends, do not store it (their account stays zeroed until the first increment), and their increment checks neither the address nor the owner of the account. The stored bump is therefore not compared, only the visit count.
transfer_sol_with_program without the paying account's signature
refused
refused
refused
refused
Anchor and TypeSOL also check that the recipient is a System-owned account and that the paying account of transfer_sol_with_program is owned by the program; the native and Pinocchio versions check only the signature and leave ownership to the runtime.
Each call creates a mint and its Metaplex metadata account; most of the cost is the Token Metadata CPI. The native and Pinocchio versions also set the mint authority as freeze authority (Anchor and TypeSOL leave it unset); the freeze authority is not part of the compared outcome. The native and Pinocchio versions do not check the metadata PDA, the program accounts or the mint authority themselves (the token and metadata programs do); the native one uses InitializeMint with the rent sysvar, the others InitializeMint2.
The taker's mint A and the maker's mint B token accounts exist before take_offer, so no implementation creates them there. Anchor and TypeSOL move tokens with TransferChecked and check the mints' token program and the offer's account type; the native and Pinocchio versions use Transfer, read the offer without an owner or type check, and send the vault's rent to the taker and the offer's to the payer (Anchor: both to the maker). Pinocchio takes the offer's bump from the client instead of searching for it.
create_token is not compared: the Pinocchio version creates only the mint, without the Metaplex metadata account the others create (the metadata is not part of the compared outcome). mint_token and transfer_tokens go to existing associated token accounts; 'transfer_tokens (new account)' sends to wallets whose associated token account the instruction creates. Anchor and TypeSOL take whole-token amounts and scale them by the mint's decimals; the native and Pinocchio versions take base units. The native and Pinocchio versions leave the associated-token-account and owner checks to the token programs. Anchor, TypeSOL and native skip the associated token account program for an existing account; Pinocchio calls CreateIdempotent every time.
One transaction per variant reaching the same mint: Anchor and TypeSOL send initialize (create, DefaultAccountState Frozen, InitializeMint2) and update_default_state(Initialized) as two instructions of that transaction; the native and Pinocchio versions do the same four steps in their single instruction. Anchor and TypeSOL check the mint's freeze authority in update_default_state; the native and Pinocchio versions leave the checks to Token-2022.
Only initialize is compared: the native and Pinocchio versions have no close instruction. Their mint also gets the mint authority as freeze authority, where Anchor and TypeSOL set none (the freeze authority is left out of the compared outcome); Anchor and TypeSOL also read the extension back after creating the mint and check its close authority.
The native and Pinocchio versions take the mint authority as an account and do not check the payer's or the token program's identity (the CPIs fail on a wrong one); Anchor and TypeSOL check the token and system programs by address.
git clone https://github.com/typesol/benchmarks
cd benchmarks
npm ci && bash scripts/setup.sh
node run.ts # Tables 2 and 3
bash official/build.sh && node official.ts # Table 4, Appendix A
The mainnet run is mainnet-escrow.ts in the same repository. Running it again spends real SOL; the
transactions in Table 5 can instead be checked directly on Solscan.