96kb Contracts, ETH Cluj Stylus, Huff and Stylus precompiles
25th April 2026
Hello everyone! It’s been a bit since our last post. I took a break from posting, instead working on Orderbookkit and Superposition (9lives). I can confirm that this blog will be an ongoing project, until we run out of things to write (not likely).
In this post:
96kb contracts are coming out for the Stylus ecosystem. We all know that contract size is a huge blocker for us! There is some discussion there about codesize optimisations in the Stylus VM that may be interesting to you. I also wanted to talk about a few interesting projects I’ve seen developing.
Stylus at ETH Cluj! 0xjsi (Jack) will be speaking about Stylus teaching people how to build on Arbitrum. We’ll dive into the talk content, including the open source material that will be coming out as a result of this program.
A Huff precompile: I wrote some Huff recently to work as a gas and codesize efficient proxy using our ed25519 and sha512 “precompiles”. These contracts are available on Arbitrum One and Superposition, making this code reusable for anyone wanting to build a high performance calldata proxy.
96kb contracts and the current ecosystem
To recap, the current contract limit is 75kb. This can be quite painful, as many of us have noticed. For 9lives, some of our contracts push up against the ceiling a fair bit:
76K contract-trading-amm-mint.wasm
72K contract-trading-amm-price.wasm
68K contract-trading-dppm-extras.wasm
68K contract-trading-dppm-quotes.wasmI know from experience that this frustrates a lot of teams. The size problem forces you to write code calling out to other contracts, offsetting the gains we get from having the Stylus contracts.
A solution that’s been thrown around a lot is implementing is to bump the ceiling to 96 kilobytes.
There has also been talk of other solutions, like replacing the native integer type with a chunk on the stack and implementing the numbers entirely in bit manipulation with software with loops, increasing the amount of external functions in the VM to support more math operations, and more.
The issues with these approaches is that don’t make sense when you consider the overhead of the translation from the WASM representation to the native machine that happens when code is compiled.
The Arbitrum node works during the activation period by using WASM as a host agnostic translation layer, and converting it to the native machine with Ahead Of Time (AOT) compilation. You upload your WASM, then it gets converted to executable code on the Arbitrum node that runs your contract. This approach isn’t possible/easy with the EVM or with other machines (for the most part, including RISC-V), due to the lack of static jumps.
Unfortunately, with WASM, we need to work around the fact that the native machine lacks 256-bit numbers and has little endianness, different from the big endian of the EVM. This ends us up with an unfortunate consequence: a dependency on crates and host-provided functions that we could choose to expand on, but always with an encoding overhead.
In light of this all, the simplest size solution is to increase the limit to 96kb. Presumably the codesize demands would peter out in the future since we’re all sharing the same code. We could even just keep increasing the codesize if we hit any more blockers.
My feeling is that for a future size increase, we could also look at ways of adding code to the dynamic code that the VM provides that’s relevant to application programmers, like muldiv and some approximation methods, and a way of pipelining math operations with a one-time serialisation fee. We could also have some external functions that don’t make big endian assumptions about their inputs, providing a little endian alternative that lets us provide the location of the ruint tuple of 64-bit words. This would eliminate the current problems with serialisation to and from the external functions, eliminating the codesize costs there too. Should we be careful to avoid having so many external functions that there’s no point developing them?1
Following that though, we could release a version of the SDK with API compatibility with the existing code that replaces alloy and makes less use of abstraction. Alloy is good, but it has a ton of dependencies, and it’s not as codesize efficient for our usecase is my feeling.
Looking forward: my feeling is that this size increase is really great for the ecosystem. Thank you to Srinjoy and everyone at OCL for listening to the community, and advocating for this change.
Let’s also discuss a change on the horizon: multi value returns are leaving the Stylus VM. This probably doesn’t affect you, but turn it off with compiler settings if you’re using a custom release pipeline that doesn’t implicitly use wat2wasm and wasm2wat, which I imagine might cut it out. But someone needs to prove this, since I’m not sure.
Interesting projects
I wanted to share some projects that I’ve seen building with Stylus after adding them to the Stylusup page. We now have 51 projects launched there as of this time of writing. Two projects I haven’t seen receive coverage in other Stylus media:
Arbitrum’s Game Of Life: an on-chain implementation of the Game Of Life for a workshop.
Orthonode’s Stylus Hardware Anchor: cheap hardware proof of provenance that’s verified on-chain using Stylus. A way to have proof at the hardware level of liveness that could be useful for on-chain distribution, voting, and more. I imagine that at a future conference we might see someone handing out badges that prove you’re human that you can use on-chain! Launched on Sepolia.
Stylus at ETH Cluj
I was scheduled to speak at ETH Cluj about Arbitrum and Stylus, but the situation with fuel insecurity (and the broader macro) derailed things. Jack (0xjsi) has taken up the task of speaking and organising the workshops, which run on the day of the 14th. The conference generally is from the 13th to the 14th.
The content will run like this:
Arbitrum’s Role in the New Ethereum Roadmap: some cultural and technical background on Ethereum, and how Arbitrum fits into Ethereum now and in the future.
Your First Smart Contract on Arbitrum: introducing everyone to building smart contracts using Solidity on Arbitrum. This workshop and open source website introduces several smart contracts that the workshop will build together, including a stablecoin, lending protocol, vault, and DEX. It explains how to build each of them, including the content and context.
Stylus for beginners: this workshop and site will introduce how blockchains work, the EVM, how Arbitrum works, and how Stylus differs from the EVM and its context. This step will introduce stack-based machines.
Deploy & Go - From Contract to App: this final step will take everything we’ve done so far, and introduce how to build a webapp from it. It will teach everyone how to work with Stylus.
Our goal is twofold:
Increase the amount of open source literature covering Stylus.
Teach students to work with Arbitrum and Stylus in person with a guided lesson-like format of Q&A. Have them walk away with the skills to work on the Arbitrum stack again.
For the first goal, we’re looking to partner with a team building a AI product atop Stylus to run a chatbot for us. If that’s you, reach out please!
I’ll be contributing online via a Telegram channel for questions during the workshop, and making available a faucet for Superposition Testnet, producing the Stylus for beginners content, and also helping create the other content where I can. Jack is doing an excellent job, and the content so far is great.
If you’re interested, grab a ticket:
A Huff calldata proxy
Tying out our content for today, I wanted to share a EVM preprocessor snippet we put out for our upcoming RFQ and RPC 9lives feature:
// LiteAccount: Simple calldata forwarder that takes an argument of the
// form WITH NO PADDING (bytes32 sigA, bytes32 sigB, uint128 nonce,
// address target, bytes... cd). Uses Superposition precompiles to
// validate the ed25519 after converting it to sha512, and the
// expectation is the signature was signed with padding.
#define constant STORAGE_ENABLED = FREE_STORAGE_POINTER()
#define constant STORAGE_NONCE = FREE_STORAGE_POINTER()
#define constant ADDR_SHA512 = 0x1f4350205a556587ff3a1f2cb627613685dacb73
#define constant ADDR_EDVERIFY = 0xc3e443be2cfa4f41a5f5e4978d012847d355b419
// [EDVERIFY] the memory, returning a yes or no word on the stack.
#define macro EDVERIFY() = takes(0) returns(1) {
0x0 0x0 0xa0 0x0 [ADDR_EDVERIFY] gas staticcall
}
#define macro MAIN() = takes(0) returns(0) {
0x1000000000000000000000000000000000000001 caller eq PRIVILEGED jumpi
[STORAGE_ENABLED] sload FALLBACK jumpi
INTERNAL_ERR:
// InternalError()
0xfe835e35
REVERT:
0x0 mstore 0x04 0x1c revert
PRIVILEGED:
0x0 calldataload [STORAGE_ENABLED] sstore
0x0 0x0 return
FALLBACK:
0x40 0x60 0x64 dup1 calldatasize sub
dup4 calldatasize sub dup5 0x20 calldatacopy // Memory: ..., nonce, target, cd
0x01 [STORAGE_NONCE] 0x0 mstore 0x11 0x1f sha3 dup1 sload INTERNAL_ERR jumpi // [true, slot for nonce], Memory: padding <32 - 1>, storage slot, nonce
sstore
address 0x0 mstore // Memory: padding <32 - 20>, address, nonce, target, cd
0x0 0x0 0x2c calldatasize sub 0x0c [ADDR_SHA512] gas staticcall // [success], Memory: padding <32 - 20>, address, nonce, target, cd
returndatasize 0x0 0x0 returndatacopy // Memory: hash A, hash B
0x2000000000000000000000000000000000000000000000000000000000000002 dup6 mstore // Memory: hash A, hash B, operator
dup5 0x0 dup6 calldatacopy // Memory: hash A, hash B, operator, sig A, sig B
EDVERIFY() // [success], Memory: hash A, hash B, operator, sig A, sig B
// Unverified()
0xcba605a3 dup2 iszero REVERT jumpi
dup4 dup6 0x0 calldatacopy // Memory: cd...
0x0 0x0 dup6 0x0 0x0 0x50 calldataload dup12 shr gas call // [success], Memory: cd...
returndatasize 0x0 0x0 returndatacopy // [success], Memory: rd...
returndatasize 0x0 dup3 SUCCESS jumpi revert
SUCCESS:
return
}
#define macro CONSTRUCTOR() = takes(0) returns(0) {
0x01 [STORAGE_ENABLED] sstore
}Have you ever worked with Huff? It’s a great macro preprocessor for the EVM. Working with EVM code is easy when you have Stylus to handle your precompiles/complex behaviour, and you can just act as a low-cost proxy. This code works calldata generated like the following:
let k = SigningKey::from_bytes(&priv_key.0);
let mut x = Sha512::new();
x.update(&contract.0);
x.update(&nonce.to_be_bytes());
x.update(&target.0);
x.update(&cd.0);
let sig = k.sign_prehashed(x, None).unwrap().to_bytes();
println!(
"{}{}{}{}",
const_hex::encode(sig),
const_hex::encode(nonce.to_be_bytes()),
const_hex::encode(target.0),
const_hex::encode(cd.clone().0)
);I understand why borsh is somewhat stigmatised as a calldata format (EVM conventions, zero byte rebate, etc), though I believe more developers would benefit from the nonstandard format when it comes to Stylus precompiles. It’s so flexible to create something quickly and to drop it in. This code is a good example of how it can be pretty easy I think. The precompiles here (sha512, ed25519) are available at https://github.com/fluidity-money/superposition-precompiles.
Stylus Saturdays is brought to you by… the Arbitrum DAO! With a grant from the Fund the Stylus Sprint program. You can learn more about Arbitrum grants here: https://arbitrum.foundation/grants. Check me out online: https://bayge.xyz
I develop Superposition, a defi-first chain that pays you to use it. You can check our ecosystem of dapps at https://superposition.so.
I didn’t want to monologue for too long here, but having a bytecode interpreter for math that’s generated by a precompile would be amazing to make someday. Someone could also use this in Solidity like a gradually deferred math operation, passing around a word that’s the concatenation of a script. Then the Stylus contract is invoked to do all the heavy lifting.



