This guide explains how to implement token swaps in your application. The process includes: requesting a quote, then executing the swap via the SWAP_FROM_QUOTE operation.
Before executing a swap, you must obtain a quote that indicates the amount of tokens you will receive in exchange, as well as the associated fees.
Endpoint : GET /v1.2/safes/swap/quote
Headers :
Authorization: Bearer <access_token> (required)Query Parameters :
sellTokenAddress (string, required) : Address of the token to sell (0x... format)buyTokenAddress (string, required) : Address of the token to buy (0x... format)amount (string, required) : Amount to sell in human-readable decimal format (e.g., "10.50")chainId (number, optional) : Target chain ID. If omitted, uses the effective chain (X-Blockchain-Id or default chain)safeAddress (string, optional) : Safe address to use as receiver. If omitted, uses the first Safe of the authenticated userprovider (string, optional) : Provider to use. Possible values: "COWSWAP", "1INCH", "BOTH" (default: "BOTH")Request Example :
GET /v1.2/safes/swap/quote?sellTokenAddress=0xcb444e90d8198415266c6a2724b7900fb12fc56e&buyTokenAddress=0xEURE12345678901234567890123456789012345678&amount=100.0&chainId=421614&provider=BOTH Authorization: Bearer <access_token>
Response (200 OK) :
{
"quoteId": "abc123-def456-ghi789",
"sellToken": "0xcb444e90d8198415266c6a2724b7900fb12fc56e",
"sellTokenDecimals": 18,
"buyToken": "0xEURE12345678901234567890123456789012345678",
"buyTokenDecimals": 18,
"sellAmount": "100.0",
"feeAmount": "0.5",
"buyAmount": "9950.25",
"validTo": 1735689600,
"comparisons": {
"cowswap": {
"provider": "COWSWAP",
"buyAmount": "9950.25",
"feeAmount": "0.5",
"validTo": 1735689600
},
"oneinch": {
"provider": "1INCH",
"buyAmount": "9948.50",
"estimatedGas": "150000"
}
},
"bestProvider": "COWSWAP"
}
quoteId : Unique quote identifier. Required for the next stepbuyAmount : Amount of tokens you will receive (after fees)feeAmount : Fees associated with the swapvalidTo : Unix timestamp until which the quote is validbestProvider : Recommended provider for this swap (COWSWAP or 1INCH)Before executing the swap, verify that the user has sufficient tokens to sell. The API automatically checks the balance when requesting a quote, but you can also verify manually via:
Endpoint : GET /v1.2/bcreader/balances?address={safeAddress}
Once the quote is obtained, you can execute the swap using the SWAP_FROM_QUOTE operation.
Endpoint : POST /v1.2/safes/operations
Headers :
Authorization: Bearer <access_token> (required)Body (JSON) :
safeAddress (string, required) : Safe address performing the swapoperations (array, required) : Array containing an operation of type SWAP_FROM_QUOTESWAP_FROM_QUOTE Operation :
type (string, required) : "SWAP_FROM_QUOTE"quoteId (string, required) : Quote identifier obtained in step 1Request Example :
POST /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>
{
"safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
"operations": [
{
"type": "SWAP_FROM_QUOTE",
"quoteId": "abc123-def456-ghi789"
}
]
}
Response (200 OK) :
{
"credentialRequestOptions": {
"challenge": "<base64url_encoded_userOpHash>",
"rpId": "foo.domain",
"userVerification": "required"
}
}
credentialRequestOptions, you must:
navigator.credentials.get() with these options to obtain a credentialPUT /v1.2/safes/operations request with the credential to finalize swap executionEndpoint : PUT /v1.2/safes/operations
Body (JSON) :
credential (object, required) : WebAuthn credential obtained with the options from the previous stepRequest Example :
PUT /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>
{
"credential": {
"id": "AVZs0qRCBSmfThZWu37g...",
"rawId": "AVZs0qRCBSmfThZWu37g...",
"type": "public-key",
"response": {
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ...",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoi...",
"signature": "MEUCIQC..."
}
}
}
Response (200 OK) :
{
"userOpHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"status": "SIGNED"
}
userOpHash : User operation hash. Use it to track the operation statusstatus : Initial operation status (SIGNED means the operation is signed and ready to be executed)Once the operation is submitted, you can track its status. Possible statuses are:
CREATED : Operation created, awaiting signatureSIGNED : Operation signed, ready to be executedEXECUTED : Operation executed on the blockchain, awaiting confirmationCONFIRMED : Operation confirmed with sufficient blocksFAILED : Operation failedEndpoint : GET /v1.2/safes/operations/{userOpHash}
Request Example :
GET /v1.2/safes/operations/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef Authorization: Bearer <access_token>
Response (200 OK) :
{
"userOpHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"status": "CONFIRMED",
"transactionHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z"
}
For CoW Protocol swaps, you can also track the swap order status via BCReader after execution:
Endpoint : POST /v1.1/swap/order/create (BCReader API)
Note : This endpoint is automatically called by the IBEX API after a CoW Protocol swap is signed. You can also call it manually to track swap orders.
Request Body :
safeAddress (string, required) : Safe address that executed the swapuid (string, required for COWSWAP) : CoW Protocol order UIDtxHash (string, required for 1INCH) : 1INCH transaction hashprovider (string, required) : "COWSWAP" or "1INCH"blockchainId (string, required) : Chain ID (e.g., "100" for Gnosis)Request Example :
POST /v1.1/swap/order/create
Content-Type: application/json
Authorization: Bearer <access_token>
{
"safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
"uid": "0xd9c6...69069a35",
"provider": "COWSWAP",
"blockchainId": "100"
}
Response (200 OK) :
{
"message": "Order surveillé créé",
"order": {
"id": 1,
"uid": "0xd9c6...69069a35",
"blockchainId": "100",
"safeAddress": "0xfaa672c06e4abdcb4a1513e9a31c3c498a321468",
"provider": "COWSWAP",
"status": "fulfilled",
"type": "traded",
"executedSell": "8999454",
"executedBuy": "7704892248123160929",
"surplus": "0",
"lastCheckedAt": "2025-11-02T12:10:35.000Z",
"createdAt": "2025-11-02T12:10:34.000Z",
"updatedAt": "2025-11-02T12:10:35.000Z"
}
}
Get Swap Order Status : GET /v1.1/swap/order/{id}
You can retrieve a tracked swap order by UID or transaction hash:
Request Example (by UID) :
GET /v1.1/swap/order/0xd9c6...69069a35 Authorization: Bearer <access_token>
Request Example (by txHash) :
GET /v1.1/swap/order/0xswapTxHashReturnedAfterExecution Authorization: Bearer <access_token>
The IBEX API supports two swap providers:
provider: "BOTH" (default), the API automatically compares both providers and returns the best quote. The bestProvider field in the response indicates which one is recommended.
If the quote has expired, you will receive an error during execution. In this case, request a new quote.
If the user does not have sufficient tokens to sell, the API will return an error. Check the balance before executing the swap.
If the quoteId is invalid or no longer exists, the API will return an error. Make sure to use a recent and valid quote.
GET /v1.2/safes/swap/quote with swap parametersquoteId received in the responsePOST /v1.2/safes/operations with SWAP_FROM_QUOTE operation and the quoteIdnavigator.credentials.get() with the credentialRequestOptionsPUT /v1.2/safes/operations with the credentialGET /v1.2/safes/operations/{userOpHash}status = "CONFIRMED", the swap is completequoteId is unique and can only be used onceCONFIRMED status indicates that the swap is complete and confirmed on the blockchainThis section provides detailed technical information for AI systems integrating the IBEX token swap flow, including architecture patterns, data models, and implementation details.
GET /v1.2/safes/swap/quote
sellTokenAddress, buyTokenAddress, amount, provider (optional)requestQuote() from CoW clientrequestQuote() from 1inch client (if enabled)UserQuote table (if userId available)buyAmountquoteId, amounts, fees, and provider comparisonDatabase Schema - UserQuote:
UserQuote {
id: String (PK, UUID)
userId: String (FK to User)
provider: String (COWSWAP | 1INCH)
sellToken: String (address)
buyToken: String (address)
receiver: String (Safe address)
sellAmount: String (wei, BigInt as string)
buyAmount: String (wei, BigInt as string)
validTo: Int (Unix timestamp)
appData: String (JSON string)
appDataHash: String
kind: String (sell | buy)
partiallyFillable: Boolean
sellTokenBalance: String (erc20 | eth)
buyTokenBalance: String (erc20 | eth)
signingScheme: String (presign | eip712)
createdAt: DateTime
updatedAt: DateTime
}
POST /v1.2/safes/operations
SWAP_FROM_QUOTEquoteId (UUID of stored UserQuote)quoteIdcredentialRequestOptions with challenge = userOpHashSafeOperation record with status CREATEDOperation record with type: SWAP_FROM_QUOTE and data: { quoteId, orderUid? }1INCH Execution Flow:
getSpender())approve(sellToken, spender, sellAmount) : Approve token spendingswap(router, swapData) : Execute swap via 1inch routerCOW_MULTISEND_CALL_ADDRESS for MultiSend delegate callCoW Protocol Execution Flow:
orderUid from quote data (deterministic hash)approve(sellToken, relayer, sellAmount) : Approve token spendingsetPreSignature(orderUid, true) : Pre-sign order for CoW settlementCOW_MULTISEND_CALL_ADDRESS for MultiSend delegate callPUT /v1.2/safes/operations/{userOpHash}
SIGNEDPOST /v1.1/swap/order/create with orderUid and providerCREATED → SIGNED → EXECUTED → CONFIRMEDQuote Response Structure:
{
"quoteId": "uuid",
"sellToken": "0x...",
"sellTokenDecimals": 18,
"buyToken": "0x...",
"buyTokenDecimals": 18,
"sellAmount": "1000000000000000000", // Human-readable amount
"feeAmount": "5000000000000000", // CoW fee (if applicable)
"buyAmount": "2000000000000000000", // Expected output
"validTo": 1234567890, // Unix timestamp
"comparisons": {
"cowswap": {
"provider": "COWSWAP",
"buyAmount": "2000000000000000000",
"feeAmount": "5000000000000000",
"validTo": 1234567890
},
"oneinch": {
"provider": "1INCH",
"buyAmount": "1950000000000000000",
"estimatedGas": "150000"
}
},
"bestProvider": "COWSWAP" // Selected provider
}
Provider Selection Logic:
provider query parameter specified: Use that provider (fail if unavailable)buyAmount, select highestPer-Safe Execution Locks:
safeExecutionLocks Set prevents concurrent execution for same SafeQuote Idempotency:
swapIdemCache Map stores (quoteId → orderUid) mappings${safeAddress}-${quoteId}Status Flow:
CREATED : Operation created, awaiting passkey signatureSIGNED : Passkey signature received, operation ready for bundlerEXECUTED : Bundler submitted userOp to blockchain, awaiting confirmationCONFIRMED : Sufficient block confirmations received (typically 1-2 blocks)FAILED : Operation failed (insufficient funds, slippage exceeded, etc.)Status Polling:
GET /v1.2/safes/operations/{userOpHash} to check statusCONFIRMED or FAILEDreceipt (transaction receipt) when confirmederror (error details) when failedsrc/routes/v1/safes.ts : GET /v1.2/safes/swap/quote endpointsrc/routes/v1/safesOperations.ts : SWAP_FROM_QUOTE operation handlersrc/clients/CoW.ts : CoW Protocol API clientsrc/clients/OneInch.ts : 1inch API clientprisma/schema.prisma : UserQuote model definitionGET /v1.2/safes/swap/quote → returns quoteId, amounts, provider comparisonPOST /v1.2/safes/operations with { type: "SWAP_FROM_QUOTE", quoteId }PUT /v1.2/safes/operations/{userOpHash} with credentialGET /v1.2/safes/operations/{userOpHash} until CONFIRMED