Skip to content

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' },
  ],
}
FieldHow to set it
userThe swapping address. Must match the caller.
inputToken / outputTokenAddresses from /api/base/info. Must differ.
inputAmountAmount in the input token's decimals.
minOutputAmountFor par routes, the same amount rescaled to the output token's decimals. For redemption, the minOutBase returned by the quote.
solverThe solver address from /api/base/info.
deadlineUnix seconds. A passed deadline is rejected; re-sign.
nonceRead 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..."
  }'
FieldRequiredDescription
userYesSwapping address.
uiAmountYesHuman decimal amount of the input. Must match order.inputAmount.
inNoInput symbol. Defaults to USDC.
outNoOutput symbol. Defaults to SBC.
orderYesThe struct above, amounts as decimal strings.
orderSigYesEIP-712 signature over the order.
v / r / sYesThe 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, or solver disagrees with the request
  • deadline has already passed, or nonce no 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 inputAmount of 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