Skip to content

Swaps

An instant swap sells collateral into the resting filler book in one on-chain transaction. The API builds the transactions; you sign and broadcast them. Nothing custodial happens on our side.

Build a swap

POST /v1/swaps

Requires trades:write.

json
{
  "chainId": 8453,
  "sellToken": "0xCNGN",
  "buyToken": "0xUSDT",
  "sellAmount": "1000000000",
  "minRate": "600000000000000000000000000",
  "taker": "0xYourWallet"
}

taker is the wallet that will sign and broadcast — it's msg.sender, so it receives the proceeds directly.

If there's liquidity, you get back a swap record and two unsigned transactions to run in order:

json
{
  "data": {
    "id": "swp_…",
    "status": "QUOTED",
    "fillable": true,
    "fillableAmount": "1000000000",
    "proceeds": "612000000",
    "requiredAllowance": "1000300000",
    "transactions": {
      "approval": {
        "to": "0xCNGN",
        "data": "0x095ea7b3…",
        "value": "0",
        "chainId": 8453
      },
      "swap": {
        "to": "0xReactor",
        "data": "0x…",
        "value": "0",
        "chainId": 8453
      }
    }
  }
}
  1. Approval — approve requiredAllowance of the collateral to the reactor. requiredAllowance is the fillable amount plus the reactor's native fee. You can skip this if the reactor already has enough allowance from a prior swap.
  2. Swap — send transactions.swap. This is the reactor's executeBatch call over the matched resting orders. value is always "0" — the fee is taken in the collateral token, not native.

Sign both with your own wallet and broadcast them. The quote is only good while the matched orders rest — treat the returned transactions as short-lived and rebuild if you wait.

Sign and broadcast (TypeScript)

The whole flow with viem — build, approve, swap, report the hash. It's server-to-server, so the signer is a server-side key, never shipped to a browser. The API hands you { to, data, value }; you sign and send them, we never touch your key.

ts
import { createWalletClient, createPublicClient, http, type Hex } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'

const account = privateKeyToAccount(process.env.TAKER_PRIVATE_KEY as Hex)
const wallet = createWalletClient({ account, chain: base, transport: http() })
const pub = createPublicClient({ chain: base, transport: http() })

const API = 'https://api.textilecredit.com/v1'
const headers = {
  Authorization: `Bearer ${process.env.TEXTILE_API_KEY}`,
  'Content-Type': 'application/json',
}

// 1. Build the swap. The API returns two unsigned transactions.
const built = await fetch(`${API}/swaps`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    chainId: 8453,
    sellToken: '0xCNGN',
    buyToken: '0xUSDT',
    sellAmount: '1000000000',
    minRate: '600000000000000000000000000',
    taker: account.address,
  }),
}).then((r) => r.json())

// No resting liquidity at your price — retry later, don't treat it as an error.
if (!built.data.fillable) throw new Error(built.data.reason)

const { approval, swap } = built.data.transactions

// 2. Approve the collateral to the reactor, then wait for it to land. Skip this
//    block if the reactor already has enough allowance from a prior swap.
const approvalHash = await wallet.sendTransaction({
  to: approval.to as Hex,
  data: approval.data as Hex,
  value: BigInt(approval.value),
})
await pub.waitForTransactionReceipt({ hash: approvalHash })

// 3. Send the swap — the reactor's executeBatch over the matched orders.
const txHash = await wallet.sendTransaction({
  to: swap.to as Hex,
  data: swap.data as Hex,
  value: BigInt(swap.value),
})
await pub.waitForTransactionReceipt({ hash: txHash })

// 4. Report the hash so we reconcile the fill and fire your webhook.
await fetch(`${API}/swaps/${built.data.id}/submit`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ txHash }),
})

The approval must confirm before the swap — the reactor pulls the collateral inside executeBatch, so broadcasting both into the same block risks the swap landing first and reverting. Wait for the approval receipt, as above.

No liquidity

If nothing fills at your price, you get 200 with no transactions — retry later rather than treating it as an error:

json
{ "data": { "fillable": false, "reason": "no_liquidity", "liveOrders": 0 } }

Fill-or-kill

The book composes whole resting orders, so fillableAmount can be less than your sellAmount — the remainder stays in your wallet. If you'd rather not execute at all than execute part of it, set requireFullFill: true. When the full amount can't be composed you get 200 with no transactions:

json
{
  "data": {
    "fillable": false,
    "reason": "partial_fill",
    "fillableAmount": "8000",
    "remainingAmount": "997",
    "nextRequiredAmount": "12006"
  }
}

nextRequiredAmount is the fee-inclusive sell amount that would clear one more whole order. For guaranteed exact amounts, see the exact-amount swaps guide.

Idempotency

Send an Idempotency-Key header on POST /v1/swaps. A retry with the same key replays the original response and never builds a second swap. Recommended for any client that retries on network errors.

Report the broadcast

POST /v1/swaps/{id}/submit
{ "txHash": "0x…" }

After you broadcast the swap, tell us the hash. We move the swap to SUBMITTED and reconcile it to FILLED once it settles on-chain (which is also when the webhook fires). Requires trades:write. Idempotent on the hash.

Track a swap

GET /v1/swaps/{id}
json
{
  "data": {
    "id": "swp_…",
    "status": "FILLED",
    "chainId": 8453,
    "txHash": "0x…",
    "fill": {
      "soldAmount": "1000000000",
      "proceeds": "612000000",
      "effectiveRateRay": "612000000000000000000000000",
      "ordersFilled": 2,
      "filledAt": "2026-07-07T12:00:05.000Z"
    }
  }
}

Status flows QUOTED → SUBMITTED → FILLED. The fill block appears once we've reconciled the on-chain settlement. Requires trades:read.

List swaps

GET /v1/swaps?status=FILLED&limit=50&cursor=<id>

Your swap history, newest first, cursor-paginated. nextCursor is null on the last page. Requires trades:read.