🏠 TypeSOL has a new home on GitHub
Our GitHub account was suspended on Nov 2, most likely by GitHub's automated protection: our CI/CD ran a lot of test runs, merges and releases in a short time. We're not alone here, lately many developers have reported the same on Reddit and GitHub's community forum.
We contacted GitHub Support and haven't heard back yet. Development didn't stop, so we moved everything to a new organization: github.com/typesol-lang
What moved
• typesol, benchmarks and the org profile, with their full history
• All 32 release tags
• typesol.com, the docs and the playground now link to the new home
What didn't change
• npm: same package, the TypeSOL CLI, installs and works as before
• typesol.com and everything on it
Once the original account is restored, the repositories move back to it, and we'll say so here first. If you starred the old repo, a star on the new one helps a lot.
Phoenix keeps coming up on Solana this week. The Phoenix program TypeSOL ported is v1, Ellipsis Labs' on-chain spot order book: in full, in TypeScript, and checked against its mainnet binary down to the log lines and event batches.
Order books are among the hardest Solana programs to get exactly right: red-black trees inside account bytes, event batches, a bounded heap. They can be written in TypeScript and still match the original byte for byte.
4
110
Porting Phoenix made the compiler faster for everyone who writes the same patterns.
Setting up an empty 1,280-byte list: 1,436 compute units down to 152, the free slots now zeroed once by the memset syscall. Cancelling a Phoenix order: 691 down to 547, the tree no longer searched twice. Pushing one event: 54 down to 19.
Same source, fewer units, and the tests of every program it touches pass unchanged.
3
133
Porting Phoenix pushed six additions into TypeSOL, and none of them is about order books.
Helpers can take Inout<T>, Rust's &mut T: the caller's place itself, never a copy. Constants can be expressions, folded at compile time. Type aliases, and structs that stay local to their file. One PDA search gives the address and its bump. And a helper or a big local can get room past a single stack frame.
Programs that use none of it compile as before.
4
108
Phoenix runs on a 32 KiB bump heap, and a big enough transaction runs out of it.
The TypeSOL port counts that heap allocation by allocation, charging each one where the Rust makes it. So a transaction that fills it, with hundreds of account entries or a CPI's larger data, aborts at the same allocation in both programs, after the same log lines and event batches.
The limit tests send 162 transactions, just under and just over each boundary.
2
2
118
"Smaller ELF, surplus rent goes back to the spill account." That SIMD-0433 line in Agave v4.5 matters most when a binary shrinks by a lot.
TypeSOL's mainnet ports compile to a fraction of the originals: Phoenix about a fifth of its size, Raydium CLMM about a third, SPL Stake Pool about three fifths. Program data pays rent by the byte, and the byte keeps getting cheaper.
The Agave v4.5 release schedule is posted with feature activations paused until after Breakpoint.
Validators, mainnet-beta version adoption drops to two steps: 20% stake → general adoption, cutting a week from our release cycle. 🧵
github.com/anza-xyz/agave/wi…
5
134
Phoenix keeps its order books as red-black trees inside the market account, in lib-sokoban's byte layout.
TypeSOL has that tree built in since v0.43.0: rbTreeAt, over account bytes. It was replayed against the crate itself with identical bytes after every operation: 21,650 recorded operations, plus a one-off run of 1,020,000.
At 4,096 entries it beats the crate built with cargo-build-sbf: an insert in 595 compute units against 742, a remove in 355 against 618.
TypeSOL v0.43.0 is on npm: Phoenix, Ellipsis Labs' on-chain limit order book, ported to TypeScript in full. All 28 instructions of a native program, from the commit a reproducible build proves is the mainnet binary.
Checked against that binary on 3,575 transactions and a seeded random walk over two order books: same outcomes, log lines, event batches, return data and account bytes.
A PostOnly order: 6,452 compute units against 20,577. The binary: 256,360 bytes against 1,344,472.
The easy part of a port is the happy path. The SPL Stake Pool port is checked on the other one too.
Each instruction is sent with one thing wrong: a wrong signer, PDA, owner or mint, a short account list, a read-only or repeated account, a fee limit or slippage bound crossed. The mainnet binary and the TypeSOL port must refuse it the same way: same outcome, same error lines, same bytes in every account.
The original's RefCell panics included.
A native Solana program gets its accounts as a list. The Stake program it calls is just another entry in it.
TypeSOL v0.42.0 adds programAt and sysvarAt: one key compare, IncorrectProgramId if it's the wrong account, and the program's typed methods if it's the right one. Typed CPIs into System, Token, Token-2022, ATA, Memo and Stake now take remaining accounts as well.
Built for the SPL Stake Pool port, there for any program. Accounts you declare keep their compile-time checks.
Private swaps, private staking, private lending. Every one of those still starts as a program: a swap curve, a stake pool, a lending book.
Those are the programs TypeSOL has ported to TypeScript and checked against their mainnet binaries: Raydium's three AMMs, SPL Stake Pool, Marginfi v2.
Privacy is arriving at the balance layer. The program layer can be TypeScript.
Today, Solana changes forever
Introducing Solana Privacy Rings - encryption at the speed of Solana
Rings are programmable, onchain environments for encrypting money, markets, and finance - without ever leaving Solana
Now live on mainnet.
2
87
SPL Stake Pool keeps its pool account as plain Borsh: no discriminator, and Options and data enums that move every field after them.
TypeSOL v0.42.0 reads it with loadBorsh and writes it back with storeBorsh. A straight-line decoder and encoder per struct, no heap: about 280 compute units to read the 611-byte pool, 610 to read and write it back.
Trailing bytes and errors behave as Rust's do. Programs that never call them compile to the same C.
2
86
Staking programs live across epochs, so their tests have to as well.
The TypeSOL test kit now has warpToEpoch and StakeCluster. One StakeCluster writes the StakeHistory a cluster would, so stake warms up and cools down epoch by epoch as on mainnet, matched to the runtime's Stake program to the lamport.
That is how the Stake Pool port runs next to the mainnet binary: two VMs, one history. Its random walk crosses 215 epochs that way.
github.com/typesol-lang/type…
1
93
SPL Stake Pool's own test suite includes a pool of 20,000 validators. The TypeSOL port runs it next to the mainnet binary, with the same outcomes.
At that size, on a 1.4M compute budget:
DepositStake: 611,313 → 153,300
WithdrawStake: 783,846 → 310,716
UpdateStakePoolBalance: 547,385 → 321,997
A quarter to three fifths of the original's compute units, on the largest pool the original tests.
v0.42.0 isn't only SPL Stake Pool. The language picked up things any program can use:
- local lists of structs on the stack, up to 2 KiB each, with a compile error naming the size past that
- pushAll, a bulk append that's a memcpy: about 0.01 compute units a byte, against 11 for a loop of push
- the instruction data as a list: for … of, and slice views
- a ternary between accounts, where only the chosen side runs
Programs that use none of it compile to the same binary as before.
5
134
Before porting SPL Stake Pool, the first question was which source is actually running on mainnet.
The deployed binary's security.txt says v2.0.3, but that string is fixed in the source, and the deployed code carries a fee-cap message that main only gained later. Rebuilding the program's v2.1.0 tag with solana-verify gives the deployed executable's exact hash.
So the TypeSOL port follows v2.1.0, the code that's really running, and every check runs against that binary.
83
A devlog from v0.42.0, from porting SPL Stake Pool.
A loop over a BigVec, the list that holds a pool's validators, sat inside a large function and shared SBF's few registers with everything held across it, so clang spilled its pointer and running total on every step. Now such a loop becomes a C function of its own, given only the variables it uses.
The pool's update sum went from 31 compute units an entry to 16, below the original's 27. Programs without a BigVec compile to the same C.
1
3
184
null, the way TypeScript already handles it, in a Solana program.
Option<u64> is u64 | null. Check it against null and TypeScript narrows it to the value; a ?? b runs b only when a is empty; x! is the value, or the instruction aborts. An optional account is just A | null: the client leaves it out, its checks run only when it's there, and referrer?.rate gives an Option.
Stored as Anchor expects: a Borsh option in arguments and events, a fixed layout in account data.














