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

# Swaps

> Quote, review, and execute same-chain and cross-chain swaps from a Server Wallet, with durable progress tracking and recovery.

`WalletSwaps` runs same-chain and cross-chain swaps from a Server Wallet. A swap is quoted first, reviewed by your application, and only then confirmed, so no funds move on a quote alone.

Swaps are durable. Progress continues across restarts, which means your application supplies storage and a scheduler rather than awaiting a single call.

Import the swap surface from the SDK's `/trails` entry point:

```typescript theme={null}
import {
  WalletSwaps,
  TrailsClient,
  EvmChainReader,
  type SwapAsset,
  type SwapStore,
} from '@polygonlabs/oms-server-wallet-sdk/trails'
```

## Configure the swap client

```typescript theme={null}
import { SerialExecutor, type ServerWallet } from '@polygonlabs/oms-server-wallet-sdk'

// Your durable swap records, and the restored wallet from the quickstart.
declare const swapStore: SwapStore
declare const wallet: ServerWallet

// A dedicated executor, separate from the one passed to ServerWallet.
const swapExecutor = new SerialExecutor()

// Host-approved asset policy. Never build this from request input.
const assets: SwapAsset[] = [
  { chainId: 137, asset: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', decimals: 6, symbol: 'USDC' },
  { chainId: 8453, asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6, symbol: 'USDC' },
]

const swaps = new WalletSwaps({
  wallet,
  executor: swapExecutor,
  store: swapStore,
  assets,
  enabled: true,
  trails: new TrailsClient({
    apiKey: 'YOUR_TRAILS_API_KEY',
    origin: 'https://wallet-api.example.com',
  }),
  chains: new EvmChainReader({
    137: 'https://YOUR_POLYGON_RPC',
    8453: 'https://YOUR_BASE_RPC',
  }),
})
```

Four of these deserve attention:

* **`assets`** is your own allowlist of tradable assets. A swap request naming an asset outside it is rejected, which is what keeps a caller from routing funds into an arbitrary token.
* **`executor`** is an exclusive executor dedicated to `WalletSwaps`, separate from the one you passed to `ServerWallet`. `WalletSwaps` holds it for each swap step and for every ordinary debit, then calls the wallet, which runs its own executor. See [storage and concurrency](/wallets/sdk/server/storage-and-concurrency).
* **`trails.apiKey`** is separate from your OMS publishable key and belongs only in your backend. Omitting `trails` raises `SWAPS_UNAVAILABLE`.
* **`enabled`** must be `true` to start new swaps. When it is not, `SWAPS_DISABLED` is raised and existing swaps still progress.

<Warning>
  Do not pass the `ServerWallet` executor to `WalletSwaps`. Every `WalletSwaps` call runs inside its executor and then calls the wallet, which waits on its own executor. When both are the same exclusive executor, the inner call waits for the outer one and neither completes.
</Warning>

Once a wallet uses swaps, send its ordinary transfers through `swaps.prepareTransfer` and `swaps.executeTransfer` rather than the `ServerWallet` methods. They take the same arguments, and they refuse a debit with `DEBIT_PENDING` while a swap, recovery, or earlier transfer on that chain is unresolved. Run other wallet calls, such as `rotate` or signing, through `swaps.walletCommand(() => wallet.rotate())` so they serialize against swap work too.

`EvmChainReader` accepts one HTTPS RPC URL per chain. A missing URL, or one carrying credentials or a fragment, raises `RPC_CONFIGURATION` when the SDK first reads that chain.

## Quote a swap

```typescript theme={null}
const swapId = crypto.randomUUID()

const quoted = await swaps.quoteSwap(swapId, {
  originChainId: 137,
  originAsset: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
  destinationChainId: 8453,
  destinationAsset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  amount: '10000000',
  slippageBps: 50,
})

console.log('Phase:', quoted.phase)
console.log('Quote revision:', quoted.quote.revision)
```

`amount` is a decimal string in the origin asset's base units, so `'10000000'` is 10 USDC at 6 decimals. Never pass a floating-point token amount.

`slippageBps` accepts `10`, `50`, or `100`. A same-chain swap uses the same call with one `chainId` on both sides.

The wallet must be restored before quoting, otherwise `WALLET_PENDING` is raised.

## Review and confirm

Quoting does not move funds. The swap stays in the `quoted` phase until you confirm it with the exact quote revision you reviewed.

`quote` carries the terms your review should apply to:

| Field | Meaning |
| - | - |
| `revision` | The identifier you pass to `confirmSwap` |
| `expectedOutput` | Destination amount at the quoted rate, in base units |
| `minimumOutput` | Least the swap will deliver at the requested slippage |
| `inputAmount` | Origin amount to be spent |
| `priceImpact` | Price impact of the route |
| `fees` | Route fees |
| `providers` | Route providers |
| `expiresAt` | When the quote stops being confirmable |

```typescript theme={null}
// Apply your own authorization rules to `quoted` before this point.
const confirmed = await swaps.confirmSwap(swapId, quoted.quote.revision)
```

If the quote changed since you read it, `confirmSwap` raises `STALE_QUOTE` rather than executing against terms you did not review. Re-read the swap, apply your rules to the new quote, and confirm that revision.

Confirming is idempotent. A second call with the same revision returns the current state instead of starting another swap.

## Drive and track progress

A confirmed swap advances through activation, funding, and settlement. Your application drives that work:

```typescript theme={null}
// Call on a schedule: a persistent timer in Node, or an alarm per wallet on Workers.
await swaps.tick()

const view = await swaps.getSwap(swapId)
console.log(view.phase, view.nextAt)

const recent = await swaps.listSwaps()
```

`tick(limit)` performs the work that is due and returns. `nextAt` on a swap view tells you when that swap next needs attention, or is `null` when it needs none. Funding a swap uses a sponsored transfer, so the wallet pays no gas.

Use `reconcileSwap(id)` when a swap's upstream result is uncertain. As with transfers, never start a second swap because a response was lost.

## Phases

| Phase | Meaning |
| - | - |
| `quoted` | Quoted, awaiting your confirmation |
| `preparing` | Preparing the funding transfer |
| `activating` | Activating the swap intent |
| `funding` | Funding in progress |
| `settling` | Awaiting settlement on the destination chain |
| `succeeded` | Complete |
| `expired` | The quote expired before confirmation |
| `failed` | Terminal failure |
| `attention` | Requires operator review |
| `recovering` | Recovery in progress |
| `refunded` | Funds returned to the wallet |

`attention` is not a failure. It means the swap cannot proceed without a decision from you, so surface it rather than retrying it.

## Recovery

When a swap leaves funds short of the destination, `prepareRecovery` builds a recovery that returns them to the wallet, and `confirmRecovery` authorizes it. Recovery follows the same review-then-confirm shape as a swap, and it is restricted to the wallet's own address and your reviewed assets.

`prepareRecovery(id, recoveryId, source)` takes the swap ID, a recovery ID that you own, and `'origin'` or `'destination'` for the side of the route that holds the funds. `confirmRecovery(id, recoveryId, revision)` authorizes the `revision` of that entry in the swap view's `recoveries`. Both return the swap view, and `tick` then drives the confirmed recovery.

Recovery is available even when `enabled` is `false`, so turning off new swaps does not strand funds from existing ones.

## Errors

| Code | Cause |
| - | - |
| `SWAPS_UNAVAILABLE` | No `trails` gateway was configured |
| `SWAPS_DISABLED` | `enabled` is not `true`, so new swaps are refused |
| `STALE_QUOTE` | The confirmed revision does not match the current quote |
| `QUOTE_EXPIRED` | The quote or recovery review expired before confirmation, so request a new one |
| `DEBIT_PENDING` | A swap, recovery, or transfer on the same chain has an unresolved debit |
| `WALLET_BUSY` | The swap store holds more than 1,000 unresolved operations for the wallet |
| `RPC_CONFIGURATION` | `EvmChainReader` has no valid HTTPS RPC URL for the chain |
| `WALLET_PENDING` | The wallet has not been restored yet |
| `WALLET_DISABLED` | The wallet is disabled |
