@prism-ing/swap-router
HTTP client SDK for the Prism meta-DEX aggregator service.
Install
pnpm add @prism-ing/swap-router @prism-ing/wallet
Aggregator Service
@prism-ing/swap-router is an HTTP client for the Prism aggregator service. You need a running instance.
Self-host: Deploy apps/aggregator-service to Fly.io — see the aggregator README for required environment variables (SWAPS_XYZ_API_KEY and ZERODEV_PROJECT_ID).
Set baseUrl in your config to your instance URL (must include /v1).
Third-party API terms Anyone operating an aggregator-service instance integrates with swaps.xyz, ZeroDev, and (optionally) Fynd. You must comply with each provider's terms of service. See docs.swaps.xyz/resources/terms-of-service and zerodev.app/terms-of-service.
Solvers
The aggregator races available solvers on every quote. The best price wins.
| Provider value | Route type | Notes |
|---|---|---|
swaps-xyz |
Same-chain EVM, same-chain Solana, cross-chain bridge | Always active |
fynd |
Same-chain EVM (Ethereum, Base, Unichain) | Optional server-side dependency |
Pin to a specific solver for testing with the provider field in QuoteRequest.
Quickstart
import { createSwapRouter } from '@prism-ing/swap-router';
const router = createSwapRouter({
baseUrl: 'https://your-aggregator.fly.dev/v1',
// Optional: provide a signer to enable execution (not just quoting)
signer: mySigner,
evmRpcUrls: { 8453: 'https://mainnet.base.org' },
solanaRpcUrl: 'https://api.mainnet-beta.solana.com',
});
// Get the best swap quote from all solvers
const quoteResult = await router.quote({
fromAsset: 'USDC:base',
toAsset: 'WETH:base',
amount: '1000000', // 1 USDC (6 decimals)
sender: '0xYourAddress',
});
if (!quoteResult.ok) {
console.error(quoteResult.error.code);
process.exit(1);
}
const quote = quoteResult.value;
console.log(
`${quote.provider} → ${quote.netAmountOut} (impact: ${quote.priceImpactBps}bps)`,
);
// Execute the swap
const execResult = await router.execute(quote);
if (execResult.ok) {
console.log('Tx hash:', execResult.value.txHash);
}
Signer Interface
Any PrismSigner from @prism-ing/wallet satisfies SwapSigner — pass wallet.signer directly:
import { createProductionWallet } from '@prism-ing/wallet';
const walletResult = await createProductionWallet({ signer: { walletName: 'my-agent' } });
const wallet = walletResult.value;
const router = createSwapRouter({
baseUrl: '...',
signer: wallet.signer, // PrismSigner → SwapSigner, no cast needed
});
signEvmTxHash is required at runtime for EVM swap execution. All built-in PrismSigner backends implement it.
Asset Formats
| Format | Example | Notes |
|---|---|---|
| Symbol + chain | "USDC:base" |
Resolved to address by the aggregator |
| Address + chainId | "0xA0b86991c...:1" |
Specific token address on chain |
| Solana symbol | "SOL:solana" |
Native SOL |
Custom tokens: For tokens not in the aggregator's registry, use
"0xContractAddress:chainId"(e.g."0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48:1"for USDC on mainnet). Symbol + chain resolution only works for tokens the aggregator knows about.
Route Types
| Route | When | Execution |
|---|---|---|
same-chain-evm |
Both assets on same EVM chain | Sign + broadcast via EVM RPC |
same-chain-solana |
Both assets on Solana | Sign + broadcast via Solana RPC |
cross-chain |
Assets on different chains | Sign + broadcast via swaps.xyz bridge |
Session Keys + Swaps
Pass a session key signer instead of the root signer for policy-scoped execution:
import { createSessionKeyManager } from '@prism-ing/wallet';
import { createSwapRouter } from '@prism-ing/swap-router';
// Create a session scoped to USDC only, 1-hour expiry, 10 USDC max per op
const manager = createSessionKeyManager(wallet.signer);
const session = manager.createSessionKey({
expiresAt: Math.floor(Date.now() / 1000) + 3600,
allowedAssets: ['USDC:base', 'USDC:arbitrum'],
maxAmountPerOp: '10000000', // 10 USDC
allowedChains: [8453, 42161],
});
// The session signer satisfies SwapSigner directly
const router = createSwapRouter({
baseUrl: 'https://your-aggregator.fly.dev/v1',
signer: session.signer, // policy-scoped
evmRpcUrls: { 8453: process.env.BASE_RPC_URL },
});
// Revoke when the agent task is done
manager.revokeSessionKey(session.sessionId);
Error Handling
All methods return Result<T, SwapRouterError>. Error codes:
| Code | Meaning |
|---|---|
QUOTE_EXPIRED |
Quote TTL elapsed before execution |
SIGNER_REQUIRED |
Execution called without a signer |
EVM_RPC_REQUIRED |
No RPC URL for the swap's chain |
SIGNING_FAILED |
Signer returned an error |
BROADCAST_FAILED |
Transaction rejected by the network |
VALIDATION_ERROR |
Unexpected aggregator response shape |
NETWORK_ERROR |
HTTP or connection failure |
TIMEOUT |
Request exceeded timeoutMs |
PAYMASTER_FAILED |
ZeroDev paymaster rejected the UserOperation |
SMART_ACCOUNT_NOT_DEPLOYED |
EVM smart account has no deployed bytecode |
BUNDLER_ERROR |
ERC-4337 bundler rejected or reverted the op |
SOLANA_SPONSORSHIP_FAILED |
ZeroDev Solana co-signing or broadcast failed |
const result = await router.execute(quote);
if (!result.ok) {
switch (result.error.code) {
case 'QUOTE_EXPIRED':
// Re-fetch a fresh quote
break;
case 'SIGNER_REQUIRED':
// Provide a signer in SwapRouterConfig
break;
case 'NETWORK_ERROR':
// Retry — transient
break;
}
}
Gasless Execution
EVM — no gas, no separate approval
When a ZeroDev project is configured, token approval + swap are bundled into a single sponsored ERC-4337 UserOperation. The user's EOA needs no native token balance.
import { createSwapRouter, createZeroDevPaymaster } from '@prism-ing/swap-router';
const router = createSwapRouter({
baseUrl: 'https://your-aggregator.fly.dev/v1',
signer: wallet.signer,
evmRpcUrls: { 8453: 'https://mainnet.base.org' },
// Gasless EVM: all three fields required
smartAccountAddress: '0xYourKernelAccountAddress',
bundlerUrl: `https://rpc.zerodev.app/api/v2/bundler/${process.env.ZERODEV_PROJECT_ID}`,
paymaster: createZeroDevPaymaster({ projectId: process.env.ZERODEV_PROJECT_ID! }),
});
The smart account must be a deployed ZeroDev Kernel v3.1 account. Counterfactual (undeployed) accounts return SMART_ACCOUNT_NOT_DEPLOYED.
Solana — no SOL fee
When a Solana paymaster is configured, ZeroDev co-signs as the transaction fee payer. Users pay zero SOL in transaction fees.
import { createSwapRouter, createZeroDevSolanaPaymaster } from '@prism-ing/swap-router';
const router = createSwapRouter({
baseUrl: 'https://your-aggregator.fly.dev/v1',
signer: wallet.signer,
solanaRpcUrl: 'https://api.mainnet-beta.solana.com',
solanaPaymaster: createZeroDevSolanaPaymaster({
projectId: process.env.ZERODEV_PROJECT_ID!,
network: 'mainnet', // or 'devnet'
}),
});
quote() automatically fetches ZeroDev's fee payer address and injects it into aggregator requests. execute() calls ZeroDev's co-signing endpoint instead of broadcasting directly.
Configuration
interface SwapRouterConfig {
baseUrl: string; // Aggregator URL (include /v1)
timeoutMs?: number; // Default: 10_000
signer?: SwapSigner; // Required for execution
evmRpcUrls?: Partial<Record<number, string>>; // Per-chain RPC endpoints
solanaRpcUrl?: string; // Solana RPC endpoint
approvalConfirmationTimeoutMs?: number; // ERC-20 approval wait. Default: 30_000
// Gasless EVM (all three required together)
smartAccountAddress?: string; // Deployed Kernel v3.1 account address
bundlerUrl?: string; // ERC-4337 bundler URL
paymaster?: PaymasterClient; // Use createZeroDevPaymaster()
// Gasless Solana
solanaPaymaster?: SolanaPaymasterClient; // Use createZeroDevSolanaPaymaster()
}
Info Endpoints
const tokens = await router.getTokens(); // Token[]
const chains = await router.getChains(); // Chain[]
Use these to discover what assets and chains are available on the connected aggregator.
License
MIT