FX

Swap one asset for another — estimate, pay the deposit address, then execute.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
CapabilitiesFX is capability-gated
Wallets and BalancesThe source funds come from the corporation wallet

Overview

FX converts one supported asset into another. It is a three-step flow, and the middle step is on-chain:
Wirex quotes the swap and returns a deposit address, the corporation sends the source tokens to that
address itself, then the corporation submits the transaction hash to execute.

POST /api/v1/fx/estimate  →  deposit_address, estimation_id, expiration_time
        ↓
on-chain transfer to deposit_address  →  payment_transaction_hash
        ↓
POST /api/v1/fx           →  trade_id

Wirex does not debit the corporation wallet for FX. Nothing moves until the corporation's own transfer
lands.

Both FX endpoints check the StableExchange capability, not FxTransfer. A corporation with
FxTransfer Active and StableExchange in any other status is refused with 400 and
Capability is not active. Read StableExchange from GET /api/v1/corporations before offering FX
in your interface.


Step 1: Estimate the Swap

POST /api/v1/fx/estimate

Requires TransactionSu, and the StableExchange capability must be Active.

Request body:

{
  "correlation_id": "550e8400-e29b-41d4-a716-446655440000",
  "source_asset_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
  "source_asset_amount": "100.50",
  "destination_asset_address": "0x5c55F314624718019A326F16a62A05D6C6d8C8A2"
}
FieldTypeRequiredDescription
source_asset_addressstringYesToken to swap from. Must be in GET /api/v1/config/tokens, and must be a regular token — see the unwrap note in Step 2
destination_asset_addressstringYesToken to swap to. Must be in GET /api/v1/config/tokens, and must be a regular token
source_asset_amountstringConditionalAmount to sell, as a positive decimal string. Exactly one of this and destination_asset_amount must be present
destination_asset_amountstringConditionalAmount to receive, as a positive decimal string. Exactly one of this and source_asset_amount must be present
correlation_idstringNoYour own tracking identifier (UUID). Generated server-side when omitted, and not returned — supply it if you need to correlate

Response:

{
  "estimation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "deposit_address": "0x1234567890abcdef1234567890abcdef12345678",
  "source_asset_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976",
  "source_asset_amount": "100.50",
  "destination_asset_address": "0x5c55F314624718019A326F16a62A05D6C6d8C8A2",
  "destination_asset_amount": "86.21",
  "trade_rate": "0.8578",
  "expiration_time": "2024-01-01T10:15:00Z"
}
FieldDescription
estimation_idIdentifier required by the execute call
deposit_addressAddress to send source_asset_amount of source_asset_address to
source_asset_amountAmount that must be deposited. Calculated when the request specified the destination amount
destination_asset_amountAmount that will be received. Calculated when the request specified the source amount
trade_rateRate applied to this quote
expiration_timeISO 8601 timestamp after which the quote is no longer valid

Amounts are decimal strings, not numbers. They are converted to the token's smallest unit using the
decimals value from GET /api/v1/config/tokens, so a source amount with more decimal places than the
token supports is rejected.

Code

const response = await fetch(`${baseUrl}/api/v1/fx/estimate`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    source_asset_address: sourceTokenAddress,
    source_asset_amount: '100.50',
    destination_asset_address: destinationTokenAddress
  })
});
const estimate = await response.json();
response = requests.post(
    f"{base_url}/api/v1/fx/estimate",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "source_asset_address": source_token_address,
        "source_asset_amount": "100.50",
        "destination_asset_address": destination_token_address,
    },
)
estimate = response.json()
body, _ := json.Marshal(map[string]string{
    "source_asset_address":      sourceTokenAddress,
    "source_asset_amount":       "100.50",
    "destination_asset_address": destinationTokenAddress,
})

req, _ := http.NewRequest("POST", baseURL+"/api/v1/fx/estimate", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+corporationToken)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var estimate FxEstimateResponse
json.NewDecoder(resp.Body).Decode(&estimate)

Step 2: Send the Source Tokens

Transfer source_asset_amount of source_asset_address from the corporation wallet to
deposit_address, and keep the transaction hash. Step 3 consumes it as
payment_transaction_hash.

Send exactly the quoted amount, before expiration_time. A different amount or a late deposit is
handled by the provider's reconciliation, not by this API.

A corporation holding unified tokens must unwrap before funding the swap. FX operates on
regular tokens — USDC, USDT, EURC — while a corporation's balance is typically held in the unified
WUSD or WEUR. deposit_address expects the regular token named by source_asset_address, so the
funding transaction has to unwrap the unified token and send the underlying token in the same operation.
Depositing a unified token is not matched to the estimate.

Push-to-card does not share this constraint — POST /api/v1/cards/transfer/estimate accepts a
unified token address in tokens and debits it directly. See
Card Transfer.


Step 3: Execute the Swap

POST /api/v1/fx

Requires TransactionSu, and the StableExchange capability must be Active.

Request body:

{
  "estimation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "payment_transaction_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
FieldTypeRequiredDescription
estimation_idstringYesestimation_id from Step 1. Must be a valid UUID
payment_transaction_hashstringYesHash of the deposit transaction from Step 2. 0x-prefixed, 66 characters

Response:

{
  "trade_id": "64120850-73a1-4df5-a074-d463258c9deb"
}
FieldDescription
trade_idInternal trade identifier. Use it to correlate the resulting activity feed entry

What Happens Next

  1. The deposit is matched against the estimate.
  2. The swap settles and the destination asset is credited to the corporation wallet.
  3. The movement appears in GET /api/v1/activity/feed.
  4. A /v2/webhooks/balances notification is delivered for each affected token balance.

There is no FX-specific webhook and no endpoint that returns a trade by trade_id. Track settlement
through the activity feed.


Limitations

  • The pair must be two tokens present in GET /api/v1/config/tokens. There is no endpoint that lists
    swappable pairs — an unsupported pair fails at estimate time.
  • There is no cancel endpoint. An expired estimate is abandoned by requesting a new one.
  • correlation_id is not echoed in either response. Store the mapping from correlation_id to
    estimation_id yourself.

Error Handling

{
  "error_reason": "ErrorGeneral",
  "error_description": "Request failed validation",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "amount", "details": "Only one of source_asset_amount or destination_asset_amount must be provided" }
  ]
}

Validation Errors (400)

Error Details KeyDescriptionResolution
source_asset_addressMissing, malformed, or zeroSend a token address from GET /api/v1/config/tokens
destination_asset_addressMissing, malformed, or zeroAs above
amountEither source_asset_amount or destination_asset_amount must be providedSend one of them
amountOnly one of source_asset_amount or destination_asset_amount must be providedRemove one
source_asset_amountNot a positive decimalSend a positive decimal string, e.g. "100.50"
destination_asset_amountNot a positive decimalAs above
source_asset_addressSource token not found or not supportedThe address parses but is not in the catalogue
destination_asset_addressDestination token not found or not supportedAs above
estimation_idInvalid estimation ID formatSend the UUID returned by the estimate
Error ReasonDescriptionResolution
ErrorGeneralCapability is not activeStableExchange is not Active. Read GET /api/v1/corporations

Server Errors (500)

Error ReasonDescriptionResolution
ErrorGeneralFailed to estimate FX swapThe provider rejected the quote — unsupported pair, or an amount outside its limits
ErrorGeneralFailed to execute FX swapThe deposit was not matched, or the estimate expired. Re-estimate rather than retrying with the same estimation_id
ErrorGeneralFailed to read tokensThe token catalogue was unavailable. Retry

Did this page help you?