> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneswap.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Swaps

> Execute token swaps with slippage protection and direct-party settlement.

## Basic swap

```typescript theme={null}
import { OneSwap } from '@oneswap/sdk'

const authClient = new OneSwap({ apiKey: 'os_live_...' })

const challenge = await authClient.walletAuth.requestChallenge('alice::12205a8c...')
const signature = await wallet.signMessage(challenge.message)
const verified = await authClient.walletAuth.verifyChallenge({
  partyId: 'alice::12205a8c...',
  nonce: challenge.nonce,
  signature,
  publicKey: wallet.publicKey,
})

const swapClient = new OneSwap({
  apiKey: 'os_live_...',
  walletToken: verified.token,
})

const swap = await swapClient.swaps.create({
  fromToken: 'Amulet',
  toToken: 'USDCx',
  amount: '100',
  walletAddress: 'alice::12205a8c...',
})
```

In SDK params and responses, use `Amulet` when you mean `CC (Amulet)`.

This returns a `SwapIntent` with everything you need:

| Field              | Description                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------------- |
| `depositAddress`   | Pool swap party that should receive the deposit                                              |
| `swapAddress`      | The same pool swap party, returned explicitly for clarity                                    |
| `depositReference` | Transfer reference to pass through when the wallet supports Canton reason/reference metadata |
| `expectedAmount`   | Final deposit amount to send, including the estimated network fee                            |
| `quotedSwapAmount` | Original deposit amount you asked OneSwap to process                                         |
| `networkFeeAmount` | Estimated network fee deducted internally from that deposit                                  |
| `poolFeeAmount`    | The 0.3% swap fee deducted before pricing into the pool                                      |
| `expectedOutput`   | Estimated output amount                                                                      |
| `minOutput`        | Minimum accepted after slippage                                                              |
| `outputAddress`    | Resolved destination party for the payout                                                    |
| `expiresAt`        | Intent expiration timestamp                                                                  |
| `instructions`     | Step-by-step transfer instructions                                                           |

The deposit must be sent from the same authenticated wallet party that created the intent. A different wallet or Canton party will not complete the swap.

Swap intents currently remain valid for 24 hours. Use `swap.expiresAt` from the response instead of hard-coding a shorter timeout.

## Direct-party flow

1. Request a OneSwap wallet-auth challenge for the user's `party ID`
2. Have the user sign the challenge with the same wallet that will deposit
3. Verify the signature and store the returned wallet token
4. Create the intent with `client.swaps.create(...)`
5. OneSwap returns the pool swap party as `depositAddress` plus a `depositReference`
6. The user sends the final deposit amount from that same party to `depositAddress` and passes through the reference when the wallet supports Canton reason/reference metadata
7. OneSwap detects the deposit and executes the swap
8. Output returns directly to the same party that made the deposit

`walletAddress` is a consistency check, not the trust root. The verified wallet token is what authorizes the swap.

## Slippage tolerance

Default is 5% (`0.05`). You can set it between 0.1% (`0.001`) and 5% (`0.05`). The SDK validates this client-side — non-finite or out-of-range values throw `ValidationError` before a request is sent, rather than silently falling back to the default:

```typescript theme={null}
const swap = await client.swaps.create({
  fromToken: 'Amulet',
  toToken: 'USDCx',
  amount: '100',
  slippageTolerance: 0.005,
  walletAddress: 'alice::12205a8c...',
})
```

If the actual output falls below `minOutput`, the swap is refunded.

## Maximum price impact

OneSwap enforces a server-side price impact cap (currently 15%). If a quote or swap-intent creation request would move the pool price beyond this limit, the API returns `400` and the SDK surfaces that as `ValidationError`. If an already-created intent later crosses the same cap during execution, it reaches terminal status `price_impact_exceeded` and `swap.wait()` throws `PriceImpactError`. The cap is returned in `quote.maxPriceImpact`. To execute large swaps, either split them into smaller amounts or add liquidity to the pool first.

## Settlement safety

OneSwap runs a pre-payout settlement safety guard. Before calling `swaps.create`, check `quote.settlementSafety` — it is `null` when the swap is clear, or `{ code, reason, message }` when a swap of this size/timing would be blocked at settlement.

```typescript theme={null}
const quote = await client.quotes.get({ from: 'Amulet', to: 'USDCx', amount: '100' })

if (quote.settlementSafety) {
  console.log(quote.settlementSafety.message) // reduce the swap size or wait a few minutes
} else {
  const swap = await client.swaps.create({ fromToken: 'Amulet', toToken: 'USDCx', amount: '100' })
}
```

If you skip the check and the guard would block, `swaps.create` returns `400` and throws `SettlementSafetyError` — no intent is created and no deposit happens. Reduce the swap size or wait a few minutes, then retry.

## Output routing

The current SDK flow always returns swap output to the same Canton party that sends the deposit. `outputAddress` remains in the type surface for backward compatibility, but the SDK ignores it and `/api/v1/swaps` resolves output back to the depositor party.

`depositReference` is recommended operational metadata. Pass it through to the wallet UX when the wallet supports Canton reason/reference fields so observability and fallback matching stay stronger.

## Disambiguating duplicate symbols

If the same token symbol exists under multiple admins, use the token list plus admin fields to select the exact pool.

```typescript theme={null}
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 swap = 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`.

## Track swap status

### Await completion

```typescript theme={null}
const result = await swap.wait()
```

`swap.wait()` resolves on `completed`. It throws typed errors for terminal failures, including `ExpiredError` when the backend reports the intent expired or cancelled. Individual polls that hit a transient `NetworkError`, `TimeoutError`, `ServerError`, or `RateLimitError` are retried automatically until the deadline, so a single blip won't abandon a settling swap. If the local `timeout` is reached, `wait()` throws `TimeoutError` (a client-side deadline) — the swap may still be settling, so resume with `client.swaps.getStatus(swap.intentId)` rather than treating it as failed.

### Listen to events

```typescript theme={null}
swap.on('processing', () => console.log('Deposit detected'))
swap.on('sending_output', () => console.log('Sending payout'))
swap.on('completed', (s) => console.log(`Got ${s.actualOutput}`))
swap.on('slippage_failed', () => console.log('Refunded: price moved'))
swap.on('price_impact_exceeded', () => console.log('Refunded: price impact too high'))
swap.on('insufficient_amount', () => console.log('Refunded: deposit below required network cost'))
swap.on('output_failed', () => console.log('Output transfer failed, input was refunded'))
swap.on('refund_failed', () => console.log('Refund failed, manual intervention required'))
swap.on('expired', () => console.log('Expired'))
```

### Poll manually

```typescript theme={null}
const status = await client.swaps.getStatus(swap.intentId)
```

### Cancel a pending swap

```typescript theme={null}
await client.swaps.cancel(swap.intentId)
// or:
await swap.cancel()
```

## Status lifecycle

```
pending → matched → deposit_received → forwarded → processing → sending_output → completed
        → cancelled
        → slippage_failed
        → price_impact_exceeded
        → insufficient_amount
        → insufficient_liquidity
        → output_failed
        → no_output_holdings
        → failed
        → refund_failed
        → expired
```

Most validation failures refund the input automatically. Rare operational states such as `no_output_holdings`, `failed`, and `refund_failed` mean the swap reached a terminal state that may require manual investigation.

## Quotes

```typescript theme={null}
const quote = await client.quotes.get({
  from: 'Amulet',
  to: 'USDCx',
  amount: '100',
  receiverParty: 'alice::12205a8c...',
})
```

`quote.estimatedTrafficCost`, `quote.networkFeeAmount`, `quote.totalInputAmount`, and `quote.inputAmountAfterPoolFee` help you account for Canton execution costs before the swap executes.

* `inputTokenAdmin` and `outputTokenAdmin` identify the exact instruments that were priced.
* `maxPriceImpact` is the server-enforced price impact cap. Swaps exceeding this are rejected.
* `inputAmount` is the exact deposit amount you asked OneSwap to process.
* `networkFeeAmount` is deducted internally from that deposit before AMM pricing.
* `inputAmountAfterPoolFee` is the amount that actually trades into the pool after deducting both the network fee and the 0.3% swap fee.
* `outputAmount` is quoted from `inputAmountAfterPoolFee`, not from `totalInputAmount`.
* `totalInputAmount` is the exact deposit amount to send if you create the intent, and in the current flow it matches `inputAmount`.
* `trafficFeeInInput` remains as a compatibility alias for `networkFeeAmount`.
* `receiverParty` lets the quote include the same destination-party traffic assumptions used by the live execution path.
* The network fee is separate from the 0.3% swap fee and is not shared with LPs or SDK developers.

## Wallet auth

For browser-based SDK apps, keep the API key and wallet proof separate:

* The API key identifies your app or developer account.
* The wallet token identifies the end user who signed the OneSwap challenge.
* `client.swaps.create(...)`, `client.swaps.getStatus(...)`, and `client.track(partyId)` require the wallet token.
* If your app refreshes wallet auth in state, pass `walletToken` as a function so the SDK reads the current token on each request.
