@eco-incorp/sauce/actions validates a document and compiles it, for one chain, into one Sauce program that runs in a Pot in one transaction. Any language that writes JSON can produce one, and the package publishes the JSON Schema it is checked against.
The same documents run from the command line with sauce actions, and sauce compile, simulate, tx and run take one wherever they take a program file.
The document
swap-supply.json
A
$schema key is allowed and ignored. Every action runs in one transaction on the chain the document is compiled for; a bridge’s destination is one of the bridge action’s own fields.
Values
Each amount must also fit the field it becomes; a value too wide for it is an error.
Names and addresses
A name lowers to the program global of that name, and the compile for a chain resolves it there, as it resolvesUSDC in a hand-written program. A document with only names and no chainId therefore compiles for every chain that has those names, each time to that chain’s addresses. An address means the same address on every chain, which suits a contract deployed at one address everywhere, such as Permit2. A document may mix names and addresses.
A swap may name its pool by its factory’s key instead of its address: uniswapV2Swap by factory, uniswapV3Swap by factory with fee (or tickSpacing for Aerodrome Slipstream), traderJoeLBSwap by factory with binStep. The program reads the pool from the factory when it runs and reverts before anything moves if there is none.
Chaining outputs
An action that spends an amount takes it from, in order:- its own
amount; amountRef, the name of an output an earlier action saved withsaveOutputAs;- otherwise, the previous action’s output.
amount and amountRef, naming an output no earlier action saved, or a first action with no amount is an error. Saving a second output under one name reassigns it. An action’s output is what it delivered to the Pot (received), the amount it spent (its amount), or, for balanceOf, nativeBalance and argument, the value read. When a later action spends an output, the action delivers it to the Pot whatever its recipient says; an output nothing spends goes to the action’s recipient.
An argument action declares a run-time parameter: it becomes a Uint256 parameter of main, so one compiled program serves every amount, and the value travels as an ABI-v1 word after the bytecode.
parseProgram
Validates a document and returns a canonical copy.Import
Usage
parse-errors.mts
Signature
Parameters
input: the document as JSON text, or an already-parsed value.
Returns
ActionProgram: a fresh copy with its keys in canonical order. It fills in nothing: an optional field left out stays out. Parsing checks how each name is spelled; whether a name exists is decided by the chain the document is compiled for.
Throws
ProgramError, listing every problem at once, each with its path.
serializeProgram
Writes a document as canonical JSON.Import
Usage
serialize.mts
Signature
Parameters
program: the document.space: indentation, asJSON.stringifytakes it. Default none.
Returns
The canonical JSON text: two equal programs serialize to the same text, andparseProgram(serializeProgram(p)) equals p.
Throws
ProgramError when program is invalid; it is validated first.
programSchema
Returns the JSON Schema (draft 2020-12) every document satisfies.Import
@eco-incorp/sauce/actions/action-program.schema.json.
Usage
schema.mts
Signature
Returns
The schema object. It is generated from the table the parser reads, so a document the schema rejects is always rejected byparseProgram. It marks which fields take a token ($defs/token, $defs/tokenOrNative), a protocol contract ($defs/contract) or either ($defs/tokenContract), and states each swap’s pool alternatives as a oneOf.
The schema cannot express four rules that the parser and the compile enforce: EIP-55 checksums, exact numeric ranges (it bounds digit counts), the chaining rules, and whether a name exists on the chain being compiled for. Validate with the schema at the edge of your system, and parse before compiling.
compileProgram
Compiles a document for one chain into a program and its cook payload.Import
Usage
compile-actions.mts
@sauce/router, the same library a hand-written program calls. The lowered program names the Pot pot and its native balance pot.balance, as a hand-written one does; a split swap compiled with no chain writes ctx.self(). Every other action is an ABI-bound call, IBinding.at(address).method(...), with the registry’s ABI wherever the registry declares the exact function. Reading any part of a bound call’s result decodes all its declared outputs; a result the program discards is neither decoded nor checked. ERC-20 transfer and approve are checked writes with OpenZeppelin SafeERC20 semantics. A call that carries native value (wrapETH, the staking deposits, the bridge fees) is a raw evm.call.
Signature
Parameters
program: the document. It is validated again, so a typed document that skippedparseProgramis held to the same rules.options.chain: the chain to compile for, a slug ("base"), a chain id (8453) or aCanonicalChain. Defaults to the document’schainId; a document without one needs it.
Returns
CompiledProgram:
bytecode: the compiled program.source: the SauceScript the document lowered to, with every name as written.contracts: the ABI bindingssourcecalls through.parameters: theargumentnames, in the ordermaintakes them.entryEncoding: always"abi-v1", the only argument encoding.payload(values?): the bytecode followed by the encoded arguments, by name: theprograma Pot’scook(engine, program)takes (Cook transactions).argumentOffset(name): where argumentname’s 32-byte big-endian word starts in the payload. The offset is fixed whatever the values, so a number measured later can be spliced into a payload built earlier.
Throws
ProgramError when the document is invalid, when chainId names another chain, or when a name does not exist on the chain, listing every such name with its path.
The SDK refuses on Arc (chain 5042) what Arc cannot do: wrapETH, unwrapETH, lidoStake, rocketPoolStake, etherFiStake, balancerV3Swap with wethIsEth: true, and a nativeBalance spent as a token amount. Arc’s native coin is USDC, the same balance as its USDC ERC-20 (Arc).
ProgramError
The error every function on this page throws for a document that breaks a rule.Import
Usage
TheparseProgram example catches it twice.
Signature
Properties
issues: every problem found, in order, each located by a path into the document (actions[2].amountRef).chain: set when the issues are the compiled chain’s, such as a name it lacks, rather than the document’s own.message: a summary line, then one line per issue.
quoteAction
Builds theeth_call that prices a swap action at its own amount. It reads nothing; you send the call.
@sauce/router has no quote function, so this is how TypeScript prices a swap before it builds a program. A program can also read a quote itself before it swaps, as Quote, swap and supply does.
Import
Usage
quote.mts
Signature
Parameters
action: a swap action with itsamount(a Carbon trade or a Balancer batch prices at the amounts it carries). Its pool must be given by address.options.chain: the chain whose names the action’s tokens and contracts resolve to; required when the action uses a name.options.router: foruniswapV3SwapanduniswapV4Swap, the quote Router atv12QuoteRouterAddress()from@eco-incorp/sauce/deployments(Deployments). The content-addressed execution Router atv12RouterAddress(), which a Pot swaps through, has no quote method.options.maverickQuoter,options.ambientImpact,options.fluidDexResolver: the quoter formaverickV2Swap,ambientSwapandfluidDexT1Swap.
get_dy, a DODO pool’s querySellBase or querySellQuote, a Trader Joe pair’s getSwapOut (zero when the pair cannot take the whole input), WooPPV2’s query. Send a Balancer V3 quote from the zero address. No quote needs a balance or an allowance.
Returns
Quote: send data to to as an eth_call, and pass the result to decode for the output amount in tokenOut’s smallest unit.
Throws
For a pool given by key: read the pool from the factory first, then givepool. For an action that names a token or contract when options.chain is missing.
compileSplitSwap
Compiles one program that divides an amount into equal slices and gives each, in turn, to the candidate pool whose onchain quote pays the most for it, then executes every candidate’s share.Import
Usage
split-swap.mts
Signature
Parameters
candidates: ordinary swap actions with noamount,amountReforsaveOutputAs, each with its pool by address (auniswapV4Swapby itspoolManagerand key) and each delivering to the Pot. Each must have an onchain quote with a fixed amount offset: every venue but Balancer (V2, its batch swap and V3) and Carbon, which the split refuses (candidates[i] (balancerV2Swap) has no on-chain quote with a runtime amount, which a split needs). Uniswap V3 and V4 candidates quote throughquoters.router, the quote Router atv12QuoteRouterAddress().amount: the total input, a base-10 string divisible bybuckets.buckets: how many equal slices the allocation hands out; at least 1.amountOutMin: the least total output, checked once after every leg. Required;"0"waives it.approvals: approvals to run first, each with an explicitamount, for a candidate that pulls through a periphery contract. A@sauce/routerswap needs none.sweep: sends the total output oftokentorecipientat the end; omit it to keep the output in the Pot.recipientis an address or"pot".quoters: the quoter contracts the candidates price through, as inquoteAction.chain: the chain the names in the candidates, approvals and sweep resolve on; needed only when they use a name.
Returns
ACompiledProgram with no run-time arguments: its payload() is the bytecode.
Throws
ProgramError for an invalid candidate or a name the chain lacks; an error for a pool given by key.
Action reference
Every action type, generated from@eco-incorp/sauce/actions/action-program.schema.json. Every action that takes amount also takes amountRef, and every action takes saveOutputAs. Output is what a later action spends.
Each field’s documentation is its
description in the schema. A @sauce/router swap (the first nine rows) needs no approval: it pushes its input to the pool, approves exactly its input itself, or the Pot’s Router pays the pool. Any other venue that pulls from the Pot needs an approve first.
TypeScript types
Every type is the JSON it describes, with amounts as strings and no class instances.ActionProgramis the document,Actionthe union of every action, andActionTypeitstype.- Each action has its own type (
UniswapV3SwapAction,AaveV3SupplyAction, …), grouped asSwapAction(withRouterSwapAction, the nine@sauce/routerswaps),BridgeAction,WrapAction,StakeAction,LendingAction,TokenActionandSourceAction. - A field’s type says what it takes:
Token(aTokenSymbolor an address),TokenOrNative,Contract(aContractName,"Namespace.Contract", or an address),TokenContractandRecipient(`0x${string}` | "pot"). - A swap that names its pool by key is a union, so TypeScript refuses the mixes the parser refuses:
UniswapV3SwapActionis a pool, or a factory with a fee, or a factory with a tick spacing.
Next steps
sauce actions: print what a document lowers to from the command line.@sauce/router: the swap library the swap actions call.- Cook transactions: wrap a payload in
Pot.cook. - Actions: swap and supply: a worked document.
