Same-chain swaps
A same-chain swap settles atomically in a single transaction. The user signs two things (an order that binds the terms and a token permit that authorizes the input transfer), and the solver relays both, pays the gas, and delivers the output from its own inventory.
The user never submits a transaction and never needs ETH or SOL.
The order the user signs
The order is an EIP-712 struct. It binds every term of the swap, so the relaying solver cannot alter what was agreed. The router re-verifies the signature on-chain and recovers it to the user.
const domain = {
name: 'BraleRfqSwap',
version: '1',
chainId, // from /api/base/info
verifyingContract: router, // from /api/base/info
}
const types = {
RfqOrder: [
{ name: 'user', type: 'address' },
{ name: 'inputToken', type: 'address' },
{ name: 'inputAmount', type: 'uint256' },
{ name: 'outputToken', type: 'address' },
{ name: 'minOutputAmount', type: 'uint256' },
{ name: 'solver', type: 'address' },
{ name: 'deadline', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
],
}| Field | How to set it |
|---|---|
user | The swapping address. Must match the caller. |
inputToken / outputToken | Addresses from /api/base/info. Must differ. |
inputAmount | Amount in the input token's decimals. |
minOutputAmount | For par routes, the same amount rescaled to the output token's decimals. For redemption, the minOutBase returned by the quote. |
solver | The solver address from /api/base/info. |
deadline | Unix seconds. A passed deadline is rejected; re-sign. |
nonce | Read nonces(user) from the router. Stale nonces are rejected. |
Alongside the order, the user signs an EIP-2612 permit for the input token, using the usdcName / usdcVersion domain fields from /api/base/info. Split that signature into v, r, s.
POST /api/base/swap
Submits the signed order. The solver re-checks every field against its own view before spending gas, then simulates and relays.
curl -X POST .../api/base/swap \
-H 'content-type: application/json' \
-d '{
"user": "0xYourAddress",
"uiAmount": "25",
"in": "USDC",
"out": "SBC",
"order": {
"user": "0xYourAddress",
"inputToken": "0x...",
"inputAmount": "25000000",
"outputToken": "0xf9FB20B8E097904f0aB7d12e9DbeE88f2dcd0F16",
"minOutputAmount": "25000000000000000000",
"solver": "0x75770a6c6746D8B5B1C82c149B61B5bBc3dEDDe5",
"deadline": "1750000000",
"nonce": "0"
},
"orderSig": "0x...",
"v": 28, "r": "0x...", "s": "0x..."
}'| Field | Required | Description |
|---|---|---|
user | Yes | Swapping address. |
uiAmount | Yes | Human decimal amount of the input. Must match order.inputAmount. |
in | No | Input symbol. Defaults to USDC. |
out | No | Output symbol. Defaults to SBC. |
order | Yes | The struct above, amounts as decimal strings. |
orderSig | Yes | EIP-712 signature over the order. |
v / r / s | Yes | The split EIP-2612 permit signature. |
Set in to a Brale stable to perform a same-chain par swap (Brale ↔ Brale). Set it to USDC for the entry buy. Both settle identically.
The response carries the transaction hash. Poll GET /api/base/confirm?hash=<hash> for inclusion.
What the solver checks before relaying
The submission is rejected, before any gas is spent, if any of these fail. Each maps to a distinct 400 message, so surface the error string to your users directly:
- The order's
user,inputToken,inputAmount,outputToken,minOutputAmount, orsolverdisagrees with the request deadlinehas already passed, ornonceno longer matches the router's current nonce for that user. Both mean re-sign- The solver's output inventory is below the amount, or its router allowance is too low
- The user's wallet doesn't hold
inputAmountof the input token - The amount exceeds the per-swap cap from
/api/base/info - The user is not on the mainnet allowlist
The on-chain router independently re-verifies the solver, nonce, and signature recovery. The server-side checks exist to fail early and legibly, not as the security boundary.
Solana
POST /api/sol/swap performs the same swap natively on Solana. Rather than a permit, it returns a base64 partially-signed transaction that the solver has already co-signed:
curl -X POST .../api/sol/swap \
-H 'content-type: application/json' \
-d '{ "net": "testnet", "user": "<pubkey>", "uiAmount": "25", "in": "USDC", "out": "SBC" }'The user signs the returned transaction and submits it. Because the solver has co-signed, the transfer of input and delivery of output are atomic within that one transaction. Poll GET /api/sol/confirm?sig=<signature> for confirmation, and GET /api/sol/recent?limit=6 for recent activity.
Redemption
Redeeming a Brale stable to USDC uses the same signing flow, with two differences: fetch /api/base/redeem/quote first, and sign the returned minOutBase as minOutputAmount. Then POST the identical payload shape to /api/base/redeem (or /api/sol/redeem).
Because pool depth moves, treat a quote as short-lived and fetch it immediately before signing.
Next steps
- Cross-chain swaps: Base ↔ Solana intents and settlement