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

@soyaradex/sdk

v0.3.0

Published

Agentic DEX SDK for GenLayer: natural-language intent parsing, best-route aggregation, live pool analysis, and consensus-gated settlement (per-order verdicts or consensus-issued mandates) whose authorisation you can verify on-chain.

Readme

@soyaradex/sdk

Build agents that trade on Soyara, an AI-native DEX on GenLayer Bradbury where every trade settles through AgentExecutor under an authority only GenLayer consensus can write.

Install

npm install @soyaradex/sdk viem

viem is a peer dependency, so install it alongside. Node 18 or newer.

Upgrading from 0.2.x: 0.2.0 targeted the retired 2026-09-07 contract pair (AgentExecutor 0x0F1E9857..., AgentValidator 0xf47492A9...) and described an attestor rail the executor no longer accepts. 0.3.0 targets the pair in service (AgentExecutor 0x1BCBad3d..., AgentValidator 0xd1D809A1...), adds the mandate rail, and settleSwap now takes the result of validate(). INTELLIGENT_CONTRACTS.liquidityValidator is gone: that contract is retired and authorised nothing.

Do you need a server?

Partly, and it is worth being precise, because it decides how you build.

| What | Needs a key? | Needs a server? | |---|---|---| | parseIntent: natural language to structured intent | no | no | | quoteBestRouteMultiHop: live best-route pricing | no | no | | analyseMarket: pool depth and price integrity | no | no | | buildProgram / buildMultiHopProgram: settlement calldata | no | no | | readSettlementPlan: which rail settles, and how fast | no | no | | verifyBindings: prove the verdict binds your order | no | no | | readMandate / mandateCoversOrder: what a mandate covers | no | no | | validate: a GenLayer consensus round | yes, a funded GenLayer account | yes | | requestMandate: a consensus round that issues a mandate | yes, a funded GenLayer account | yes | | settleSwap / addLiquidity / removeLiquidity | yes, an authorised agent | yes |

Everything an agent needs to think (understand a request, find the best route, price it, judge whether the price is sound, build the calldata, and verify that the authorisation really covers what it is about to do) runs anywhere with a public RPC and no key at all. Only the steps that sign transactions need a backend, because those keys must never reach client-side JavaScript. Point baseUrl at your own deployment of the Soyara API routes.

How a trade is authorised

Not by an agent key. This is what an agent can independently check rather than trust.

Every swap settles through AgentExecutor, under exactly one of two authorities, and the GenLayer AgentValidator Intelligent Contract is the only address that can write either (recordVerdict and recordMandate are onlyValidator, reached over the validator's ghost contract; the owner's key cannot write one either):

| Rail | Authority | Settled by | When | |---|---|---|---| | consensus | a single-use verdict for this order's commitment | executeSwap | when the round finalizes (the appeal window) | | mandate | a mandate an earlier round issued for this user, pair and direction | executeSwapUnderMandate | one transaction, seconds |

The server chooses the rail when you call validate, before any round is opened, so one trade never has both. There is no direct rail: an order no verdict or mandate covers does not settle.

The commitment. AgentExecutor derives it from the whole order:

user · tokenIn · tokenOut · amountIn · minAmountOut · quotedAmountOut
slippageBps · deadline · router · feeBps · feeCollector · routeHash · nonce

Settlement re-derives the commitment from the order it is handed and consumes the matching verdict. Change any field and you get a commitment no verdict backs, so the transaction reverts with NoConsensusVerdict. Verdicts are single use (CommitmentAlreadyUsed).

The mandate. Consensus sets a per-trade ceiling, a total budget, a slippage and fee ceiling, the fee collector, the router, an expiry, and the one route it covers: the single-hop V2 pool the validators build themselves. The executor checks every trade against all of it and prices the trade from that pool's live reserves, so the relayer chooses only the size, inside those ceilings. A trade whose best route is somewhere else (V3, multi-hop, native GEN) settles on its own verdict instead, because Soyara always takes the best route.

verifyBindings asks the deployed contract to prove the consensus-rail binding per trade:

import { verifyBindings } from '@soyaradex/sdk';

const result = await verifyBindings({
  order,        // verdict.order from validate()
  program,      // verdict.program: the aggregator route bytes
  commitment,   // verdict.commitment: what consensus approved
  user,         // who should receive the output
});

if (!result.bound) throw new Error('This verdict does not cover this order');
for (const c of result.checks) console.log(c.passed ? 'ok' : 'FAIL', c.name, c.detail);

The package's test suite proves the property against the live contract: mutate any one of the eleven mutable fields and the commitment changes.

Quick start

import { understand, SoyaraClient, verifyBindings, readSettlementPlan } from '@soyaradex/sdk';

// 1. Understand, price and judge the market. No key needed.
const { intent, quote, analysis, redirect } = await understand('swap 50 USDC to USDT');

if (redirect) return reply(redirect.message);        // liquidity goes to the pools app
if (!intent.confident) {
  // Never guess: a wrong guess here spends real funds.
  return ask(`I need: ${intent.needs.join(', ')}`);
}
if (!analysis.safeToTrade) {
  // The quote can be arithmetically perfect and still come off a mispriced pool.
  for (const c of analysis.concerns) console.warn(c.severity, c.message);
}

// 2. Validate. The server picks the rail before opening anything.
const soyara = new SoyaraClient({ baseUrl: 'https://your-deployment.example' });
const verdict = await soyara.validate({
  action: 'SWAP', user, tokenIn: 'USDC', tokenOut: 'USDT', amountIn: '50', slippageBps: 50,
  mandateIds,   // optional: ids from requestMandate(); a covering one skips the round
}, {
  onProgress: ({ attempt, phase }) => console.log(`round in flight (${phase}) #${attempt}`),
});
if (!verdict.approved) return reply(verdict.reason);  // verdict.retryable: run it again

// 3. Check what you are about to rely on, and how long it takes.
const plan = await readSettlementPlan({
  commitment: verdict.commitment, mandateId: verdict.mandateId,
  order: verdict.order, program: verdict.program,
});
console.log(`settles via ${plan.rail} (~${plan.etaSeconds}s): ${plan.rationale}`);

if (verdict.rail === 'consensus') {
  const bindings = await verifyBindings({
    order: verdict.order, program: verdict.program, commitment: verdict.commitment, user,
  });
  if (!bindings.bound) throw new Error('verdict does not bind this order');
}

// 4. Settle on that rail, and only that one.
try {
  const receipt = await soyara.settleSwap(verdict);
  console.log('settled:', receipt.explorerUrl);
} catch (err) {
  if (err.pending) scheduleRetry();                    // verdict still in its appeal window
  else if (err.mandateUnavailable) revalidateWithout(verdict.mandateId);
  else throw err;
}

Settling in seconds: mandates

A consensus-rail trade waits for its round to finalize, which on Bradbury is tens of minutes. An agent that trades one pair repeatedly asks consensus once, for a mandate, and settles each later trade in one transaction:

const req = await soyara.requestMandate({
  user, tokenIn: USDC, tokenOut: USDT,          // ERC-20 addresses, one direction
  maxAmountIn: 2n * 10n ** 18n,                 // per trade, raw units
  totalBudgetIn: 20n * 10n ** 18n,              // across all trades
  maxSlippageBps: 100,
});

// The mandate reaches the executor when its round finalizes. Each status check
// also nudges that round toward finalization.
let s;
do { await sleep(60_000); s = await soyara.mandateStatus(req); } while (!s.live);

// From now on, pass it with each trade. A covered trade opens no round.
const v = await soyara.validate({ ...proposal, mandateIds: [req.mandateId] });
v.rail;        // 'mandate'
await soyara.settleSwap(v);

readMandate(id) reads a mandate straight from the executor, and mandateCoversOrder applies the executor's own rules to an order, so an agent can see why a trade did or did not qualify.

Liquidity

The aggregator routes and settles swaps. Adding or removing liquidity is handled at https://app.soyara.xyz/pools.

understand() recognises a liquidity request and returns a redirect instead of a quote, so a deposit can never be priced, or settled, as a trade:

const { intent, quote, redirect } = await understand('add liquidity 10 USDC and USDT');
if (redirect) return reply(redirect.message);   // quote is null here

SoyaraClient.addLiquidity / removeLiquidity settle V2 deposits and withdrawals under the same gate: the server opens a validate_liquidity_v2_add or validate_liquidity_v2_remove round for the exact operation, and executeAddLiquidityV2 / executeRemoveLiquidityV2 consume its verdict. There is no V3 liquidity path: the AgentValidator has no V3 liquidity validator, so no verdict for a V3 mint or burn can exist, and the executor's V3 entry points revert. Use normaliseAction() if you route by action name: an unrecognised value resolves to 'UNKNOWN' and goes nowhere, never to 'SWAP'.

The parser asks rather than guesses

An under-specified request returns confident: false and a needs list. This is deliberate: an earlier version defaulted the missing side of a trade, and "add 10 usdt and usdc" was read as a swap that really did sell the user's USDT.

parseIntent('swap 50 USDC to USDT')      // → SWAP, confident
parseIntent('add 10 usd and usdt liquidity') // → ADD_LIQUIDITY (usd → USDC)
parseIntent('remove 50% liquidity from usdc usdt pool') // → REMOVE_LIQUIDITY, percent 50
parseIntent('wrap 5 gen')                // → WRAP
parseIntent('swap 34 udc to usdt')       // → needs: which token to swap from
parseIntent('add 10 usdc to usdt')       // → needs: SWAP or ADD LIQUIDITY?

Swaps always take the best route

quoteBestRouteMultiHop prices direct and two-hop paths and returns whichever fills best. Venue is an outcome, never an input: pinning V2 or V3 can only match or worsen the fill.

// WBTC/USDT has no direct pool; routing through WGEN makes it tradeable.
const q = await quoteBestRouteMultiHop(WBTC, USDT, amountInWei, 'best');
q.isMultiHop  // true
q.hops        // [{pool, poolType, tokenIn, tokenOut}, ...]: feed to buildMultiHopProgram

Pass q.hops to buildMultiHopProgram so settlement executes exactly the path that was quoted and validated, rather than re-deriving a possibly different one.

What "consensus-gated" actually means

  1. validate submits a write to the AgentValidator Intelligent Contract (validate_swap). Validators are selected by VRF, each independently re-executes the proposal against live pools, then they commit and reveal. This takes tens of seconds; that is the design, not a bug.
  2. The verdict is recorded in contract state (read back with get_validation) and, when the round finalizes, the Intelligent Contract delivers it to AgentExecutor itself. recordVerdict is onlyValidator: no key an operator holds can write one, the owner's included.
  3. Settlement re-derives the commitment from the exact order being settled (route, fee, fee collector, recipient, quote, deadline, nonce) and consumes the matching verdict. Change any field and it reverts with NoConsensusVerdict; a second attempt reverts with CommitmentAlreadyUsed.
  4. Repeated trades in one direction can instead settle under a mandate an earlier round issued (issue_trading_mandate). The executor checks each trade against it (size, budget, fee, route) and prices it from the pool itself.

A slow or undecided round is a network condition, not a rejection. validate reports { approved: false, retryable: true } and never throws for it.

Keeping the SDK in step with the app

parseIntent, dexQuote, programBuilder and mandateCoverage are the app's own implementations, copied rather than rewritten. npm run sync copies them from the app; npm run sync:check fails when they differ.

Notes

  • Every deployed test token uses 18 decimals, including USDC/USDT/WBTC. They are testnet mocks, so the usual 6/8-decimal assumptions do not hold.
  • Pools are shallow. Check quote.priceImpactPct before executing size.
  • GEN is native; pools hold WGEN. understand() wraps automatically.

Licence

MIT