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

# EVM and Solana

> One SauceScript language compiles for EVM chains and for Solana. Read this before writing a program for Solana, or one meant to run on both.

SauceScript is one language for EVM and Solana. The compiler has two targets, and the chain you compile for picks one:

| Target | Chains | Runs on |
| - | - | - |
| `evm` | Ethereum, Base, Arc, the testnets Arc Testnet, Base Sepolia and Ethereum Sepolia, and every EVM preview chain in the registry | the V12 engine, through the owner's Pot |
| `svm` | Solana, Solana Devnet and Solana Testnet | the V12 engine, through the Kitchen or directly |

A program that uses only what both targets have compiles for both. The calls into other contracts and the accounts a program touches are where the targets differ.

```ts pay.sauce.ts theme={null}
// Pays `amount` USDC from the Pot to `to` and returns what `to` received.
function main(to: Address, amount: Uint256): Uint256 {
  require(pot.USDC >= amount, "not enough USDC");
  const before = to.USDC;
  USDC.transfer(to, amount);
  return to.USDC - before;
}
```

This file compiles for Base, Ethereum, Arc and Solana. On the EVM chains the compiled program is 328 bytes. On Solana it is 792 bytes, and the compiler also returns the four accounts the transfer moves through ([below](#accounts-on-solana)).

## What compiles on each target

Something that exists on only one target is a compile error on the other, never a silent difference.

| Feature | EVM | Solana |
| - | - | - |
| Types, operators, control flow, functions, `require`, `throw`, `revert` | yes | yes |
| `Int256`, signed operators, `Int256(…)`/`Uint256(…)` conversions | yes | yes |
| `program(closure, …args)` | yes | yes |
| `crypto.keccak256`, `crypto.ecdsaVerify`, `crypto.findPDA`, `Math` | yes | yes |
| `ctx.self()` | the Pot | the engine's program id |
| `ctx.msgSender()` | the immediate caller | the first signer among the engine's accounts |
| `ctx.blockNumber()` | the block number | the slot |
| `ctx.msgValue()`, `ctx.gasLeft()`, `ctx.codeSize()`, `ctx.isContract()`, `ctx.txOrigin()`, `ctx.baseFee()`, `ctx.blockHash()` and the other block and transaction reads | yes | compile error |
| `log(data, topic, …)` | up to 4 topics | `log(data)` only |
| `abi.encode`, `abi.decode` | yes | compile error |
| `evm.call`, `evm.static`, `evm.delegate`, `evm.sload`/`sstore`, `evm.tload`/`tstore`, `evm.create*` | yes | compile error |
| `svm.call`, `svm.sload`, `svm.sstore`, `svm.remainingAccounts()` | compile error | yes |
| ABI bindings, protocol namespaces, `.catch()`, `.gas(n)` | yes | compile error |
| Atomic `try { … } catch (error) { … }` | yes | compile error |
| Venue namespaces (`Orca.Whirlpool.swap(…)`) | compile error | yes |
| `Account` parameters with roles (`Account<Writable>`, `Account<Signer>`) | bare `Account` only, an address | yes |
| `acct.owner`, `acct.dataLen` | compile error | yes |
| `Account(key)`, `Account.pda(…)`, `Account.systemProgram` and the other programs | compile error | yes; accounts added to `main` |
| `pot.owner` | the Pot's owner, read from the Pot's `owner()` | the Pot's owner's account, which signs the cook |
| Token symbols, `pot.<TOKEN>`, `pot.balance`, `Token(…)`, the token methods | yes | yes; accounts added to `main` |
| `@sauce/token` | the ERC-20 arm | the SPL Token and Token-2022 arm |
| `@sauce/router` | one function per EVM venue | one function per Solana venue |

The error names the target and, where there is one, the replacement:

| Refused | Compiled for | Compiler message |
| - | - | - |
| `evm.static(ctx.self(), data)` | Solana | `` `evm.static` is not available on the svm target `` |
| `svm.call(program, [], data)` | an EVM chain | `` `svm.call` is not available on the evm target; an EVM external call is evm.call(...) or Contract.at().method() `` |
| `return ctx.msgValue();` | Solana | `` `ctx.msgValue` is not available on the svm target; pass the amount as an argument and move the lamports with a system-program transfer through svm.call `` |

[Builtins](https://docs.eco.com/sauce/builtins.md) lists every builtin with its targets.

## Accounts on Solana

A Solana transaction lists every account it reads or writes before it runs, each with a role: read-only, writable or signer. A program cannot reach an account the transaction did not list. When you compile for Solana, the compiler returns an **account manifest**: every account the program needs, in order, with its role and how to find it.

`sauce compile pay.sauce.ts --chain solana --json` gives this manifest for the program above:

```json theme={null}
{
  "accounts": [
    { "kind": "slot", "index": 0, "name": "potUSDC", "requires": "writable", "derive": "pot:USDC" },
    { "kind": "slot", "index": 1, "name": "pot", "requires": "readonly_signer", "derive": "pot" },
    { "kind": "slot", "index": 2, "name": "toUSDC", "requires": "writable", "derive": "@to:USDC" },
    { "kind": "slot", "index": 3, "name": "tokenProgram", "requires": "readonly", "derive": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }
  ]
}
```

Each slot's `derive` says where the account comes from: `pot` is the Pot, `pot:USDC` the Pot's USDC account, `@to:USDC` the USDC account of `main`'s argument `to`, and the last slot is the SPL Token program. `sauce run`, `sauce simulate` and `sauce tx` derive every such slot from the owner, the Pot and `main`'s arguments, so the program runs with `--args` alone. An account given with `--account` for a slot derived from the Pot, the owner or the authority must be the one its derivation gives; another key is refused.

A program that does not use the Pot derives the accounts its token methods move through from `authority` instead (`authority`, `authority:USDC`): the Pot through the Kitchen, the signer in a direct cook on the engine. Reading `pot`, `pot.USDC` or `pot.balance` binds the program to the Pot, so `--via engine` cannot run it. Reading only `pot.owner` does not bind a Pot and works with `--via engine`.

A program gets its accounts three ways:

| The account | Write |
| - | - |
| A token account a token method moves through | nothing: `USDC.transfer(to, amount)` adds it |
| An account derivable from values known when the cook is built | an expression where it is used: `Account(key)`, `Account.pda(program, [seeds])`, `USDC.accountOf(holder)`, `Account.systemProgram` |
| An account computed while the program runs, or chosen by the caller | an `Account` parameter of `main`, attached with `--account NAME=<value>` |

### Derived accounts

```ts derived.solana.sauce.ts theme={null}
// Reads a program-derived account of `program` for the seed "state", and `to`'s
// associated USDC account.
function main(program: Address, to: Address): Uint256 {
  const state = Account.pda(program, ["state"]);
  const toUsdc = USDC.accountOf(to);
  return state.dataLen + balanceOf(USDC, toUsdc);
}
```

* **A program-derived address (PDA)** is an address a program computes from seeds, with no key behind it. Only that program can sign for it. The Pot is one, derived by the Kitchen.
* **An associated token account (ATA)** is the token account the token program derives for a holder and a mint. It is where that holder's balance of that token lives.
* **Every holder and seed must be known when the cook is built**: `pot`, `pot.owner`, `ctx.self()`, an `Address` parameter of `main`, a key written out, or a program account. For an account computed while the program runs, take an `Account` parameter instead.
* **`.writable()` and `.signer()`** give an account the role a call needs, and chain: `pot.owner.writable().signer()`. Account expressions are read-only unless given a role; uses of the same account combine their roles.

### Account parameters

```ts accounts.solana.sauce.ts theme={null}
// Moves `amount` of `mint` from `source` to `destination` and returns what arrived.
function main(
  tokenProgram: Account,
  source: Account<Writable>,
  destination: Account<Writable>,
  authority: Account<Signer>,
  mint: Address,
  amount: Uint256
): Uint256 {
  const before = balanceOf(mint, destination);
  transfer(mint, tokenProgram, source, destination, authority, amount);
  return balanceOf(mint, destination) - before;
}
```

* `Account` is read-only; `Account<Writable>` and `Account<Signer>` declare the role the transaction must grant. The manifest records `readonly`, `writable`, `readonly_signer` or `writable_signer` per slot. Roles are what you declare; the compiler does not infer them from a call.
* `Account` parameters are not encoded as arguments. `sauce compile --json` lists them as `accountsToGive`, and a cook missing one is refused before anything is read.
* An `Account` exposes `.address`, `.owner` (the program that owns it) and `.dataLen`. Check `.owner` before trusting an account's bytes: anyone can create an account with any layout. `pot.owner` is not this property: it is the Pot's owner, described under `ctx.self()` and `pot` below.
* An account passed as a parameter is used as attached. Nothing checks whose token account `destination` is; check it with `ownerOf(mint, destination)` when it matters, or take an `Address` and let the token method derive the account.

### A program checks its accounts

A transaction built by someone else could attach a different account in a slot, so every derived account is checked on chain at the top of `main`, before the program's own code. A wrong account fails the whole cook with a reason: `wrong recipient account`, `wrong holder account`, `wrong program-derived address`, `wrong mint` or `wrong program`. The Pot and the owner are pinned by their signatures.

## Token programs

Every Solana token belongs to one of two token programs, and so does each of its token accounts:

| Program | Mints in the registry | Moved by |
| - | - | - |
| SPL Token | `USDC`, `USDT`, `WSOL` | `transfer`, `approve` |
| Token-2022 | `PYUSD`, every tokenized stock | `transferChecked`, `approveChecked` |

The token methods pick the write by the mint's program, so `USDC.transfer(…)` and `PYUSD.transfer(…)` look the same in a program. `Token(mint)` reads the mint's program from the cluster when the cook is built, and refuses a mint whose program cannot be read. Token-2022's transfer fee is withheld from what arrives, and a mint whose transfer hook names a program is refused. [`@sauce/token`](https://docs.eco.com/sauce-libraries/token) lists both of its arms.

## Protocol calls

On an EVM chain a program calls a contract through a protocol namespace or an ABI binding, typed by the ABI: `UniswapV3.Factory.getPool(WETH, USDC, 500)`. See [Contracts and ABIs](https://docs.eco.com/sauce/contracts-and-abis.md).

Solana has no protocol namespaces. A program calls another program with `svm.call(program, [accounts], data)`, a cross-program invocation (CPI) with accounts the transaction already lists. A failed `svm.call` fails the whole transaction; Solana has neither `.catch()` nor `try`/`catch`. Swaps use the venue namespaces, which compile to `@sauce/router`'s Solana functions and derive the pool's accounts:

```ts venue.solana.sauce.ts theme={null}
// Swaps `amountIn` USDC for WSOL at Orca's USDC/WSOL whirlpool at tick spacing 4; the WSOL lands
// in the Pot's WSOL account.
function main(amountIn: Uint256, priceLimit: Uint256, minOut: Uint256): Uint256 {
  return Orca.Whirlpool.swap(Orca.Whirlpool.pool(USDC, WSOL, 4), USDC, WSOL, amountIn, priceLimit, minOut);
}
```

## ctx.self(), pot and pot.owner

| Expression | EVM | Solana |
| - | - | - |
| `ctx.self()` | the Pot, the executing contract | the engine's program id |
| `pot` | the Pot's address | the Pot's key; its signer slot (`Account<Signer>`) where an `Account` is taken |
| `pot.USDC` | the Pot's USDC | the Pot's USDC |
| `pot.balance` | the Pot's native coin, in wei | the Pot's lamports |
| `pot.owner` | the address returned by the Pot's `owner()`; zero for a Shared Pot | the owner's account, which signs the cook |
| `ctx.self()` as a token holder (`USDC.transfer`, `balanceOf(USDC, ctx.self())`) | the Pot | the Pot |

On Solana only a Kitchen cook makes the Pot sign, so a use of `pot` that signs as the Pot, such as a token write or `pot.signer()` in `svm.call`, fails with another account in its slot. `pot.owner` is the owner's account, not the program that owns the Pot account. Write `Account(pot.address).owner` for that program.

On Solana the engine's program id owns every Pot account, and other accounts too, so `acct.owner == ctx.self()` does not prove an account is the Pot. To bind a slot to the Pot, re-derive the Pot's address with `crypto.findPDA` from the Kitchen's program id, `"pot"`, the owner and the salt, and require the slot to be it. The Kitchen's program id is on [Deployments](https://docs.eco.com/sauce-sdk/reference/deployments.md); see also [Pot](https://docs.eco.com/sauce/pot.md).

## Transaction size and staging

A Solana transaction holds at most 1,232 bytes. A cook carries the program, its arguments (32 bytes for each `Uint256` or `Address`), the accounts, the signatures and the transaction's setup. `sauce compile --json --chain solana` reports the program's `size`, and `sauce tx` reports whether the cook fits in one transaction. Compiled for Solana:

| Program | Size | Accounts | One transaction |
| - | - | - | - |
| `USDC.transfer(to, amount)` alone | 361 bytes | 4 | yes |
| `pay.sauce.ts` above (checks and measures what arrived) | 792 bytes | 4 | no: staged |
| `venue.solana.sauce.ts` (one Orca swap) | 2,128 bytes | 17 | no: staged |

A program too large for one transaction is **staged**: uploaded first, over several transactions, into a code buffer account of the Kitchen, then cooked from that buffer. The Kitchen runs only a buffer that was finalized, and finalizing checks the SHA-256 digest of the upload. After the cook the buffer is closed and its rent returned. `sauce run` stages a program when it does not fit, and `sauce tx` returns the steps as a plan for another signer.

Every account a transaction lists takes 32 bytes. An address lookup table lets a transaction refer to an account with one byte. `sauce run` creates one when a cook's accounts do not fit.

On EVM chains the program travels as the calldata of the cook; there is no staging.

A Solana cook is built for one release set's engine and Kitchen: the engine's program id and the Kitchen's instruction format are part of it. Build every cook, staged or not, with the installed SDK, and never replay a serialized cook or staging instruction built by another SDK release.

## Compute and memory on Solana

* **Compute budget:** a transaction has 1,400,000 compute units, and every instruction spends from it, the engine's and each called program's. A cook that runs out fails whole. A concentrated-liquidity swap spends units for every initialized tick it crosses, so a wide price limit on a deep pool can exhaust the budget. `sauce` holds each Solana pool's swap short of its limit where the budget would not cover it, and on Raydium's code and Crema walks a swap on into the furthest tick array its accounts give ([Swaps at a price and compute units](https://docs.eco.com/sauce-sdk/cli/solana.md#swaps-at-a-price-and-compute-units)).
* **Memory:** a program has 64 KiB of working memory per run. Every cook requests a 256 KiB heap frame, larger than Solana's default. `sauce` and the TypeScript client add that request. A cook built by hand prepends `buildHeapFramePrepend()` from `@eco-incorp/sauce/svm` once.

## CPI depth

Solana allows 4 levels of CPI below a transaction's instruction, and 8 on a cluster where SIMD-0268 is active. Through the Kitchen, the Kitchen's call into the engine uses one of them:

| Cook | CPI levels left for the program's own calls |
| - | - |
| Through the Kitchen (default) | 3 on mainnet; 7 where SIMD-0268 is active |
| Directly to the engine | 4 on mainnet; 8 where SIMD-0268 is active |

A direct cook (`--via engine`) runs with no Pot. The program acts with the signer's authority and spends the signer's own token accounts, so a program that names `pot` is refused there. A program that calls an aggregator, which calls a pool, which calls a token program, can need the extra level.

## Native coins

| Chain | Native coin | Balance in a program |
| - | - | - |
| Ethereum, Base | ETH, 18 decimals | `pot.balance` (`ctx.selfBalance()`), `holder.balance` |
| Arc | USDC, 18 decimals | `pot.balance` (`ctx.selfBalance()`), `holder.balance` |
| Solana | SOL, in lamports (9 decimals) | `pot.balance`, `holder.balance` |

On an EVM chain only `evm.call(address, value, data)` sends native value with a call; ABI bindings send none. On Solana a program moves lamports with a System Program transfer through `svm.call`.

On Arc the native coin and the ERC-20 `USDC` are one balance at two scales: 18 decimals natively, 6 for the token. Sending native value and calling `USDC.transfer` move the same money, and the gas and the Kitchen's fee are paid from it too. `pot.USDC` reads the 6-decimal token and `pot.balance` the 18-decimal native coin:

```ts arc-pay.sauce.ts theme={null}
// Pays `amount` of Arc's USDC token (6 decimals) from the Pot to `to`, and returns the Pot's
// native balance afterwards (18 decimals).
function main(to: Address, amount: Uint256): Uint256 {
  require(pot.USDC >= amount, "not enough USDC");
  USDC.transfer(to, amount);
  return pot.balance;
}
```

Native fees paid by the program, such as bridge or messenger fees, spend the Pot's balance, so leave enough USDC for them. The ordinary cook's Kitchen fee comes from the owner's attached transaction value and is forwarded before the program runs; a [signed cook](https://docs.eco.com/sauce/pot.md#signed-cooks) can pay it from existing Pot funds.

## Next steps

* [Names](https://docs.eco.com/sauce/names.md): what each name resolves to on each chain.
* [@sauce libraries](https://docs.eco.com/sauce-libraries/overview.md): `@sauce/token` and `@sauce/router` on both targets.
* [Kitchen](https://docs.eco.com/sauce/kitchen.md) and [Pot](https://docs.eco.com/sauce/pot.md): how a cook runs on each target.
* [Chains](https://docs.eco.com/sauce-sdk/reference/chains.md): every chain, its target and its support.


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