Skip to main content
This tutorial walks you through the steps to test your StableFX integration as a taker. This tutorial contains general instructions on testing your integration with successful trades. If you want to test specific failure scenarios, see Magic Numbers for Testing StableFX.

Prerequisites

Before you begin, ensure that you have:
  • Obtained an API key for StableFX from Circle
  • Obtained testnet USDC in a supported wallet on Arc
  • Installed cURL on your development machine
This guide provides API requests in cURL format, along with example responses. In all examples, replace ${YOUR_API_KEY} with your actual API key and any other placeholder values with the appropriate data for your test.
StableFX uses a single endpoint for all API requests. The base URL is https://api.circle.com/. The StableFX API uses your API key for authentication and to determine which environment to use. The TEST environment executes against Arc testnet and returns mock data.When developing or testing your integration, you should use your TEST API key. When you are ready to move to production, just update your code to use your LIVE API key.

Step 1: Create a trade

To test your integration, you need to create at least one trade. Use the following steps to request a tradable quote, sign the Permit2 typed data, and create the trade.

1.1. Request a tradable quote

Request a tradable quote for a trade from USDC to EURC using the create a quote endpoint. Provide a value for the amount parameter in either the from or to fields, but not both. Set type to tradable to receive an executable quote. Include a recipientAddress for tradable quote requests. The recipientAddress is the wallet address that receives the destination currency (EURC in this example) after the trade settles:
curl --request POST \
  --url https://api.circle.com/v1/exchange/stablefx/quotes \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
  "from": {
    "currency": "USDC",
    "amount": "10"
  },
  "to": {
    "currency": "EURC"
  },
  "tenor": "instant",
  "type": "tradable",
  "recipientAddress": "0x1f531ce3c418bbd830d06138a9e5b5eacfdfb3d6"
}
'
Response The response includes the standard quote fields and a typedData object containing the Permit2 EIP-712 typed data you need to sign before creating the trade.
{
  "id": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5",
  "rate": 0.915,
  "to": {
    "currency": "EURC",
    "amount": "9.15"
  },
  "from": {
    "currency": "USDC",
    "amount": "10"
  },
  "createdAt": "2025-01-01T12:04:05Z",
  "expiresAt": "2025-01-01T12:04:05Z",
  "fee": {
    "currency": "USDC",
    "amount": "0.01"
  },
  "typedData": {
    "domain": {
      "name": "Permit2",
      "chainId": 11155111,
      "verifyingContract": "0xffd21ca8F0876DaFAD7de09404E0c1f868bbf1AE"
    },
    "types": {
      "EIP712Domain": [
        { "name": "name", "type": "string" },
        { "name": "chainId", "type": "uint256" },
        { "name": "verifyingContract", "type": "address" }
      ],
      "Consideration": [
        { "name": "quoteId", "type": "string" },
        { "name": "base", "type": "address" },
        { "name": "quote", "type": "address" },
        { "name": "baseAmount", "type": "uint256" },
        { "name": "quoteAmount", "type": "uint256" },
        { "name": "maturity", "type": "uint256" }
      ],
      "TakerDetails": [
        { "name": "consideration", "type": "Consideration" },
        { "name": "recipient", "type": "address" },
        { "name": "fee", "type": "uint256" }
      ],
      "TokenPermissions": [
        { "name": "token", "type": "address" },
        { "name": "amount", "type": "uint256" }
      ],
      "PermitWitnessTransferFrom": [
        { "name": "permitted", "type": "TokenPermissions" },
        { "name": "spender", "type": "address" },
        { "name": "nonce", "type": "uint256" },
        { "name": "deadline", "type": "uint256" },
        { "name": "witness", "type": "TakerDetails" }
      ]
    },
    "primaryType": "PermitWitnessTransferFrom",
    "message": {
      "permitted": {
        "token": "0x3600000000000000000000000000000000000000",
        "amount": 429000000
      },
      "spender": "0xa8f94168b4981840ba27d423f4ad6332bedee006",
      "nonce": 309585810,
      "deadline": 1770302983,
      "witness": {
        "consideration": {
          "quoteId": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5",
          "base": "0x3600000000000000000000000000000000000000",
          "quote": "0x4200000000000000000000000000000000000000",
          "baseAmount": "10",
          "quoteAmount": "9.15",
          "maturity": 1716153600
        },
        "recipient": "0x1f531ce3c418bbd830d06138a9e5b5eacfdfb3d6",
        "fee": 80000
      }
    }
  }
}

1.2. Sign the typed data

Using a Permit2-compliant EIP-712 compatible wallet or signing library, sign the typedData object returned in the quote response. Use the domain, types, primaryType, and message fields from the response to construct the EIP-712 signature. After signing, you have a hex-encoded signature string (for example, 0x1234...). You use this signature in the next step to create the trade.

1.3. Create the trade

Accept the quote and create a trade on StableFX using the create a trade endpoint. You need to provide the quote ID, your wallet address, the message from the quote response’s typedData.message field, the signature from the previous step, and a randomly generated idempotency key in UUIDv4 format (${randomUUID} in the following example). The following is an example request:
curl --request POST \
  --url https://api.circle.com/v1/exchange/stablefx/trades \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
  "idempotencyKey": "${randomUUID}",
  "quoteId": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5",
  "address": "0x1f531ce3c418bbd830d06138a9e5b5eacfdfb3d6",
  "message": {
    "permitted": {
      "token": "0x3600000000000000000000000000000000000000",
      "amount": 429000000
    },
    "spender": "0xa8f94168b4981840ba27d423f4ad6332bedee006",
    "nonce": 309585810,
    "deadline": 1770302983,
    "witness": {
      "consideration": {
        "quoteId": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5",
        "base": "0x3600000000000000000000000000000000000000",
        "quote": "0x4200000000000000000000000000000000000000",
        "baseAmount": "10",
        "quoteAmount": "9.15",
        "maturity": 1716153600
      },
      "recipient": "0x1f531ce3c418bbd830d06138a9e5b5eacfdfb3d6",
      "fee": 80000
    }
  },
  "signature": "0xsignature"
}
'
Response
{
  "id": "c2558cd1-98b5-4ccd-90b8-96891512af20",
  "contractTradeId": "24",
  "status": "pending_settlement",
  "rate": 0.915,
  "from": {
    "currency": "USDC",
    "amount": "10"
  },
  "to": {
    "currency": "EURC",
    "amount": "9.15"
  },
  "createDate": "2025-01-01T12:04:05Z",
  "updateDate": "2025-01-01T12:04:05Z",
  "quoteId": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5"
}
Verify that the response contains "status": "pending_settlement". This confirms that the trade was created and is awaiting settlement. For a full list of trade states, see Trade States.

Step 2: Fund the trade

Use the following steps to fund the trade onchain.

2.1. Get trades that are ready for funding

Before you send funds onchain, confirm that the trade is ready for funding. To do this, call the get all trades endpoint. Filter the response by the pending_settlement status. Your trade ID should be listed in the response.
curl --request GET \
  --url https://api.circle.com/v1/exchange/stablefx/trades?type=taker&status=pending_settlement \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}'
Response
{
  "id": "c2558cd1-98b5-4ccd-90b8-96891512af20",
  "contractTradeId": "24",
  "status": "pending_settlement",
  "rate": 0.915,
  "from": {
    "currency": "USDC",
    "amount": "10"
  },
  "to": {
    "currency": "EURC",
    "amount": "9.15"
  },
  "createDate": "2025-01-01T12:04:05Z",
  "updateDate": "2025-01-01T12:04:05Z",
  "quoteId": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5"
}

2.2. Get the funding signature data

To use the StableFX API to deliver the funds onchain, you must first sign the funding typed data with an EIP-712 signature. To get the data to sign, call the generate funding presign data endpoint. Your request must include the contract ID of the trade (${stablefx_contract_trade_id} in the example, replaced with the contractTradeId value from the step 1.3 response) and the side of the trade that you are taking. The following is an example request:
curl --request POST \
  --url https://api.circle.com/v1/exchange/stablefx/signatures/funding/presign \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
  "contractTradeIds": ["${stablefx_contract_trade_id}"],
  "type": "taker"
}
'
Response
{
  "typedData": {
    "domain": {
      "name": "Permit2",
      "chainId": 11155111,
      "verifyingContract": "0xffd21ca8F0876DaFAD7de09404E0c1f868bbf1AE"
    },
    "types": {
      "EIP712Domain": [
        {
          "name": "name",
          "type": "string"
        },

        {
          "name": "chainId",
          "type": "uint256"
        },

        {
          "name": "verifyingContract",
          "type": "address"
        }
      ],
      "TokenPermissions": [
        {
          "name": "token",
          "type": "address"
        },

        {
          "name": "amount",
          "type": "uint256"
        }
      ],
      "SingleTradeWitness": [
        {
          "name": "id",
          "type": "uint256"
        }
      ],
      "PermitWitnessTransferFrom": [
        {
          "name": "permitted",
          "type": "TokenPermissions"
        },

        {
          "name": "spender",
          "type": "address"
        },

        {
          "name": "nonce",
          "type": "uint256"
        },

        {
          "name": "deadline",
          "type": "uint256"
        },

        {
          "name": "witness",
          "type": "SingleTradeWitness"
        }
      ]
    },
    "primaryType": "PermitWitnessTransferFrom",
    "message": {
      "permitted": {
        "token": "0xTOKEN",
        "amount": "1000"
      },
      "spender": "0xffd21ca8F0876DaFAD7de09404E0c1f868bbf1AE",
      "nonce": 42,
      "deadline": 1735689600,
      "witness": {
        "id": "10"
      }
    }
  },
  "deliverables": [
    {
      "currency": "USDC",
      "amount": "1000.00"
    }
  ],
  "receivables": [
    {
      "currency": "EURC",
      "amount": "915.00"
    }
  ]
}

2.3. Sign the funding data

Using a Permit2-compliant EIP-712 compatible wallet or signing library, sign the data from the typedData field returned in the previous step. Use the domain, types, primaryType, and message fields to construct the EIP-712 signature. This signature authorizes the Permit2 contract to transfer your funds to the escrow contract. After signing, you have a hex-encoded signature string. You submit this signature in the next step to fund the trade.

2.4. Fund the trade with the StableFX API

The StableFX API can handle the onchain transaction for you. You must submit a permit2-compliant data object along with the signature to the fund trades endpoint. The endpoint allows you to submit either a single object or a batch of objects along with the required signatures. The following is an example request for a single trade:
curl --request POST \
  --url https://api.circle.com/v1/exchange/stablefx/fund \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}' \
  --header 'Content-Type: application/json' \
  --data '
{
  "type": "taker",
  "signature": "0xsignature",
  "permit2": {
    "permitted": {
      "token": "0xTOKEN1",
      "amount": "10"
    },
    "spender": "0xTOKEN1",
    "nonce": 123456,
    "deadline": 1752149700,
    "witness": {
      "id": "123456"
    }
  }
}
'
If the signed data is accepted, the API returns a blank 200 response. This confirms the funding transaction was submitted successfully.
You don’t need to submit the funding transaction through the StableFX API. You can also submit the transaction onchain using your own web3 provider or wallet.

2.5. Confirm the trade is funded

To confirm the trade was funded, call the get a trade by ID endpoint. The trade is funded when the status is taker_funded.
curl --request GET \
  --url https://api.circle.com/v1/exchange/stablefx/trades/${trade_id} \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ${YOUR_API_KEY}'
Verify that the response contains "status": "taker_funded". This confirms that your funding transaction was processed successfully.
After a trade reaches the taker_funded status, the maker funds their side of the trade. The trade then transitions to maker_funded, and finally to settled once the onchain settlement completes and both parties receive their funds. If either side fails to fund in the required window, the trade transitions to breaching, then breached and any funded amounts are returned.

Testing trade batches

To test a batch integration where you fund multiple trades at once, repeat Part 1 to create multiple trades. Get the contractTradeId for each trade that requires funding by calling the get all trades endpoint filtered by the pending_settlement status. Once you have the array of IDs, you can use the same fund trades endpoint to fund multiple trades by submitting the permit2 data for each trade along with the appropriate signatures.