Skip to main content
On Solana the sauce commands are the ones you use on Base: compile, simulate, tx, run, pot. This page covers what differs: the accounts a transaction lists, the two ways a cook reaches the engine, the execution fee, programs too large for one transaction, Token-2022 tokens, and the accounts a run leaves open.

Prerequisites

  • The sauce command: npm install -g @eco-incorp/sauce (Installation).
  • A Solana signer for run: a Solana wallet (sauce wallet new --solana) or a keypair file for --keypair. compile and tx need only the owner’s public key.
  • For simulate and run, the release’s engine must be deployed on the cluster: sauce pot --chain solana --owner <pubkey> reports whether it is. sauce tx on Solana does not check for the engine, as it does on an EVM chain: without it, its simulation of the cook fails with AccountNotFound and it builds no cook.

Accounts

A Solana transaction lists every account it touches. For most programs you pass none: compiling adds each account the program needs to main, with how to derive it, and simulate, tx and run derive each one from the owner, the Pot and main’s arguments. This program pays PYUSD with the token method:
pay-pyusd.sauce.ts
sauce compile --json lists the accounts in its manifest, in order, each with its role and derivation:
A recipient with no token account for the mint gets one, created before the cook; the owner pays its rent. This program never names pot, so its accounts derive from authority, the account that signs the engine call: the Pot through the Kitchen, and the owner with --via engine. A program that names pot anywhere derives them from the Pot (pot, pot:USDC), as payout.sauce.ts does, and runs through the Kitchen only. The tx outputs on this page were built against Solana mainnet, where the engine and Kitchen are deployed. Nothing was signed or sent.

Name an account with —account

A program can take an account as a parameter of type Account, Account<Writable> or Account<Signer>. You name each one with --account NAME=<value>, or give them all at once with --accounts <json | file>, a JSON object of names to values. A token account is the holder’s associated token account for the mint, under the token program that owns the mint. A tokenized stock is named by its symbol in any case (pot:TSLAx). Every account a derivation needs must be known when the transaction is built. Through the Kitchen, an account given for a slot the program derives from the Pot, the owner or authority must be the account that derivation gives, or the command exits 2. Giving the owner for pay-pyusd.sauce.ts’s pot slot:
The same check holds a slot derived from the owner (owner, owner:USDC, a pda: the owner seeds) to the owner’s account, on simulate, run and tx, --account and --accounts alike. A program that takes accounts and leaves them out is refused before anything is read, with the values that suit each slot and the JSON to save as an --accounts file:
sauce compile --json lists the same as accountsToGive. A wallet is not a token account: where a program writes a token account, give the wallet’s (dest=<wallet>:USDC), not the wallet itself.

Kitchen or engine: —via

simulate, tx and run take --via <kitchen|engine>: With --via engine nothing is created and there is no Pot, so a pot or pot:<token> account cannot be attached, and a program that uses pot, pot.balance or pot.<TOKEN> needs the Kitchen. A program that reads only pot.owner does not bind a Pot: it names the signing owner and runs with either route.
The engine’s direct cook is a smaller transaction: no Kitchen, Pot or salt.
The token accounts did not exist yet, so the plan creates them before the cook.

The execution fee

Every cook pays the Kitchen’s execution fee in lamports, read from the cluster, through the Kitchen or directly on the engine alike. Each output prints it as fee. The cook signs that fee as its max_execution_fee, so it pays that amount or fails if the fee rises before it lands. --max-execution-fee <lamports> sets your own ceiling, checked before anything is signed:
The command exits 1. A first run also pays rent for the Pot and for each new token account, and transaction fees. sauce pot --chain solana prints the current fee.

Large programs and lookup tables

A Solana transaction holds at most 1,232 bytes. sauce compile --json with --args reports the cook’s size as cook, as sauce run would build it, through the Kitchen or with --via engine:
With --via engine, the same cook is 1,048 bytes. Staging. A cook past the limit stages its program in a Kitchen code buffer first, written over a few transactions, and runs from there. This program compiles to 1,421 bytes:
payout.sauce.ts
sauce tx prints the plan’s first step, unsigned, with a cursor for the next:
The owner signs each transaction; their blockhash expires in about a minute. sauce send with --cursor sends a step and builds the next one from what the cluster shows. sauce run does every step itself, and closes the code buffer after the cook so its rent comes back. Lookup tables. Each account a transaction lists costs 32 bytes; one taken through an address lookup table costs one byte. When a staged cook’s accounts do not fit, sauce run creates a lookup table, paid by the signer, prints its address, and keeps it for the next cook that names the same accounts. --lookup-table <address> on run, simulate and tx takes a cook’s accounts through a table that holds them.

Swaps at a price and compute units

A Solana transaction has 1,400,000 compute units, and a concentrated-liquidity swap spends units for every initialized tick it crosses, so a wide price limit can run a cook out of units, and a cook that runs out fails whole. For a program that swaps at a price through the venue namespaces (Orca.Whirlpool.swap(…), Raydium.CLMM.swap(…) and the rest), simulate, run and tx read each pool’s tick arrays and work out how far each swap may walk, the pool buying the most per unit first:
  • Held or skipped. A pool the cook’s units do not cover is held short of its limit, or skipped when it cannot be paid for at all. Its swap is compiled to the venue’s Stopped form, with the stop added to main’s values.
  • Walked on. A venue’s swap walks only the tick arrays the cook gives it. On Raydium’s code (Raydium CLMM, PancakeSwap CLMM, Byreal, Stabble CLMM) and Crema, that window ends where the furthest of those arrays begins. Where a pool’s limit takes its walk past there, the command reads that array’s last initialized tick the walk’s way, its deep tick, and compiles the swap to its Stopped form with it; the swap then walks on to a tick inside that array. The Stopped form reads the tick in its array as the cook runs: a tick no longer initialized leaves the swap where it would have stopped, and an array not laid out as read reverts (deep: the tick array is not laid out as Raydium's, or Crema's).
  • Every other swap stays the plain one, and a program no stop binds is the same program, byte for byte.
Each command prints a line for a pool it held or skipped, and --json gives every pool’s stop in computeStops, with deepTick for a walk on. tx works the stops out from a table of each venue’s costs alone, so the cook it builds can differ from the one simulate reports; simulate and run check them in a simulation first, and tighten them if the cook would still run out. A price that moves between building the cook and landing it can still run the cook out of units. The Solana distributed swap with a budget of 100,000 USDC, simulated at slot 460512539, walked its PancakeSwap CLMM pool on into its furthest tick array:
A stop is one 256-bit word: its low 192 bits are a tighter price limit (0 for none), bits 192 to 223 a start bound past which the swap skips the pool, and bits 224 to 255 the deep tick, each tick stored with its sign bit flipped. This stop carries only the deep tick: 50132 in bits 224 to 255, and no limit or start bound. @sauce/router lists the Stopped forms.

Token-2022 checked writes

A Solana mint belongs to the SPL Token program or to Token-2022. USDC and USDT are SPL Token mints; PYUSD and the tokenized stocks are Token-2022 mints.
  • Token methods pick the write for the mint. PYUSD.transfer(to, amount) compiles to a checked transfer, with the registry’s decimals and the mint as one more account. The manifest above shows it: token2022Program and mintPYUSD.
  • Explicit checked writes work with either token program: transferChecked(mint, tokenProgram, source, mintAccount, dest, authority, amount, decimals) and approveChecked(mint, tokenProgram, source, mintAccount, delegate, authority, amount, decimals). The program requires tokenProgram to be one of the two token programs and to own the source, the destination and the mint; mintAccount to be mint; and decimals to be the mint’s.
  • Legacy writes, transfer, transferFrom and approve with explicit accounts, are SPL Token only. Token-2022 refuses its plain transfer for a mint with a transfer fee, a transfer hook or a pause.
A mint whose transfer hook names a program is refused with transfer hook. A transfer fee is withheld from what the destination receives, so measure the balance that arrived rather than the amount sent. This program moves a Token-2022 amount between accounts named on the command line:
sweep.sauce.ts

Building a cook

Build cooks with the installed sauce command or SDK, which use the engine and Kitchen on Deployments. The Kitchen validates the named pot account with the salt and the Pot’s canonical bump. The command and the SDK’s high-level client add the 262,144-byte heap frame request every cook needs. A cook you assemble yourself in TypeScript prepends buildHeapFramePrepend() from @eco-incorp/sauce/svm exactly once (Solana from TypeScript).

Leftovers and sauce reclaim

A run closes the code buffer it staged. A run that was stopped, or a plan never finished, can leave a buffer open, and lookup tables stay open for later cooks. sauce reclaim lists both, with the rent they hold, and closes them:
With --owner alone it prints the closing transactions unsigned; with --keypair or --wallet it sends them. A lookup table closes in two steps, about three and a half minutes apart.

Next steps