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

empx-swap-sdk

v2.2.0

Published

Multi-chain DEX swap SDK — TypeScript-native, AI-agent-first, with dual on-chain + off-chain affiliate models

Readme

empx-swap-sdk

TypeScript-native multi-chain DEX swap SDK for EVM chains. It finds swap paths, builds transaction calldata, returns USD price quotes, supports affiliate routing, and exposes schema-friendly helpers for AI agent workflows.

Install

npm install empx-swap-sdk

Package Entrypoints

const sdk = require("empx-swap-sdk");
const wallet = require("empx-swap-sdk/wallet");
const agent = require("empx-swap-sdk/agent");

ESM imports are also supported:

import { createRouter, CHAIN_IDS } from "empx-swap-sdk";
import { createBurnerWallet } from "empx-swap-sdk/wallet";

The package ships dual CommonJS and ESM output with TypeScript declarations.

Quick Start

const { createRouter, CHAIN_IDS, getProtocolFeeBps } = require("empx-swap-sdk");

const router = createRouter(CHAIN_IDS.ARBITRUM);

console.log(getProtocolFeeBps()); // "28"

const tradeInfo = await router.getTradeInfo(
  "1000000000000000000",
  "0xTokenIn",
  "0xTokenOut",
  3,
  200
);

const calldata = router.getSwapCalldata(tradeInfo, "0xRecipient");

// Give calldata to a wallet, backend signer, or transaction orchestration layer.
console.log(calldata);

Core Flow

  1. Create a router for one chain with createRouter(chainId, provider?, config?).
  2. Call getTradeInfo() to fetch a route, quote metadata, and slippage-adjusted output.
  3. For ERC-20 input tokens, call checkAllowance() and build approval calldata if needed.
  4. Build swap calldata with getSwapCalldata(), getSwapFromNativeCalldata(), getSwapToNativeCalldata(), prepareSwap(), or the legacy swap() alias.
  5. Submit the returned { to, data, value } through your own wallet or signer.

Quotes expire after 30 seconds. Check tradeInfo.validUntil > Date.now() before building calldata, and refetch if the quote is stale.

Supported Chains

| Chain | Chain ID | Native Token | | --- | ---: | --- | | PulseChain | 369 | PLS | | BSC | 56 | BNB | | Arbitrum | 42161 | ETH | | Base | 8453 | ETH | | Polygon | 137 | POL | | Avalanche | 43114 | AVAX | | Optimism | 10 | ETH | | Monad | 143 | MON | | Sonic | 146 | S | | Sei | 1329 | SEI | | Berachain | 80094 | BERA | | Rootstock | 30 | RBTC | | EthPOW | 10001 | ETHW | | HyperEVM | 999 | HYPE |

API Reference

createRouter(chainId, provider?, config?)

Returns a router instance bound to one chain.

const { createRouter, CHAIN_IDS } = require("empx-swap-sdk");

const defaultRouter = createRouter(CHAIN_IDS.ARBITRUM);
const rpcRouter = createRouter(CHAIN_IDS.BASE, "https://mainnet.base.org");
const providerRouter = createRouter(CHAIN_IDS.POLYGON, ethersProvider);
const signerRouter = createRouter(CHAIN_IDS.BSC, ethersSigner);

const fallbackRouter = createRouter(CHAIN_IDS.ARBITRUM, [
  "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY",
  "https://arb1.arbitrum.io/rpc",
  "https://arbitrum.llamarpc.com",
]);

provider may be an RPC URL, an RPC URL array, an ethers provider, or an ethers signer. RPC URL arrays use an ethers FallbackProvider with quorum: 1, ordered priority, and a short stall timeout.

createRouters(chainIds, config?)

Creates routers for an explicit chain list and returns a record keyed by chain ID.

const { createRouters, CHAIN_IDS } = require("empx-swap-sdk");

const routers = createRouters([
  CHAIN_IDS.ARBITRUM,
  CHAIN_IDS.BASE,
  CHAIN_IDS.BSC,
]);

const routersWithProviders = createRouters([
  CHAIN_IDS.ARBITRUM,
  CHAIN_IDS.BASE,
], {
  providers: {
    [CHAIN_IDS.ARBITRUM]: [
      "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY",
      "https://arb1.arbitrum.io/rpc",
    ],
    [CHAIN_IDS.BASE]: "https://mainnet.base.org",
  },
});

Use providers for batch usage because RPC URLs are usually chain-specific. Batch validation rejects empty chain lists, duplicate chain IDs, invalid chain IDs, provider overrides outside the requested chain list, and empty RPC fallback arrays.

getAllChainRouters(config?)

Convenience wrapper around createRouters(getSupportedChainIds(), config).

const { getAllChainRouters } = require("empx-swap-sdk");

const routers = getAllChainRouters();

Use getAllChainRouters() only when the workflow truly needs every supported chain. Prefer createRouters() for known subsets.

createAffiliateRouter(chainId, integratorId, provider?)

Creates a router that encodes the on-chain affiliate router ABI variant for every swap calldata call.

const { createAffiliateRouter, CHAIN_IDS } = require("empx-swap-sdk");

const router = createAffiliateRouter(
  CHAIN_IDS.BASE,
  "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
);

integratorId must be a bytes32 hex string. Affiliate support is currently available on PulseChain, Sonic, Base, and Monad.

To register as an integrator, contact EmpX with your protocol name and contact details.

  • Telegram: t.me/EmpXEmpseal
  • X: @EmpXio

Router Methods

Path Finding

const result = await router.findBestPath(
  "1000000000000000000",
  "0xTokenIn",
  "0xTokenOut",
  3
);

Use router.chain.NATIVE_ADDRESS for native input or output tokens.

Trade Info

const tradeInfo = await router.getTradeInfo(
  "1000000000000000000",
  "0xTokenIn",
  "0xTokenOut",
  3,
  200
);

tradeInfo includes amountIn, amountOut, fee, amounts, path, adapters, quoteId, validUntil, and sdkVersion.

Allowance and Approval

const allowance = await router.checkAllowance(
  "0xTokenIn",
  "0xOwner",
  tradeInfo.amountIn
);

if (!allowance.approved) {
  const approval = router.getApprovalCalldata("0xTokenIn", tradeInfo.amountIn);
  console.log(approval);
}

getApprovalCalldata(tokenAddress) builds unlimited approval calldata. getApprovalCalldata(tokenAddress, amount) builds exact-amount approval calldata.

Swap Calldata

const erc20ToErc20 = router.getSwapCalldata(tradeInfo, "0xRecipient");
const nativeToErc20 = router.getSwapFromNativeCalldata(tradeInfo, "0xRecipient");
const erc20ToNative = router.getSwapToNativeCalldata(tradeInfo, "0xRecipient");

prepareSwap() fetches trade info and selects the correct calldata builder. swap() is retained as the legacy alias for the same calldata-only behavior:

const { tradeInfo, calldata, swapType } = await router.prepareSwap(
  amountIn,
  tokenIn,
  tokenOut,
  "0xRecipient",
  3,
  200
);

swapType is one of WrapNative, UnwrapNative, ERC20ToERC20, NativeToERC20, or ERC20ToNative.

If the router was created with a signer, executeSwap() sends the transaction and waits for the receipt:

const result = await router.executeSwap(amountIn, tokenIn, tokenOut, recipient);
console.log(result.hash);

Wrap and Unwrap

const wrap = router.getWrapCalldata({ amountIn: "1000000000000000000" });
const unwrap = router.getUnwrapCalldata({ amountIn: "1000000000000000000" });

USD Quotes

const price = await router.getTokenPriceUSD(router.chain.WRAPPED_NATIVE);

const quote = await router.getQuoteUSD(
  "0xTokenAddress",
  "5000000000000000000"
);

const prices = await router.getMultipleTokenPricesUSD([
  "0xTokenA",
  "0xTokenB",
  router.chain.WRAPPED_NATIVE,
]);

Token Helpers

const decimals = await router.getTokenDecimals("0xTokenAddress");
const symbol = await router.getTokenSymbol("0xTokenAddress");

Affiliate Earnings

const earning = await router.estimateAffiliateEarning(
  "0xTokenIn",
  "1000000000000000000"
);

Chain Helpers

const {
  CHAIN_IDS,
  CHAINS,
  getChainConfig,
  getAllChains,
  getSupportedChainIds,
} = require("empx-swap-sdk");

const chain = getChainConfig(CHAIN_IDS.ARBITRUM);
const chains = getAllChains();
const chainIds = getSupportedChainIds();

Each router also exposes router.chain, including chainId, name, ROUTER_ADDRESS, NATIVE_ADDRESS, WRAPPED_NATIVE, USD_STABLE, STABLE_TOKENS, and TRUSTED_TOKENS.

AI Agent Integration

The SDK is designed for deterministic, schema-defined, side-effect-controlled agent workflows. Agents should build calldata and return it to a signer unless the application explicitly grants transaction authority.

Standard Agent Swap Workflow

  1. Call router.getTradeInfo(...).
  2. If the input token is an ERC-20, call router.checkAllowance(...).
  3. If approval is needed, call router.getApprovalCalldata(...) and return the approval calldata to the signer.
  4. Check tradeInfo.validUntil > Date.now().
  5. Call router.getSwapCalldata(...) or router.swap(...).
  6. Return calldata to the wallet, user, or signer.

Recommended agent response shape:

{
  "chainId": 42161,
  "swapType": "ERC20ToERC20",
  "tradeInfo": {
    "amountIn": "1000000000000000000",
    "amountOut": "960400000000000000",
    "fee": "28",
    "quoteId": "a3f2c1d4-...",
    "validUntil": 1712345678000,
    "sdkVersion": "2.2.0"
  },
  "calldata": {
    "to": "0x...",
    "data": "0x...",
    "value": "0"
  }
}

Tool Schemas

const {
  TOOL_SCHEMAS,
  getOpenAITools,
  getClaudeTools,
  getLangChainSchemas,
} = require("empx-swap-sdk");

const openAiTools = getOpenAITools();
const claudeTools = getClaudeTools();
const langChainSchemas = getLangChainSchemas();

console.log(TOOL_SCHEMAS.getTradeInfo);
console.log(TOOL_SCHEMAS.createRouters);
console.log(TOOL_SCHEMAS.getAllChainRouters);

Use createRouters() for agent workflows that inspect a known set of networks. Use getAllChainRouters() only for broad discovery or full network scans.

Wallet Helpers

const { createRouter, CHAIN_IDS } = require("empx-swap-sdk");
const {
  createBurnerWallet,
  fromPrivateKey,
  fromMnemonic,
  readOnly,
  describeWallet,
  getNativeBalance,
} = require("empx-swap-sdk/wallet");

const wallet = createBurnerWallet({ rpcUrl: "https://arb1.arbitrum.io/rpc" });
const router = createRouter(CHAIN_IDS.ARBITRUM, wallet.signer);

Browser wallet helpers are also exported: connectMetaMask, connectRabby, connectInjected, connectPrivy, and connectWagmi.

EIP-6963 Wallet Discovery

const {
  discoverInjectedProviders,
  connectViaEip6963,
  KNOWN_WALLET_RDNS,
} = require("empx-swap-sdk");

const wallets = await discoverInjectedProviders();
const rabby = await connectViaEip6963(KNOWN_WALLET_RDNS.RABBY);

connectMetaMask() and connectRabby() prefer EIP-6963 discovery and fall back to legacy window.ethereum behavior.

EIP-5792 Wallet Calls

const {
  calldataToWalletCall,
  getWalletCapabilities,
  sendWalletCalls,
} = require("empx-swap-sdk");

const approval = router.getApprovalCalldataForAmount(tokenIn, {
  mode: "exact",
  amount: amountIn,
});
const prepared = await router.prepareSwap(amountIn, tokenIn, tokenOut, recipient);

await getWalletCapabilities(provider, wallet.address, "0xa4b1");
await sendWalletCalls(provider, {
  version: "2.0.0",
  chainId: "0xa4b1",
  from: wallet.address,
  calls: [
    calldataToWalletCall(approval),
    calldataToWalletCall(prepared.calldata),
  ],
});

Polished Wallet Execution Planning

For wallet integrations, prepareWalletSwap() chooses the safest available flow in this order: already-approved swap, ERC-2612 permit, EIP-5792 batched exact approval + swap, then exact approval followed by swap.

const {
  prepareWalletSwap,
  createRouter,
  CHAIN_IDS,
} = require("empx-swap-sdk");

const router = createRouter(CHAIN_IDS.ARBITRUM, rpcProvider);
const plan = await prepareWalletSwap({
  router,
  account: wallet.address,
  amountIn,
  tokenIn,
  tokenOut,
  recipient,
  preferPermit: true,
  permit: {
    signer,
    tokenName,
    tokenVersion: "1",
    nonce,
    deadline,
  },
  preferBatch: true,
  eip1193Provider: browserProvider,
});

if (plan.strategy === "permit") {
  await signer.sendTransaction(plan.swap);
} else if (plan.strategy === "batch") {
  await browserProvider.request({
    method: "wallet_sendCalls",
    params: [{
      version: "2.0.0",
      chainId: `0x${router.chain.chainId.toString(16)}`,
      from: wallet.address,
      calls: plan.walletCalls,
    }],
  });
} else if (plan.strategy === "approval-then-swap") {
  await signer.sendTransaction(plan.approval);
  await signer.sendTransaction(plan.swap);
} else {
  await signer.sendTransaction(plan.swap);
}

x402 RPC Provider

const {
  createRouter,
  CHAIN_IDS,
  createX402Provider,
  PRESET_X402_ENDPOINTS,
} = require("empx-swap-sdk");

const provider = createX402Provider({
  endpoint: PRESET_X402_ENDPOINTS.QUICKNODE,
  paymentSigner,
});

const router = createRouter(CHAIN_IDS.BASE, provider);

Use x402 when your RPC provider requires signed payment headers.

Advanced Fees

const {
  getProtocolFeeBps,
  setProtocolFeeBps,
  makeAffiliateConfig,
  classifyAffiliateTier,
  enablePairTypeFees,
  disablePairTypeFees,
} = require("empx-swap-sdk");

Protocol fee and affiliate helpers are exported for integrations that need explicit fee reporting or tier classification. Pair-type fees are opt-in.

For server-side multi-tenant integrations, prefer instance-scoped config:

const router = createRouter(CHAIN_IDS.ARBITRUM, provider, {
  protocolFeeBps: 28,
  pairTypeFees: {
    volatileVolatileBps: 28,
    volatileStableBps: 15,
    stableStableBps: 9,
  },
});

The global fee setters are retained for compatibility, but per-router config avoids crosstalk between tenants.

Permit and Approval Helpers

const {
  buildPermitTypedData,
  splitPermitSignature,
} = require("empx-swap-sdk");

const typed = buildPermitTypedData({
  tokenName,
  tokenVersion: "1",
  chainId,
  verifyingContract: tokenIn,
  owner,
  spender: router.chain.ROUTER_ADDRESS,
  value: amountIn,
  nonce,
  deadline,
});

const signature = await signer.signTypedData(
  typed.domain,
  typed.types,
  typed.message
);
const permit = splitPermitSignature(signature, deadline);
const calldata = router.getSwapWithPermitCalldata(tradeInfo, recipient, permit);

For ordinary approvals, getApprovalCalldata(token) still builds unlimited approval for compatibility. Agent integrations should prefer:

const approval = router.getApprovalCalldataForAmount(tokenIn, {
  mode: "exact",
  amount: amountIn,
});

Viem and Wagmi Adapters

const { toViemTransaction } = require("empx-swap-sdk/adapters/viem");
const { toWagmiTransaction } = require("empx-swap-sdk/adapters/wagmi");

const prepared = await router.prepareSwap(amountIn, tokenIn, tokenOut, recipient);
const viemTx = toViemTransaction(prepared.calldata);
const wagmiTx = toWagmiTransaction(prepared.calldata);

Split Routing

Split routing is available on every existing router object; there is no new factory or separate router type. This works with createRouter(), createAffiliateRouter(), createRouters(), and getAllChainRouters().

const { createRouter, CHAIN_IDS } = require("empx-swap-sdk");

const router = createRouter(CHAIN_IDS.BASE, provider);
const prepared = await router.splitSwap(
  amountIn,
  tokenIn,
  tokenOut,
  recipient,
  {
    routing: "auto",
    maxSteps: 3,
    slippageBps: 200,
    maxSplits: 3,
    minSavingsBps: 10,
  }
);

console.log(prepared.routing); // "split" or "single"
console.log(prepared.calldata); // { to, data, value }

| routing | Behavior | | --- | --- | | "auto" | Uses an atomic split only when deployed, compatible, and beneficial; otherwise returns the existing single-route result. This is the default. | | "split" | Requires a beneficial split; throws SPLIT_UNAVAILABLE or SPLIT_NOT_BENEFICIAL instead of falling back. | | "single" | Calls the existing router.swap() flow unchanged. |

When prepared.routing === "split", ERC-20 approval must target the multicall router. For a single result, keep using the ordinary approval methods:

if (prepared.routing === "split") {
  const allowance = await router.checkSplitAllowance(
    tokenIn,
    signer.address,
    amountIn
  );
  if (!allowance.approved) {
    const approval = router.getSplitApprovalCalldataForAmount(tokenIn, {
      mode: "exact",
      amount: amountIn,
    });
    await (await signer.sendTransaction(approval)).wait();
  }
} else {
  const allowance = await router.checkAllowance(tokenIn, signer.address, amountIn);
  if (!allowance.approved) {
    const approval = router.getApprovalCalldataForAmount(tokenIn, {
      mode: "exact",
      amount: amountIn,
    });
    await (await signer.sendTransaction(approval)).wait();
  }
}

const receipt = await (await signer.sendTransaction(prepared.calldata)).wait();

Agent and calldata-only integrations should return prepared.calldata and the prepared.routing discriminator to the wallet/execution layer instead of sending the transaction inside the agent.

Batch factories need no special handling:

const routers = createRouters([CHAIN_IDS.BASE, CHAIN_IDS.ARBITRUM], {
  defaultProvider: provider,
});
const prepared = await routers[CHAIN_IDS.ARBITRUM].splitSwap(
  amountIn,
  tokenIn,
  tokenOut,
  recipient
);

Current multicall deployments:

| Chain | Chain ID | Multicall router | | --- | ---: | --- | | Base | 8453 | 0x6B2e4876f4d0D0fb437Ce52c4B67ecde2bAC80ee | | Arbitrum | 42161 | 0xFCc2Dd827E5E398a3288f31f7fF2e4124199001a | | Optimism | 10 | 0xe5E108A8ADF9cB5fb1CF1642C2E1E1980CA1A2BC |

The current deployments are treated as V1. They support the standard ERC-20-to-ERC-20 split path; native-input and native-output requests fall back in auto mode and are rejected in forced split mode.

createAffiliateRouter() and createRouter(..., { integratorId }) also expose splitSwap(). On the current V1 multicall routers, auto returns the existing integrator-aware single route because V1 cannot carry integratorId. A future router can be activated by replacing only MULTICALL_ROUTER_ADDRESS, provided it retains multiSwap(...) and implements both exact V2 signatures:

function multicallVersion() external pure returns (uint256); // returns 2
function multiSwapWithIntegrator(MulticallLeg[] calldata legs, bytes32 integratorId) external payable;

The SDK detects V2 per provider/address, uses the integrator-aware batch entry point automatically, propagates RPC detection failures, and rejects unknown versions. Advanced consumers can still import findBestSplitRouting(), buildSplitMultiSwapCalldata(), and buildSplitMultiSwapWithIntegratorCalldata() directly.

Full Example: Execute With ethers.js

This example is for wallet or backend signer integrations. Agent-only workflows should return calldata instead of sending transactions directly.

const { ethers } = require("ethers");
const { createRouter, CHAIN_IDS } = require("empx-swap-sdk");

const provider = new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc");
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const router = createRouter(CHAIN_IDS.ARBITRUM, provider);

async function executeSwap(tokenIn, tokenOut, amountIn, recipient) {
  const isNative = tokenIn === router.chain.NATIVE_ADDRESS;

  if (!isNative) {
    const allowance = await router.checkAllowance(tokenIn, signer.address, amountIn);
    if (!allowance.approved) {
      const approval = router.getApprovalCalldata(tokenIn, amountIn);
      const approveTx = await signer.sendTransaction(approval);
      await approveTx.wait();
    }
  }

  const { calldata } = await router.swap(
    amountIn,
    tokenIn,
    tokenOut,
    recipient,
    3,
    200
  );

  const swapTx = await signer.sendTransaction(calldata);
  return swapTx.wait();
}

Structured Errors

All SDK errors use EmpxError with a machine-readable code, retryable flag, and optional context.

const { EmpxError } = require("empx-swap-sdk");

try {
  await router.getTradeInfo(amountIn, tokenIn, tokenOut);
} catch (err) {
  if (err instanceof EmpxError) {
    console.log(err.code);
    console.log(err.retryable);
    console.log(err.toJSON());
  }
}

| Code | Retryable | Meaning | | --- | --- | --- | | INVALID_INPUT | No | Missing or malformed parameter | | INVALID_ADDRESS | No | Invalid EVM address | | INVALID_AMOUNT | No | Zero or invalid integer amount | | SLIPPAGE_TOO_HIGH | No | Slippage exceeds 1000 bps | | STEPS_OUT_OF_RANGE | No | maxSteps must be 1 through 4 | | AMOUNT_TOO_SMALL | No | Input too small after protocol fee | | NO_ROUTE_FOUND | Yes | No route found, possibly transient RPC state | | QUOTE_EXPIRED | Yes | Quote TTL elapsed and trade info must be refetched | | SPLIT_UNAVAILABLE | No | Forced split is not deployed or compatible for this request | | SPLIT_NOT_BENEFICIAL | No | No candidate met the forced split savings threshold |

Retry and Rate Limits

  • Retry only errors with retryable: true.
  • Use short exponential backoff such as 500 ms, 1 s, then 2 s.
  • Cap retries to avoid loops.
  • Use private or dedicated RPC endpoints for production agents.
  • Prefer getMultipleTokenPricesUSD() over calling getTokenPriceUSD() in a loop.

Testing

npm test               # Smoke test, no RPC
npm run test:fast      # Smoke + unit tests, no RPC
npm run test:unit      # Unit tests only
npm run test:pathfind  # Pathfinding and quotes across selected chains
npm run test:affiliate # Affiliate model tests
npm run test:split     # Split routing tests
npm run test:all       # Full suite, requires RPC access

License

MIT