Transfers
POST /transfers with { from_currency, currency, amount, recipient } and an Idempotency-Key header. recipient must be an EVM address. The rail is chosen by whether the currencies differ, and the response’s transfer.kind tells you which one you got.
Cross-currency (kind: "swap")
from_currency ≠ currency → one atomic swap delivered straight to the payee (e.g. pay EUR from a USD balance).
The response carries a permit2_witness signing payload, the same shape and signing method as an AMM FX order. Sign it and POST /transfers/{id}/submit { "signature": "0x…" }; the relayer broadcasts it. When the recipient is not the signing wallet, the BlankFX transfer fee is taken from the amount sent, on-chain, before the swap.
States: awaiting_signature → broadcasting → confirmed (or failed).
Same-currency (kind: "self_transfer")
Same token → a non-custodial self-broadcast transfer. The response returns instructions:
transfer of amount (in whole token units; convert with the token’s decimals) from instructions.from to instructions.to yourself, then POST /transfers/{id}/submit { "tx_hash": "0x…" }. BlankFX checks the transaction contains a matching ERC-20 Transfer from one of your wallets before marking the transfer confirmed.
States: created → confirmed (or failed).
Same-currency sends are self-broadcast today: you pay the gas and broadcast the transaction yourself. Cross-currency sends are gasless.
Reading transfers
GET /transfers and GET /transfers/{id} are scoped to the calling key’s account. GET /transfers returns up to the 100 most recent transfers, newest first, with no pagination.
Webhooks transfer.confirmed and transfer.failed fire when a submit reaches those states. See Webhooks.