> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Builtins

> Look up a builtin, or a name the SDK adds, with its signature, what it returns and the targets it compiles for. For anyone writing or reviewing a program.

A builtin is a name the compiler provides: a statement such as `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)).

| Group | Entries |
| - | - |
| [Statements](#statements) | `require`, `revert`, `throw`, `log`, `eval`, and the atomic `try`/`catch` (EVM) |
| [`program`](#program) | a closure compiled as a separate program |
| [`ctx`](#ctx) | context reads: caller, Pot, chain, block, balances, code |
| [`crypto`](#crypto) | `keccak256`, `ecdsaVerify`, `findPDA` |
| [`Math`](#math) | integer functions that JavaScript's `Math` lacks |
| [Integer conversions](#integer-conversions) | `Int256`, `Uint256`, `Int256.fromBits`, `Uint256.fromBits` |
| [`abi`](#abi) | `abi.encode`, `abi.decode` (EVM) |
| [`evm`](#evm) | calls, storage, deployment (EVM) |
| [`svm`](#svm) | account data, calls, remaining accounts (Solana) |
| [Methods on values](#methods-on-values) | `.slice`, `.concat`, `.toBe`, `.toLe`, `.toBytesBe`, `.toBytesLe`, `.length` |
| [Account properties](#account-properties) | `.address`, `.owner`, `.dataLen`, `==` |
| [Constructors and unary minus](#constructors-and-unary-minus) | `hex`, `new Array`, `-x` |
| [Contract bindings](#contract-bindings) | `.at`, `.view`, `.lib`, bound variables, `.gas()`, `.catch()` (EVM) |
| [Ambient token functions](#ambient-token-functions) | the [`@sauce/token`](https://docs.eco.com/sauce-libraries/token) functions, with no import |
| [Names resolved at compile time](#names-resolved-at-compile-time) | tests of a registry name, `Token`, `pot` |

## Compiler or SDK

The `sauce` 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](https://docs.eco.com/sauce-sdk/typescript/compiling.md)). 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`.

| Name | Provided by | `compile` alone | `sauce compile` |
| - | - | - | - |
| The language: `require`, `ctx.self()`, `crypto.keccak256`, `Math.mulDiv`, `Int256()`, `new Array`, `hex`, `.toBytesBe`, `program`, `eval`, `evm.sload` (EVM) | compiler | compiles, EVM and Solana | compiles |
| `chainId` | SDK | "`chainId` is not a declared variable" | compiles |
| `Token(address)` | SDK | "`Token` is called but never declared" | compiles |
| the Pot, `pot.owner`, `pot.<TOKEN>`, `<holder>.<TOKEN>`, `<holder>.balance` | SDK | "`pot` is not a declared variable" | compiles, EVM and Solana |
| Accounts written where they are used: `Account(key)`, `Account.pda(program, [seeds])`, `Account.systemProgram` (Solana) | SDK | "`Account` is called but never declared" | compiles, Solana |
| A token symbol's methods, `USDC.balanceOf(holder)` | SDK | "this call does not name a function, builtin, or contract method in scope" | compiles |
| [Ambient token functions](#ambient-token-functions), such as `transfer` and `balanceOf` | SDK | "`transfer` is called but never declared" | compiles |
| Protocol namespaces: `UniswapV3.Factory`, `Base.UniswapV3.Factory`, `Orca.Whirlpool` (Solana) | SDK | "`UniswapV3` is not a declared variable" | compiles |
| `import … from "@sauce/token"` and `"@sauce/router"` | SDK | "no module at `@sauce/token`" | compiles |

A host that compiles with the compiler alone supplies these itself: [Compiling programs](https://docs.eco.com/sauce-sdk/typescript/compiling.md) covers `defines`, `ambient` and the SDK's resolvers.

## How to read the tables

* **Targets.** `EVM` is every EVM chain, `Solana` is Solana. An entry marked `EVM` or `Solana` alone fails to compile for the other target.
* **Types.** A builtin takes and returns the types of [Types](https://docs.eco.com/sauce/types.md). `scalar` is a number or a bool; `bytes` is a byte string. An integer is a 256-bit `Uint256` (unsigned, the default) or `Int256` (signed); [Integer conversions](#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.txOrigin` is not available on the svm target", naming the entry and the target (`svm` or `evm`), and often followed by what to write instead. The tables do not repeat that message per row; [Target refusals](#target-refusals) quotes real ones.
* **Examples.** An example named `*.solana.sauce.ts` compiles for Solana only. An example named `*-evm.sauce.ts`, or one that uses what the tables mark EVM only (an entry, or `log` with topics), compiles for Base and Ethereum only: `ambient-token-evm.sauce.ts` uses 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.ts` and `catch.sauce.ts` import `Counter.json`, the ABI shown above `bindings.sauce.ts`, and compile only with it in the same directory. An address is a `main` parameter. A contract is normally called through its namespace (`AaveV3.Pool.supply(...)`); the raw `evm.call` and `evm.static` entries below are the low-level form, and [Contracts and ABIs](https://docs.eco.com/sauce/contracts-and-abis.md) covers the normal spelling.
* **Results.** A result stated for an example comes from `sauce simulate` on 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`.

| Signature | Returns | Targets | Meaning |
| - | - | - | - |
| `require(condition, message?)` | none | EVM, Solana | Reverts unless `condition` is nonzero. `condition` is a scalar, `message` is bytes. |
| `revert(data?)` | none | EVM, Solana | Reverts with `data` as the revert data. |
| `throw data` | none | EVM, Solana | The same as `revert(data)`. |
| `log(data, topic1, …)` | none | EVM, Solana | Emits a log record. `data` is bytes. Up to 4 scalar topics, on EVM only. |
| `eval(program, ...args)` | `bytes` | EVM, Solana | Runs another compiled program and returns what it returns. Up to 16 scalar `args` become its `main` arguments. |

* `require`, `revert` and `throw` revert the whole transaction, and so do a failing call and every checked operation: nothing that ran before them survives. On EVM, a `try`/`catch` block (below) or a call's [`.catch()`](#contract-bindings) confines the rollback to its block. See [Errors and reverts](https://docs.eco.com/sauce/errors.md).
* `require` is a statement: `const ok = require(x)` is rejected ("`require` aborts execution and produces no value — use it as a statement").
* On Solana `log` takes `data` alone. A program that needs an event discriminator concatenates it in front of the data.
* `eval` runs 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 program `ctx.msgSender()` is the engine and `ctx.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.

```ts guards.sauce.ts theme={null}
// Runs on every chain.
function main(out: Uint256, minOut: Uint256, code: Uint256) {
  require(out >= minOut, "slippage");
  if (code == 1) {
    throw "below floor";
  }
  if (code == 2) {
    revert(code.toBytesBe(32));
  }
  log(out.toBytesLe(8));
}
```

```ts eval.sauce.ts theme={null}
// Runs the compiled program `inner` with one argument and returns its result.
function main(inner: bytes, amount: Uint256): bytes {
  return eval(inner, amount);
}
```

`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:

```ts log-topics.sauce.ts theme={null}
function main(to: Address, amount: Uint256) {
  const paid = crypto.keccak256("Paid(address,uint256)");
  log(amount.toBytesBe(32), paid, to);
}
```

### `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()`](#contract-bindings), with its narrower boundary. [Errors and reverts](https://docs.eco.com/sauce/errors.md) covers both forms.

```ts try-catch.sauce.ts theme={null}
// Moves `amount` USDC to `to` and checks it against `limit` in one atomic block.
function main(to: Address, amount: Uint256, limit: Uint256): Uint256 {
  let stage = 1;
  try {
    stage = 2;
    USDC.transfer(to, amount);
    require(amount <= limit, "over limit");
  } catch (error) {
    return stage * 1000 + error.length;
  }
  return stage;
}
```

With a Pot holding 1 USDC and `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`

| Signature | Returns | Targets | Meaning |
| - | - | - | - |
| `program(closure, ...args)` | `{ code: bytes, args: bytes }` | EVM, Solana | Compiles an inline arrow function as a separate program. `code` is its bytecode and `args` its arguments, one per arrow parameter. |

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.

```ts program.sauce.ts theme={null}
// Divides `delta` by 2 in a separate program and returns that program's result.
function main(delta: Int256): bytes {
  const nested = program((divisor: Int256): Int256 => delta / divisor, 2);
  return eval(nested.code.concat(nested.args));
}
```

With `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

| Signature | Returns | EVM | Solana |
| - | - | - | - |
| `ctx.msgSender()` | `Uint256` | The Pot's immediate caller (`msg.sender`): the owner for a `cook` on a private Pot, the submitter for a `cookSigned`, who can be a third party. It is not proof of ownership: pass the intended recipient as a `main` parameter. The Pot's owner is `pot.owner`. | The first signer among the accounts the engine receives. It is not proof of who sent the transaction. |
| `ctx.self()` | `Uint256` | The executing contract: the Pot, which holds the program's balances and allowances. | The engine's program id, which owns every Pot account. `acct.owner == ctx.self()` holds for a Pot and for any other account assigned to the engine, so it does not prove an account is the Pot; re-deriving the Pot's address does. |
| `ctx.chainId()` | `Uint256` | The chain id. | 1399811149 on mainnet, 1399811150 on devnet and testnet. |
| `ctx.blockNumber()` | `Uint256` | The block number. | The slot. |
| `ctx.timestamp()` | `Uint256` | The block time, in seconds. | The cluster time, in seconds. |
| `ctx.selfBalance()` | `Uint256` | The executing contract's balance, in wei. | The executing program's lamports. |
| `ctx.msgData()` | `bytes` | The program's whole input, arguments included. | The instruction data. |
| `ctx.balance(address)` | `Uint256` | The balance of `address`, in wei. | The lamports of the attached account with that key. Pass `acct.address`, not the account: passing the account fails with "ctx.balance's address: expected a value, found an account". A key that is not attached fails at run time. |

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](https://docs.eco.com/sauce/targets.md) lists the other differences.

```ts ctx-both.sauce.ts theme={null}
// Pays nothing; reads the context on every chain.
function main(deadline: Uint256, vault: Account): Uint256 {
  require(ctx.timestamp() <= deadline, "expired");
  log(ctx.self().toBytesBe(32));
  return ctx.balance(vault.address) + ctx.selfBalance() + ctx.blockNumber() + ctx.chainId();
}
```

On Solana, `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

| Signature | Returns | Meaning |
| - | - | - |
| `ctx.msgValue()` | `Uint256` | The native value sent with the call (`msg.value`). |
| `ctx.gasLeft()` | `Uint256` | The gas remaining. |
| `ctx.codeSize(address)` | `Uint256` | The size of `address`'s code. |
| `ctx.codeHash(address)` | `Uint256` | The hash of `address`'s code. |
| `ctx.isContract(address)` | `bool` | True if `address` has code. |
| `ctx.isEOA(address)` | `bool` | True if `address` has no code. |
| `ctx.txOrigin()` | `Address` | The account that signed the transaction (`tx.origin`). |
| `ctx.coinbase()` | `Address` | The block's fee recipient (`block.coinbase`). |
| `ctx.prevrandao()` | `Uint256` | The block's randomness value (`block.prevrandao`). |
| `ctx.blockGasLimit()` | `Uint256` | The block's gas limit (`block.gaslimit`), not the transaction's. `ctx.gasLeft()` is what the program has left. |
| `ctx.baseFee()` | `Uint256` | The block's base fee per gas, in wei (`block.basefee`). |
| `ctx.gasPrice()` | `Uint256` | The price per gas the transaction pays, in wei (`tx.gasprice`): the base fee plus the priority fee, not the fee cap. |
| `ctx.blockHash(n)` | `Uint256` | The hash of block `n`, for the 256 blocks before the current one. It is 0 for the current block, a later one or an older one, and never reverts. |
| `ctx.blobHash(i)` | `Uint256` | The versioned hash of the transaction's blob `i`. It is 0 past the last blob, and for every index of a transaction that carries none. |
| `ctx.blobBaseFee()` | `Uint256` | The block's base fee per unit of blob gas, in wei (`block.blobbasefee`). |

* **`ctx.txOrigin()` is not the caller.** It differs from `ctx.msgSender()` whenever a contract stands between the signer and the program, such as a smart wallet or a relayer. Authorize on `ctx.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; write `ctx.gasLeft()` instead". A bare `blockHash(n)`, `blobHash(i)` or `blobBaseFee()` is pointed to its `ctx` spelling the same way, and so are `origin()` and `gasLimit()`, to `ctx.txOrigin()` and `ctx.blockGasLimit()`.

```ts ctx-evm.sauce.ts theme={null}
// Guards a program on the chain's base fee, the target's code and the value sent.
function main(token: Address, maxBaseFee: Uint256): Uint256 {
  require(ctx.codeSize(token) > 0, "no code at token");
  require(ctx.isContract(token), "not a contract");
  require(ctx.baseFee() <= maxBaseFee, "base fee too high");
  require(ctx.msgValue() == 0, "no value expected");
  require(ctx.msgSender() == ctx.txOrigin(), "called through a contract");
  return ctx.blockHash(ctx.blockNumber() - 1) + ctx.gasLeft();
}
```

## `crypto`

| Signature | Returns | Targets | Meaning |
| - | - | - | - |
| `crypto.keccak256(data)` | `Uint256` | EVM, Solana | The keccak-256 hash of `data` (bytes). |
| `crypto.ecdsaVerify(hash, signature, signer)` | `bool` | EVM, Solana | True if `signer` signed `hash`. `signature` is the 65-byte `r‖s‖v`. On EVM a contract signer is also checked (EIP-1271); on Solana only key signatures are checked. |
| `const [address, bump] = crypto.findPDA(programId, seeds)` | two `Uint256` | EVM, Solana | Derives a Solana program address and its bump from a program id (a number) and `seeds` (`bytes[]`). On EVM it derives the same Solana address. |

* `crypto.keccak256` takes bytes. A number gives "keccak256's argument requires a bytes value"; hash a word with `crypto.keccak256(x.toBytesBe(32))`.
* `crypto.findPDA` must be destructured: "`crypto.findPDA` produces two values — bind them by destructuring, as `const [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.sha256` is not a crypto intrinsic".

```ts crypto.sauce.ts theme={null}
function main(programId: Address, user: Address, digest: Uint256, sig: bytes): Uint256 {
  require(crypto.ecdsaVerify(digest, sig, user), "bad signature");
  const [vault, bump] = crypto.findPDA(programId, [hex`01`, "vault", user.toBytesBe(32)]);
  return vault + bump + crypto.keccak256("Transfer(address,address,uint256)");
}
```

## `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".

| Signature | Returns | Meaning | Reverts when |
| - | - | - | - |
| `Math.sqrt(x)` | `Uint256` | The integer square root. | `x` is a negative `Int256` |
| `Math.mulDiv(x, y, d)` | `Uint256` | ⌊x·y / d⌋, without intermediate overflow. | `d` is 0, or the result does not fit 256 bits |
| `Math.addMod(a, b, n)` | `Uint256` | (a + b) mod n. | `n` is 0 |
| `Math.mulMod(a, b, n)` | `Uint256` | (a · b) mod n. | `n` is 0 |
| `Math.signExtend(value, byte)` | `Int256` | Reads the low `byte + 1` bytes of `value` as a signed integer, where byte 0 is the lowest. | never |

`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.

```ts math.sauce.ts theme={null}
// A fee in basis points, a square root, and a pool tick read from a raw 24-bit word.
function main(amount: Uint256, bps: Uint256, rawTick: Uint256): Int256 {
  const fee = Math.mulDiv(amount, bps, 10000);
  const root = Math.sqrt(amount);
  const tick = Math.signExtend(rawTick, 2);
  if (tick < 0) {
    return tick / 2 + Int256(Math.addMod(fee, root, 7));
  }
  return tick + Int256(Math.mulMod(fee, root, 7));
}
```

## Integer conversions

An integer is a `Uint256` 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:

| Signature | Returns | Meaning | Reverts when |
| - | - | - | - |
| `Int256(value)` | `Int256` | The same number, signed. | `value` is above 2²⁵⁵ − 1 |
| `Uint256(value)` | `Uint256` | The same number, unsigned. | `value` is negative |
| `Int256.fromBits(value)` | `Int256` | The same 256 bits read as two's complement. | never |
| `Uint256.fromBits(value)` | `Uint256` | The same 256 bits read as unsigned. | never |

`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".

```ts negate.sauce.ts theme={null}
function main(amount: Uint256): Int256 {
  return -Int256(amount);
}
```

```ts to-unsigned.sauce.ts theme={null}
function main(delta: Int256): Uint256 {
  return Uint256(delta);
}
```

```ts from-bits.sauce.ts theme={null}
function main(word: Uint256): Int256 {
  return Int256.fromBits(word) / 2;
}
```

```ts bits-unsigned.sauce.ts theme={null}
function main(delta: Int256): Uint256 {
  return Uint256.fromBits(delta);
}
```

| Program | Argument | Result |
| - | - | - |
| `negate.sauce.ts` | 10 | −10 |
| `negate.sauce.ts` | 2²⁵⁵ | reverts with no data: 2²⁵⁵ does not fit an `Int256` |
| `to-unsigned.sauce.ts` | 5 | 5 |
| `to-unsigned.sauce.ts` | −5 | reverts with no data |
| `from-bits.sauce.ts` | 2²⁵⁶ − 10 | −5 |
| `bits-unsigned.sauce.ts` | −1 | 2²⁵⁶ − 1 |

`amount + delta`, with `amount` a `Uint256` and `delta` an `Int256`, is refused with the message above. Convert one operand first:

```ts mixed-add.sauce.ts theme={null}
// Adds a signed `delta` to `amount`; reverts when `amount` is above 2²⁵⁵ − 1.
function main(amount: Uint256, delta: Int256): Int256 {
  return Int256(amount) + delta;
}
```

## `abi`

`abi` is EVM only. Program arguments still work on Solana; they are decoded separately from `abi`.

| Signature | Returns | Targets | Meaning |
| - | - | - | - |
| `abi.encode(v1, …, vN)` | `bytes` | EVM | Solidity ABI encoding of 1 to 255 values, as one argument list. Accounts are not accepted. A single record argument is encoded as its fields. |
| `abi.decode(data, [shape, …])` | record | EVM | Decodes `data` (bytes) into a record with unnamed fields. The shapes are written in place as an array of strings. |

* **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: "`uint256` is 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; `.name` on 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".

```ts abi.sauce.ts theme={null}
function main(total: Uint256, recipient: Address, data: bytes): bytes {
  const [amount, to] = abi.decode(data, ["Uint256", "Address"]);
  require(amount <= total, "over total");
  return abi.encode(amount, to, total);
}
```

## `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

| Signature | Returns | Meaning |
| - | - | - |
| `evm.call(address, value, data, gas?)` | `bytes` | A call with native `value`. It returns the raw return data and is the only way to send value with a call. |
| `evm.static(address, data, gas?)` | `bytes` | A read-only call (staticcall). |
| `evm.delegate(address, data, gas?)` | `bytes` | A delegatecall. |

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)`](#contract-bindings).

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](#contract-bindings)).

```ts evm-calls.sauce.ts theme={null}
// Wraps native coin into a wrapped-native contract, then reads that contract's total supply.
function main(wrapped: Address, amount: Uint256): Uint256 {
  evm.call(wrapped, amount, hex`d0e30db0`);
  const supply = evm.static(wrapped, hex`18160ddd`);
  return abi.decode(supply, ["Uint256"])[0];
}
```

`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](https://docs.eco.com/sauce/names.md).

```ts static-gas.sauce.ts theme={null}
// Returns 1 if USDC's totalSupply() answers within `cap` gas, and 0 if that call fails.
function main(cap: Uint256): Uint256 {
  let ok = 1;
  evm.static(USDC, hex`18160ddd`, cap).catch(() => {
    ok = 0;
  });
  return ok;
}
```

On Base, `cap` 50000 returns 1; `cap` 100 and `cap` 0 return 0.

### Storage

Storage belongs to the executing contract, the Pot.

| Signature | Returns | Meaning |
| - | - | - |
| `evm.sload(slot)` | `Uint256` | Reads the Pot's storage at `slot`. |
| `evm.sstore(slot, value)` | none | Writes the Pot's storage. A statement: used as a value it gives "`evm.sstore` writes storage and produces no value — use it as a statement". |
| `evm.tload(key)` | `Uint256` | Reads transient storage (EIP-1153), which is cleared after the transaction. |
| `evm.tstore(key, value)` | none | Writes transient storage. A statement. |

A top-level `let` in a program is not storage: it is scratch space, reset on every run.

```ts evm-storage.sauce.ts theme={null}
// Counts runs in the Pot's storage, and flags re-entry in transient storage.
function main(slot: Uint256, key: Uint256): Uint256 {
  require(evm.tload(key) == 0, "re-entered");
  evm.tstore(key, 1);
  const count = evm.sload(slot) + 1;
  evm.sstore(slot, count);
  evm.tstore(key, 0);
  return count;
}
```

`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](https://docs.eco.com/sauce/security-considerations.md#signed-cooks) for what a write there changes.

### Deployment

| Signature | Returns | Meaning |
| - | - | - |
| `evm.create(initcode, value?)` | `Address` | Deploys from the Pot with CREATE. Returns zero on failure. |
| `evm.create2(initcode, salt, value?)` | `Address` | Deploys with CREATE2. Returns zero on failure. |
| `evm.create3(initcode, salt, value?)` | `Address` | Deploys with CREATE3. Reverts on failure. |
| `evm.createAddress(deployer, nonce)` | `Address` | Where `evm.create` from `deployer` at `nonce` lands. |
| `evm.create2Address(deployer, salt, initCodeHash)` | `Address` | Where `evm.create2` from `deployer` lands. `initCodeHash` is `crypto.keccak256(initcode)`. |
| `evm.create3Address(salt)` | `Address` | Where `evm.create3` run by the Pot lands. It takes no deployer, because the deployer is always the Pot running the program: the address depends on the Pot and the salt, not on the init code. |

`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.

```ts evm-deploy.sauce.ts theme={null}
function main(initcode: bytes, salt: Uint256, nonce: Uint256): Address {
  const expected = evm.create2Address(pot, salt, crypto.keccak256(initcode));
  const deployed = evm.create2(initcode, salt);
  require(deployed == expected, "create2");
  require(evm.create3Address(salt + 1) != expected, "create3");
  require(evm.createAddress(pot, nonce) != expected, "create");
  return deployed;
}
```

`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>`.

| Signature | Returns | Meaning |
| - | - | - |
| `svm.sload(account, offset, length)` | `bytes` | Reads `length` bytes of the account's data from `offset`. Reading past the end fails the transaction, and that failure cannot be caught: check `account.dataLen` first. |
| `svm.sstore(account, offset, data)` | none | Writes `data` (bytes) into the account's data at `offset`. The account must be declared `Account<Writable, Signer>`. A statement. |
| `svm.call(program, accounts, data)` | `bytes` | Invokes another program with the listed accounts and returns its return data. The program is an `Account` parameter too, and `accounts` is an array of accounts (`[]` is accepted). A failed call fails the whole transaction. |
| `svm.remainingAccount(i)` | `Account` | The `i`-th account attached after the ones `main` declares. Available in any function. |
| `const rest = svm.remainingAccounts()` | `Account[]` | Every account attached after the declared ones, in any function. Only as a `let` or `const` initializer. |

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".

```ts svm-data.solana.sauce.ts theme={null}
// Reads a counter from the first 8 bytes of `state` and writes it back increased by `step`.
function main(state: Account<Writable, Signer>, step: Uint256) {
  require(state.dataLen >= 8, "state too small");
  const count = svm.sload(state, 0, 8).toLe();
  const next = count + step;
  svm.sstore(state, 0, next.toBytesLe(8));
}
```

```ts svm-call.solana.sauce.ts theme={null}
// Invokes a program with three accounts and the instruction data given as an argument.
function main(
  tokenProgram: Account,
  source: Account<Writable>,
  destination: Account<Writable>,
  owner: Account<Signer>,
  instruction: bytes,
): bytes {
  return svm.call(tokenProgram, [source, destination, owner], instruction);
}
```

```ts svm-remaining.solana.sauce.ts theme={null}
// Forwards every account attached after `router` to the router program.
function main(router: Account, data: bytes): bytes {
  const first = svm.remainingAccount(0);
  require(first != router, "first extra account is the router");
  const rest = svm.remainingAccounts();
  return svm.call(router, rest, data);
}
```

## Methods on values

These work on both targets, on a variable, an expression or a literal: ``hex`a9`.concat(d)`` compiles.

| Signature | Returns | Meaning |
| - | - | - |
| `bytes.slice(start, end)` | `bytes` | Bytes `start` up to, but not including, `end`. `end < start` reverts. Arrays have no `.slice`: "a bytes method's receiver requires a bytes value". |
| `bytes.concat(b1, b2, …)` | `bytes` | Joins bytes values. |
| `bytes.toBe()` | `Uint256` | Reads the bytes as a big-endian number. At most 32 bytes: longer input fails when run. |
| `bytes.toLe()` | `Uint256` | Reads the bytes as a little-endian number, Solana's layout. At most 32 bytes. |
| `value.toBytesBe(width)` | `bytes` | The low `width` bytes of a scalar, big-endian. `width` is 1 to 32. |
| `value.toBytesLe(width)` | `bytes` | The same, little-endian. |
| `bytes.length`, `array.length` | `Uint256` | The byte count or the element count. It cannot be assigned. On a record, `.length` looks for a field named `length`. |

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.

```ts methods.sauce.ts theme={null}
// Rebuilds a transfer call's data from a call's input.
function main(input: bytes, to: Uint256, amount: Uint256): bytes {
  const selector = input.slice(0, 4);
  const first = input.slice(4, 36).toBe();
  const second = input.slice(36, 44).toLe();
  require(first + second + input.length > 0, "empty");
  return selector.concat(to.toBytesBe(32), amount.toBytesBe(32), amount.toBytesLe(8));
}
```

```ts length.sauce.ts theme={null}
function main(amounts: Uint256[]): Uint256 {
  let total = 0;
  for (let i = 0; i < amounts.length; i++) {
    total += amounts[i];
  }
  return total;
}
```

## Account properties

An `Account` parameter has these properties. They are read-only: assigning one gives "`.owner` is read-only — …".

| Property | Returns | Targets | Meaning |
| - | - | - | - |
| `account.address` | `Uint256` | EVM, Solana | On Solana, the account's public key. On EVM, the address itself. |
| `account.owner` | `Uint256` | Solana | The program that owns the account. `pot.owner` is not this property: it is the Pot's owner ([Names resolved at compile time](#names-resolved-at-compile-time)). The program that owns the Pot's account is `Account(pot.address).owner`. |
| `account.dataLen` | `Uint256` | Solana | The size of the account's data, in bytes. |
| `account == account` | `bool` | EVM, Solana | Compares two accounts by identity, with `==` and `!=`. Ordering comparisons are rejected: "accounts compare for identity only — `==` and `!=`. …". |

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`.

```ts account-both.sauce.ts theme={null}
function main(a: Account, b: Account, expected: Uint256) {
  require(a != b, "same account");
  require(a.address == expected, "wrong account");
}
```

```ts account-solana.solana.sauce.ts theme={null}
// Checks that `mint` is owned by the token program and is 82 bytes, an SPL Token mint with no extensions.
function main(mint: Account, tokenProgram: Account) {
  require(mint.owner == tokenProgram.address, "not owned by the token program");
  require(mint.dataLen == 82, "not a mint");
}
```

## Constructors and unary minus

These work on both targets.

| Form | Returns | Meaning |
| - | - | - |
| ``hex`digits` `` (the hex literal) | `bytes` | Raw bytes from an even number of hex digits, with an optional `0x` prefix; spaces are ignored. A malformed literal gives "`hex`…\`\` is not a valid hex literal (expected an even number of `[0-9a-fA-F]` digits, optional `0x` prefix)". |
| `new Array(length)` | `Uint256[]` | An array of `length` zeros, to fill by index. |
| `-x` (unary minus) | `Int256` | Checked negation. A negative literal such as `-1` is an `Int256`. Negating a nonzero `Uint256` compiles but reverts when run, so negate an amount as `-Int256(amount)`. Negating the `Int256` minimum overflows and reverts. |

With `x` a `Uint256` parameter, `return -x;` returns 0 for 0 and reverts with no data for 5.

```ts constructors.sauce.ts theme={null}
function main(n: Uint256, x: Uint256): Int256 {
  const xs = new Array(n);
  xs[0] = x;
  const tag = hex`0xdeadbeef`;
  const negative = -Int256(x);
  return negative + Int256(xs[0] + tag.length);
}
```

## Contract bindings

An ABI imported from a JSON file binds a name, here `Counter`. This is the low-level spelling: a contract the registry names is called through its namespace, and [Contracts and ABIs](https://docs.eco.com/sauce/contracts-and-abis.md) covers both. Bindings are EVM only.

| Form | Meaning |
| - | - |
| `Contract.at(address).method(...args)` | Read-only for `view` and `pure` methods; otherwise a regular call that sends no value. |
| `Contract.view(address).method(...args)` | Always read-only. |
| `Contract.lib(address).method(...args)` | Always a delegatecall. |
| `const t = Contract.at(address); t.method(...args)` | The same, through a bound contract variable. |

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](https://docs.eco.com/sauce/security-considerations.md) covers what to check instead.

```json Counter.json theme={null}
[
  {
    "type": "function",
    "name": "count",
    "stateMutability": "view",
    "inputs": [],
    "outputs": [{ "name": "", "type": "uint256" }]
  },
  {
    "type": "function",
    "name": "add",
    "stateMutability": "nonpayable",
    "inputs": [{ "name": "n", "type": "uint256" }],
    "outputs": []
  }
]
```

```ts bindings.sauce.ts theme={null}
import Counter from "./Counter.json";

function main(counter: Address, impl: Address, n: Uint256): Uint256 {
  const c = Counter.at(counter);
  c.add(n);
  const before = Counter.view(counter).count();
  Counter.lib(impl).add(n);
  return before + c.count();
}
```

### `.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".

```ts gas-cap.sauce.ts theme={null}
// Reads the Pot's USDC balance with at most `cap` gas forwarded; 0 if that read fails.
function main(cap: Uint256): Uint256 {
  return USDC.balanceOf(pot).gas(cap).catch(() => {
    return 0;
  });
}
```

With the Pot holding 5 USDC, `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`.

| Form | Meaning |
| - | - |
| `<call>.catch((error) => { … });` | The statement form. If the call fails, its effects are undone and the block runs instead. `error`, which may be left out, holds at most the first 256 bytes of the revert data. The block may use and assign the enclosing function's variables. |
| `const v = <call>.catch(() => { return <constant>; });` | The value form. If the call fails, `v` is the constant. The handler takes no parameter. The call must return one value, so a bound method with one output. A raw `evm.call`, `evm.static` or `evm.delegate` returns bytes, which no constant stands in for ("a `.catch()` fallback: expected a bytes value, found a number"): test a raw read with the statement form, then read again only when it worked. |

* `.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. Check `ctx.isContract(address)` first when the address may have no code. To catch a decode as well, put the call and the read in a [`try`/`catch`](#statements) block.
* `error` is 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". A `return` or a `break` or `continue` leaving the block is not allowed in the statement form. The value form needs exactly `return <constant>;`, a constant that fits the declared ABI result width.
* A handler, here or in a `try`/`catch`, compiles to at most 255 bytes: "a `catch` block 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.

```ts catch.sauce.ts theme={null}
import Counter from "./Counter.json";

function main(counter: Address, n: Uint256): Uint256 {
  let errorLength = 0;
  Counter.at(counter).add(n).catch((error) => {
    errorLength = error.length;
  });
  const current = Counter.at(counter).count().catch(() => {
    return 0;
  });
  return errorLength + current;
}
```

## Ambient token functions

These functions are in scope with no import in every program the `sauce` command, the MCP server, actions documents and `routes.compileSauceProgram` compile. They are the functions of [`@sauce/token`](https://docs.eco.com/sauce-libraries/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](https://docs.eco.com/sauce-sdk/typescript/compiling.md)). A function a target lacks is refused there: "`ownerOf` is called but never declared".

| Function | Targets | Kind |
| - | - | - |
| `balanceOf` | EVM, Solana | read |
| `totalSupply` | EVM, Solana | read |
| `decimals` | EVM, Solana | read |
| `allowance` | EVM, Solana | read |
| `ownerOf` | Solana | read |
| `transfer` | EVM, Solana | write |
| `transferFrom` | EVM, Solana | write |
| `approve` | EVM, Solana | write |
| `transferChecked` | Solana | write |
| `approveChecked` | Solana | write |
| `permit` | EVM | allowance on a signature |
| `permitTransferFrom` | EVM | pull on a signature |

Their arguments differ by target: on Solana they take the accounts they read or move through. [`@sauce/token`](https://docs.eco.com/sauce-libraries/token) gives each signature, its checks and its refusals. On EVM the writes have SafeERC20 semantics.

```ts ambient-token-evm.sauce.ts theme={null}
// Pays `amount` of USDC from the Pot to `to`, after checking the Pot holds it.
function main(to: Address, amount: Uint256): Uint256 {
  require(balanceOf(USDC, pot) >= amount, "not enough USDC");
  transfer(USDC, to, amount);
  return decimals(USDC);
}
```

```ts ambient-token.solana.sauce.ts theme={null}
// Returns a token account's USDC balance and the account's owner.
function main(account: Account): { balance: Uint256; owner: Address } {
  return { balance: balanceOf(USDC, account), owner: ownerOf(USDC, account) };
}
```

## 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](#compiler-or-sdk)). [Names](https://docs.eco.com/sauce/names.md) covers them in full.

| Form | Returns | Targets | Meaning |
| - | - | - | - |
| A registry name in a test: `if (X)`, `!X`, `X != 0`, `X == 0`, `X ? a : b`, `X && …`, `X \|\| …` | decided when compiling | EVM, Solana | Asks whether the chain has a contract or a token: a name the chain lacks is zero there. `if (UniswapV3.Factory) { … }` keeps its branch where the chain has the contract and removes it, names included, where it does not. |
| `Token(address)` | a token | EVM, Solana | A token by address (on Solana, its mint): `.transfer`, `.approve`, `.transferFrom` and `.balanceOf`. On EVM the writes are SafeERC20. |
| `pot` | the Pot | EVM, Solana | The Pot's address; on Solana its key, and its signer slot (`Account<Signer>`) where an `Account` is taken. |
| `pot.owner` | the owner | EVM, Solana | On EVM, the address returned by the Pot's `owner()` (zero for a Shared Pot). On Solana, the owner's account; `pot.owner.writable().signer()` gives it both roles. This read alone does not bind a Pot and permits `--via engine`. |
| `Account(key)`, `Account.pda(program, [seeds])` | `Account` | Solana | An account at a key, or a program-derived account. Values must be known when the cook is built. |
| `Account.systemProgram`, `Account.tokenProgram`, `Account.token2022Program`, `Account.associatedTokenProgram`, `Account.memoProgram` | `Account` | Solana | Standard program accounts. Account expressions are read-only unless `.writable()` or `.signer()` gives them a role. |
| `<holder>.<TOKEN>` | `Uint256` | EVM, Solana | A holder's balance of a token symbol, `USDC.balanceOf(holder)` written short. `pot.USDC` is the USDC the Pot holds. |
| `<holder>.balance` | `Uint256` | EVM, Solana | A holder's native coin, in wei or lamports: `pot.balance` is `ctx.selfBalance()`, `holder.balance` is `ctx.balance(holder)`. On a record, list, decoded value or call result it reads a field. |

* 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 as `t.<method>(...)`.
* `pot.owner`, `pot.balance` and `pot.<TOKEN>` are read-only members. Keep a value you need to change in a local variable.
* A program's declaration or import of `pot` or `Account` shadows that global in its scope. A compile-time binding of either name also takes precedence. `Account` remains the account type in type positions.
* On Solana, `Account(pot.address).owner` reads the program that owns the Pot account, not the owner who signs the cook.

```ts compile-time.sauce.ts theme={null}
function main(token: Address, tokenA: Address, tokenB: Address, to: Address, amount: Uint256) {
  if (UniswapV3.Factory) {
    const pool = UniswapV3.Factory.getPool(tokenA, tokenB, 500);
    require(pool != 0, "no 0.05% pool");
  }
  require(pot.USDC >= amount, "not enough USDC");
  const t = Token(token);
  t.transfer(to, amount);
}
```

## 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:

| Refused | Compiled for | Compiler message |
| - | - | - |
| An EVM-only `ctx` read: `return ctx.txOrigin();` | Solana | `` `ctx.txOrigin` is not available on the svm target `` |
| A Solana-only call: `return svm.sload(a, 0, 8);` | an EVM chain | `` `svm.sload` is not available on the evm target; read another contract via a staticcall, or your own storage with evm.sload/evm.sstore `` |
| An account property only Solana has: `return a.owner;` | an EVM chain | `` `account.owner` is not available on the evm target; an address on the EVM is the code — check it with ctx.isContract `` |
| A contract binding: `Counter.view(counter).count()` | Solana | `` `Contract.view` is not available on the svm target `` |
| An atomic `try { … } catch (error) { … }` | Solana | `` `try/catch` is not available on the svm target `` |

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](https://docs.eco.com/sauce/program-layout.md#one-module-an-arm-per-target)).

## Gas costs

This page gives no gas figures. A program's gas is what `sauce simulate` reports for it; [cook()](https://docs.eco.com/sauce/cook.md) lists what a cook costs.

## Next steps

> **[Errors and reverts](https://docs.eco.com/sauce/errors.md)**
>
> `require`, `throw`, atomic `try`/`catch` and `.catch()` in the context of a whole program.

> **[Contracts and ABIs](https://docs.eco.com/sauce/contracts-and-abis.md)**
>
> Calling a contract through its namespace, an ABI import or an address.

> **[EVM and Solana](https://docs.eco.com/sauce/targets.md)**
>
> Every difference between the two targets, including accounts and `ctx`.

> **[Names](https://docs.eco.com/sauce/names.md)**
>
> Token symbols, `Token(address)`, `pot` and protocol namespaces.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.