npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eco-incorp/sauce-sdk

v0.12.0

Published

Sauce protocol tooling: SDK, action primitives, and a local dev environment.

Readme

@eco-incorp/sauce-sdk

Build, compile and verify Sauce programs — cross-chain intents whose programs execute as bytecode on the Eco Pot. One package, TypeScript, EVM and SVM.

npm install @eco-incorp/sauce-sdk

Node only. The compiler is loaded through createRequire (its node build is CommonJS reading a sibling .wasm off disk), and the route path imports node:fs/node:path, so the Quickstart, the builders and /compiler all require a Node runtime. The registry and decoding subpaths import no Node builtin and work anywhere: /verify, /chains, /deployments, /protocols/*, /evm/engine and /svm/engine. The compiler package does ship a browser build, but this package does not expose it.

The SauceScript compiler comes with it as a dependency. The only optional extra is @solana/web3.js, needed just for the /svm/engine/web3js interop subpath.

Quickstart

Open a route on a destination chain, and the SDK compiles the program and assembles the intent:

import { routes } from '@eco-incorp/sauce-sdk'

// Both deadlines are ABSOLUTE unix timestamps, not durations: the chain compares
// `block.timestamp > route.deadline`. And rewardDeadline must be >= deadline — a solver may fulfil
// right up to the route deadline, so an earlier reward deadline is already refundable by then.
// pot, portal, sourcePortal, creator and prover are addresses for YOUR deployment; the package
// ships no defaults for those. Token addresses it does ship — see the registry note below.
const now = BigInt(Math.floor(Date.now() / 1000))

const built = routes.openRoute('base', 'function main() { return 1n; }', {
  source: 'ethereum',                  // the SOURCE chain — where the reward is posted
  pot,                                 // the Pot on the destination chain, which runs the program
  portal,                              // the DESTINATION Portal — lands in route.portal
  sourcePortal,                        // the SOURCE Portal — where the intent is published
  creator, prover,
  deadline: now + 3600n,
  rewardDeadline: now + 7200n,
}).reward([{ token: usdc, amount: 1_000_000n }])

built.compiled.bytecode[0]   // the program
built.intent                 // the assembled Intent
built.publishCalldata()      // calldata for sourcePortal — omit sourcePortal above and this throws

Common token addresses are already there: routes.TOKEN_REGISTRY carries probe-verified USDC and WETH for ethereum, optimism, polygon, base and arbitrum, the wrapped native under whatever symbol it actually reports (polygon's is WPOL), and USDT on ethereum and optimism only. USDT is absent elsewhere because a row is registered under a ticker only when the deployed contract's own symbol() string-equals it: the Tether-migrated polygon and arbitrum deployments return USDT0/USD₮0 and fail that gate, and base's was never probed. So a missing row means "not provenance-verified under that ticker", NOT "not deployed" — tokenDefines overrides any key when you want one anyway. Ask routes.knownTokenSymbols(requireChain('polygon')) for what a chain actually carries. Look one up with routes.registryTokenAddress(requireChain('base'), 'USDC') (requireChain is a root export -- see Registries), which returns a bigint, or undefined when that chain does not carry the symbol -- not a 0x string, so format it yourself if you need one: '0x' + address.toString(16).padStart(40, '0') gives base USDC as 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913. openRoute also folds them in as ambient defines, so a route body can write USDC.transfer(to, amount) with no configuration at all.

routes.openRoute(destination, …) is what the chain globals forward to, so Base(sauce, options).reward(…) is the same call. Those globals are installed on globalThis as a side effect of importing the package — they are not exports, so import { Base } fails. Opt out by setting globalThis.__ECO_ROUTES_NO_GLOBALS__ = true before the first import — it is read at module evaluation, so setting it afterwards is too late and silently does nothing. After the fact, uninstallRouteGlobals() is the one that works. Or just use routes.openRoute and ignore them.

.reward() takes a token-amount array as above. A bare bigint is a NATIVE reward — .reward(1_000_000n) posts 1e6 wei of the source chain's gas token, not 1 USDC.

Route bodies are unannotated SauceScript. The SDK runs source rewrites over the body before compiling, and those parse plain JS — function main(): Uint256 { … } is rejected. Annotated SauceScript is fine when you call the compiler directly (below), just not through a route.

Building programs without writing SauceScript

The token, swap and deposit namespaces emit SauceScript for you. token returns the source and its base dirs together; swap and deposit return a bare string, with base dirs as separate constants:

import { routes, token } from '@eco-incorp/sauce-sdk'

// toSauceScript returns the baseDirs the program's own imports need, alongside the source
const { source, baseDirs } = token.Token
  .transfer({ token: usdc, to: recipient, amount: 1_000_000n })
  .toSauceScript('evm')

const built = routes
  .openRoute('base', source, { ...options, compile: { baseDirs: [...baseDirs] } })
  .reward(reward)

The baseDirs matter: the emitted program does import { IERC20 } from "./artifacts/IERC20.json", and a route supplies token base dirs automatically only when its own USDC.transfer(…) rewrite fires — which it does not for source a builder already emitted. Without them the compile fails on that import. Spread them: toSauceScript's array is readonly, the option is not.

.source(target) gives you the source alone if you are supplying baseDirs yourself.

swap and deposit have no toSauceScript. They return the source directly, so pair each with its own constant:

import { routes, swap, deposit } from '@eco-incorp/sauce-sdk'

routes.openRoute('base', swap.swapSource(spec),
  { ...options, compile: { baseDirs: [...swap.SWAP_BASE_DIRS] } }).reward(reward)

routes.openRoute('base', deposit.depositSource(spec),
  { ...options, compile: { baseDirs: [...deposit.DEPOSIT_BASE_DIRS] } }).reward(reward)

routes.compileSauceRoute is the lower level underneath, if you want the compiled call without an intent — it returns { calls, compiled, source } and has no .reward().

On the SVM target this route path does not work. toSauceScript('svm') emits main with typed account parameters — that parameter list is the account manifest, so it cannot be dropped — and a route runs an acorn-based accessor rewrite that rejects annotations. compile: { accessors: false } gets past the parse, but a route then still requires execution.buffer naming an already-staged program buffer, so unless you have staged one, compile the source directly instead.

The compiler

/compiler re-exports the published Rust/wasm SauceScript compiler:

import { compile } from '@eco-incorp/sauce-sdk/compiler'

const { bytecode } = compile({
  target: 'evm',                                  // or 'svm'
  entry: 'main.js',
  resolve: (path) => bytesFor(path),              // module BYTES (Uint8Array), or undefined
})

main may take parameters. They arrive at run time as 32-byte big-endian words appended after the program, on both targets, so one program serves many argument sets — compile-time substitution is defines instead.

That applies to a direct compile. A route slots compiled.bytecode[0] in as the whole cook ingredient and appends no argument tail, so a parameterized main in a route body reads past the end of its own ingredient. Use defines for routes, parameters for programs you assemble yourself.

A bytes parameter is not padded — it occupies its fixed compile-time length, so the tail is not uniformly 32-byte words once one is present.

Neither call returns an argument layout, so callers build the tail themselves. Mind which CompileResult you have — there are two, and they are not the same shape:

| | shape | from | |---|---|---| | the compiler's | { bytecode: Uint8Array; manifest? } | a direct compile | | the SDK's | { bytecode: Uint8Array[]; warnings: string[] } | built.compiled, compileSauceRoute |

The SDK's bytecode is a list of SEGMENTS, so compiled.bytecode[0] is the first segment, not the first byte. On SVM an Account-typed parameter is not a payload argument at all: it becomes the account manifest in the COMPILER's CompileResult.manifest — the route seam drops it (rs-compile.ts), so built.compiled.manifest is undefined with no error. An SVM caller that needs the manifest compiles through svm/ directly.

A main that falls off the end has no return type of its own, and a raw compile rejects it (`main` does not return on every path). Generated programs hit this whenever they fall through — which most do, though not all: a trailing Token.balanceOf emits a return, and annotating that one : void would break it. Either annotate it yourself (function main(): void { … }) or go through routes.compileSauceRoute, which annotates it for you. Note that one takes (destination, sauce, execution, options): the execution seam is required, and an SVM destination needs an already-staged buffer, so it is the route path rather than a bare compile helper.

Registries

129 protocols and 39 canonical chains ship with the package — 38 EVM plus Solana:

import { listProtocols, getProtocol, requireChain } from '@eco-incorp/sauce-sdk'

listProtocols().length          // 129
getProtocol('uniswap-v3')       // metadata: category, per-chain deployments, audit status
                                // (ABIs live on the subpath: '@eco-incorp/sauce-sdk/protocols/uniswap-v3')

requireChain and getChain read different registries: CANONICAL_CHAINS has all 39, while the chains export has 33 of the 38 EVM ones — unichain, sonic, ronin, plasma and ink are canonical but absent from it, so getChain(130) is undefined where requireChain('unichain') resolves.

requireChain('base').id         // 8453 — reads CANONICAL_CHAINS (all 39)

Subpaths

| Subpath | What's there | | --- | --- | | . | routes DSL, token/swap/deposit builders, chain + protocol registries | | /compiler | the SauceScript compiler | | /actions | routing-intent lowering (actionsToSauce), AMM/bridge action primitives | | /protocols/* | one protocol's ABIs and metadata, e.g. /protocols/uniswap-v3 | | /chains, /deployments | canonical chain list; deployed contract addresses | | /recipes | shipped programs (settle, CCTP split-transfer) and their sources | | /svm, /svm/engine, /svm/verify | SVM client, account resolution, engine harness, settle verification | | /svm/engine/web3js | the same engine against @solana/web3.js (optional peer) | | /recipes/settle.sauce.ts, /svm/recipes/settle.sauce.ts | the settle program sources themselves, for partners reproducing bytes | | /evm/engine | EVM engine helpers | | /verify | decodes legacy v1-grammar settle programs. viem-only, browser-safe | | /skills | AI skill files per protocol |

/verify is for programs already on chain: it reads the v1 prologue that the retired TypeScript compiler emitted. Programs built by this SDK today are not v1-grammar, so it will refuse them.

Verifying a program you built means recompiling it from the same source and comparing bytes. /svm/verify does exactly that for SVM settle programs; there is no EVM counterpart in the package today, so an EVM caller applies the technique themselves.

Development

pnpm install
pnpm build       # sdk + actions
pnpm typecheck
pnpm test

Publishing

Tag-driven, via .github/workflows/publish.yml:

git tag v1.2.3 && git push origin v1.2.3

The workflow stamps the version from the tag inside the runner (npm version --no-git-tag-version) and publishes to npmjs.com only. The version in package.json is therefore not the source of truth and will read older than the latest release — that is expected.