@oneswap/sdk
v0.2.8
Published
Official TypeScript SDK for the OneSwap API
Downloads
465
Maintainers
Readme
@oneswap/sdk
Official TypeScript SDK for the OneSwap API. Typed methods for pools, tokens, quotes, wallet auth, swaps, and wallet-scoped tracking — with built-in awaitable swap lifecycle. Works in Node.js and browsers.
Install
npm install @oneswap/sdkMaintainers: see PUBLISHING.md for the release workflow and the local ignored npm auth-file setup used in this repo.
Quick Start
import { OneSwap } from '@oneswap/sdk'
const apiKey = 'os_live_...'
const authClient = new OneSwap({ apiKey })
const partyId = 'alice::12205a8c...'
const challenge = await authClient.walletAuth.requestChallenge(partyId)
const signature = await wallet.signMessage(challenge.message)
const verified = await authClient.walletAuth.verifyChallenge({
partyId,
nonce: challenge.nonce,
signature,
publicKey: wallet.publicKey,
})
const client = new OneSwap({
apiKey,
walletToken: verified.token,
})
// Get a traffic-aware quote for the same party that will deposit
const quote = await client.quotes.get({
from: 'Amulet',
to: 'USDCx',
amount: '100',
receiverParty: partyId,
})
console.log(`Output: ${quote.outputAmount} ${quote.outputToken}`)
console.log(`Traffic: ${quote.trafficFeeInInput ?? '0'} ${quote.inputToken}`)
// Create a swap and wait for completion
const intent = await client.swaps.create({
fromToken: 'Amulet',
toToken: 'USDCx',
amount: '100',
walletAddress: partyId,
})
console.log(`Deposit to: ${intent.depositAddress}`)
console.log(`Transfer reference: ${intent.depositReference}`)
// Listen for status changes
intent.on('processing', () => console.log('Deposit detected'))
intent.on('completed', (status) => console.log(`Swap done: ${status.actualOutput}`))
// Await final result
const result = await intent.wait()
console.log(`Received: ${result.actualOutput} ${result.outputToken}`)Use os_live_... SDK keys for integrations. Pair the key with a verified wallet token from client.walletAuth.* for user-bound actions. SDK keys are created and managed through the wallet-authenticated OneSwap developer portal.
walletAddress is the user's actual Canton party ID. Deposits go directly to the pool party, and swap output returns directly to that same source party in the current execution flow. If your wallet flow supports Canton reason/reference metadata, pass through intent.depositReference for faster fallback matching. The SDK does not expose wallet registration, wallet balance lookup, LP add, LP withdraw, or other liquidity management.
If token names are unique across pools, name-only selection is still fine. If the same symbol exists under multiple admins, pass poolId or both token admins so the API cannot guess the wrong pool.
Environment Targeting
Mainnet remains the default:
const client = new OneSwap({ apiKey })Devnet now has a built-in default backend too:
const client = new OneSwap({
apiKey,
environment: 'devnet',
})Create and manage SDK keys from the matching developer portal:
- Mainnet:
https://oneswap.cc/developer/keys - Devnet:
https://devnet.oneswap.cc/developer/keys
You can still override it with an explicit backend URL:
const client = new OneSwap({
apiKey,
environment: 'devnet',
baseUrl: 'https://devnet-backend.example.com',
})If you omit baseUrl, the SDK defaults to:
- Mainnet:
https://api.oneswap.cc - Devnet:
https://devnet.api.oneswap.cc
Getting An SDK API Key
Developer access is wallet-authenticated. SDK developer auth and SDK swap execution do not require a site-access token or access code.
- Authenticate a Canton wallet with OneSwap.
- Exchange that wallet session on
POST /api/sdk/wallet-login. - Use the returned user JWT on
/api/sdk/keysto create or rotateos_live_...keys. - Use the resulting API key in
new OneSwap({ apiKey }).
Portal entry points:
- Mainnet SDK keys:
https://oneswap.cc/developer/keys - Devnet SDK keys:
https://devnet.oneswap.cc/developer/keys
Devnet SDK Keys
Use the devnet site when you want a devnet SDK key:
- Open
https://devnet.oneswap.cc/developer/keys. - Connect and authenticate the devnet wallet you want to use.
- Create or rotate the SDK key from the devnet developer portal.
- Use that key with:
const client = new OneSwap({
apiKey: 'os_live_...',
environment: 'devnet',
})POST /api/sdk/register and POST /api/sdk/login are retired compatibility endpoints and now return 410 Gone.
API Reference
new OneSwap(config)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | Your OneSwap SDK API key (os_live_...) |
| environment | 'mainnet' \| 'devnet' | 'mainnet' | Logical target environment. |
| baseUrl | string | Mainnet: https://api.oneswap.cc, Devnet: https://devnet.api.oneswap.cc | Override the API origin. Use this for alternate Devnet or self-hosted backends. |
| walletToken | string \| (() => string \| Promise<string \| undefined> \| undefined) | none | Optional end-user wallet token, or a lazy getter, for wallet-authenticated requests |
| timeout | number | 30000 | Request timeout (ms) |
Pools
| Method | Returns | Description |
|--------|---------|-------------|
| client.pools.list() | PoolListResponse | List all pools |
| client.pools.get(poolId) | PoolDetail | Get pool details |
| client.pools.getStats(poolId, { days? }) | PoolStats | Get pool APR/volume/TVL. Defaults to a 7-day annualized view. |
Tokens
| Method | Returns | Description |
|--------|---------|-------------|
| client.tokens.list() | TokenListResponse | List available tokens |
client.tokens.list() returns { name, admin } entries. Duplicate token names are allowed when admins differ.
Quotes
| Method | Returns | Description |
|--------|---------|-------------|
| client.quotes.get({ from, to, amount, receiverParty?, poolId?, fromAdmin?, toAdmin? }) | Quote | Get swap quote. Pass poolId or both admins when token names are ambiguous. amount is the exact deposit amount you want the user to send. |
Quotes are directional execution estimates, not inverse mid-prices.
- OneSwap prices swaps with the current pool reserves, the configured swap fee, and constant-product slippage.
- Traffic recovery is deducted from the deposit before pricing, and the 0.1% pool fee is deducted from the remainder, so
outputAmountis based on the effective post-fee input rather than the raw requested deposit. CC -> USDCxandUSDCx -> CCtherefore should not be exact reciprocals, and a round-trip loses value to fee, slippage, and possibly traffic recovery on both legs.- If
receiverPartyis provided, quote-time traffic estimation follows the current payout path. CC output and utility-token output can use different transfer machinery, so direction can matter even more.
For all-in comparisons, check these fields together:
inputAmounttotalInputAmountnetworkFeeAmountpoolFeeAmounttrafficFeeInInputinputAmountAfterPoolFeeoutputAmountrate
totalInputAmount currently echoes the exact deposit amount to send. rate is based on the effective post-fee input amount, not necessarily the original requested deposit.
Swaps
| Method | Returns | Description |
|--------|---------|-------------|
| client.swaps.create(params) | SwapIntent | Create swap intent |
| client.swaps.getStatus(intentId) | SwapStatusResponse | Get swap status |
| client.swaps.cancel(intentId) | { success: boolean } | Cancel a pending swap intent |
outputAddress is deprecated on client.swaps.create(...) and ignored by the SDK client. Current OneSwap execution always returns output to the same source party that makes the deposit.
SwapIntent includes depositReference. Treat it as optional metadata: pass it through when your wallet flow supports Canton reason/reference fields, but do not depend on it as the only way a swap will settle.
Disambiguating Duplicate Symbols
const tokens = await client.tokens.list()
const usdc = tokens.tokens.find(
(token) => token.name === 'USDCx' && token.admin === 'admin::issuer-a'
)
const quote = await client.quotes.get({
from: 'USDCx',
to: 'Amulet',
amount: '250',
fromAdmin: usdc?.admin,
toAdmin: 'DSO',
})
const intent = await client.swaps.create({
fromToken: 'USDCx',
toToken: 'Amulet',
amount: '250',
walletAddress: 'alice::12205a8c...',
poolId: quote.poolId,
fromAdmin: usdc?.admin,
toAdmin: 'DSO',
})If you omit those fields for an ambiguous pair, the API returns 409 ambiguous_pool_pair and the SDK throws AmbiguousPoolPairError.
SwapIntent (with event emitter):
.wait(opts?)— Polls until terminal state. Resolves withSwapStatusResponse, or throws a typed terminal error such asManualReviewRequiredError..cancel()— Cancels a pending swap intent..on(event, handler)/.once(event, handler)— Listen for status changes.- Events:
pending,matched,deposit_received,forwarded,processing,sending_output,completed,slippage_failed,price_impact_exceeded,insufficient_liquidity,insufficient_amount,output_failed,no_output_holdings,failed,refund_failed,manual_review,expired,cancelled
Track
| Method | Returns | Description |
|--------|---------|-------------|
| client.track(partyId) | TrackResponse | Get swap intents only for the currently authenticated wallet party |
client.track(...) does not return liquidity intents or LP positions. LP add and LP withdraw remain website-only and are intentionally absent from the SDK surface.
Error Handling
All errors extend OneSwapError so you can catch them uniformly:
import {
OneSwap,
AuthError,
ValidationError,
AmbiguousPoolPairError,
SlippageError,
PriceImpactError,
InsufficientLiquidityError,
InsufficientAmountError,
OutputFailedError,
SwapFailedError,
RefundFailedError,
ExpiredError,
TimeoutError,
NetworkError,
} from '@oneswap/sdk'
try {
const intent = await client.swaps.create({ ... })
const result = await intent.wait()
} catch (err) {
if (err instanceof SlippageError) {
console.log('Slippage exceeded, swap refunded')
} else if (err instanceof PriceImpactError) {
console.log('Price impact exceeded the server cap')
} else if (err instanceof InsufficientLiquidityError) {
console.log('Not enough liquidity in pool')
} else if (err instanceof InsufficientAmountError) {
console.log('Amount too low to cover traffic cost')
} else if (err instanceof OutputFailedError) {
console.log('Output transfer failed - contact support')
} else if (err instanceof SwapFailedError) {
console.log('Swap failed - retry after reviewing the pool state')
} else if (err instanceof RefundFailedError) {
console.log('Refund failed - manual intervention is required')
} else if (err instanceof ExpiredError) {
console.log('Intent expired before deposit')
} else if (err instanceof TimeoutError) {
console.log('Request timed out')
} else if (err instanceof NetworkError) {
console.log('Network connectivity issue')
} else if (err instanceof AuthError) {
console.log('Invalid API key')
} else if (err instanceof ValidationError) {
console.log('Invalid params:', err.message)
} else if (err instanceof AmbiguousPoolPairError) {
console.log('Specify poolId or token admins:', err.candidates)
}
}Quote-time or create-time price-impact rejections still arrive as ValidationError because the API responds with HTTP 400. PriceImpactError is reserved for an already-created intent that later reaches price_impact_exceeded while wait() is polling.
Wait Options
const result = await intent.wait({
pollInterval: 5000, // Poll every 5s (default: 3s)
timeout: 600000, // Timeout after 10min (default: 35min)
})License
MIT
