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

# verify: Decoding settle programs

> Decoding settle programs: 11 exports of the verify module of @eco-incorp/sauce, including decodeSettleProgram, validateSettleProgram, encodeSettleProgram, parseSettleProgram, bestEffortDecode, SettleDecodeError.

`@eco-incorp/sauce/verify` validates an EVM settlement payload against the pinned settle program (`SETTLE_WIRE`: compiler 2.3.0, 356 bytes) and decodes its runtime argument tail. It depends on viem only, so it runs in browsers and edge runtimes without loading the compiler.

This page covers `sdk/dist/verify/decode.d.ts`.

<Note>
  Generated from the type declarations shipped in `@eco-incorp/sauce` 0.99.4. Each entry shows the package authors' JSDoc and declaration for this SDK version. For runtime compatibility and deployed addresses, see [architecture and deployments](/programmable-transactions/sauce/architecture-and-deployments).
</Note>

### `decodeSettleProgram`

*Function* · `sdk/dist/verify/decode.d.ts`

Decode a settle payload back into `(tokens, minOut, recipient)`. STRICT: rejects a non-canonical
tokens pointer, a tail longer or shorter than the token count implies, an address word with
dirty upper bytes, and a zero recipient - see §4 for why. Throws `SettleDecodeError` (carrying a
stable `.code`) on any of the failures in `SettleFailureCode`.

This is a STRUCTURAL decode: it proves the payload is `356 bytes || a canonical argument tail`.
It does NOT prove those 356 bytes are the settle program you audited - any 356-byte prefix
passes. Use `validateSettleProgram` when that matters, which is nearly always.

```typescript theme={null}
export declare function decodeSettleProgram(payload: Hex, programBytes?: number): DecodedSettleProgram;
```

### `validateSettleProgram`

*Function* · `sdk/dist/verify/decode.d.ts`

`decodeSettleProgram` plus the authenticity pin: the payload's 356 program bytes must hash to
`SETTLE_WIRE.PROGRAM_HASH`. Because the arguments no longer live inside the program, a match
means the payload runs the audited `recipes/settle.sauce.ts` and nothing else - the whole
question, not the structural half.

Throws `SettleDecodeError` with code `PROGRAM_HASH` when the program is a different 356 bytes,
and whatever `decodeSettleProgram` would throw otherwise.

`accepted` overrides the pin. The dependency is a caret range and `SETTLE_WIRE` documents that a
codegen change is answered by RE-PINNING - at which point every intent already on chain carries the
OLD program, and a single hard-coded pin makes all of them unverifiable. Pass the pins you accept
(the current one plus any historical ones) to keep those payloads readable across a re-pin.

A PIN IS A HASH *AND* A LENGTH, which is why a bare hash is not enough. The payload is split into
program and argument tail at a byte offset, before any hash is computed - so if a re-pin changes the
program's length (the likely outcome of a codegen change), splitting a historical payload at the NEW
length yields a wrong program and a truncated tail, and it dies as `ARGS_TRUNCATED` without any hash
ever being compared. Each entry may be `{ hash, bytes }`; a bare `Hex` means the CURRENT
`SETTLE_WIRE.PROGRAM_BYTES` and is only safe for a same-length re-pin. Candidates are tried in order
and the first whose program hash matches wins. Defaults to the current pin.

```typescript theme={null}
export declare function validateSettleProgram(payload: Hex, accepted?: readonly (Hex | ProgramPin)[]): DecodedSettleProgram;
```

### `encodeSettleProgram`

*Function* · `sdk/dist/verify/decode.d.ts`

§5 of the wire spec - the cheaper, EQUIVALENT alternative to scan-and-check: when the caller
already knows the intended `(tokens, minOut, recipient)` (the normal case - they asked for the
swap), encode them directly and `memcmp`/hash-compare the whole payload rather than decoding.
Given §4 this produces the UNIQUE canonical encoding, so `encodeSettleProgram(...)` equals
`payload` exactly when `validateSettleProgram(payload)` succeeds with those values - in one
comparison, with no parser exposed to a hostile input at all. This is the recommended
non-TypeScript (Solidity/Go/Python) implementation, where a payload is bytes and the comparison is
a plain `memcmp`.

THE EQUIVALENCE IS OVER BYTES, NOT OVER HEX SPELLINGS. `validateSettleProgram` accepts any spelling
of the same bytes - uppercase, a `0X` prefix, no prefix at all - while this returns canonical
lowercase `0x`. So `validateSettleProgram(p.toUpperCase())` succeeds where
`encodeSettleProgram(...) === p.toUpperCase()` is `false`. A string `===` is only equivalent once
both sides are in canonical spelling; normalize first, or compare decoded bytes.

`programHex` is supplied by the caller - pass the program you compiled yourself. There is
deliberately no default: the bytes you compare against should be ones you derived, not ones
shipped alongside the claim they support. `SETTLE_WIRE.PROGRAM_HASH` is what to check them
against once you have them.

```typescript theme={null}
export declare function encodeSettleProgram(tokens: readonly (bigint | `0x${string}`)[], minOut: bigint, recipient: bigint | `0x${string}`, programHex: Hex): Hex;
```

### `parseSettleProgram`

*Function* · `sdk/dist/verify/decode.d.ts`

Best-effort single left-to-right pass - never throws. This is the shared engine both
`decodeSettleProgram` (throws on `parse.fatal`) and `bestEffortDecode` (keeps whatever DID
parse even when a later stage failed) run on top of.

```typescript theme={null}
export declare function parseSettleProgram(payload: Hex, programBytes?: number): SettleParse;
```

### `bestEffortDecode`

*Function* · `sdk/dist/verify/decode.d.ts`

Decode whenever the shape is well-formed enough to name a full `(tokens, minOut, recipient)` - even when a check that does not block reading (a trailing-slack tail, a dirty address word, a
zero recipient) would make `decodeSettleProgram` throw - so a rejected payload's decoded intent is
still visible to a caller debugging the rejection.

DECLINES wherever the report would not match execution - the whole point being that on a hostile
payload, reporting nothing beats reporting a wrong answer confidently. Two cases:

* a non-canonical tokens pointer: the engine follows it, this reads at the canonical offset, so the
  two name different TOKENS.
* a dirty RECIPIENT word: `recipient` is masked here, but the engine does not mask that word (§4),
  so execution addresses a different 256-bit key than the masked answer would suggest.

A dirty TOKEN word is NOT declined: the engine masks a call target to 160 bits, so there the masked
answer is exactly what executes, and reporting it is what shows the payload's true meaning.

```typescript theme={null}
export declare function bestEffortDecode(parse: SettleParse): DecodedSettleProgram | null;
```

### `SettleDecodeError`

*Class* · `sdk/dist/verify/decode.d.ts`

```typescript theme={null}
export declare class SettleDecodeError extends Error {
    readonly code: SettleFailureCode;
    constructor(code: SettleFailureCode, message: string);
}
```

### `DecodedSettleProgram`

*Interface* · `sdk/dist/verify/decode.d.ts`

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

### `ProgramPin`

*Interface* · `sdk/dist/verify/decode.d.ts`

An accepted program: its `keccak256` AND the byte length the payload splits at. Both are needed - the split happens before any hash is computed, so a historical pin whose program was a different
length cannot be matched by hash alone.

```typescript theme={null}
export interface ProgramPin {
    hash: Hex;
    bytes: number;
}
```

### `SettleFailureCode`

*Type* · `sdk/dist/verify/decode.d.ts`

Stable failure codes - safe to `switch` on. Every one is a REAL rejection this decoder makes.

BREAKING vs the v1 grammar: eight codes are GONE, because the defects they named cannot occur on a
fixed-width wire. `NON_MINIMAL_PUSH`, `TRUNCATED_PUSH`, `TRUNCATED_MINOUT`, `TRUNCATED_RECIPIENT`
and `OVERSIZE_ADDRESS` all described a variable-length PUSH prologue that no longer exists;
`ARITY_MISMATCH` and `NOT_SETTLE_SHAPED` described shape inference the whole-program hash now
settles outright; `BODY_LENGTH` described a body slice that is no longer a separate region. Their
uniqueness role is taken by §4's canonicality rules and `PROGRAM_HASH`. Only `EMPTY` and
`ZERO_RECIPIENT` carry over. A consumer with an exhaustive `switch` on the old union will compile
and silently stop matching, which is why this ships as a breaking change.

```typescript theme={null}
export type SettleFailureCode = "EMPTY" | "PROGRAM_TRUNCATED" | "ARGS_TRUNCATED" | "ARGS_OVERLONG" | "TOKENS_OFFSET" | "TOKENS_EMPTY" | "DIRTY_ADDRESS_WORD" | "ZERO_RECIPIENT" | "PROGRAM_HASH";
```

### `SettleParse`

*Interface* · `sdk/dist/verify/decode.d.ts`

Internal parse result - used by both the throwing `decodeSettleProgram` and the non-throwing
`bestEffortDecode`, which keeps partial state past a failure.

```typescript theme={null}
export interface SettleParse {
    bytes: Uint8Array;
    program: Uint8Array | null;
    programHash: Hex | null;
    args: Uint8Array | null;
    tokensOffset: bigint | null;
    minOut: bigint | null;
    recipient: bigint | null;
    recipientWord: bigint | null;
    recipientNonZero: boolean;
    tokenCount: bigint | null;
    tokenWords: bigint[];
    fatal: {
        code: SettleFailureCode;
        message: string;
    } | null;
}
```

### `Address20`

*Type* · `sdk/dist/verify/decode.d.ts`

A 20-byte address rendered as lowercase 0x-hex - deliberately NOT `viem`'s `Address` branded
type: callers that want an EIP-55 checksum should `getAddress()` it themselves.

```typescript theme={null}
export type Address20 = `0x${string}`;
```
