The on-chain execution layer. Three contracts handle call dispatch, offset
decoding, and account abstraction. The core contract (MulticallScripter) is
written entirely in Yul assembly for gas efficiency and precise memory control.
A companion DSL (CallBuilder) provides a Solidity-native way to build test
call chains, and 7702Caller adds EIP-7702 compatibility.
| File | Lines | Purpose |
|---|---|---|
src/MulticallScripter.sol |
236 | Core executor: execute() runs chained calls with mcopy-based return data wiring |
src/CallBuilder.sol |
294 | Offset encoding helpers + Scripter DSL for building call chains in tests |
src/7702Caller.sol |
— | EIP-7702 account abstraction wrapper |
graph LR
CB[CallBuilder] -->|inherits| K[Constants]
MSC[MulticallScripter] -->|inherits| K
SCR[Scripter] -->|inherits| CB
SCR -->|calls| MSC
T7702[7702Caller] -->|delegates| MSC
The canonical definition lives in schema/offset-schema.json — the Solidity constants
in src/MulticallScripter.sol must match it exactly.
There are 5 call type flags, each with a unique bit layout packed into one uint256:
| Flag | Type | Layout |
|---|---|---|
0xFF |
Static call | [8:flag][120:memTarget][120:resultLength] |
0xFE |
State-changing call | [8:flag][8:valueIndex][120:memTarget][120:resultLength] |
0xFC |
Static partial return | [8:flag][120:memTargets(40×3)][48:resultLengths(16×3)][48:returnOffsets(16×3)][16:returnDataSize][8:num_vars] |
0xFB |
Call partial return | Same as 0xFC but includes valueIndex and uses call instead of staticcall |
0xFD |
Delegate call | Reserved, currently reverts with InvalidCalltype |
The execute() function (lines 45-233 of MulticallScripter.sol) is pure Yul assembly:
- Copies all calldata entries contiguously into memory
- Advances free memory pointer past calldata region
- Loops through all calls, extracting
calltypeviashr(VALUE_OFFSET, offset) - Dispatches to the matching handler based on flag
- After each static/partial-return call, uses
mcopyto copy return data into target calldata positions
In state-changing calls, valueIndex=0 means "no msg.value". valueIndex=1 references values[0]. This is converted to byte offsets with shl(5, value) (×32 bytes per word).
mstore(0, 0x8f61746f) // InvalidCalltype
revert(0x1c, 0x04) // revert with 4 bytes starting at position 28The 4-byte selector is written to the rightmost bytes of the first memory word, then reverted with a small slice.
- Bit alignment drift: Every change to offset bit packing must be mirrored exactly in
js/index.js. The two layers share no schema. Roundtrip tests intest/JsLibrary.t.solandtest/MulticallScripter.t.solcatch mismatches. - mcopy requirement: The contract uses
mcopy(EIP-5656, Cancun+). Running on pre-Cancun EVMs will fail at the opcode level. - Stateless contract:
receive()rejects all ETH. The contract must never hold balances — it's a pure execution engine. - Partial return bounds:
memTarget + resultLengthmust not overflow into the pre-allocated calldata region. Violations revert withInvalidMemoryTarget(). - Delegate call unimplemented:
DELEGATE_CALL_FLAG (0xFD)reverts. Adding it requires handlingdelegatecall's context (storage, msg.sender, msg.value are inherited from caller).
- How the JS layer wires calls → docs/javascript.md
- Test patterns for contracts → docs/testing.md
- Solidity coding conventions → .claude/rules/solidity.md
- Architecture overview → docs/OVERVIEW.md