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

# Settlement verification

> Check from TypeScript that a payload, a cook or a transaction someone hands you runs the settlement program the package ships.

The package ships a settlement program: it sweeps a Pot's balance of a list of tokens to one recipient, with a floor on the first token. Verification answers one question about a payload, a cook or a transaction someone else built: is the program inside it that settle program, and with what parameters?

That is narrower than "is this transaction safe to sign". A genuine settle can sit beside other calls, on a chain you did not expect, aimed at a Pot or an engine you do not trust. Verification identifies the program and decodes its parameters; the chain, the target, the engine, the accounts and every other call in the transaction remain yours to check.

| Import | What it does | Runs in |
| - | - | - |
| `@eco-incorp/sauce/verify` | EVM: checks the program's hash against a pin and decodes its argument tail strictly. | Node, browsers and edge runtimes. It depends on `viem` and never loads the compiler. |
| `@eco-incorp/sauce/svm/verify` | Solana: recompiles the settle program with the parameters you expect, byte-compares, and resolves the instruction's accounts. | Node and browsers. It loads the compiler, so call `await ready()` first in a browser. |

[`sauce verify`](https://docs.eco.com/sauce-sdk/cli/verify) runs the same checks on a payload, a cook's calldata or a transaction from the command line.

## The settle programs

| Import | What it is |
| - | - |
| `settleSource()` from `@eco-incorp/sauce/recipes` | The EVM settle program's source text. |
| `@eco-incorp/sauce/recipes/settle.sauce.ts` | The same source as a file in the package. |
| `svmSettleSource(escrowCount)`, `svmSettleRefs(escrowCount)` from `@eco-incorp/sauce/svm` | The Solana settle program for 1 to 16 escrows, and its account references in manifest order. |
| `@eco-incorp/sauce/svm/recipes/settle.sauce.ts` | The one-escrow Solana program as a file. |

On EVM, `main(tokens, minOut, recipient)` takes its three values as run-time arguments, so one compiled program of `SETTLE_WIRE.PROGRAM_BYTES` (410) bytes serves every token list, floor and recipient. Before any transfer it requires the Pot's balance of `tokens[0]` to be at least `minOut` (`0` turns the floor off), then sweeps the Pot's whole current balance of every listed token to `recipient`. Each transfer has OpenZeppelin `SafeERC20` semantics: a token that returns nothing is swept, and a `false` return, a malformed return or an address with no code reverts the sweep.

The floor reads the Pot's current balance, so a balance that was already there, or that someone donated, counts toward it and is swept with the rest. The floor says what the Pot holds, not what an earlier swap produced.

On Solana the floor and the split between the two token-program slots are compiled into the program, and each escrow is an attached account: `3N + 3` accounts for `N` escrows.

## validateSettleProgram

Checks that an EVM payload is the pinned settle program followed by a canonical argument tail, and decodes the tail.

### Import

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

### Usage

```ts ver-validate.mts theme={null}
import { Base } from "@eco-incorp/sauce";
import {
  SETTLE_PROGRAM,
  SETTLE_WIRE,
  SettleDecodeError,
  encodeSettleProgram,
  validateSettleProgram,
} from "@eco-incorp/sauce/verify";

// A payload is the shipped settle program followed by its arguments: tokens, minOut, recipient.
const payload = encodeSettleProgram(
  [Base.USDC.address],
  1_000_000n, // the floor on tokens[0]: 1 USDC
  "0x1111111111111111111111111111111111111111",
  SETTLE_PROGRAM,
);

const settle = validateSettleProgram(payload);
console.log(settle.tokens, settle.minOut, settle.recipient);
console.log(settle.programHash === SETTLE_WIRE.PROGRAM_HASH, SETTLE_WIRE.PROGRAM_BYTES);

// One byte of the program changed: a different program, refused by its hash.
const at = 2 + 2 * 20;
const flipped = (parseInt(payload.slice(at, at + 2), 16) ^ 0xff).toString(16).padStart(2, "0");
const tampered = `${payload.slice(0, at)}${flipped}${payload.slice(at + 2)}` as `0x${string}`;
try {
  validateSettleProgram(tampered);
} catch (error) {
  if (!(error instanceof SettleDecodeError)) throw error;
  console.log(error.code);
}
```

```text theme={null}
[ '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ] 1000000n 0x1111111111111111111111111111111111111111
true 410
PROGRAM_HASH
```

### Signature

```ts theme={null}
export declare function validateSettleProgram(payload: Hex, accepted?: readonly (Hex | ProgramPin)[]): DecodedSettleProgram;
export declare function encodeSettleProgram(tokens: readonly (bigint | `0x${string}`)[], minOut: bigint, recipient: bigint | `0x${string}`, programHex: Hex): Hex;
export declare const SETTLE_PROGRAM: Hex;

export interface ProgramPin {
    hash: Hex;
    bytes: number;
}

export interface DecodedSettleProgram {
    tokens: Address20[];
    minOut: bigint;
    recipient: Address20;
    floorToken: Address20;
    program: Hex;
    programHash: Hex;
    args: Hex;
    argsSize: number;
    payloadSize: number;
}
```

### Parameters

* `payload`: the settle program's bytes followed by its ABI-v1 arguments: the `program` argument of `Pot.cook(engine, program)`.
* `accepted`: the program hashes or `{ hash, bytes }` pins to accept. Default: the current pin, `SETTLE_WIRE.PROGRAM_HASH` at `SETTLE_WIRE.PROGRAM_BYTES` bytes. An explicit list replaces the default, and an empty list accepts nothing.

### Returns

`DecodedSettleProgram`: the tokens in source order (`tokens[0]` is also `floorToken`), `minOut`, `recipient`, the program bytes and their `keccak256`, and the argument tail. Addresses are lower-case hex.

### Throws

`SettleDecodeError`, whose `code` is one of `EMPTY`, `PROGRAM_TRUNCATED`, `ARGS_TRUNCATED`, `ARGS_OVERLONG`, `TOKENS_OFFSET`, `TOKENS_EMPTY`, `DIRTY_ADDRESS_WORD`, `ZERO_RECIPIENT` and `PROGRAM_HASH`. The tail must be canonical: the canonical token-array offset, a non-empty token list, a non-zero recipient, clean address words and no trailing bytes. Some other encodings run on the engine but are refused here, so a payload that validates has one byte representation of its values.

The pin is the hash of the settle program with ABI-v1 arguments, the only argument encoding. A payload whose arguments use any other encoding is not accepted.

### Inspection levels

| Function | Result |
| - | - |
| `validateSettleProgram(payload, accepted?)` | The program's identity by hash, and a strict decode. Use this to validate. |
| `decodeSettleProgram(payload, programBytes?)` | A strict decode at the expected program length. It does not authenticate the program bytes. |
| `parseSettleProgram(payload, programBytes?)` | A report that never throws, with a `fatal` failure when there is one. |
| `bestEffortDecode(parse)` | Whatever `parseSettleProgram` could read, or `null` where reporting values would mislead. |
| `encodeSettleProgram(tokens, minOut, recipient, program)` | The canonical payload. |

Only the hash check establishes that the bytes are the settle program; a structural decode accepts any bytes of the right length. `SETTLE_VECTORS` and `SETTLE_REJECTIONS` are the conformance payloads, valid and refused, for a decoder written in another language.

## decodeSettleCall

Unwraps `Pot.cook(engine, program)` calldata and validates the program it carries.

### Import

```ts theme={null}
import { decodeSettleCall, extractEvmSettleFromCalls } from "@eco-incorp/sauce/verify";
```

### Usage

```ts ver-cook.mts theme={null}
import { Base } from "@eco-incorp/sauce";
import { v12EngineAddress } from "@eco-incorp/sauce/deployments";
import { v12Pot } from "@eco-incorp/sauce/evm/engine";
import {
  SETTLE_PROGRAM,
  decodeSettleCall,
  encodeSettleProgram,
  extractEvmSettleFromCalls,
} from "@eco-incorp/sauce/verify";
import { getAddress } from "viem";

const trustedEngine = getAddress(v12EngineAddress());
const trustedPot = "0x2222222222222222222222222222222222222222"; // your Pot
const payload = encodeSettleProgram(
  [Base.USDC.address],
  1_000_000n,
  "0x1111111111111111111111111111111111111111",
  SETTLE_PROGRAM,
);
const cook = v12Pot.encodeCook(trustedEngine, payload); // Pot.cook(engine, program) calldata

// One cook's calldata: the settle it carries, and the engine it names.
const call = decodeSettleCall(cook);
console.log(call?.minOut, call?.ingredientCount, call?.engine === trustedEngine.toLowerCase());

// A batch: which call carries the settle. The other calls, the target and the engine stay yours.
const found = extractEvmSettleFromCalls([
  { data: "0x", target: trustedPot },
  { data: cook, target: trustedPot },
]);
console.log(found?.callIndex, found?.target === trustedPot, found?.recipient);

console.log(decodeSettleCall("0xdeadbeef")); // not a cook of the settle program
```

```text theme={null}
1000000n 1 true
1 true 0x1111111111111111111111111111111111111111
null
```

### Signature

```ts theme={null}
export declare function decodeSettleCall(callData: Hex, accepted?: readonly (Hex | ProgramPin)[]): DecodedSettleCall | null;
export declare function extractEvmSettleFromCalls(calls: readonly CallLike[], accepted?: readonly (Hex | ProgramPin)[]): ExtractedEvmSettle | null;

export interface DecodedSettleCall extends DecodedSettleProgram {
    ingredientIndex: number;
    ingredientCount: number;
    engine?: `0x${string}`;
}
export interface CallLike {
    data: Hex;
    target?: `0x${string}`;
}
export interface ExtractedEvmSettle extends DecodedSettleCall {
    callIndex: number;
    target?: `0x${string}`;
}
```

### Returns

* `decodeSettleCall`: the decoded settle with `engine`, the engine the cook names (lower-case), or `null` when the calldata is not a cook or its program is not the settle program. It never throws for those. It reads `Pot.cook` calldata only: a signed cook (`cookSigned`) returns `null`.
* `extractEvmSettleFromCalls`: the first call in the batch that carries the settle, with its `callIndex` and `target`, or `null`.

Compare `engine` with the engines you trust, without regard to case: the settle program on an engine someone else chose is that engine's computation. Compare `target` with your Pot: matching is by selector and program bytes only, so a contract that exposes the same `cook` selector and ignores the payload would match. `extractEvmSettleFromCalls` reports the first settle it finds and examines no other call, every one of which executes: check the call count and each call's position yourself. The decoder also reads the `SauceRouter.cook(bytes[])` form, where only the first ingredient is examined and `engine` is absent; check `ingredientCount` there.

## Pins and reproduction

`SETTLE_WIRE` holds the current pin. A settle program with another hash or length is accepted only when you pass its pin explicitly.

### Usage

```ts ver-reproduce.mts theme={null}
import { compile, resolveSdkModule } from "@eco-incorp/sauce/compiler";
import { settleSource } from "@eco-incorp/sauce/recipes";
import { SETTLE_PROGRAM, SETTLE_WIRE, type ProgramPin } from "@eco-incorp/sauce/verify";
import { bytesToHex, keccak256 } from "viem";

// Recompile the shipped source with the installed compiler and compare with the pinned program.
const settle = new TextEncoder().encode(settleSource());
const { bytecode } = compile({
  target: "evm",
  entry: "settle.sauce.ts",
  resolve: (path) => (path === "settle.sauce.ts" ? settle : resolveSdkModule(path)),
});
console.log(bytecode.length, bytesToHex(bytecode) === SETTLE_PROGRAM.toLowerCase());
console.log(keccak256(bytecode) === SETTLE_WIRE.PROGRAM_HASH);

// The pins validateSettleProgram accepts. An explicit list replaces the default, so keep the current pin.
export const accepted: ProgramPin[] = [
  { hash: SETTLE_WIRE.PROGRAM_HASH, bytes: SETTLE_WIRE.PROGRAM_BYTES },
  // { hash: "0x…", bytes: <length> }, another build you decided to accept
];
```

```text theme={null}
410 true
true
```

### Signature

```ts theme={null}
export declare const SETTLE_WIRE: {
    readonly PROGRAM_BYTES: 410;
    readonly PROGRAM_HASH: "0x…";
    readonly WORD_BYTES: 32;
    readonly ADDRESS_BYTES: 20;
    readonly HEAD_SLOTS: 3;
    readonly HEAD_BYTES: 96;
    // … and PROGRAM_COMPILER: the compiler package and version that produced the pinned bytes
};
export declare function settleSource(): string;
```

A compiler change can change the program's bytes without a change to its source, so the current pin moves with the compiler the SDK pins. `validateSettleProgram` accepts a program with another hash or length only when you pass its `{ hash, bytes }` pin explicitly, beside the current one. Pass the full record: a bare hash is read at the current length, so it cannot match a program of another length. Import the SDK's pins rather than copying their values. `sauce verify --pin <hash:bytes>` adds a pin on the command line. The verifier itself neither loads the source nor recompiles it.

## verifySvmSettleProgram

Recompiles the Solana settle program with the floor and split you expect and compares the bytes, and checks the manifest's account names.

### Import

```ts theme={null}
import { verifySvmSettleExecution, verifySvmSettleProgram } from "@eco-incorp/sauce/svm/verify";
```

### Usage

```ts ver-svm.mts theme={null}
import { createHash } from "node:crypto";
import { compile } from "@eco-incorp/sauce/compiler";
import { v12SvmKitchenProgramId } from "@eco-incorp/sauce/deployments";
import { address, derivePotPda, getAddressEncoder, svmSettleRefs, svmSettleSource } from "@eco-incorp/sauce/svm";
import { EXECUTE_DISCRIMINATOR, EXECUTE_FROM_ACCOUNT_DISCRIMINATOR } from "@eco-incorp/sauce/svm/engine";
import { verifySvmSettleExecution, verifySvmSettleProgram } from "@eco-incorp/sauce/svm/verify";
import { getAddressDecoder } from "@solana/kit";

// The SVM settle program for one escrow, as shipped: no floor (minOut 0), splCount 0.
const source = new TextEncoder().encode(svmSettleSource(1));
const program = compile({ target: "svm", resolve: (path) => (path === "main.js" ? source : undefined) });
if (program.manifest === undefined) throw new Error("expected an SVM account manifest");

// 1. The program: recompiled with the values you expect and byte-compared.
console.log(verifySvmSettleProgram(program.bytecode, program.manifest, { minOut: 0n, splCount: 0n }));
const wrongFloor = verifySvmSettleProgram(program.bytecode, program.manifest, { minOut: 1_000_000n, splCount: 0n });
console.log(wrongFloor.genuine, wrongFloor.mismatch);

// 2. An execution: the engine instruction the Kitchen sends, header and accounts as on the wire.
const owner = address("GHRJMiop6cEM9E9oqHqyf4JfC4dMsr5pvJfzqkLiJ88p");
const salt = new Uint8Array(32);
const { address: pot, bump } = await derivePotPda(address(v12SvmKitchenProgramId()), owner, salt);
const gate = [...EXECUTE_DISCRIMINATOR, ...getAddressEncoder().encode(owner), ...salt, bump];
// Stand-ins for the settle's accounts, in manifest order: token programs, escrow, mint, destination, owner.
const users = svmSettleRefs(1).map((ref, i) => ({ ref, address: getAddressDecoder().decode(new Uint8Array(32).fill(i + 1)) }));

const inline = verifySvmSettleExecution({
  instructionData: new Uint8Array([...gate, ...program.bytecode]),
  accounts: [...users, pot], // wireFormat defaults to "gated-v1"
  minOut: 0n,
  splCount: 0n,
});
console.log(inline.genuine, inline.verifiedBy, inline.instruction, inline.gate?.pot === pot, inline.escrowCount);

// 3. A staged execution carries no program bytes: the pin is the caller's evidence.
const staged = {
  instructionData: new Uint8Array([...EXECUTE_FROM_ACCOUNT_DISCRIMINATOR, ...gate.slice(8)]),
  accounts: [address("11111111111111111111111111111111"), ...users, pot], // a stand-in code account first
  minOut: 0n,
  splCount: 0n,
};
const pin = new Uint8Array(createHash("sha256").update(program.bytecode).digest());
console.log(verifySvmSettleExecution(staged).verifiedBy, verifySvmSettleExecution(staged).genuine);
console.log(verifySvmSettleExecution({ ...staged, pin }).verifiedBy, verifySvmSettleExecution({ ...staged, pin }).genuine);
```

```text theme={null}
{
  genuine: true,
  escrowCount: 1,
  refs: [
    'tokenProgram0',
    'tokenProgram1',
    'escrow0',
    'mint0',
    'dest0',
    'owner'
  ]
}
false bytecode does not byte-match svmSettleSource(1) at minOut=1000000, splCount=0
true inline-bytecode execute true 1
none false
caller-asserted-pin true
```

To check a program you received, pass its bytecode and manifest with the floor and split you expect; the example compiles one to have something to check.

### Signature

```ts theme={null}
export declare function verifySvmSettleProgram(bytecode: Uint8Array, manifest: SettleManifestLike, expected: SvmSettleExpectation): SvmSettleVerification;

export interface SvmSettleExpectation {
    minOut: bigint;
    splCount: bigint;
}
export interface SvmSettleVerification {
    genuine: boolean;
    escrowCount: number | null;
    refs: string[];
    mismatch?: string;
}
```

### Parameters

* `bytecode`, `manifest`: the program and its account manifest.
* `expected.minOut`: the floor on escrow 0; `0n` for none.
* `expected.splCount`: how many escrows sweep through the first token-program slot; the rest use the second.

### Returns

`genuine` is true when the bytecode equals the recompiled `svmSettleSource(N)` for those values and the manifest's slot names equal `svmSettleRefs(N)`. `escrowCount` is inferred from the `3N + 3` account shape. A non-genuine result carries `mismatch` and never throws. It says nothing about the addresses attached to a real instruction.

## verifySvmSettleExecution

Decodes a raw engine instruction, resolves its accounts against the settle references by position, and checks the program it runs.

### Signature

```ts theme={null}
export declare function verifySvmSettleExecution(input: SvmSettleExecutionVerifyInput): SvmSettleExecutionVerification;
export declare function decodeSvmSettleExecution(input: SvmSettleExecutionInput): DecodedSvmSettleExecution;

export type SvmExecutionWireFormat = "gated-v1" | "legacy-ungated";
export interface SvmSettleExecutionInput {
    instructionData: Uint8Array;
    accounts: readonly SvmSettleAccountLike[];
    wireFormat?: SvmExecutionWireFormat;
}
export interface SvmSettleExecutionVerifyInput extends SvmSettleExecutionInput, SvmSettleExpectation {
    bytecode?: Uint8Array;
    pin?: Uint8Array;
}
export type SvmSettleVerifiedBy = "inline-bytecode" | "bytecode" | "caller-asserted-pin" | "none";
export interface SvmSettleExecutionVerification extends DecodedSvmSettleExecution {
    genuine: boolean;
    verifiedBy: SvmSettleVerifiedBy;
    pinMatchesCanonical: boolean | null;
    pinMatchesBytecode: boolean | null;
    bytecodeMatchesCanonical: boolean | null;
    mismatch?: string;
}
```

The [`verifySvmSettleProgram` example](#verifysvmsettleprogram) calls it on an inline and a staged instruction.

### Parameters

* `instructionData`: the raw engine instruction's data. A Kitchen `cook` or `cook_from_account` instruction has another layout and must not be passed here.
* `accounts`: the instruction's full account list, as addresses or `{ address, ref }`.
* `wireFormat`: `"gated-v1"` by default, the released engine's instructions as the Kitchen sends them.
* `minOut`, `splCount`: the values you expect, as for `verifySvmSettleProgram`.
* `bytecode`: for a staged execution, the staged bytes you read yourself from the code account the instruction names.
* `pin`: for a staged execution, a SHA-256 of the staged bytes from your own evidence, normally the matching Kitchen `cook_from_account` instruction.

### Wire formats

With `"gated-v1"`, the decoder reads:

| Instruction | Data | Accounts |
| - | - | - |
| `execute` | discriminator (8 bytes), owner (32), salt (32), bump (1), bytecode | the settle accounts, then the Pot |
| `execute_from_account` | discriminator (8 bytes), owner (32), salt (32), bump (1) | the code account, the settle accounts, then the Pot |

It removes the code account and the trailing Pot, resolves the `3N + 3` settle slots by position, and returns the parsed `gate` (owner, salt, bump, Pot) and the `codeAccount`. It refuses a truncated header, extra staged data, an invalid address and an account count that fits no settle. Labels in `accounts` are diagnostic; positions decide identities.

`"legacy-ungated"` reads the historical 8-byte header, for inspecting old instructions only; it does not make them executable on the released engine. The two are not interchangeable: given released data, `"legacy-ungated"` reads the header as program bytes and the result is not genuine, and given legacy data, the default `"gated-v1"` throws on the account count.

### Returns

Check `genuine` and `verifiedBy` together:

| `verifiedBy` | What was compared |
| - | - |
| `inline-bytecode` | The program inside an `execute` instruction equals the canonical program; a supplied `bytecode` must match it too. |
| `bytecode` | The supplied staged bytes equal the canonical program, and the supplied pin hashes those bytes. |
| `caller-asserted-pin` | The supplied pin equals the hash of the canonical program; no staged bytes were supplied. |
| `none` | No basis to authenticate the staged program: `genuine` is false. |

`execute_from_account` carries no content pin. A `bytecode` or `caller-asserted-pin` result is as good as your evidence connecting that pin to the code account the instruction runs; the helper fetches nothing. Canonical bytes supplied without a pin cannot authenticate a staged execution.

### Throws

`decodeSvmSettleExecution` and `verifySvmSettleExecution` throw for data or an account list they cannot decode in the chosen wire format.

## What a genuine result proves

A genuine result proves the program's identity: the bytes that run are the settle program the package ships, with the parameters reported, under the pin evidence you supplied for a staged program. It does not prove:

* that the engine or the call's target is one you trust: on EVM compare `engine` and `target`; on Solana check the engine program id against `v12SvmEngineProgramId()` ([Deployments](https://docs.eco.com/sauce-sdk/reference/deployments.md));
* that the gate is valid: the parsed owner, salt, bump and Pot are not checked as a Pot derivation or a signature;
* the roles, owners, mints or destinations of the attached accounts;
* that the Kitchen's own instruction around the engine call is sound, or that any other instruction or call in the transaction is;
* that the transaction succeeded, or what it moved. For a transaction that has already run, read its result.

## Next steps

* [`sauce verify`](https://docs.eco.com/sauce-sdk/cli/verify): the same checks from the command line.
* [Cook](https://docs.eco.com/sauce/cook.md): what `Pot.cook(engine, program)` runs.
* [Security considerations](https://docs.eco.com/sauce/security-considerations.md): what Sauce checks and what it leaves to you.
* [Deployments](https://docs.eco.com/sauce-sdk/reference/deployments.md): the engines and Kitchens the SDK pins.


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