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

# Getting started with Sauce

> Install @eco-incorp/sauce, build a first Eco intent that runs a Sauce program on Base, prepare the Executor-owned Pot and the Kitchen fee, fund it on the source chain, run a program directly through your own Pot, and execute on Solana through the Kitchen client.

This guide takes a program from source to a funded Eco intent, then shows the two other ways to execute: directly through your own Pot on the EVM, and through the Kitchen client on Solana. The examples use the APIs documented in the SDK's [bundled manual](https://unpkg.com/@eco-incorp/sauce@0.99.4/docs/README.md).

## Prerequisites

* Node 24 or later
* `npm install --save-exact @eco-incorp/sauce@0.99.4`; the compiler comes with it
* `viem` for chain reads and address formatting
* Access to Sauce, which is closed access: [contact Eco](mailto:partners@eco.com)

<Steps>
  <Step title="Build the intent">
    `Base(body, options).reward(reward)` compiles the body for Base and packages it as an Eco intent. The body is read as source and compiled, not run as JavaScript, so it captures nothing: every external value goes through `defines`. Route assets delivered by the solver arrive in the Portal's Executor, and the program spends from the Pot, so a `precalls` transfer moves them across first.

    ```typescript first-intent.ts theme={null}
    import { routes, token } from "@eco-incorp/sauce";
    import { hexToBigInt, toHex, type Address } from "viem";

    export interface TransferIntentConfig {
      pot: Address;
      engine: Address;
      executionFee: bigint;
      portal: Address;
      sourcePortal: Address;
      creator: Address;
      prover: Address;
      recipient: Address;
      deadline: bigint;
      rewardDeadline: bigint;
      rewardAmount: bigint;
    }

    // Build after prepareDestinationPot returns ready: true.
    // Use Portal and prover addresses from your Eco integration configuration.
    export function buildTransferIntent(config: TransferIntentConfig) {
      const baseUsdcValue = routes.registryTokenAddress(Base.chain, "USDC");
      const sourceUsdcValue = routes.registryTokenAddress(Ethereum.chain, "USDC");
      if (baseUsdcValue === undefined || sourceUsdcValue === undefined) {
        throw new Error("Missing USDC registry entry");
      }
      const baseUsdc = toHex(baseUsdcValue, { size: 20 });
      const sourceUsdc = toHex(sourceUsdcValue, { size: 20 });
      const recipient = hexToBigInt(config.recipient);
      const amount = 1_000_000n; // 1 USDC on Base.

      return Base(
        () => {
          USDC.transfer(recipient, amount);
        },
        {
          execution: {
            pot: config.pot,
            engine: config.engine,
            value: config.executionFee,
          },
          nativeAmount: config.executionFee,
          portal: config.portal,
          sourcePortal: config.sourcePortal,
          deadline: config.deadline,
          defines: { recipient, amount },
          tokens: [{ token: baseUsdc, amount }],
          precalls: [token.Token.transfer({
            token: baseUsdc, to: config.pot, amount,
          }).toCall()],
        },
      ).reward({
        source: Ethereum.chain,
        creator: config.creator,
        prover: config.prover,
        deadline: config.rewardDeadline,
        tokens: [{ token: sourceUsdc, amount: config.rewardAmount }],
      });
    }
    ```

    Deadlines are absolute Unix seconds. `route.deadline` bounds fulfillment on the destination; `reward.deadline` is when the source funds become refundable, so leave room for fulfillment and proof delivery. The source reward must cover the solver's delivery, fees and gas; the SDK does not price it.
  </Step>

  <Step title="Prepare the destination Pot and fee">
    A direct intent route executes through a Pot owned by the destination Portal's **Executor**. Any other owner rejects the call with `NotOwner()`. Every `cook` also pays the Kitchen's current `executionFee()` in native value, including programs that move only ERC-20s. This helper, from the SDK examples, reads the Executor, resolves the release Kitchen and engine, predicts the Pot, returns a deployment request when the Pot does not exist yet, and quotes the fee.

    ```typescript evm-execution.ts theme={null}
    import { routes } from "@eco-incorp/sauce";
    import { v12EngineAddress, v12KitchenAddress } from "@eco-incorp/sauce/deployments";
    import { v12Kitchen, v12Pot } from "@eco-incorp/sauce/evm/engine";
    import { getAddress, parseAbi, type Address, type Hex, type PublicClient } from "viem";

    const portalAbi = parseAbi(["function executor() view returns (address)"]);
    const feeAbi = parseAbi(["function executionFee() view returns (uint256)"]);
    const potKitchenAbi = parseAbi(["function kitchen() view returns (address)"]);

    // Call with a client for the destination chain. Importing this example performs no RPC.
    export async function prepareDestinationPot(client: PublicClient, portal: Address, salt: Hex) {
      const kitchen = getAddress(v12KitchenAddress());
      const engine = getAddress(v12EngineAddress());
      const [kitchenCode, engineCode] = await Promise.all([
        client.getCode({ address: kitchen }),
        client.getCode({ address: engine }),
      ]);
      if (!kitchenCode || kitchenCode === "0x" || !engineCode || engineCode === "0x") {
        throw new Error("Current Kitchen and engine must be deployed on the destination chain");
      }

      const executor = await client.readContract({
        address: portal,
        abi: portalAbi,
        functionName: "executor",
      });
      const pot = await client.readContract({
        address: kitchen,
        abi: v12Kitchen.abi,
        functionName: "predictPot",
        args: [executor, salt],
      });
      const executionFee = await client.readContract({
        address: kitchen,
        abi: feeAbi,
        functionName: "executionFee",
      });
      const context = { kitchen, engine, executor, pot, executionFee };
      const code = await client.getCode({ address: pot });
      if (!code || code === "0x") {
        const deploy = routes.sauceEvmDeployPotCall({ kitchen, owner: executor, salt });
        // Send this destination-chain request with your signer, wait for a successful receipt,
        // then call prepareDestinationPot again. Do not build a route until ready is true.
        return {
          ...context,
          ready: false as const,
          deployment: { to: kitchen, data: deploy.data, value: 0n },
        };
      }

      const [owner, actualKitchen] = await Promise.all([
        client.readContract({ address: pot, abi: v12Pot.abi, functionName: "owner" }),
        client.readContract({ address: pot, abi: potKitchenAbi, functionName: "kitchen" }),
      ]);
      if (getAddress(owner) !== getAddress(executor) || getAddress(actualKitchen) !== kitchen) {
        throw new Error("Pot must belong to this Portal Executor and the current Kitchen");
      }
      return { ...context, ready: true as const };
    }
    ```

    Pass the returned `pot`, `engine` and `executionFee` to `buildTransferIntent` only when `ready` is `true`. Set `execution.value` to the fee plus any native value the program spends, and budget `nativeAmount` for every call value in the route. Read the fee shortly before building: Eco can change it per chain, and an increase can make the cook revert or reduce the native value available to the program.

    A Pot owned by the Executor can be called by routes through that Portal. Deliver assets, use them and return leftovers within the same fulfillment; do not leave funds or allowances in it.
  </Step>

  <Step title="Fund on the source chain">
    ```typescript theme={null}
    import type { routes } from "@eco-incorp/sauce";

    export function fundingRequests(built: routes.BuiltIntent) {
      return [...built.approvals(), built.publishAndFundCalldata(false)];
    }
    ```

    Each request is `{ to, data, value }`. Send them from the funder on `built.source`, in order: the approvals authorize the source Portal to pull the reward into the intent vault, and `publishAndFundCalldata(false)` publishes and fully funds the intent. `built.hash()` returns the intent, route and reward hashes, and `built.vaultCall()`, `hashCall()` and `fundedCall()` return source-chain read requests with decoders. A solver then fulfills the intent on Base, where the Executor runs your program through the Pot.
  </Step>

  <Step title="Run a program directly">
    Outside an intent, compile with the SDK's compiler export and call `cook` on a Pot you own. The compiled program is reusable; `main` arguments are ABI-encoded and appended per execution.

    ```typescript theme={null}
    import { compile } from "@eco-incorp/sauce/compiler";
    import { v12Pot } from "@eco-incorp/sauce/evm/engine";
    import { encodeAbiParameters, hexToBytes, bytesToHex } from "viem";

    const source = `
    function main(amounts: Uint256[], minimum: Uint256): Uint256 {
      let total = 0n;
      for (let i = 0; i < amounts.length; i = i + 1) {
        total = total + amounts[i];
      }
      require(total >= minimum);
      return total;
    }
    `;

    const entry = new TextEncoder().encode(source);
    const artifact = compile({
      target: "evm",
      entry: "main.ts",
      resolve: (path) => (path === "main.ts" ? entry : undefined),
    });

    const args = hexToBytes(
      encodeAbiParameters([{ type: "uint256[]" }, { type: "uint256" }], [[25n, 75n], 100n]),
    );
    const payload = new Uint8Array(artifact.bytecode.length + args.length);
    payload.set(artifact.bytecode);
    payload.set(args, artifact.bytecode.length);

    // Given a compatible engine address:
    // const data = v12Pot.encodeCook(engine, bytesToHex(payload));
    // Send `data` from the Pot owner with value = executionFee + programNativeValue.
    ```

    Simulate the complete call and estimate gas with your destination RPC before submitting. The Kitchen fee is separate from network gas.

    Deploy your own Pot once with `Kitchen.deployPot(owner, salt)`; `cook` is owner-only. Standalone programs use compiler-native syntax, so contract calls need an ABI import such as `ERC20.at(token).approve(...)` rather than the SDK's `USDC` globals. See [Compiler API](/sdk-reference/sauce-compiler/api) for options such as `compactArgs`.
  </Step>
</Steps>

## Solana

Solana programs compile with `target: "svm"` and return an account manifest that names the accounts to attach. Execution goes through the SDK's Kitchen client with the released program ids. The client derives a Pot for its payer and resolves accounts against the manifest. Use `simulate` before sending; `maxExecutionFee` caps the lamports accepted for the Kitchen fee and defaults to zero, so set it from the Kitchen's fee configuration.

```typescript theme={null}
import { v12SvmEngineProgramId, v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import { createSauceSvmClient, type AccountResolution } from "@eco-incorp/sauce/svm";
import type { CompileResult } from "@eco-incorp/sauce/compiler";
import { address, type TransactionSigner } from "@solana/kit";

export async function simulateProgram(
  rpcUrl: string,
  payer: TransactionSigner,
  fee: bigint,
  compiled: CompileResult,
  accounts: AccountResolution,
) {
  if (!compiled.manifest) throw new Error("Compile with target: svm");
  try {
    const client = await createSauceSvmClient({
      rpcUrl,
      payer,
      kitchen: address(v12SvmKitchenProgramId()),
      programId: address(v12SvmEngineProgramId()),
      maxExecutionFee: fee, // Read from the Kitchen's ExecutionFeeConfig account.
    });
    return await client.simulate(compiled.bytecode, compiled.manifest, accounts);
  } catch (error) {
    throw new Error("Could not simulate the Sauce program", { cause: error });
  }
}
```

The SDK's SVM route helpers (`Solana(body, ...)`, `buildSauceSvmCall`) emit a legacy instruction that the released engine does not accept, so do not distribute them as executable intents. The SDK's [Solana guide](https://unpkg.com/@eco-incorp/sauce@0.99.4/docs/guides/solana.md) covers account resolution, fee reads and staging larger programs.

## Troubleshooting

| Symptom | Check |
| - | - |
| A token or protocol name does not resolve | Check its destination-chain registry entry. For application values, supply `defines` rather than relying on a callback closure. |
| `NotOwner()` | The Pot owner must be the account calling `cook`. For a direct intent route, use the destination Portal's Executor. |
| Execution fails after a fee change | Read the Kitchen fee again and rebuild with enough value for the fee and the program. |
| No code at an engine or Kitchen address | Confirm the destination network and deployment before proceeding. |
| An RPC read or simulation fails | Check the RPC endpoint and inspect the returned error. Do not submit until the required reads and simulation succeed. |
| A Solana fee or account check fails | Check `maxExecutionFee`, the manifest's account order and roles, and the selected program IDs. |

## Next steps

<CardGroup cols={3}>
  <Card title="Programs as intents" icon="route" href="/programmable-transactions/sauce/programs-as-intents">
    Intent options, token and protocol globals, builders, nested intents.
  </Card>

  <Card title="SauceScript reference" icon="code" href="/programmable-transactions/sauce/saucescript">
    The language: types, control flow, contract calls, builtins.
  </Card>

  <Card title="Cookbook" icon="book-open" href="/cookbook/overview">
    Complete programs shipped in the packages.
  </Card>
</CardGroup>
