@oneswap/sdk
v1.4.0
Published
Official TypeScript SDK for OneSwap on Canton: swaps, quotes, pools, and live market data
Maintainers
Readme
@oneswap/sdk
Official TypeScript SDK for OneSwap on Canton. Execute swaps (create a swap for a user, tell them the party to deposit to, and OneSwap returns the swapped output to the depositing party — no wallet integration required on your side), and read pools, live market data, and swap quotes. Works in Node.js and browsers.
Install
npm install @oneswap/sdkMaintainers: see PUBLISHING.md for the release workflow.
Quick Start
import { OneSwap } from '@oneswap/sdk'
const client = new OneSwap({ apiKey: 'sk_live_...' })
// 1. Create a swap for one of your users.
const swap = await client.swaps.createSwap({
userRef: 'user-1234', // your stable id for this user
inSymbol: 'CC',
amountIn: 100,
outSymbol: 'CBTC',
poolId: 'rt-private-pool', // optional; pin a visible/private pool
slippageBps: 50, // optional; or pass an absolute minOut
})
// 2. Tell the user (or your system) where to deposit the input.
// The output is returned to whoever makes this deposit (return-to-sender).
console.log(`Send ${swap.amountIn} ${swap.inSymbol} to ${swap.depositParty}`)
console.log(`Deposit before ${swap.deadline}`)
// 3. Wait for the swap to finish (polls until terminal).
const result = await client.swaps.waitForSwap(swap.id)
if (result.status === 'returned') {
console.log(`Swapped! Received ${result.amountOut} ${result.outSymbol}`)
} else {
console.log(`Swap did not complete: ${result.status} (${result.error ?? 'no detail'})`)
}You can also poll manually with client.swaps.getSwap(swap.id).
Market data & quotes
// Which tokens and pools exist?
const tokens = await client.tokens.list()
const pools = await client.pools.list()
// A quote: how much CBTC for 100 CC? The SDK finds the pool that trades the
// pair and derives the direction for you.
const quote = await client.quotes.get({ from: 'CC', to: 'CBTC', amount: 100 })
console.log(`~${quote.amountOut} CBTC (price impact ${quote.priceImpactPct}%)`)
console.log(`via pool ${quote.poolId}`)
// Live market data for a pool: USD price and 24h USD volume.
const ticker = await client.pools.getTicker(quote.poolId)
console.log(`${ticker.pair}: $${ticker.price.usd}, 24h volume $${ticker.volume24h.usd ?? '—'}`)
// TVL comes with the pool list.
for (const p of pools) console.log(`${p.id}: TVL $${p.tvlUsd ?? '—'}`)
// One pool in full, with its settled-swap history.
const { pool, swaps } = await client.pools.get(quote.poolId)Notes:
- Every swap settles against exactly one pool. If no pool trades your pair
directly,
quotes.getthrowsNoDirectPoolError— there is no multi-hop routing, so run two swaps through an intermediate asset (usually CC) yourself. If several pools trade the pair it throwsAmbiguousPoolPairErrorwith the candidates; retry withpoolId. ticker.priceChange24hfields arenulluntil the backend's hourly price snapshots are at least 24h old, andprice.usdisnullfor pools that can't be USD-priced yet (e.g. unseeded pools).
Authentication
Every request is authenticated with your integrator API key, sent as the
x-sdk-key HTTP header. Live keys look like sk_live_.... Create one from
your OneSwap account — swap.oneswap.cc for mainnet,
devnet.oneswap.cc for devnet. Never expose a live
key in client-side code.
Configuration
new OneSwap({
apiKey: 'sk_live_...', // required
environment: 'mainnet', // 'mainnet' (default) | 'devnet'
timeout: 30000, // optional; per-request timeout in ms (default 30000)
})The SDK resolves the backend URL itself from environment — mainnet
(https://api.oneswap.cc/swapv2, the default) or devnet
(https://devnet.api.oneswap.cc/swapv2). You never pass a raw URL. The instance
exposes readonly baseUrl (the resolved URL) and environment.
Swap statuses
| Status | Terminal | Meaning |
| ------------------ | :------: | ---------------------------------------------------------- |
| awaiting_deposit | no | Swap created; send the input to depositParty. |
| deposit_detected | no | The deposit landed and is being processed. |
| swapping | no | The swap is executing against the pool. |
| recovering | no | A step failed with the deposit in hand; it is being retried and the funds returned to the sender. |
| needs_review | no | Automatic recovery was exhausted. Funds are safe; a human is resolving it. |
| returned | yes | Success — the output was returned to the depositing party. |
| refunded | yes | The input was returned to the depositing party. |
| expired | yes | The deposit deadline passed before a deposit arrived. |
| failed | yes | The swap could not be completed. |
| cancelled | yes | Closed via cancel() before any deposit arrived. |
Helpers isTerminal(status) and TERMINAL_STATUSES are exported for
convenience. waitForSwap resolves once a terminal status is reached (or its
timeout elapses).
API
client.swaps.createSwap(args): Promise<Swap>
client.swaps.getSwap(id): Promise<Swap>
client.swaps.waitForSwap(id, { pollMs?, timeoutMs? }): Promise<Swap>
client.swaps.getOpenSwap(userRef): Promise<Swap | null>
client.swaps.cancel(id): Promise<Swap>
client.quotes.get({ from, to, amount, poolId?, walletId? }): Promise<QuoteResult>
client.pools.list(): Promise<Pool[]>
client.pools.get(poolId): Promise<PoolDetail> // { pool, swaps }
client.pools.getTicker(poolId): Promise<PoolTicker>
client.tokens.list(): Promise<Token[]>createSwap args: { userRef, inSymbol, amountIn, outSymbol, poolId?, minOut?, slippageBps? }.
The symbol pair must match a direct pool accessible to your SDK key. Available tokens
cannot be combined arbitrarily: CC/HANDL and CC/USDCx pools do not imply a HANDL/USDCx
route. An unsupported pair returns HTTP 400 (no pool for HANDL/USDCx, for example)
before an intent or deposit wallet is created. A missing or inaccessible explicit
poolId returns HTTP 404. Choose a supported pair; retrying the same unsupported
request will not make it executable.
Use poolId to target a pool returned by pools.list() or quotes.get(),
including a private pool allowlisted for your API key.
waitForSwap polls getSwap every pollMs (default 3000) until the status is
terminal or timeoutMs (default 900000) elapses, then resolves with the latest
Swap — it does not throw on timeout, so always inspect result.status.
Recovering from OpenSwapExistsError
A userRef may hold only one open swap at a time. If you no longer have the id
createSwap returned, getOpenSwap finds it — then either wait it out or
release it:
try {
swap = await client.swaps.createSwap({ userRef, inSymbol: 'CC', amountIn: 100, outSymbol: 'CBTC' })
} catch (err) {
if (!(err instanceof OpenSwapExistsError)) throw err
const open = await client.swaps.getOpenSwap(userRef)
if (open?.status === 'awaiting_deposit') {
await client.swaps.cancel(open.id) // no deposit arrived — release it and retry
} else if (open) {
await client.swaps.waitForSwap(open.id) // funds are in flight — let it finish
}
}cancel only closes a swap still in awaiting_deposit. Once a deposit has
arrived the swap holds funds and must run to completion — cancelling then throws
ConflictError (409). Cancelling an already-finished swap returns it unchanged.
client.escrow (the 1.0.0 name for client.swaps) still works as a deprecated
alias of the same instance.
quotes.get returns the v2 Quote (amountOut, priceImpactPct,
networkFeeIn, effFeeBps, lpFee, platformFee, …) plus the resolved
poolId and direction xToY. The pool list backing symbol resolution is
cached for 30s per client instance.
Errors
All errors extend OneSwapError, which carries an optional status (HTTP status
code) and data (parsed response body):
OneSwapError, AuthError (401), ValidationError (400), NotFoundError (404),
RateLimitError (429), ConflictError (409), OpenSwapExistsError (409 — the
user already has an open swap), ServerError (5xx), NetworkError,
TimeoutError, NoDirectPoolError (no pool trades the requested pair — the
multi-hop case), AmbiguousPoolPairError (several pools trade the pair; carries
candidates, retry with poolId).
import { OpenSwapExistsError, RateLimitError } from '@oneswap/sdk'
try {
await client.swaps.createSwap({ userRef, inSymbol: 'CC', amountIn: 100, outSymbol: 'CBTC' })
} catch (err) {
if (err instanceof OpenSwapExistsError) {
// this user already has an open swap — resolve it before starting another
} else if (err instanceof RateLimitError) {
// back off and retry
} else {
throw err
}
}Availability
Swaps are live on devnet and mainnet — all you need is an SDK API key
(sk_live_...) from your OneSwap account (swap.oneswap.cc
for mainnet, devnet.oneswap.cc for devnet). Market
data and quotes (pools, quotes, tokens) are read-only and available on
both networks.
License
MIT
