Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.bebop.xyz/llms.txt

Use this file to discover all available pages before exploring further.

swapWithFallback() is BopAMM’s safety net for high-value flow that can’t tolerate a same-block-execution failure (StaleBook, StaleUpdate) or insufficient on-chain liquidity. The function tries the BopAMM leg in a try/catch and falls through on any revert to a whitelisted fallback target with pre-encoded calldata. The standard fallback target is the Bebop RFQ settlement contract. You don’t compose this calldata yourself. The BopAMM /quote endpoint builds the whole thing: it prices the BopAMM leg, fetches a matching RFQ quote, and returns a ready swapWithFallback(...) call in the response tx. You sign and submit it like any other transaction. There’s no separate RFQ request, no EIP-712 signature, and no calldata splicing on your side. You burn more gas than a plain swap(), but you trade reliably even when the registry is behind or the on-chain book is too thin.
Fallback doesn’t bypass the builder requirement. The wrapping transaction still has to land in a block where the registry update is fresh for the BopAMM leg to be attempted - otherwise it reverts on entry and falls through immediately, wasting gas on a no-op BopAMM attempt. Same-block coordination still matters; the fallback is insurance against the BopAMM leg failing despite that coordination. See the quickstart for builder submission.

When to use it

  • High-value flow where a single revert is more expensive than the gas overhead of carrying a fallback.
  • Long-tail assets with sparse maker coverage on BopAMM where the on-chain book may not always reach minAmountOut.
  • Bursty traffic where you want to avoid coordinating around builder windows for every swap.
If you’re routing small, frequent flow on majors, plain swap() is cheaper. Use swapWithFallback selectively rather than as the default.

Get the fallback calldata

Request a quote from the BopAMM /quote endpoint. When your partner key has the RFQ fallback feature, the response’s tx.data is a complete swapWithFallback(...) call.
GET https://api.bebop.xyz/bopamm/ethereum/v1/quote
X-API-Key: <your-api-key>
ParameterNotes
sell_tokens, buy_tokensToken addresses.
sell_amountsInteger base units (buy_amounts is not supported).
taker_addressThe EOA that will send the transaction.
receiver_addressOptional. Defaults to taker_address. Applies to both legs (see caveats).
slippageOptional decimal fraction (for example 0.005). Sets the BopAMM leg’s minAmountOut. Defaults to 0.003.
gaslessMust be false. BopAMM quotes are self-executed; gasless=true is rejected.
amount_in = 1 * 10**USDC_DECIMALS  # sell 1 USDC

quote = httpx.get(
    f"{API_BASE}/quote",
    params={
        "sell_tokens": USDC,
        "buy_tokens": WETH,
        "sell_amounts": str(amount_in),
        "taker_address": account.address,
        "slippage": "0.005",   # 0.5% on the BopAMM leg's minAmountOut
        "gasless": "false",    # required: BopAMM quotes are self-executed
    },
    headers={"X-API-Key": API_KEY},
).json()
The response is a standard Bebop quote. The fields that matter for the fallback path:
{
  "status": "Success",
  "chainId": 1,
  "expiry": 1780382915,
  "slippage": 0.5,
  "approvalType": "Standard",
  "taker": "0xC205Eb18a2B0E05cdCC11961d853F36da232da9f",
  "receiver": "0xC205Eb18a2B0E05cdCC11961d853F36da232da9f",
  "settlementAddress": "0xdB13ad0fcD134E9c48f2fDaEa8f6751a0F5349ca",
  "approvalTarget": "0xdB13ad0fcD134E9c48f2fDaEa8f6751a0F5349ca",
  "requiredSignatures": [],
  "sellTokens": {
    "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": {
      "amount": "1000000", "decimals": 6, "symbol": "USDC"
    }
  },
  "buyTokens": {
    "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": {
      "amount": "504024636724243", "minimumAmount": "501504513540621",
      "decimals": 18, "symbol": "WETH"
    }
  },
  "tx": {
    "to": "0xdB13ad0fcD134E9c48f2fDaEa8f6751a0F5349ca",
    "value": "0x0",
    "data": "0x...calldata..."
  }
}
FieldDescription
tx.toThe BopAmmV2 contract. Same as settlementAddress and approvalTarget.
tx.dataThe calldata for the swap, with the RFQ settlement embedded as the fallback. Send it as-is.
tx.value0x0, or amountIn in hex when selling native ETH.
requiredSignaturesEmpty. Nothing to sign off-chain - the transaction is the order.
buyTokens[].minimumAmountThe BopAMM leg’s minAmountOut, derived from your slippage.
expiryThe shared deadline for both legs (see caveats).
Behind the endpoint, the RFQ leg is quoted with the BopAmmV2 contract as the taker_address, because that contract is the on-chain taker that pulls tokenIn and delivers tokenOut on the fallback path. In self-execution mode the maker’s signature in the RFQ calldata is sufficient and no taker signature is required, which is why requiredSignatures is empty.

Submit it

Approve the BopAmmV2 contract for tokenIn if you haven’t already (see Approve the BopAMM contract), then build, sign, and submit tx through a block builder. The simulation, gas-estimation, and builder mechanics are identical to a plain swap.
to = Web3.to_checksum_address(quote["tx"]["to"])
data = quote["tx"]["data"]
value = int(quote["tx"]["value"], 16)

estimated_gas = web3.eth.estimate_gas(
    {"from": account.address, "to": to, "data": data, "value": value}
)

tx_req = {
    "from": account.address,
    "to": to,
    "data": data,
    "value": value,
    "nonce": web3.eth.get_transaction_count(account.address),
    "gas": int(estimated_gas * 1.5),
    "maxFeePerGas": max_fee,             # from Blocknative; see quickstart
    "maxPriorityFeePerGas": max_priority_fee,
    "chainId": web3.eth.chain_id,
}

signed_tx = web3.eth.account.sign_transaction(tx_req, private_key=PRIVATE_KEY)

response = httpx.post(
    BUILDER_RPC_URL,
    json={
        "jsonrpc": "2.0",
        "method": "eth_sendRawTransaction",
        "params": [f"0x{signed_tx.raw_transaction.hex()}"],
        "id": 1,
    },
)
print(response.json())
Submit through a builder RPC, not the public mempool. The BopAMM leg enforces same-block inclusion behind the operator’s registry update; a mempool submission reverts with StaleBook and falls straight through to the (more expensive) fallback. See Submit through a block builder.

What the calldata encodes

You don’t assemble this yourself, but it helps to know what tx.data decodes to. The on-chain function is:
function swapWithFallback(
    address tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 minAmountOut,
    uint256 expiry,
    address recipient,
    address fallbackTarget,
    bytes calldata fallbackCallData
) external payable;
The first six parameters match plain swap() and apply to the BopAMM leg. The last two carry the fallback the endpoint composed for you.
ParameterApplies toSet by the endpoint to
tokenIn, tokenOut, amountInBopAMM legYour requested pair and sell amount.
minAmountOutBopAMM leg onlybuyTokens[].minimumAmount, derived from your slippage.
expiryBoth legsThe RFQ quote’s expiry, so the on-chain deadline matches the fallback’s.
recipientBopAMM leg onlyYour receiver_address.
fallbackTargetFallback legThe Bebop RFQ settlement contract (tx.to of the RFQ quote).
fallbackCallDataFallback legThe signed RFQ settlement calldata.

Caveats

A few BopAMM-specific behaviors to be aware of:
  • recipient is BopAMM-only. The fallback leg delivers to the receiver baked into fallbackCallData. The endpoint quotes the RFQ leg with your receiver_address and rejects any mismatch, so both legs deliver to the same destination - but it’s your receiver_address that drives it.
  • minAmountOut is BopAMM-only. It does not protect the fallback leg; the fallback’s slippage is whatever the RFQ quote committed to.
  • One shared expiry. The endpoint sets the BopAMM leg’s expiry to the RFQ quote’s expiry (typically ~60-75s) and reports it as the response expiry. Your whole transaction must land before then, including the builder delay.
  • No native ETH out on the fallback path. swapWithFallback reverts with EthOutNotSupportedInFallback if tokenOut is the native ETH sentinel. ETH out works on plain swap() and on the BopAMM leg, just not as the fallback target’s output.
  • Residual sweep. If the fallback leg consumes less than amountIn, any residual tokenIn or ETH is swept back to msg.sender automatically.

Events

The events emitted depend on which leg actually settled:
Leg takenEvent
BopAMM succeededSwap(user, tokenIn, tokenOut, amountIn, amountOut) + one MakerFill per filling maker
Fallback takenSwapFallback(user, tokenIn, tokenOut, amountIn)
Off-chain, watch for SwapFallback to detect when your fallback paths are firing. A spike in fallback usage on a specific asset usually means the BopAMM book is too thin for your typical size on that pair - either size down, route elsewhere, or contact the team to broaden maker coverage.

Whitelisting

The endpoint composes the fallback against the Bebop RFQ settlement contract, which is whitelisted by default. The contract owner controls the whitelist via setFallback(). If you need a different fallback target (for example an internal router of your own), reach out via support - this isn’t a self-serve operation.