require, a namespace such as ctx or Math, a method on a value, or a property of an account. This page lists each one with its signature, the value it returns, the targets it compiles for, and the message the compiler prints on a target that lacks it. It also lists the names the sauce command and routes.compileSauceProgram add before compiling. Those are not builtins: the compiler alone does not know them (Compiler or SDK).
Compiler or SDK
Thesauce command, the MCP server, actions documents and routes.compileSauceProgram add names to a program before the compiler sees it: the chain’s tokens, protocols and chain id, the token functions and the libraries. routes.compileSauceRoute adds neither the chain id nor the token functions (Compiling programs). The published compiler, @eco-incorp/sauce-compiler, provides only the language. Each row below was compiled both ways: with the raw compile from @eco-incorp/sauce/compiler (the published compiler, re-exported, given no defines and no ambient, and a resolver that serves only the program), and with sauce compile --chain base or --chain solana.
A host that compiles with the compiler alone supplies these itself: Compiling programs covers
defines, ambient and the SDK’s resolvers.
How to read the tables
- Targets.
EVMis every EVM chain,Solanais Solana. An entry markedEVMorSolanaalone fails to compile for the other target. - Types. A builtin takes and returns the types of Types.
scalaris a number or a bool;bytesis a byte string. An integer is a 256-bitUint256(unsigned, the default) orInt256(signed); Integer conversions moves a value between the two. - Refusals. Compiling an entry for a target that lacks it fails with a message of the form “
ctx.txOriginis not available on the svm target”, naming the entry and the target (svmorevm), and often followed by what to write instead. The tables do not repeat that message per row; Target refusals quotes real ones. - Examples. An example named
*.solana.sauce.tscompiles for Solana only. An example named*-evm.sauce.ts, or one that uses what the tables mark EVM only (an entry, orlogwith topics), compiles for Base and Ethereum only:ambient-token-evm.sauce.tsuses the token functions’ EVM signatures, which take a token address where Solana’s take accounts. Every other example compiles for Base, Ethereum and Solana.bindings.sauce.tsandcatch.sauce.tsimportCounter.json, the ABI shown abovebindings.sauce.ts, and compile only with it in the same directory. An address is amainparameter. A contract is normally called through its namespace (AaveV3.Pool.supply(...)); the rawevm.callandevm.staticentries below are the low-level form, and Contracts and ABIs covers the normal spelling. - Results. A result stated for an example comes from
sauce simulateon a local fork of Base at block 52965000 with the current contracts.
Statements
These are available on both targets.require, revert, throw and log are statements; eval returns a value. Their names are reserved: a function cannot be named require, revert, log or eval.
require,revertandthrowrevert the whole transaction, and so do a failing call and every checked operation: nothing that ran before them survives. On EVM, atry/catchblock (below) or a call’s.catch()confines the rollback to its block. See Errors and reverts.requireis a statement:const ok = require(x)is rejected (“requireaborts execution and produces no value — use it as a statement”).- On Solana
logtakesdataalone. A program that needs an event discriminator concatenates it in front of the data. evalruns the inner program read-only: it can read state and return a value, and a write in it reverts the whole cook. If the inner program reverts, so does the outer one. On EVM, inside the inner programctx.msgSender()is the engine andctx.self()is still the Pot; a storage write or a token transfer there reverts. On Solana the inner program can read accounts but cannot write them, and it keeps the transaction’s signer flags, so an account that signed the transaction is still a signer in a call the inner program makes.
guards.sauce.ts
eval.sauce.ts
inner runs read-only, whoever compiled it. On a local fork of Base at block 52992458 with the current contracts, an inner program that returned abi.encode(ctx.msgSender(), ctx.self()) returned the engine’s address and the Pot’s, where the outer program’s own ctx.msgSender() was the owner; an inner evm.sstore and an inner USDC.transfer each reverted the cook with no data.
log topics exist on EVM only. A topic is a scalar, so an event signature is hashed first:
log-topics.sauce.ts
try/catch
On EVM, try { … } catch (error) { … } is a statement. A block of several statements is atomic: if any of it fails, its calls, storage writes and assignments are all undone, and then the handler runs. Unlike JavaScript, a variable assigned inside the block has its old value again in the handler. Work before the block stays. error is the revert data as raw bytes, at most the first 256 of them, and may be empty; check its length before decoding it. catch { … } without a name discards it. There is no finally (“a finally block is not supported in SauceScript”), and Solana has no try/catch. A block holding a single external call is that call’s .catch(), with its narrower boundary. Errors and reverts covers both forms.
try-catch.sauce.ts
limit 100: amount 50 returns 2 and moves 50 of USDC’s smallest units to to. amount 200 returns 1010: stage is 1 again, error is the 10 bytes of "over limit", and no USDC moves, because the transfer was undone with the block.
program
Run the result with
eval(parcel.code.concat(parcel.args)). The arrow may read the enclosing function’s values. A value known at compile time folds into the closure’s code; a run-time number, address or bool becomes a captured argument. Captures are read-only: “a belongs to the program around this closure: the closure’s program reads it, and can neither assign it nor change what it holds”. Run-time bytes cannot be captured: “b is a run-time bytes of the program around this closure, which the closure’s program cannot read: it reads a number, an address or a bool, and the values the compiler knows”. An arrow function is accepted only here and in a .catch() handler.
program.sauce.ts
delta −10 it returns the 32-byte word of −5.
ctx
ctx reads the context a program runs in. Every read is a call: ctx.self(), not ctx.self. A wrong argument count gives “ctx.timestamp takes 0 argument(s), but this call passes 1”, and an unknown name gives “ctx.origin is not a context builtin”.
Reads on both targets
On Solana the engine sees the Pot as one of its accounts, so
ctx.self() is the engine’s program id and not the Pot; pot, and pot as a token holder (pot.USDC), are still the Pot. EVM and Solana lists the other differences.
ctx-both.sauce.ts
vault is an attached account and ctx.balance(vault.address) reads its lamports. On EVM, an Account is an address, and the same call reads that address’s balance.
Reads on EVM only
ctx.txOrigin()is not the caller. It differs fromctx.msgSender()whenever a contract stands between the signer and the program, such as a smart wallet or a relayer. Authorize onctx.msgSender(): a check of the origin passes for any contract the signer calls.ctx.prevrandao()can be biased by whoever produces the block. Do not decide anything with it that they would profit from choosing.ctx.blobBaseFee()is not implemented on every chain. On Arbitrum and Polygon, a cook that reaches the read halts, using up its gas and returning no revert data, although the program still compiles for those chains. Read it only on a chain that has it.- A bare name is refused.
gasLeft()gives ”… is called but never declared; writectx.gasLeft()instead”. A bareblockHash(n),blobHash(i)orblobBaseFee()is pointed to itsctxspelling the same way, and so areorigin()andgasLimit(), toctx.txOrigin()andctx.blockGasLimit().
ctx-evm.sauce.ts
crypto
crypto.keccak256takes bytes. A number gives “keccak256’s argument requires a bytes value”; hash a word withcrypto.keccak256(x.toBytesBe(32)).crypto.findPDAmust be destructured: “crypto.findPDAproduces two values — bind them by destructuring, asconst [address, bump] = crypto.findPDA(programId, seeds)”.[a]and[, bump]are accepted. At most 15 seeds: more compile but fail when run.- An unknown name gives “
crypto.sha256is not a crypto intrinsic”.
crypto.sauce.ts
Math
These are SauceScript’s integer functions. They are not JavaScript’s Math: Math.min, Math.max, Math.abs, Math.pow and Math.floor give “Math.min is not a Math intrinsic”. Write an if or a ternary for a minimum, and ** for a power.
Every Math function is available on EVM and Solana. A wrong argument count gives “Math.sqrt takes 1 argument(s), but this call passes 2”.
sqrt, mulDiv, addMod and mulMod work on unsigned integers, and a negative Int256 argument reverts: Math.sqrt of an Int256 −4 reverts with no data, and of 16 returns 4. Math.signExtend(raw, 2) reads a 24-bit int24 word, such as a pool tick: with raw 16777206 (0xfffff6) it returns −10. Assigning a negative result to a Uint256 reverts.
Signed arithmetic and comparisons need no function: the operators /, %, >>, <, >, <= and >= are signed on Int256 values.
math.sauce.ts
Integer conversions
An integer is aUint256 from 0 through 2²⁵⁶ − 1 or an Int256 from −2²⁵⁵ through 2²⁵⁵ − 1. A negative literal is an Int256, and arithmetic that mixes the two is refused: “cannot combine Uint256 and Int256 in this expression; convert an operand with Int256(value) or Uint256(value) first (conversions check bounds)”. These four builtins convert, on EVM and Solana:
Int256(…) and Uint256(…) keep the number and check that it fits; .fromBits keeps the bits and changes the number. Use .fromBits only to reinterpret a raw word, such as a signed value read from storage. A return type, a parameter type or an annotation converts a scalar with the same checks as Int256(…) and Uint256(…). Int256 and Uint256 are reserved names: “Int256 is a builtin constructor and cannot be bound — constructor calls resolve against the builtin before any binding is consulted, so pick another name”.
amount + delta, with amount a Uint256 and delta an Int256, is refused with the message above. Convert one operand first:
mixed-add.sauce.ts
abi
abi is EVM only. Program arguments still work on Solana; they are decoded separately from abi.
- Shapes. A field type is a SauceScript name:
"Uint256","Int256","Address","bool","bytes","string","number","bigint","boolean", any of them followed by[], and records such as"{Uint256, bool}". Solidity spellings are rejected: “uint256is not an abi.decode field type — …”. Shapes held in a variable are rejected too: “abi.decode’s second argument must be an array literal of shape strings, e.g. [“Uint256”, “bytes[]”]”. - Fields. A
"bool"field decodes as a bool, an"Address"field keeps only the low 20 bytes, an"Int256"field decodes a signed word, and every other single-value field decodes as an unsigned number. Read a field by position (t[0]) or by destructuring;.nameon the result is rejected because the fields are unnamed. - Depth. Deeply nested values can exceed the engine’s execution limits when the program runs. Keep encoded structures shallow, and simulate the largest input the program accepts.
- Errors: “abi.encode requires at least one value to encode”; “abi.encode takes at most 255 arguments, got 256”; “abi.decode takes exactly 2 arguments (data, shape), got N”; “abi.decode’s first argument must be bytes”.
abi.sauce.ts
evm
evm is EVM only. On Solana every function here gives “evm.<name> is not available on the svm target”, with a pointer to its svm equivalent.
A call to another contract is normally written through its name, AaveV3.Pool.supply(...). The evm.call, evm.static and evm.delegate entries below are the raw calls such a call lowers to; use them when the callee has no registry entry and no ABI file.
Calls
The optional last argument
gas caps the gas forwarded to the call. Without it, the call gets what the EVM allows; with 0 it gets none. A bound call takes the same cap as .gas(cap).
Errors: “evm.call() expects 3 argument(s), or 4 with a gas cap, but this call passes 2”; “evm.call()’s calldata argument must be bytes”. All three accept .catch() (Contract bindings).
evm-calls.sauce.ts
d0e30db0 is the selector of deposit() and 18160ddd that of totalSupply(). Moving a token through its name (WETH.transfer) is the normal spelling; see Names.
static-gas.sauce.ts
cap 50000 returns 1; cap 100 and cap 0 return 0.
Storage
Storage belongs to the executing contract, the Pot.
A top-level
let in a program is not storage: it is scratch space, reset on every run.
evm-storage.sauce.ts
evm.sstore writes any slot of the Pot’s storage, the Pot’s own included: the bitmap of used and cancelled signed-cook nonces, and, on a Pot whose Router is not fixed, the Router it calls for pool callbacks. A Pot deployed with deployPot(owner, salt, true) keeps its Router in its code, out of a program’s reach. A slot taken from an argument, as here, can be one of those. Derive the slots a program owns by hashing a name of its own, and see Security considerations for what a write there changes.
Deployment
initcode is creation bytecode, not the deployed code, and must be bytes: “evm.create’s init code requires a bytes value”. An omitted value sends none, and the Pot pays any value given.
evm-deploy.sauce.ts
evm.create and evm.create3 take the same init code and an optional value: evm.create(initcode), evm.create3(initcode, salt, value).
svm
svm is Solana only. On EVM every function here gives “svm.<name> is not available on the evm target”, with a pointer to its evm equivalent. An unknown name gives “svm.rent is not an svm builtin; svm is a reserved namespace root”.
An account is a main parameter, in the order the transaction attaches them, optionally with the roles it must have: Account, Account<Writable>, Account<Signer> or Account<Writable, Signer>.
Refusals: a
main account not declared writable gives “state is not declared writable, so svm.sstore may not write it — declare it Account<Writable> (or Account<Writable, Signer>)”, then “state is not declared a signer, so svm.sstore may not write it — … declare it Account<Writable, Signer>”. svm.remainingAccounts() used anywhere but an initializer gives “svm.remainingAccounts() can only initialize a let/const — bind it first (const rest = svm.remainingAccounts();), then use the binding”.
svm-data.solana.sauce.ts
svm-call.solana.sauce.ts
svm-remaining.solana.sauce.ts
Methods on values
These work on both targets, on a variable, an expression or a literal:hex`a9`.concat(d) compiles.
A constant
width outside 1 to 32 gives “a .toBytesBe/.toBytesLe width must be between 1 and 32 bytes, got 33”. A computed width above 32 reverts, and a computed 0 gives empty bytes.
methods.sauce.ts
length.sauce.ts
Account properties
AnAccount parameter has these properties. They are read-only: assigning one gives “.owner is read-only — …”.
On Solana an account is not an ordinary value. Returning one gives “a return value can’t be an account — …”, and using one where a number is expected, as in arithmetic, gives ”…: expected a number, found an account”. Use
account.address when you need its key. On EVM, account.owner has no meaning, because an address is its code: check it with ctx.isContract.
account-both.sauce.ts
account-solana.solana.sauce.ts
Constructors and unary minus
These work on both targets.
With
x a Uint256 parameter, return -x; returns 0 for 0 and reverts with no data for 5.
constructors.sauce.ts
Contract bindings
An ABI imported from a JSON file binds a name, hereCounter. This is the low-level spelling: a contract the registry names is called through its namespace, and Contracts and ABIs covers both. Bindings are EVM only.
A binding never sends value: use
evm.call(address, value, data) for that. An ABI intN output is an Int256. Every other single-value output is an unsigned number, bool and address included, so a : bool function cannot return a call directly. A method with no outputs returns its raw return bytes, and one with several returns a record named after the ABI’s outputs.
A result that nothing reads is not decoded or checked, even when it is assigned to an unused variable: an empty or malformed reply, or a call to an address with no code, then passes. Reading any part of a result decodes all of it. Security considerations covers what to check instead.
Counter.json
bindings.sauce.ts
.gas()
<call>.gas(cap) forwards at most cap gas to one external call: a bound method, or a protocol or token call written by name. The raw evm.call, evm.static and evm.delegate take the cap as their last argument instead. .gas() and .catch() combine in either order. On Solana it is refused: “a call's .gas() cap is not available on the svm target; an SVM cross-program invocation runs on its transaction’s one compute budget, which only the transaction’s ComputeBudget instruction sets; there is no per-call cap”.
gas-cap.sauce.ts
cap 50000 returns 5000000 and cap 100 returns 0.
.catch()
.catch() survives a failed external EVM call. It works on bound contract calls, calls written by name, and evm.call, evm.static and evm.delegate.
.catch()covers the call alone. Evaluating its arguments, and decoding a successful reply that the program reads, happen outside it: a call to an address with no code succeeds with empty return data,.catch()does not run, and decoding that empty reply reverts the cook. Checkctx.isContract(address)first when the address may have no code. To catch a decode as well, put the call and the read in atry/catchblock.erroris untrusted data from the callee and may be empty or cut short: check its length before decoding it. A second handler parameter is refused: “a.catch()handler with more than one parameter (it takes the revert data) is not supported in SauceScript”.- Use a block, not
() => value: “a concise-body.catch()handler (use a{ ... }block body) is not supported in SauceScript”. Areturnor abreakorcontinueleaving the block is not allowed in the statement form. The value form needs exactlyreturn <constant>;, a constant that fits the declared ABI result width. - A handler, here or in a
try/catch, compiles to at most 255 bytes: “acatchblock or.catch()handler compiles to 320 bytes, over the 255-byte limit — shorten the handler”. Move a long handler’s work into a function and call it.
catch.sauce.ts
Ambient token functions
These functions are in scope with no import in every program thesauce command, the MCP server, actions documents and routes.compileSauceProgram compile. They are the functions of @sauce/token, which those add to the compile; the compiler alone does not declare them, and neither does routes.compileSauceRoute unless you pass ambient: ["@sauce/token"] (Compiling programs). A function a target lacks is refused there: “ownerOf is called but never declared”.
Their arguments differ by target: on Solana they take the accounts they read or move through.
@sauce/token gives each signature, its checks and its refusals. On EVM the writes have SafeERC20 semantics.
ambient-token-evm.sauce.ts
ambient-token.solana.sauce.ts
Names resolved at compile time
These names are not functions of the language: the SDK resolves them, before compiling, against the chain the program is compiled for. The compiler alone (@eco-incorp/sauce-compiler) refuses them (Compiler or SDK). Names covers them in full.
- A test takes a contract or a token symbol. Any other use of a name the chain lacks is refused at the use, naming the chain. A name no chain has is an error, not
false, so a typo cannot drop a branch. A name is tested by writing the name itself, never by a call. - A
Token(address)bound to a name must be declared once, never reassigned, and used only ast.<method>(...). pot.owner,pot.balanceandpot.<TOKEN>are read-only members. Keep a value you need to change in a local variable.- A program’s declaration or import of
potorAccountshadows that global in its scope. A compile-time binding of either name also takes precedence.Accountremains the account type in type positions. - On Solana,
Account(pot.address).ownerreads the program that owns the Pot account, not the owner who signs the cook.
compile-time.sauce.ts
Target refusals
A builtin used on a target that lacks it fails at compile time, never as a silent difference. Each of these is refused, with the compiler’s message:
Compile a program that uses a target’s builtins for that target only, or put each target’s code in its own arm (One module, an arm per target).
Gas costs
This page gives no gas figures. A program’s gas is whatsauce simulate reports for it; cook() lists what a cook costs.
Next steps
Errors and reverts
require, throw, atomic try/catch and .catch() in the context of a whole program.Contracts and ABIs
Calling a contract through its namespace, an ABI import or an address.
EVM and Solana
Every difference between the two targets, including accounts and
ctx.Names
Token symbols,
Token(address), pot and protocol namespaces.