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

# Solana from TypeScript

> Compile and cook programs on Solana from TypeScript, for applications that build their own Solana transactions.

On Solana a program runs through the Kitchen on the owner's Pot, or directly on the engine with no Pot ([Kitchen](https://docs.eco.com/sauce/kitchen.md)). From TypeScript you compile the program for `solana`, resolve the accounts its manifest names, and build the cook with the Kitchen client or with the instruction builders. The library exports no RPC endpoint: every function that reads the cluster takes your RPC client or `rpcUrl`.

```bash theme={null}
npm install @eco-incorp/sauce @solana/kit
```

The examples take `@solana/kit`'s `Address`, RPC client and `TransactionSigner`; install the major version the package depends on. `@eco-incorp/sauce/svm` re-exports the kit functions its own functions take (`address`, `generateKeyPairSigner`, `getProgramDerivedAddress`, `AccountRole`, `getAddressEncoder`, `blockhash`). The program and Kitchen ids come from `v12SvmEngineProgramId()` and `v12SvmKitchenProgramId()` in `@eco-incorp/sauce/deployments` ([Deployments](https://docs.eco.com/sauce-sdk/reference/deployments.md)).

## Compile and keep the account manifest

`routes.compileSauceProgram("solana", source)` returns the bytecode and the account manifest: the accounts the program needs, in order, with the role each requires. The token methods and venue namespaces add their accounts to `main`, and `derivedAccounts` says how each is derived.

```ts sol-compile.mts theme={null}
import { routes } from "@eco-incorp/sauce";

const source = `
function main(to: Address, amount: Uint256) {
  require(pot.USDC >= amount, "not enough USDC");
  USDC.transfer(to, amount);
}
`;

export const program = routes.compileSauceProgram("solana", source);

console.log("main takes:", program.entryParams.map((p) => `${p.name}: ${p.type}`).join(", "));
const slots = program.manifest?.accounts.flatMap((a) => (a.kind === "slot" ? [`${a.name} (${a.requires})`] : []));
console.log("accounts:", slots?.join(", "));
console.log("derived:", program.derivedAccounts);
```

```text theme={null}
main takes: to: Address, amount: Uint256
accounts: potUSDC (writable), pot (readonly_signer), tokenProgram (readonly), toUSDC (writable)
derived: {
  potUSDC: 'pot:USDC',
  pot: 'pot',
  tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
  toUSDC: '@to:USDC'
}
```

`entryParams` holds `main`'s values; the account slots are not among them. Encode the values with `routes.encodeEntryArguments` and append them to the bytecode ([Entry arguments](https://docs.eco.com/sauce-sdk/typescript/entry-arguments.md)).

The program names `pot`, so its accounts derive from the Pot (`pot`, `pot:USDC`) and it runs through the Kitchen only; `potMention` gives the line and column of that first mention. A program that never names `pot` derives them from `authority` (`authority:USDC`), the account that signs the engine call: the Pot through the Kitchen, the owner on a direct cook, so it runs either way. The two programs below never name `pot`.

### Venue accounts and data

A venue namespace call adds every account its swap takes to `main`, and `derivedAccounts` names each one. This program swaps at Orca's whirlpool of tick spacing 4 ([`@sauce/router` on Solana](https://docs.eco.com/sauce-libraries/router#on-solana)):

```ts orca-swap.sauce.ts theme={null}
// Buys WSOL with up to `amountIn` of the Pot's USDC at Orca's SOL/USDC whirlpool of tick spacing 4,
// never paying more than `priceLimit` a unit, and keeps it in the Pot. A pool already past the
// limit is skipped (the swap returns 0); below `minOut` the whole transaction reverts.
//
//   sauce compile orca-swap.sauce.ts --chain solana
//
//   amountIn: Uint256     the most USDC to pay, in its smallest units (6 decimals)
//   priceLimit: Uint256   the most one WSOL base unit may cost, fee included: USDC base units per
//                         WSOL base unit, times 10^18
//   minOut: Uint256       the least WSOL to receive, in its smallest units (9 decimals)
function main(amountIn: Uint256, priceLimit: Uint256, minOut: Uint256): Uint256 {
  return Orca.Whirlpool.swap(
    Orca.Whirlpool.pool(USDC, WSOL, 4),
    USDC,
    WSOL,
    amountIn,
    priceLimit,
    minOut,
  );
}
```

```ts orca-compile.mts theme={null}
import { readFileSync } from "node:fs";
import { routes } from "@eco-incorp/sauce";

const program = routes.compileSauceProgram("solana", readFileSync("orca-swap.sauce.ts", "utf8"));

console.log(program.bytecode.length, "bytes");
console.log(program.entryParams.map((p) => `${p.name}: ${p.type}`).join(", "));
for (const [name, derivation] of Object.entries(program.derivedAccounts ?? {})) {
  console.log(`  ${name} = ${derivation}`);
}
```

```text theme={null}
2128 bytes
amountIn: Uint256, priceLimit: Uint256, minOut: Uint256
  orcaWhirlpool4Program = slot:Orca.Whirlpool(USDC,WSOL,4):USDC.program
  orcaWhirlpool4Account = slot:Orca.Whirlpool(USDC,WSOL,4):USDC.pool
  …
  orcaWhirlpool4Ticks2 = slot:Orca.Whirlpool(USDC,WSOL,4):USDC.ticks2
  pot = authority
  potUSDC = authority:USDC
  mintUSDC = mint:USDC
  mintWSOL = mint:WSOL
  tokenProgram = TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
  token2022Program = TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
  memoProgram = MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr
  potWSOL = authority:WSOL
```

A program or code generator that calls a venue's `@sauce/router` function itself needs those accounts in the venue's own order. `routerSvmAccountOrder(venue, options)` and `routerSvmAccounts(venue, options, refs)` from `@eco-incorp/sauce/svm` list them, with whether the instruction writes each and whether it signs; `routerSvmDataTemplate(venue, options)` encodes the instruction's fields once in TypeScript; and `ROUTER_SVM_VENUES` gives each venue's program and its `…Data` and `…Cpi` functions ([Router swap accounts](https://docs.eco.com/sauce-sdk/api/svm/router-accounts.md), [Router swap data](https://docs.eco.com/sauce-sdk/api/svm/router-data.md)).

## resolveSvmAccountName

Resolves one account name, in the language `sauce run --account NAME=<value>` takes, to the address a cook attaches.

### Import

```ts theme={null}
import { resolveSvmAccountName } from "@eco-incorp/sauce/svm";
```

### Usage

```ts sol-accounts.mts theme={null}
import { requireChain, routes } from "@eco-incorp/sauce";
import { v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import {
  address,
  derivePotPda,
  resolveSvmAccountName,
  type AccountResolution,
} from "@eco-incorp/sauce/svm";

const program = routes.compileSauceProgram(
  "solana",
  `function main(to: Address, amount: Uint256) { USDC.transfer(to, amount); }`,
);

const owner = address("GHRJMiop6cEM9E9oqHqyf4JfC4dMsr5pvJfzqkLiJ88p"); // signs and pays
const to = address("HstnoYUjLqBatkcYonFkiFk41XE99kMMY9YKykAoLKsL"); // main's `to`
const kitchen = address(v12SvmKitchenProgramId());
const { address: pot } = await derivePotPda(kitchen, owner, new Uint8Array(32)); // the default Pot

// Each derived account, resolved as `sauce run` resolves `--account NAME=<value>`.
const resolution: AccountResolution = {};
for (const [name, derivation] of Object.entries(program.derivedAccounts ?? {})) {
  const resolved = await resolveSvmAccountName(derivation, {
    chain: requireChain("solana"),
    owner,
    pot,
    args: { to },
  });
  // The Kitchen signs for the Pot during the cook, so the transaction attaches it unsigned.
  resolution[name] =
    resolved.value === "pot" ? { address: pot, signer: false }
    : resolved.value === "owner" ? owner
    : resolved.value;
  console.log(`${name} = ${derivation} -> ${resolved.kind} ${resolved.value === "pot" ? pot : resolved.value}`);
}

export { program, resolution, owner, pot };
```

```text theme={null}
tokenProgram = TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA -> address TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
pot = authority -> pot EdR4Mbo2rRLkDmwrHC8rrQTiFZVuMaSXRiaHUp2jSaMz
potUSDC = authority:USDC -> account 8EUJiaFX2cCydibGXpKafVPkhUMrKHn4XDmHnJMQAPHZ
toUSDC = @to:USDC -> account Gx88vBUtgTehFYxSQy7eGF47SJYNLMnmTXYA9VBhXJms
```

### Signature

```ts theme={null}
export declare function resolveSvmAccountName(value: string, ctx: SvmAccountNameContext): Promise<ResolvedSvmAccountName>;

export interface SvmAccountNameContext {
    readonly chain: CanonicalChain;
    readonly owner: Address;
    readonly pot: Address;
    readonly via?: "kitchen" | "engine";
    readonly args?: Readonly<Record<string, Address>>;
    readonly integers?: Readonly<Record<string, bigint>>;
    readonly programOf?: SvmMintProgramOf;
    readonly load?: OwnedAccountLoader;
    readonly slots?: Map<string, Promise<SvmSlot>>;
    readonly found?: Map<string, Address | null>;
    readonly keyedPools?: Map<string, Promise<Address>>;
}
```

### Names

| Name | Resolves to |
| - | - |
| `pot`, `owner` | The cook's Pot, or its owner (the signer). Any `pot` form is refused with `via: "engine"`, which has no Pot. |
| `authority` | The account that signs the engine call: the Pot through the Kitchen, the owner on a direct cook. |
| a base58 key, or `0x` and 64 hex digits | That address. |
| `system` | The System program. |
| `@<param>` | The key given as `main`'s argument `<param>` (`ctx.args`). |
| `<holder>:<token>` | The holder's associated token account for the mint, under the program that owns the mint. A holder is `pot`, `owner`, `authority`, `@<param>` or a base58 address. |
| `mint:<token>` | The mint. |
| `program:<token>` | The token program that owns the mint. |
| `slot:<pool>:<token>.<part>` | One of the nine accounts of a venue's swap at a price, read from the pool through `ctx.load`. The pool may be a key, `@<param>`, or a venue's pool by key, `Orca.Whirlpool(USDC,WSOL,4)`. |
| `pda:<program>:<seeds>` | The address `<program>` derives for comma-separated seeds (`"text"`, a key, `mint:<token>`, `u64:<n>`, `0x<hex>`). |

A token is a registered symbol (`USDC`, `TSLAx`, matched without regard to case), a mint's base58 address, or `@<param>`. A registered mint's program comes from the registry; any other is read through `ctx.programOf`.

### Returns

`ResolvedSvmAccountName`: `value` is `"pot"`, `"owner"` or an address; `kind` is `pot`, `owner`, `address`, `account`, `mint` or `program`; `account` carries the mint, holder and token program of a token account.

### Throws

`SvmAccountNameError` for a name that cannot be resolved, such as a slot without `ctx.load` or an unregistered mint without `ctx.programOf`.

## resolveSvmToken

Reads a mint and resolves its token program, decimals and Token-2022 extensions.

### Import

```ts theme={null}
import { resolveSvmToken, svmTokenAccount } from "@eco-incorp/sauce/svm";
```

### Usage

```ts sol-token.mts theme={null}
import { routes } from "@eco-incorp/sauce";
import {
  address,
  coalescingOwnedAccountLoader,
  kitBatchAccountLoader,
  resolveSvmToken,
  svmTokenAccount,
} from "@eco-incorp/sauce/svm";
import { createSolanaRpc } from "@solana/kit";

const rpcUrl = process.argv[2]; // your mainnet-beta JSON-RPC URL: the SDK ships none
const load = coalescingOwnedAccountLoader(kitBatchAccountLoader(createSolanaRpc(rpcUrl)));
const holder = address("GHRJMiop6cEM9E9oqHqyf4JfC4dMsr5pvJfzqkLiJ88p");

for (const symbol of ["USDC", "PYUSD"]) {
  const row = routes.SVM_TOKEN_REGISTRY["mainnet-beta"].find((r) => r.symbol === symbol)!;
  const token = await resolveSvmToken(load, address(row.mint)); // reads the mint and its owner
  console.log(symbol, {
    tokenProgram: token.tokenProgram,
    decimals: token.decimals,
    transferFee: token.extensions.transferFee !== undefined,
    advisories: token.extensions.advisories,
    transferRefusals: token.transferRefusals,
    holderAccount: await svmTokenAccount(token, holder), // derived under the mint's program
  });
}
```

```bash theme={null}
node sol-token.mts <your mainnet-beta RPC URL>
```

```text theme={null}
USDC {
  tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
  decimals: 6,
  transferFee: false,
  advisories: [],
  transferRefusals: [],
  holderAccount: 'BLjQGRdW6MPvxn5LmeCnyWCFBMxQ7zBNA41NPyTqtNK'
}
PYUSD {
  tokenProgram: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb',
  decimals: 6,
  transferFee: true,
  advisories: [
    'PermanentDelegate (a delegate authority can move/burn balances)',
    'ConfidentialTransferMint (confidential transfers are opt-in; plain transfers still work)'
  ],
  transferRefusals: [],
  holderAccount: 'VFWT7mtENL7aFPWExNgiugHUpqtqvevXDouKLB9mB8P'
}
```

### Signature

```ts theme={null}
export declare function resolveSvmToken(load: OwnedAccountLoader, mint: Address): Promise<SvmToken>;
export declare function svmTokenAccount(token: Pick<SvmToken, "mint" | "tokenProgram">, owner: Address): Promise<Address>;
export declare function svmTokenReceived(token: SvmToken, amount: bigint, epoch: bigint): bigint;
export declare function svmTokenToSend(token: SvmToken, amount: bigint, epoch: bigint): bigint;

export interface SvmToken {
    readonly mint: Address;
    readonly tokenProgram: Address;
    readonly decimals: number;
    readonly extensions: MintExtensions;
    readonly transferRefusals: readonly string[];
}
```

### Parameters

* `load`: an owned-account loader, which returns each account's data and owner. `kitBatchAccountLoader(rpc)` wraps a kit RPC's `getMultipleAccounts`, and `coalescingOwnedAccountLoader` batches the loads made in one turn into one request. The owner is the only way to tell an extensionless Token-2022 mint from an SPL Token one.
* `mint`: the mint's address.

### Returns

* `tokenProgram`: SPL Token or Token-2022, the program that owns the mint: a checked write's `tokenProgram` account, and the program a token account derives under.
* `decimals`: a checked write's `decimals` argument.
* `extensions`: `transferFee` and `transferHook` where set, `blocking` (non-transferable, frozen by default, paused) and `advisories` (permanent delegate, confidential transfers and other flags that do not stop a plain transfer).
* `transferRefusals`: why `@sauce/token`'s checked transfer of this mint would be refused: a transfer hook that names a program, or a `blocking` extension. Empty when none.
* `svmTokenAccount(token, owner)`: the owner's associated token account under the mint's program. A legacy derivation of a Token-2022 holder's account gives an address nothing created.
* `svmTokenReceived` and `svmTokenToSend`: what a transfer of `amount` delivers at `epoch`, and what to send for the destination to receive exactly `amount`, netting the mint's transfer fee as Token-2022 computes it.

### Throws

`resolveSvmToken` throws when the account is missing, is owned by neither token program, or is not an initialized mint.

A Token-2022 transfer fee is withheld from what the destination receives, so a program that pays one measures what arrived. In a program, `@sauce/token`'s `transferChecked` and `approveChecked` move either program's tokens; the legacy `transfer`, `transferFrom` and `approve` are SPL Token only ([Names](https://docs.eco.com/sauce/names.md)).

## The execution fee and a cook built by hand

Every cook pays the Kitchen's execution fee in lamports, and signs a maximum. `fetchExecutionFee` reads the fee from the Kitchen's `["execution_fee"]` config account. The instruction builders then assemble a cook for any owner without a signature, which is how you measure or inspect one before a wallet signs it.

### Usage

```ts sol-cook-unsigned.mts theme={null}
import { v12SvmEngineProgramId, v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import {
  address,
  buildCookInstruction,
  buildHeapFramePrepend,
  deriveExecutionFeeConfigPda,
  fetchExecutionFee,
  getTransactionSize,
  measureExecuteTransaction,
  resolveAccounts,
  TRANSACTION_SIZE_LIMIT,
} from "@eco-incorp/sauce/svm";
import { createSolanaRpc } from "@solana/kit";
import { owner, pot, program, resolution } from "./sol-accounts.mts";

const rpcUrl = process.argv[2]; // your mainnet-beta JSON-RPC URL: the SDK ships none
const kitchen = address(v12SvmKitchenProgramId());
const engine = address(v12SvmEngineProgramId());

// The fee is read from the Kitchen's config account, and the cook signs it as its maximum.
const rpc = createSolanaRpc(rpcUrl);
const fee = await fetchExecutionFee(rpc, kitchen);
console.log("execution fee:", fee, "lamports, at slot", await rpc.getSlot().send());

if (program.manifest === undefined) throw new Error("expected an SVM account manifest");
const tail = resolveAccounts(program.manifest, resolution, owner); // in manifest order
const { address: feeConfig } = await deriveExecutionFeeConfigPda(kitchen);

const instructions = [
  buildHeapFramePrepend(), // exactly once: the engine's 262,144-byte heap frame
  buildCookInstruction({
    kitchen,
    owner,
    engineProgram: engine,
    potSalt: new Uint8Array(32),
    pot, // validated by the Kitchen with potSalt and the Pot's canonical bump, which the builder derives
    feeConfig,
    bytecode: program.bytecode, // with main's arguments appended in a real cook
    maxExecutionFee: fee,
    tail,
  }),
];

// Compiled with no signature and a placeholder blockhash: nothing is signed or sent.
const unsigned = measureExecuteTransaction({ feePayer: owner, instructions });
console.log("program accounts:", tail.map((meta) => meta.address).length);
console.log("size:", getTransactionSize(unsigned), "of", TRANSACTION_SIZE_LIMIT, "bytes");
```

```text theme={null}
tokenProgram = TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA -> address TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
…
execution fee: 85672n lamports, at slot 460263335n
program accounts: 4
size: 860 of 1232 bytes
```

### Signature

```ts theme={null}
export declare function fetchExecutionFee(rpc: Rpc<GetAccountInfoApi>, kitchen: Address): Promise<bigint>;
export declare function resolveAccounts(manifest: AccountManifest, resolution: AccountResolution, payer: Address, { appendPayerSigner }?: ResolveAccountsOptions): ResolvedAccountMeta[];
export declare function buildCookInstruction({ kitchen, owner, engineProgram, potSalt, pot, feeConfig, bytecode, maxExecutionFee, feePayer, tail, }: CookInput): Instruction;
export declare function buildHeapFramePrepend(bytes?: number): Instruction;
export declare function measureExecuteTransaction({ feePayer, instructions, lookupTables, }: MeasureExecuteTransactionInput): Transaction;

export interface AccountResolution {
    [ref: string]: Address | {
        address: Address;
        signer?: boolean | TransactionSigner;
    };
}
```

### Notes

* `fetchExecutionFee` throws when the config is absent, is not owned by the Kitchen, or does not decode: every cook is refused on chain then. A cook whose `maxExecutionFee` is below the live fee is refused before anything runs, so read the fee immediately before signing.
* `resolveAccounts` keeps manifest order. An entry's `signer` replaces the manifest's requirement: the Pot is attached with `signer: false` because the Kitchen signs for it during the cook, and a non-payer authority is `{ address, signer: transactionSigner }`. The reserved `payer` reference resolves to the fee payer. A manifest ending in `remaining` leaves the final tail to you.
* Every cook transaction carries `buildHeapFramePrepend()` exactly once: the request for the engine's 262,144-byte heap frame. Without it the engine fails before any instruction of the program runs, and a second ComputeBudget instruction of the same type fails the whole transaction. The client adds it.
* `buildCookInstruction` names the Pot as a readonly account, and the Kitchen validates it with `potSalt` and the Pot's canonical bump, which the builder derives from `kitchen`, `owner` and `potSalt`. Pass `pot` as `derivePotPda(kitchen, owner, potSalt).address`. The Kitchen signs for the Pot during the cook; a Pot the program writes also belongs in `tail`, unsigned.
* The Kitchen's cook instruction carries the Pot's bump after its salt. Build every cook with this SDK's builders.
* `measureExecuteTransaction` compiles the transaction with no signature and a placeholder blockhash, for its size and accounts. A transaction holds at most 1,232 bytes.

## createSauceSvmClient

Builds a client that simulates, builds, signs and sends Kitchen cooks for one payer.

### Import

```ts theme={null}
import { createSauceSvmClient } from "@eco-incorp/sauce/svm";
```

### Usage

```ts sol-client.mts theme={null}
import { routes } from "@eco-incorp/sauce";
import { v12SvmEngineProgramId, v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import {
  address,
  createSauceSvmClient,
  fetchExecutionFee,
  type AccountResolution,
} from "@eco-incorp/sauce/svm";
import { createSolanaRpc, type TransactionSigner } from "@solana/kit";

export async function signedCook(
  rpcUrl: string,
  payer: TransactionSigner, // the Pot's owner: signs and pays
  feeBudgetLamports: bigint,
  compiled: routes.CompiledSauceProgram,
  accounts: AccountResolution,
) {
  const kitchen = address(v12SvmKitchenProgramId());
  const fee = await fetchExecutionFee(createSolanaRpc(rpcUrl), kitchen);
  if (fee > feeBudgetLamports) throw new Error(`the execution fee ${fee} exceeds the budget`);

  const client = await createSauceSvmClient({
    rpcUrl,
    payer,
    kitchen,
    programId: address(v12SvmEngineProgramId()),
    maxExecutionFee: fee, // a fee raised above this before the cook lands refuses the cook
  });
  if (compiled.manifest === undefined) throw new Error("expected an SVM account manifest");

  const simulated = await client.simulate(compiled.bytecode, compiled.manifest, accounts);
  if (!simulated.ok) throw new Error(`the cook would fail: ${JSON.stringify(simulated.err)}`);

  // Signed by `payer`, not sent: hand it to your own sender, or call client.execute(...) instead.
  return client.cookTransaction(compiled.bytecode, compiled.manifest, accounts, {
    computeUnitLimit: "auto",
  });
}
```

### Signature

```ts theme={null}
export declare function createSauceSvmClient({ rpcUrl, wsUrl, programId, kitchen, payer, potSalt, maxExecutionFee, via, }: SauceSvmClientConfig): Promise<SauceSvmClient>;
```

### Parameters

| Field | Required | Meaning |
| - | - | - |
| `rpcUrl` | yes | Your cluster's JSON-RPC URL. There is no default. |
| `wsUrl` | no | Its websocket URL. Default: `rpcUrl` with `http` swapped for `ws`. |
| `programId` | yes | The engine program: `v12SvmEngineProgramId()`. |
| `kitchen` | yes | The Kitchen program: `v12SvmKitchenProgramId()`. Needed for a direct cook too, which pays the fee its config sets. |
| `payer` | yes | The `TransactionSigner` that signs and pays; through the Kitchen, the Pot's owner. |
| `potSalt` | no | Which of the payer's Pots to cook on, 32 bytes. Default all zeros. |
| `maxExecutionFee` | no | The most each cook may pay the Kitchen, in lamports. Default `0n`, which fails while the Kitchen charges a fee. |
| `via` | no | `"kitchen"` (default) or `"engine"`: see [two ways to cook](#two-ways-to-cook). |

### Returns

`SauceSvmClient`:

| Member | What it does |
| - | - |
| `executionFee()` | Reads the Kitchen's fee now, in lamports. |
| `simulate(bytecode, manifest, resolution, opts?)` | Simulates the cook; `ok`, `unitsConsumed`, `returnData`, `logs`, `revert.payload` for a program's revert, and with `opts.accounts`, each account's state afterwards. |
| `execute(bytecode, manifest, resolution, opts?)` | Builds, signs, sends and confirms the cook; resolves to the signature, return data and logs. |
| `cookTransaction(bytecode, manifest, resolution, opts?)` | The signed transaction `execute` would send, not sent. |
| `simulateStaged`, `executeStaged`, `stagedCookTransaction` | The same for a staged program, given the code buffer's metadata address. |
| `cookLookupTable`, `stagedCookLookupTable` | The address lookup table a cook needs, made ready, or `undefined` when it fits without one. |
| `ensureLookupTable(addresses, opts?)` | Creates or extends a lookup table over addresses you choose. |

`computeUnitLimit: "auto"` simulates first and sets the limit from the units used, padded 20% and capped at 1,400,000. A cook's own `maxExecutionFee` option overrides the client's.

### Throws

When `rpcUrl` is missing or `via` is not `"kitchen"` or `"engine"`. A simulation failure is a result with `ok: false`, not a throw.

## cookTransaction

The signed cook transaction, built as `execute` builds it, for a caller that sends it another way. It is a method of the client above.

### Signature

```ts theme={null}
cookTransaction(bytecode: Uint8Array, manifest: AccountManifest, resolution: AccountResolution, opts?: ExecuteOpts): Promise<SignedExecuteTransaction>;

export interface ExecuteOpts extends SimulateOpts {
    computeUnitLimit?: number | "auto";
}
export interface SimulateOpts {
    prepends?: readonly Instruction[];
    lookupTables?: AddressesByLookupTableAddress;
    appendPayerSigner?: boolean;
    maxExecutionFee?: bigint;
    accounts?: readonly Address[];
}
```

### Returns

A kit transaction signed by the client's `payer`, with a fresh blockhash, the heap-frame request and, with `computeUnitLimit: "auto"`, a compute limit from a simulation. Nothing is sent. To build a cook with no signature, use the instruction builders in [the section above](#the-execution-fee-and-a-cook-built-by-hand).

### When the accounts do not fit

A cook that names about thirty accounts no longer fits one transaction. `execute` and `executeStaged` then create an address lookup table holding the accounts a table may hold (all but signers and called programs), wait until it is usable, and send through it. The payer pays the table's rent and owns it; the result's `lookupTableAddress` names it, and passing it as `lookupTable` to a later cook reuses it. `lookupTable: false` sends the cook as it is. To simulate such a cook or build it with `cookTransaction`, get the table first with `cookLookupTable` and pass its `lookupTables`.

## Stage a large program

A program larger than one transaction is staged into a Kitchen code buffer, then cooked from it. The client has no staging method: build these Kitchen instructions and send them in order.

### Usage

```ts sol-stage.mts theme={null}
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { routes } from "@eco-incorp/sauce";
import { v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import {
  address,
  buildCreateCodeBufferInstruction,
  buildFinalizeCodeBufferInstruction,
  buildWriteCodeBufferInstruction,
  deriveCodeBufferPda,
  deriveCodePda,
  getTransactionSize,
  GROWTH_STEP,
  measureExecuteTransaction,
  TRANSACTION_SIZE_LIMIT,
} from "@eco-incorp/sauce/svm";

const program = routes.compileSauceProgram("solana", readFileSync("orca-swap.sauce.ts", "utf8"));
const bytecode = program.bytecode;
const sha256 = new Uint8Array(createHash("sha256").update(bytecode).digest());

const kitchen = address(v12SvmKitchenProgramId());
const owner = address("GHRJMiop6cEM9E9oqHqyf4JfC4dMsr5pvJfzqkLiJ88p");
const nonce = 0n; // one buffer per (owner, nonce)
const { address: codeBuffer } = await deriveCodeBufferPda(kitchen, owner, nonce);
const { address: code } = await deriveCodePda(kitchen, codeBuffer);
const accounts = { kitchen, owner, codeBuffer, code };

console.log("program:", bytecode.length, "bytes; growth steps needed:", Math.ceil(bytecode.length / GROWTH_STEP) - 1);

// 1. Create the buffer, sized for the program.
const steps = [[buildCreateCodeBufferInstruction({ ...accounts, nonce, capacity: bytecode.length })]];
// 2. Write the bytes, one chunk per transaction.
const CHUNK = 900;
for (let offset = 0; offset < bytecode.length; offset += CHUNK) {
  const chunk = bytecode.subarray(offset, offset + CHUNK);
  steps.push([buildWriteCodeBufferInstruction({ ...accounts, offset, chunk })]);
}
// 3. Finalize: the Kitchen checks the digest and shrinks the code account to the program's length.
steps.push([buildFinalizeCodeBufferInstruction({ ...accounts, len: bytecode.length, expectedSha256: sha256 })]);

for (const instructions of steps) {
  const size = getTransactionSize(measureExecuteTransaction({ feePayer: owner, instructions }));
  console.log(`transaction of ${size} bytes (limit ${TRANSACTION_SIZE_LIMIT})`);
}
// 4. Cook it with buildCookFromAccountInstruction({ codeBuffer, code, pin: sha256, ... }), or
//    client.executeStaged(codeBuffer, manifest, resolution).
```

```text theme={null}
program: 2128 bytes; growth steps needed: 0
transaction of 291 bytes (limit 1232)
transaction of 1155 bytes (limit 1232)
transaction of 1155 bytes (limit 1232)
transaction of 583 bytes (limit 1232)
transaction of 282 bytes (limit 1232)
```

`orca-swap.sauce.ts` is the program in [Venue accounts and data](#venue-accounts-and-data).

### Steps

1. `deriveCodeBufferPda(kitchen, owner, nonce)` and `deriveCodePda(kitchen, codeBuffer)` give the buffer's metadata and code accounts.
2. `buildCreateCodeBufferInstruction` allocates them. A capacity above `GROWTH_STEP` (10,240 bytes) needs `Math.ceil(capacity / GROWTH_STEP) - 1` more `buildGrowCodeBufferInstruction` calls, which can share one transaction.
3. `buildWriteCodeBufferInstruction` writes each chunk at its offset.
4. `buildFinalizeCodeBufferInstruction` seals the buffer with the program's length and SHA-256 digest. The Kitchen checks the digest and shrinks the code account to that length.
5. Cook from it: the Kitchen's `buildCookFromAccountInstruction`, the client's `simulateStaged` and `executeStaged`, or on a direct cook `buildCookAndPayFromAccountPinnedInstruction`. `buildCloseCodeBufferInstruction` closes the buffer and returns its rent.

### What the pin checks

* **Through the Kitchen**, `buildCookFromAccountInstruction` takes a `pin`, and the Kitchen refuses the cook unless the staged bytes hash to it; the Kitchen runs only a finalized buffer the signing owner holds. The client's `expectedSha256` option throws on this path: it is not that pin.
* **Direct to the engine**, the client always pins a staged cook, to `expectedSha256` or to the `sha256` of the `StagedBuffer` you pass, and refuses a bare buffer address with neither. `buildCookAndPayFromAccountInstruction`, unpinned, runs whatever the account holds when the transaction lands, with the signer's authority: use it only for an account no one else can change.

## Two ways to cook

| | `via: "kitchen"` (default) | `via: "engine"` |
| - | - | - |
| The transaction calls | the Kitchen's `cook`, or `cook_from_account` for a staged program | the engine's `cookAndPay`, or `cookAndPayFromAccountPinned` |
| The program's authority | the owner's Pot, which the Kitchen signs for, and any signer it is given | only the signers the transaction gives it; nothing signs for a Pot |
| The execution fee | charged by the Kitchen before it calls the engine | charged by the engine before the program runs |

The fee and the compiled program are the same either way. A direct cook cannot write a Pot's storage, and a transfer whose authority is a Pot fails for the missing signature; it leaves the program one more level of calls into other programs.

## /svm/engine

The engine's wire format and instruction builders, without the rest of `/svm`.

### Usage

```ts sol-engine.mts theme={null}
import { v12SvmEngineProgramId, v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import { address, deriveExecutionFeeConfigPda } from "@eco-incorp/sauce/svm";
import {
  buildCookAndPayInstruction,
  engineFaultName,
  ENGINE_GAS_LIMIT_CU,
  HEAP_FRAME_BYTES,
} from "@eco-incorp/sauce/svm/engine";
import { program } from "./sol-compile.mts";

const wallet = address("GHRJMiop6cEM9E9oqHqyf4JfC4dMsr5pvJfzqkLiJ88p");
const { address: feeConfig } = await deriveExecutionFeeConfigPda(address(v12SvmKitchenProgramId()));

// A direct cook: the engine itself, no Kitchen and no Pot, paying the fee from `feePayer`.
const instruction = buildCookAndPayInstruction({
  engineProgram: address(v12SvmEngineProgramId()),
  bytecode: program.bytecode,
  feeConfig,
  feePayer: wallet,
  maxExecutionFee: 85_672n,
  tail: [],
});

console.log("data:", instruction.data?.length, "bytes; accounts:", instruction.accounts?.length);
console.log("heap frame:", HEAP_FRAME_BYTES, "bytes; compute budget:", ENGINE_GAS_LIMIT_CU);
console.log("Custom(0) is", engineFaultName(0));
```

```text theme={null}
…
data: 711 bytes; accounts: 3
heap frame: 262144 bytes; compute budget: 1400000
Custom(0) is Revert
```

### Exports

```ts theme={null}
export declare function buildCookAndPayInstruction({ engineProgram, bytecode, tail, ...payment }: CookAndPayInput): Instruction;
export declare function buildCookAndPayFromAccountInstruction({ engineProgram, code, tail, ...payment }: CookAndPayFromAccountInput): Instruction;
export declare function buildCookAndPayFromAccountPinnedInstruction({ expectedSha256, engineProgram, code, tail, ...payment }: CookAndPayFromAccountPinnedInput): Instruction;
export declare function buildExecuteInstruction({ programId, bytecode, accounts, }: ExecuteInstructionInput): Instruction;
export declare function buildExecuteFromAccountInstruction({ programId, code, accounts, }: ExecuteFromAccountInstructionInput): Instruction;
export declare function engineFaultName(code: number): EngineFault | undefined;
export declare const ENGINE_FAULTS: { /* fault name -> Custom(n) code */ };
export declare const HEAP_FRAME_BYTES = 262144;
export declare const ENGINE_GAS_LIMIT_CU = 1400000;
export declare const MAX_RETURN_DATA = 1024;
export declare const MAX_CPI_ACCOUNTS = 64;
```

* The three `buildCookAndPay…` builders are the engine's paid direct cooks. Each appends the fee payer, the System program and the fee config after the program's `tail`; the engine takes them off before the program runs. The fee payer must be a System-owned account holding no data.
* `buildExecuteInstruction` and `buildExecuteFromAccountInstruction` build the engine's `execute` and `execute_from_account`, the instructions the Kitchen sends the engine during a cook. The released engine refuses them from anything but the Kitchen.
* `engineFaultName(code)` names a failed engine instruction's `Custom(n)` code. `Custom(0)` is `Revert`, a program's own `throw` or `require`.

## /svm/engine-artifacts

File paths to the packaged engine and Kitchen programs and the Kitchen IDL, for a local LiteSVM harness. Node only.

### Usage

```ts sol-artifacts.mts theme={null}
import { existsSync } from "node:fs";
import { basename } from "node:path";
import { svmEngineArtifacts } from "@eco-incorp/sauce/svm/engine-artifacts";

// File paths inside the installed package, for a local LiteSVM harness. Node only.
const paths = svmEngineArtifacts("mainnet");
for (const [name, path] of Object.entries(paths)) {
  console.log(name, basename(path), existsSync(path));
}
```

```text theme={null}
engineSo engine-mainnet.so true
kitchenSo kitchen.so true
kitchenIdlJson kitchen-idl.json true
```

### Signature

```ts theme={null}
export type EngineCluster = "devnet" | "mainnet";
export declare const SVM_ARTIFACTS_DIR: string;
export declare function svmEnginePath(cluster?: EngineCluster, path?: string): string;
export declare function svmKitchenPath(path?: string): string;
export declare function svmKitchenIdlPath(path?: string): string;
export declare function svmEngineArtifacts(cluster?: EngineCluster, paths?: SvmEngineArtifactPaths): Required<SvmEngineArtifactPaths>;
```

### Parameters

* `cluster`: which engine build. Default `"devnet"`.
* `path`, `paths`: an explicit file to use instead of the packaged one.

### Returns

Absolute paths. Load each program into LiteSVM or a local validator at `v12SvmEngineProgramId()` and `v12SvmKitchenProgramId()`. `@eco-incorp/sauce/testing` runs programs on such a local Solana from a test ([Testing](https://docs.eco.com/sauce-sdk/typescript/testing.md)).

### Throws

When a file is absent. `svmEngineArtifacts` throws if any piece is missing, rather than returning a partial set.

## Next steps

* [Kitchen](https://docs.eco.com/sauce/kitchen.md) and [Pot](https://docs.eco.com/sauce/pot.md): what a Solana cook runs through.
* [`sauce run` on Solana](https://docs.eco.com/sauce-sdk/cli/solana): the same steps from the command line.
* [`@sauce/router` on Solana](https://docs.eco.com/sauce-libraries/router#on-solana): the Solana swap functions.
* [Distributed swap on Solana](https://docs.eco.com/quickstarts/sauce/solana-distributed-swap.md) and [Solana swap](https://docs.eco.com/quickstarts/sauce/solana-swap.md).


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