# Competition Source: https://docs.bebop.xyz/aggregation-api/api-reference/competition /specs/aggregation-api.json get /v2/competition/{quote_id} Get the solver competition results for a given quote, including which solvers participated and their offered prices. # Quote Source: https://docs.bebop.xyz/aggregation-api/api-reference/quote /specs/aggregation-api.json get /v2/quote Get a solver quote and a ready-to-broadcast settlement transaction. Broadcast tx before expiry. # Supported Chains Source: https://docs.bebop.xyz/aggregation-api/api-reference/supported-chains /specs/aggregation-api.json get /chains Returns a mapping of chain names to chain IDs supported by the Aggregation API. # Introduction Source: https://docs.bebop.xyz/aggregation-api/introduction Route trades through competing solvers for comprehensive token coverage. The Aggregation API connects you to Bebop's network of solvers who compete to find the best execution path across all available decentralized liquidity sources. ## When to Use * You need **broad token coverage** including long-tail assets that private market makers may not quote * You prefer **solver competition** across all available on-chain liquidity rather than a single market maker's inventory * You are willing to configure **slippage tolerance** in exchange for access to deeper, more diverse liquidity pools ## At a Glance | | | | - | - | | **Transport** | REST | | **Authentication** | API key | | **Signing** | Sign the `tx` object from the quote. | | **On-chain transaction** | Broadcast `tx` before the quote expires. | | **Complexity** | Medium - similar to RFQ, plus slippage configuration | ## How It Works Aggregation API clients broadcast the returned transaction for on-chain settlement. | Step | Action | You send | You get back | | - | - | - | - | | 1 | Request a quote | Token pair, amount | Best solver price and a `tx` object | | 2 | Approve the sell token | `approvalTarget` and amount | Token allowance | | 3 | Broadcast on-chain | Signed `tx` object | On-chain settlement | See [Execution](/core-concepts/execution-modes) for the transaction flow. ## Slippage Protection The Aggregation API applies slippage protection to account for price movement between the quote and settlement. This is a key difference from the RFQ API, where quotes are firm with guaranteed execution and guaranteed fill. The `slippage` parameter on the quote request sets the maximum acceptable price deviation (0-50%, up to 2 decimal places). When omitted, the solver determines an appropriate slippage based on the pair and current market conditions. In the quote response: | Field | Description | | - | - | | `buyTokens.{addr}.amount` | Expected fill amount (best case) | | `buyTokens.{addr}.minimumAmount` | Guaranteed minimum after slippage | The settlement transaction reverts if the solver cannot deliver the minimum amount. Any output above that minimum benefits the taker. ## Key Endpoints | Endpoint | Purpose | | - | - | | `GET /jam/{network}/v2/quote` | Request a quote through solver competition | ## Next Steps Make your first trade in 10-15 minutes. Set up ERC-20 approvals before trading. # Quickstart Source: https://docs.bebop.xyz/aggregation-api/quickstart Make your first trade using the Aggregation API, from quote request to on-chain settlement. This guide shows how to request a quote, approve tokens, and broadcast the settlement transaction. **What you will build:** A complete trade flow that uses solver auction liquidity. **Time required:** 10-15 minutes **Prerequisites:** Basic knowledge of EVM wallets and token approvals. ## How It Differs from the RFQ API The RFQ API provides firm market maker quotes with guaranteed execution and fill. The Aggregation API runs a solver auction across on-chain liquidity sources. It supports more tokens but requires a slippage tolerance. | | RFQ API | Aggregation API | | - | - | - | | Liquidity source | Direct market maker quotes | Solver auction across DEXs and aggregators | | Token coverage | Major pairs | Broad, including long-tail assets | | Slippage | None | Configurable tolerance | | Execution | You broadcast the transaction | You broadcast the transaction | | Base URL | `/pmm/{network}/v3/` | `/jam/{network}/v2/` | ## 1. Request a Quote ```text theme={null} GET /jam/{network}/v2/quote ``` Required parameters: | Parameter | Description | Example | | - | - | - | | `sell_tokens` | Token addresses that you sell | `0xC02a...` (WETH) | | `buy_tokens` | Token addresses that you buy | `0xA0b8...` (USDC) | | `sell_amounts` or `buy_amounts` | Amounts in base units. Use one field. | `1000000000000000000` | | `taker_address` | Address that supplies the sell tokens | `0xYourWalletAddress` | Optional parameters: | Parameter | Description | Default | | - | - | - | | `approval_type` | Use `Standard` | `Standard` | | `slippage` | Maximum acceptable slippage from 0% to 50% | Solver value | | `receiver_address` | Address that receives the bought tokens | `taker_address` | * Use `sell_amounts` when you know the exact sell amount. * Use `buy_amounts` when you know the exact buy amount. The following example sells 1 WETH for USDC on Ethereum: ```python theme={null} import httpx from eth_account import Account from web3 import Web3 NETWORK = "ethereum" PRIVATE_KEY = "0x" RPC_URL = "https://eth.llamarpc.com" w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) response = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", "taker_address": account.address, }, ) response.raise_for_status() quote = response.json() ``` ### Understand the Response The response includes the winning solver price and a ready-to-broadcast `tx` object. ```json theme={null} { "requestId": "76c0e766-5894-4f20-8799-515f7f9fe0d3", "status": "Success", "quoteId": "dbddb28f-d459-4a7e-8d53-d29dc74946ab", "chainId": 1, "approvalType": "Standard", "taker": "0x2e7E7c...e9693", "receiver": "0x2e7E7c...e9693", "expiry": 1773827406, "slippage": 0.1, "buyTokens": { "0xA0b869...eB48": { "amount": "2330815183", "minimumAmount": "2328484367", "decimals": 6, "symbol": "USDC" } }, "sellTokens": { "0xC02aaA...6Cc2": { "amount": "1000000000000000000", "decimals": 18, "symbol": "WETH" } }, "settlementAddress": "0xbeb0b0...4ea6", "approvalTarget": "0xC5a350...579a", "solver": "solver-name", "tx": { "chainId": 1, "from": "0x2e7E7c...e9693", "to": "0xbeb0b0...4ea6", "value": "0x0", "data": "0x2143d82c...", "gas": 906396 } } ``` | Field | Description | | - | - | | `quoteId` | Quote identifier for logs and support requests | | `solver` | Solver that won the auction | | `expiry` | Unix timestamp after which the transaction is invalid | | `buyTokens.minimumAmount` | Minimum output that the transaction enforces | | `approvalTarget` | Contract that must have a token allowance | | `tx` | Complete transaction that you sign and broadcast | Always approve the `approvalTarget` from the current quote. Do not use the `settlementAddress` as the approval target. ## 2. Approve Tokens Approve the `approvalTarget` before you broadcast the settlement transaction. ```python theme={null} sell_token = list(quote["sellTokens"].keys())[0] sell_amount = int(quote["sellTokens"][sell_token]["amount"]) approval_target = quote["approvalTarget"] # Check the allowance and approve when necessary. # See the Token Approvals guide for the complete implementation. ``` See [Token Approvals](/core-concepts/token-approvals) for the complete allowance flow. ## 3. Broadcast the Transaction Use the `tx` object from the quote response. Add current fee and nonce values before you sign it. ```python theme={null} tx = quote["tx"] if tx["from"].lower() != account.address.lower(): raise ValueError("The quote taker does not match the signing account") tx |= { "nonce": w3.eth.get_transaction_count(account.address), "gas": max(quote["tx"]["gas"], 500_000), "gasPrice": int(w3.eth.gas_price * 1.5), } signed_tx = w3.eth.account.sign_transaction(tx, account.key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) print(f"Transaction: {tx_hash.hex()}") ``` Broadcast the transaction before `expiry`. The transaction reverts after that time. ## 4. Confirm Settlement Wait for the transaction receipt through your RPC provider. ```python theme={null} receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) if receipt["status"] != 1: raise RuntimeError("Settlement transaction reverted") print(f'Settled in block {receipt["blockNumber"]}') ``` ## Full Example ```python theme={null} import httpx from eth_account import Account from web3 import Web3 PRIVATE_KEY = "0x" NETWORK = "ethereum" RPC_URL = "https://eth.llamarpc.com" w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) response = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", "taker_address": account.address, }, ) response.raise_for_status() quote = response.json() # Approve quote["approvalTarget"] before this step when necessary. tx = quote["tx"] tx |= { "nonce": w3.eth.get_transaction_count(account.address), "gas": max(tx["gas"], 500_000), "gasPrice": int(w3.eth.gas_price * 1.5), } signed_tx = w3.eth.account.sign_transaction(tx, PRIVATE_KEY) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) if receipt["status"] != 1: raise RuntimeError("Settlement transaction reverted") print(f'Transaction: {tx_hash.hex()}') print(f'Settled in block {receipt["blockNumber"]}') ``` ## Next Steps Set up ERC-20 approvals before trading. Compare solver competition with firm market maker pricing. # Security & Audits Source: https://docs.bebop.xyz/audits Security audit reports for Bebop's smart contracts. Below is a list of reports detailing the audits that Bebop's contracts have undergone. | Date | Type | Audited by | Report | | - | - | - | - | | Jun 2026 | Audit | [**Cyfrin**](https://cyfrin.io/) | [**Cyfrin report**](https://bebop-public-images.s3.eu-west-2.amazonaws.com/2026-06-12-cyfrin-bebop-router-v2.0.pdf) | | Dec 2025 | Audit | [Offside Labs](https://offside.io/) | [Offside Labs report](https://bebop-public-images.s3.eu-west-2.amazonaws.com/Bebop-RFQ-Dec-2025-OffsideLabs.pdf) | | Dec 2024 | Audit | [Nethermind](https://www.nethermind.io/) | [Nethermind report](https://bebop-public-images.s3.eu-west-2.amazonaws.com/Nethermind-Bebop-Dec%202024.pdf) | | Apr 2024 | Audit | [ABDK Consulting](https://abdk.consulting/) | [ABDK report](https://bebop-public-images.s3.eu-west-2.amazonaws.com/ABDK_Bebop_Bebop_v_1_0.pdf) | | Nov 2023 | Audit | [Decurity](https://github.com/Decurity/audits/blob/master/Bebop/bebop-jam-audit-report-1.1.pdf) | [Decurity report](https://bebop-public-images.s3.eu-west-2.amazonaws.com/DecurityAudit_November2023.pdf) | | Nov 2023 | Audit | [Pessimistic](https://bebop.xyz/pessimistic-security-analysis.pdf) | [Pessimistic report](https://bebop.xyz/pessimistic-security-analysis.pdf) | | Jul 2023 | Audit | [MixBytes](https://github.com/mixbytes/audits_public/tree/master/Bebop) | [MixBytes report](https://github.com/mixbytes/audits_public/tree/master/Bebop) | | Feb 2023 | Audit | [Zellic](https://www.zellic.io/) | [Zellic report](https://bebop-public-images.s3.eu-west-2.amazonaws.com/ZellicAudit_February2023.pdf) | | May 2022 | Pen Test | [Pen Test Partners](https://www.pentestpartners.com/penetration-testing-services/papa/) | [Pen Test Partners report](https://bebop-public-images.s3.eu-west-2.amazonaws.com/PenTestPartnersAudit_May2022.pdf) | Bebop's smart contracts have evolved over time. The audits listed above may pertain to historical versions, not necessarily the current contracts used in production. # Falling back to RFQ Source: https://docs.bebop.xyz/bopamm/guides/falling-back-to-rfq Use the /quote endpoint to get swapWithFallback calldata that tries BopAMM first and settles via RFQ if the on-chain book can't fill. `BopAmmRouter.swapWithFallback()` is BopAMM's safety net for high-value flow that can't tolerate a same-block-execution failure or insufficient on-chain liquidity. The function tries the BopAMM leg in a try/catch and falls through on **any** revert to an allowlisted fallback adapter with pre-encoded calldata. The standard fallback adapter executes against Bebop RFQ settlement. You don't compose this calldata yourself. The BopAMM `/quote` endpoint builds the whole thing: it prepares 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 `swapWithAllowance()`, but you trade reliably even when the BopAMM leg can't land or the book is too thin. ## 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 and can submit through a supported builder, plain `swapWithAllowance()` 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 `BopAmmRouter.swapWithFallback(...)` call. ```text theme={null} GET https://api.bebop.xyz/bopamm/ethereum/v1/quote Authorization: Bearer ``` | Parameter | Notes | | - | - | | `sell_tokens`, `buy_tokens` | Single token addresses. Use `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` for native ETH at the router boundary. | | `sell_amounts` | Integer base units (`buy_amounts` is not supported yet). | | `taker_address` | The EOA that will send the transaction. | | `receiver_address` | Optional. Defaults to `taker_address`. Applies to both legs (see caveats). | | `slippage` | Optional decimal fraction (for example `0.005`). Sets the router `minAmountOut`. Defaults to `0.003`. | | `approval_type` | Must be `Standard`. | | `gasless` | Must be `false`. BopAMM quotes are self-executed; `gasless=true` is rejected. | | `fee` | Optional integrator fee in bps, capped at 1,000. Charged only when the BopAMM leg succeeds. | | `fee_recipient` | Required when `fee` is non-zero. Receives the integrator share of the router fee. | | `fee` | Optional integrator fee in bps of gross BopAMM output, capped at 1,000 bps. See [Fees](/bopamm/guides/fees). | | `fee_recipient` | Required when `fee` is non-zero. Receives the integrator share. | ```python theme={null} 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% minAmountOut tolerance "approval_type": "Standard", "gasless": "false", # required: BopAMM quotes are self-executed "fee": "25", # optional: 0.25% on successful BopAMM output "fee_recipient": "0x" }, headers={"Authorization": f"Bearer {API_KEY}"}, ).json() ``` ```python theme={null} 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% minAmountOut tolerance "approval_type": "Standard", "gasless": "false", # required: BopAMM quotes are self-executed }, headers={"Authorization": f"Bearer {API_KEY}"}, ).json() ``` The response is a standard Bebop quote. The fields that matter for the fallback path: ```json theme={null} { "requestId": "e0c1f1e4-6d5f-4a9c-8f63-6f47f7c13f1a", "type": "121", "status": "Success", "quoteId": "121-1780382915-e0c1f1e4", "chainId": 1, "expiry": 1780382915, "slippage": 0.5, "approvalType": "Standard", "nativeToken": "ETH", "taker": "0xC205Eb18a2B0E05cdCC11961d853F36da232da9f", "receiver": "0xC205Eb18a2B0E05cdCC11961d853F36da232da9f", "settlementAddress": "0xB098881c587f623FAC85eAe60809bEE7A174CeE7", "approvalTarget": "0xB098881c587f623FAC85eAe60809bEE7A174CeE7", "requiredSignatures": [], "sellTokens": { "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { "amount": "1000000", "decimals": 6, "symbol": "USDC" } }, "buyTokens": { "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { "amount": "504024636724243", "minimumAmount": "501504513540621", "decimals": 18, "symbol": "WETH" } }, "tx": { "to": "0xB098881c587f623FAC85eAe60809bEE7A174CeE7", "value": "0x0", "data": "0x...calldata..." } } ``` | Field | Description | | - | - | | `tx.to` | The BopAMM router. Same as `settlementAddress` and `approvalTarget`. | | `tx.data` | The calldata for `swapWithFallback`, with the RFQ settlement embedded as adapter calldata. Send it as-is. | | `tx.value` | `0x0`, or `amountIn` in hex when selling native ETH. | | `requiredSignatures` | Empty. Nothing to sign off-chain - the transaction is the order. | | `buyTokens[].amount` | Expected BopAMM output after router fees and before slippage. Expected BopAMM output after any router fee and before slippage. | | `buyTokens[].minimumAmount` | The router `minAmountOut`, derived from your net amount and `slippage`. The router `minAmountOut`, derived from the post-fee amount and your `slippage`. | | `expiry` | The shared deadline for both legs (see caveats). | ## Submit it Approve the BopAMM router for `tokenIn` if you haven't already (see [Approve the BopAMM router](/bopamm/quickstart#1-approve-the-bopamm-router)), then build, sign, and submit `tx`. Unlike a plain `swapWithAllowance()`, you don't have to route this through a builder: the fallback is caught inside the same transaction, so a public-mempool submission can still settle via RFQ when the BopAMM leg can't land. ```python theme={null} 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), "gasPrice": web3.eth.gas_price, "chainId": web3.eth.chain_id, } signed_tx = web3.eth.account.sign_transaction(tx_req, private_key=PRIVATE_KEY) tx_hash = web3.eth.send_raw_transaction(signed_tx.raw_transaction) print(Web3.to_hex(tx_hash)) ``` ## 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: ```solidity theme={null} function swapWithFallback( address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, address recipient, uint256 deadline, address adapter, bytes calldata adapterData, uint256 fee ) external payable returns (uint256 amountOut, bool usedFallback); ``` The first six parameters define the BopAMM leg. The last three carry the fallback adapter and router fee configuration. | Parameter | Applies to | Set by the endpoint to | | - | - | - | | `tokenIn`, `tokenOut`, `amountIn` | BopAMM leg | Your requested pair and sell amount. Native ETH uses the sentinel address. | | `minAmountOut` | Both legs | `buyTokens[].minimumAmount`, derived from the post-fee amount and your `slippage`. | | `recipient` | Output delivery | Your `receiver_address`, or `taker_address` when omitted. | | `deadline` | Both legs | The RFQ quote's expiry, so the on-chain deadline matches the fallback's. | | `adapter` | Fallback leg | An allowlisted Bebop fallback adapter. | | `adapterData` | Fallback leg | The signed RFQ settlement calldata. | | `fee` | BopAMM leg | Packed from `fee` and `fee_recipient`, or `0` when omitted. | ## Fees To have the endpoint encode a router fee, pass both `fee` and `fee_recipient` to `/quote`. The endpoint packs the fee into the returned `swapWithFallback` calldata and returns `buyTokens[].amount` / `minimumAmount` net of the BopAMM router fee. ```text theme={null} fee=25 fee_recipient=0x ``` The router fee is charged only when the BopAMM leg succeeds. A configured percentage of the fee goes to the protocol, and the rest goes to `fee_recipient`. Fallback output is not charged by the BopAMM router. ## Caveats A few BopAMM-specific behaviors to be aware of: * **`minAmountOut` protects both paths.** On the BopAMM path it is checked after router fees. On the fallback path it is checked against fallback output. * **Fallback output is not router-fee charged.** Any RFQ terms or fees are part of the embedded RFQ calldata. * **One shared `deadline`.** The endpoint sets the BopAMM leg's `deadline` to the RFQ quote's expiry and reports it as the response `expiry`. Your whole transaction must land before then. * **Native ETH is router-only.** The router maps the native ETH sentinel to WETH internally and unwraps WETH for native output. Pairs that normalize to the same token are invalid. * **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 taken | Event | | - | - | | BopAMM succeeded | Core `Swapped(sender=router, ...)`, one core `MakerFill` per filling maker, and `FeeCharged` when a non-zero direct router fee is used. | | Fallback taken | Router `SwapFallback(user, adapter, tokenIn, tokenOut, amountIn, amountOut)`. | 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. # Fees Source: https://docs.bebop.xyz/bopamm/guides/fees Add optional integrator fees to BopAMM router swaps and /quote-generated fallback calldata. BopAMM router fees let integrators take a share of successful BopAMM output. They are optional: pass `0` when you do not want a fee. Fees apply only when the BopAMM leg succeeds. Fallback output from `swapWithFallback` is not fee-charged by the BopAMM router. ## How fees work The requested `fee` is the total fee on gross BopAMM output, in basis points, capped at 1,000 bps. The router applies its configured protocol share to that total fee, sends the remainder to your `fee_recipient`, and delivers the remaining output to the swap recipient. For example, if the gross BopAMM output is `1,000` units and `fee = 25`, the total router fee is `2.5` units. A configured protocol share applies to that `2.5`, and the rest goes to your fee recipient. The router checks `minAmountOut` after fees. If you set a fee yourself, calculate your minimum from the post-fee amount. ```python theme={null} amount_after_fee = amount_out * (10_000 - FEE_BPS) // 10_000 min_amount_out = amount_after_fee * (10_000 - SLIPPAGE_BPS) // 10_000 ``` ## Pack the router fee `BopAmmRouter` takes a single packed `uint256` fee argument. | Bits | Value | | - | - | | Low 160 bits | Integrator fee recipient address. | | High bits | Fee bps of gross BopAMM output. | ```python theme={null} from web3 import Web3 def pack_router_fee(fee_bps: int, fee_recipient: str | None) -> int: if fee_bps == 0: return 0 if fee_recipient is None: raise ValueError("fee_recipient is required when fee_bps is non-zero") return (fee_bps << 160) | int(Web3.to_checksum_address(fee_recipient), 16) FEE_BPS = 25 # 0.25% of gross BopAMM output FEE_RECIPIENT = "0x0000000000000000000000000000000000000005" FEE = pack_router_fee(FEE_BPS, FEE_RECIPIENT) ``` ## Add a fee to router calldata When you build `swapWithAllowance` calldata yourself, pass the packed fee as the final argument. ```python theme={null} amount_out = core.functions.quote(USDC, WETH, amount_in).call( block_identifier=state["stateBlock"], state_override=state_override, ) amount_after_fee = amount_out * (10_000 - FEE_BPS) // 10_000 min_amount_out = amount_after_fee * (10_000 - SLIPPAGE_BPS) // 10_000 swap = router.functions.swapWithAllowance( USDC, WETH, amount_in, min_amount_out, account.address, deadline, FEE, ) ``` The same packed `FEE` argument is used when composing `swapWithFallback` manually. ## Add a fee through `/quote` For `/quote`-generated `swapWithFallback` calldata, pass `fee` and `fee_recipient` as query parameters. The endpoint packs them into `tx.data` for you. ```python theme={null} 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", "approval_type": "Standard", "gasless": "false", "fee": "25", "fee_recipient": FEE_RECIPIENT, }, headers={"Authorization": f"Bearer {API_KEY}"}, ) quote.raise_for_status() quote = quote.json() ``` Quote response buy amounts are net of the total router fee. `buyTokens[].minimumAmount` already reflects fee plus slippage, and the returned `tx.data` carries the packed router fee. # Introduction Source: https://docs.bebop.xyz/bopamm/introduction Coordinated, oracle-priced AMM liquidity from Bebop's market maker network. You need BopAMM API access to call authenticated endpoints and receive production support. BopAMM (Block Oracle Priced AMM) is Bebop's Ethereum execution primitive: a coordinated, oracle-priced AMM updated by participating market makers and built for best execution at size. ## Contracts BopAMM now has two Ethereum contracts: | Contract | Address | Use | | - | - | - | | `BopAmm` | `0xB09AAA8933626d7E4C48D65dAd2D77021CFBCA9a` | Core ERC-20 pool for `quote`, push-payment `swap`, and `swapWithCallback`. | | `BopAmmRouter` | `0xB098881c587f623FAC85eAe60809bEE7A174CeE7` | Taker and integrator entrypoint for allowance swaps, native ETH, router fees, and fallback swaps. | The core `BopAmm` contract is ERC-20 only. Native ETH is supported at the router boundary with the sentinel address `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`. ## Swapping There are two integration surfaces. ### Direct Pool Integration Use the core `BopAmm` contract when you are building a router, solver, or contract integration that can manage token movement itself. ```solidity theme={null} function quote(address tokenIn, address tokenOut, uint256 amountIn) external view returns (uint256 amountOut); function swap( address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, address recipient, uint256 deadline ) external returns (uint256 amountOut); function swapWithCallback( address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, address recipient, uint256 deadline, bytes calldata callbackData ) external returns (uint256 amountOut); ``` `swap` is a push-payment entrypoint. The caller must transfer `amountIn` of `tokenIn` to `BopAmm` before calling `swap`, similar to how Uniswap-style pools consume tokens already sent to the pool. `BopAmm` does not pull the taker's input with allowance on this path. `swapWithCallback` is a flash-style entrypoint. `BopAmm` delivers `tokenOut` first, then calls `msg.sender.bopAmmSwapCallback(...)`; the callback must provide enough `tokenIn` before returning so maker payment can complete. Direct pool integration does not support native ETH and does not apply router fees. ### Router Integration Use `BopAmmRouter` for normal taker and integrator flows. ```solidity theme={null} function swapWithAllowance( address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, address recipient, uint256 deadline, uint256 fee ) external payable returns (uint256 amountOut); function swapWithFallback( address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, address recipient, uint256 deadline, address adapter, bytes calldata adapterData, uint256 fee ) external payable returns (uint256 amountOut, bool usedFallback); ``` `swapWithAllowance` pulls ERC-20 input from `msg.sender` after the taker approves the router. For native ETH input, pass the native ETH sentinel as `tokenIn` and send `msg.value == amountIn`. For native ETH output, pass the sentinel as `tokenOut`; the router unwraps WETH and sends ETH to `recipient`. `swapWithFallback` first tries the BopAMM leg. If that leg reverts or cannot satisfy `minAmountOut` after fees, the router calls an allowlisted fallback adapter with pre-encoded Bebop RFQ calldata. See [Falling back to RFQ](/bopamm/guides/falling-back-to-rfq). ## Fees `BopAmmRouter` supports integrator fees on successful BopAMM output. The router `fee` argument packs an integrator fee recipient and fee bps into a single `uint256`: The router charges the total fee before `minAmountOut` is checked. A configured percentage of that fee goes to the protocol, and the rest goes to the integrator recipient. Fallback output is not fee-charged by the BopAMM router. The `/quote` endpoint accepts `fee` and `fee_recipient`, packs them into the returned `swapWithFallback` calldata, and returns output amounts net of the BopAMM router fee. Custom router integrations can also pack the fee directly in calldata: ```python theme={null} def pack_router_fee(recipient: str, fee_bps: int) -> int: if fee_bps == 0: return 0 if fee_bps > 1_000: raise ValueError("BopAMM router fees are capped at 1,000 bps") return (fee_bps << 160) | int(Web3.to_checksum_address(recipient), 16) ``` Both router functions accept an optional packed `fee` argument for integrator fees. See [Fees](/bopamm/guides/fees). ## Builder Support Plain BopAMM settlement should be submitted through a builder that receives BopAMM updates. Current supported builders are: * Titan * BuilderNet * Quasar * Bombora `swapWithFallback` can still settle through the fallback path when the BopAMM leg misses. ## API Surface | Endpoint | Description | | - | - | | `GET /assets` | Public asset metadata: asset ID, token address, tick size, lot size, decimals, and operator address. | | `GET /state` | Current aggregated books and state overrides for `eth_call` quoting and simulation. | | `WS /state` | Binary protobuf stream of the same state snapshots. | | `GET /quote` | Returns a standard Bebop quote response with `BopAmmRouter.swapWithFallback` calldata. | Quote, simulate, and submit a `swapWithAllowance` transaction. Add optional integrator fees to BopAMM router swaps. Get API access and direct support from the Bebop team. # Quickstart Source: https://docs.bebop.xyz/bopamm/quickstart Fetch the live BopAMM state, quote and simulate a swap mid-block, and submit it through a block builder. This guide walks you through a complete BopAMM swap on Ethereum: approving the router, fetching the current state, quoting against the live on-chain book, simulating the router swap to catch reverts, and submitting it through a supported block builder. You'll sell 1 USDC for WETH. **What you'll build:** A USDC to WETH `swapWithAllowance` transaction against BopAMM, quoted and simulated mid-block, then submitted through a block builder. **Time required:** 15-20 minutes **Prerequisites:** Basic EVM knowledge and web3.py, a BopAMM API key, a funded signing key, and the ability to submit transactions through a supported builder RPC. ## Setup Install `web3`, `eth-account`, and `httpx`, then define the constants and contract handles used throughout. This quickstart uses `BopAmm.quote()` to quote the swap and `BopAmmRouter.swapWithAllowance()` to execute it. ```python theme={null} import httpx from eth_account import Account from web3 import Web3 from web3.exceptions import ContractLogicError API_KEY = "" PRIVATE_KEY = "0x" API_BASE = "https://api.bebop.xyz/bopamm/ethereum/v1" RPC_URL = "https://ethereum-rpc.publicnode.com" # Send the signed transaction to a builder route that supports BopAMM. BUILDER_RPC_URL = "" # EIP-1559 fee estimates. BLOCKNATIVE_GAS_URL = "https://api.blocknative.com/gasprices/blockprices" BLOCKNATIVE_CONFIDENCE = 75 BOPAMM_CORE_ADDRESS = Web3.to_checksum_address("0xB09AAA8933626d7E4C48D65dAd2D77021CFBCA9a") BOPAMM_ROUTER_ADDRESS = Web3.to_checksum_address("0xB098881c587f623FAC85eAe60809bEE7A174CeE7") NATIVE_ETH = Web3.to_checksum_address("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") WETH = Web3.to_checksum_address("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") USDC = Web3.to_checksum_address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") WETH_DECIMALS = 18 USDC_DECIMALS = 6 SLIPPAGE_BPS = 50 # 0.5% slippage tolerance FEE_BPS = 0 # no integrator fee in this quickstart FEE_RECIPIENT = "0x0000000000000000000000000000000000000000" CORE_ABI = [ { "name": "quote", "type": "function", "stateMutability": "view", "inputs": [ {"name": "tokenIn", "type": "address"}, {"name": "tokenOut", "type": "address"}, {"name": "amountIn", "type": "uint256"}, ], "outputs": [{"name": "amountOut", "type": "uint256"}], } ] ROUTER_ABI = [ { "name": "swapWithAllowance", "type": "function", "stateMutability": "payable", "inputs": [ {"name": "tokenIn", "type": "address"}, {"name": "tokenOut", "type": "address"}, {"name": "amountIn", "type": "uint256"}, {"name": "minAmountOut", "type": "uint256"}, {"name": "recipient", "type": "address"}, {"name": "deadline", "type": "uint256"}, {"name": "fee", "type": "uint256"}, ], "outputs": [{"name": "amountOut", "type": "uint256"}], } ] web3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) core = web3.eth.contract(address=BOPAMM_CORE_ADDRESS, abi=CORE_ABI) router = web3.eth.contract(address=BOPAMM_ROUTER_ADDRESS, abi=ROUTER_ABI) def pack_router_fee(recipient: str, fee_bps: int) -> int: if fee_bps == 0: return 0 if fee_bps > 1_000: raise ValueError("BopAMM router fees are capped at 1,000 bps") return (fee_bps << 160) | int(Web3.to_checksum_address(recipient), 16) def amount_after_router_fee(amount_out: int, fee_bps: int) -> int: return amount_out - (amount_out * fee_bps // 10_000) FEE = pack_router_fee(FEE_RECIPIENT, FEE_BPS) amount_in = 1 * 10**USDC_DECIMALS # sell 1 USDC ``` Keep `API_KEY` and `PRIVATE_KEY` out of source. Load them from environment variables (for example with `os.environ` and `python-dotenv`) rather than hardcoding them. ## 1. Approve the BopAMM router Before swapping ERC-20 input, the router needs permission to pull your USDC. Approve `BopAmmRouter` for the scope of this demo (10 USDC). This is the standard check-and-approve pattern. See [Token Approvals](/core-concepts/token-approvals) for the canonical helper and the max-vs-exact tradeoffs. ```python theme={null} ERC20_ABI = [ { "constant": True, "inputs": [ {"name": "_owner", "type": "address"}, {"name": "_spender", "type": "address"}, ], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "type": "function", }, { "constant": False, "inputs": [ {"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}, ], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "type": "function", }, ] DEMO_SCOPE = 10 * 10**USDC_DECIMALS # approve up to 10 USDC for this demo usdc = web3.eth.contract(address=USDC, abi=ERC20_ABI) current = usdc.functions.allowance(account.address, BOPAMM_ROUTER_ADDRESS).call() if current < DEMO_SCOPE: approve_tx = usdc.functions.approve(BOPAMM_ROUTER_ADDRESS, DEMO_SCOPE).build_transaction({ "from": account.address, "nonce": web3.eth.get_transaction_count(account.address), "gasPrice": web3.eth.gas_price, }) signed = web3.eth.account.sign_transaction(approve_tx, private_key=PRIVATE_KEY) tx_hash = web3.eth.send_raw_transaction(signed.raw_transaction) web3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) print(f"Approved 10 USDC for {BOPAMM_ROUTER_ADDRESS}") ``` A few BopAMM-specific notes: * The approval target is the **BopAmmRouter contract** (`BOPAMM_ROUTER_ADDRESS`). The core pool does not pull taker funds on the router path. * **Native ETH in** needs no approval. Send `msg.value == amountIn` and pass the sentinel address `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` as `tokenIn`. * **Cross-pair swaps** (for example WETH to WBTC) don't require a USDC approval. Intermediate USDC stays inside the contracts. This demo approves a fixed 10 USDC to keep the allowance scoped. Programmatic integrators usually approve the maximum amount once per token to avoid re-approving on every trade. See [Token Approvals](/core-concepts/token-approvals#approval-strategies). ## 2. Fetch the live state You can get the current snapshot two ways: a call to `GET /state`, or the streaming WebSocket, which pushes a fresh snapshot whenever a new aggregated book is available. Both carry the same data: the snapshot block, the aggregated `books`, and the `state_overrides` you need to quote against the live book before the on-chain update lands. Use REST for a single swap and the stream for continuous quoting. The operator exposes the current aggregated book at `GET /state`. ```text theme={null} GET https://api.bebop.xyz/bopamm/ethereum/v1/state Authorization: Bearer ``` ```python theme={null} response = httpx.get(f"{API_BASE}/state", headers={"Authorization": f"Bearer {API_KEY}"}) response.raise_for_status() state = response.json() state_contract = Web3.to_checksum_address(state["contract"]) state_override = {state_contract: {"stateDiff": state["state_overrides"]}} print( f"state_block={state['stateBlock']} " f"target_block={state['targetBlock']} " f"overrides={len(state['state_overrides'])}" ) ``` Example response (abridged): ```json theme={null} { "stateBlock": 25203086, "stateBlockTimestamp": 1780382800, "targetBlock": 25203087, "targetBlockTimestamp": 1780382812, "contract": "0xda7afeed021eafc1c1af9c362de477dad0396b81", "state_overrides": { "0x99131f24ab938b63c7f74c5439520f0db9ce5184595a612398a2fe92fa728537": "0x01d02000000000000000000000000001dfff027fff0000000000000000000000", "0x99131f24ab938b63c7f74c5439520f0db9ce5184595a612398a2fe92fa728536": "0x6a19e77b02008187ff8000000000000000000000000000000000000000000000" }, "books": { "1": { "bids": [["2006.7400000000", "0.0320000000"]], "asks": [["2007.3200000000", "4.0950000000"], ["2007.4200000000", "4.0950000000"]] } } } ``` The operator streams `StateSnapshot` protobuf messages over a binary WebSocket. On connect, the server sends the current cached snapshot immediately, so you don't wait for the next tick. ```text theme={null} wss://api.bebop.xyz/bopamm/ethereum/v1/state Authorization: Bearer ``` The wire format is protobuf. Save the schema as `state_ws.proto` and generate a Python stub: ```protobuf state_ws.proto theme={null} syntax = "proto3"; package bopamm; message Level { string price = 1; // decimal string, USDC per base unit string size = 2; // decimal string, base-token units } message AggregatedBook { repeated Level bids = 1; // sorted descending by price repeated Level asks = 2; // sorted ascending by price } message StateSnapshot { uint64 state_block = 1; // latest confirmed block uint64 target_block = 2; // next block the signed books target map books = 6; // assetId -> aggregated book map state_overrides = 7; // slot hex -> value hex uint64 state_block_ts = 8; // unix seconds for state_block uint64 target_block_ts = 9; // unix seconds for target_block } ``` ```bash theme={null} protoc --python_out=. state_ws.proto ``` This produces `state_ws_pb2.py` next to the proto file. Subscribe, skip the metadata-only frames, and take the first snapshot with populated levels: ```python theme={null} import asyncio import websockets import state_ws_pb2 as state_pb WS_URL = "wss://api.bebop.xyz/bopamm/ethereum/v1/state" async def get_latest_snapshot(): async with websockets.connect( WS_URL, additional_headers={"Authorization": f"Bearer {API_KEY}"} ) as ws: while True: snap = state_pb.StateSnapshot() snap.ParseFromString(await ws.recv()) if any(b.bids or b.asks for b in snap.books.values()): return snap snap = asyncio.run(get_latest_snapshot()) # The stream carries the per-block book and overrides but not the state # override target, which doesn't change block to block. Read it once from # GET /state and reuse it for every frame. state_contract = Web3.to_checksum_address( httpx.get(f"{API_BASE}/state", headers={"Authorization": f"Bearer {API_KEY}"}).json()["contract"] ) # Normalize into the same shape the REST path produces, so the quote, # simulate, and submit steps below are identical. state = { "stateBlock": snap.state_block, "state_overrides": dict(snap.state_overrides), } state_override = {state_contract: {"stateDiff": state["state_overrides"]}} print(f"state_block={state['stateBlock']} overrides={len(state['state_overrides'])}") ``` The first cached frame the server sends on connect is sometimes metadata-only: the `books` map has entries but the `bids` and `asks` arrays are empty until the next live tick. Iterate until you see populated levels (or apply a short timeout) before quoting. On the stream the fields arrive as `state_block`, `target_block`, `books`, and `state_overrides`, plus `state_block_ts` and `target_block_ts` (unix seconds). The `contract` field is not in the stream, so it's read once from `GET /state` above and reused for every frame. Key fields: | Field | Description | | - | - | | `stateBlock` | The block the snapshot is valid for. Use it as `block_identifier` when you quote and simulate. | | `stateBlockTimestamp` | Unix timestamp of `stateBlock`. | | `targetBlock` | The next block the signed books target. | | `targetBlockTimestamp` | Unix timestamp expected for `targetBlock`. | | `contract` | The address to apply `state_overrides` to in `eth_call` stateDiff. | | `state_overrides` | Slot-to-value pairs you pass as `stateDiff` to evaluate against the live book. | | `books` | The aggregated book per asset id, with `bids` and `asks` as `[price, size]` pairs. This quickstart quotes through `quote()` rather than reading `books` directly. | ## 3. Get a quote `quote()` is a view function on the core pool that walks the book and returns the expected output for a given input. To quote against the latest book at any point in the block, apply the `state_overrides` as a `stateDiff` against the `contract` from the snapshot. ```python theme={null} gross_amount_out = core.functions.quote(USDC, WETH, amount_in).call( block_identifier=state["stateBlock"], state_override=state_override, ) amount_out = amount_after_router_fee(gross_amount_out, FEE_BPS) amount_after_slippage = amount_out * (10_000 - SLIPPAGE_BPS) // 10_000 print(f"Quoted amount after fee: {amount_out / 10**WETH_DECIMALS} WETH") # Quoted amount after fee: 0.000498176673375445 WETH ``` For native ETH at the router boundary, quote the core pool with WETH. The router normalizes the native ETH sentinel to WETH before calling `BopAmm`. ## 4. Add a fee (optional) To charge an integrator fee on the BopAMM leg, set `FEE_BPS` and `FEE_RECIPIENT` before building the router calldata: ```python theme={null} FEE_BPS = 25 # 0.25% FEE_RECIPIENT = "0x" FEE = pack_router_fee(FEE_RECIPIENT, FEE_BPS) ``` The fee is taken from successful BopAMM output before `minAmountOut` is checked, so quote and simulate with `amount_after_router_fee(...)` as shown above. A configured percentage of the fee goes to the protocol; the rest goes to `FEE_RECIPIENT`. ## 5. Simulate and size gas Before broadcasting, run the router swap as an `eth_call` against the same override to catch reverts without spending gas, then estimate gas with the override to set a real gas limit. ```python theme={null} deadline = web3.eth.get_block("latest")["timestamp"] + 120 swap = router.functions.swapWithAllowance( USDC, WETH, amount_in, amount_after_slippage, account.address, deadline, FEE, ) sim_kwargs = dict( transaction={"from": account.address, "value": 0}, block_identifier=state["stateBlock"], state_override=state_override, ) try: simulated_out = swap.call(**sim_kwargs) except ContractLogicError as exc: raise SystemExit(f"Swap simulation reverted: {exc}") estimated_gas = swap.estimate_gas(**sim_kwargs) print(f"Simulated amount out: {simulated_out / 10**WETH_DECIMALS} WETH") print(f"Estimated gas: {estimated_gas}") # Simulated amount out: 0.000498176673375445 WETH # Estimated gas: 166077 ``` Pass the same `state_override` to `estimate_gas`. A plain `estimate_gas` at the chain head can revert before the current book has landed on-chain. ## 6. Submit through a block builder This is the step that differs from RFQ and Aggregation. A plain BopAMM swap settles reliably when a builder that supports BopAMM includes it in the same block as the matching book update. Current supported builders are: * Titan * BuilderNet * Quasar * Bombora For a plain `swapWithAllowance`, submit directly to a supported builder RPC. A public-mempool submission only goes through when a supporting builder happens to win the block. To drop this constraint, use [`swapWithFallback`](/bopamm/guides/falling-back-to-rfq), which settles via RFQ in the same transaction when the BopAMM leg can't land. Fetch EIP-1559 fees from Blocknative: ```python theme={null} gas_resp = httpx.get(BLOCKNATIVE_GAS_URL) estimates = gas_resp.json()["blockPrices"][0]["estimatedPrices"] ``` Each estimate pairs a `confidence` (the chance of inclusion in the next block) with the fees that buy it. Higher confidence costs more: ```json theme={null} { "unit": "gwei", "blockPrices": [ { "blockNumber": 25203087, "baseFeePerGas": 1.021795326, "estimatedPrices": [ { "confidence": 99, "maxPriorityFeePerGas": 0.098, "maxFeePerGas": 2.1 }, { "confidence": 95, "maxPriorityFeePerGas": 0.094, "maxFeePerGas": 2.1 }, { "confidence": 90, "maxPriorityFeePerGas": 0.089, "maxFeePerGas": 2.1 }, { "confidence": 80, "maxPriorityFeePerGas": 0.079, "maxFeePerGas": 2.1 }, { "confidence": 70, "maxPriorityFeePerGas": 0.069, "maxFeePerGas": 2.1 } ] } ] } ``` Pick the cheapest estimate that still clears your confidence threshold, then build and sign the transaction. Pad the estimated gas (here by 50%) to absorb book changes between simulation and inclusion: ```python theme={null} # Lowest estimate that still meets the confidence threshold. estimate = min( [e for e in estimates if int(e["confidence"]) >= BLOCKNATIVE_CONFIDENCE], key=lambda e: int(e["confidence"]), default=estimates[0], ) max_fee = int(float(estimate["maxFeePerGas"]) * 1e9) max_priority_fee = int(float(estimate["maxPriorityFeePerGas"]) * 1e9) # With a 75% threshold, the 80% estimate wins: max_fee=2.1 gwei, priority=0.079 gwei. tx = swap.build_transaction({ "from": account.address, "nonce": web3.eth.get_transaction_count(account.address), "gas": int(estimated_gas * 1.5), "maxFeePerGas": max_fee, "maxPriorityFeePerGas": max_priority_fee, "chainId": web3.eth.chain_id, "value": 0, }) signed_tx = web3.eth.account.sign_transaction(tx, private_key=PRIVATE_KEY) raw_tx = Web3.to_hex(signed_tx.raw_transaction) ``` Then send the raw transaction to the builder RPC: ```python theme={null} response = httpx.post( BUILDER_RPC_URL, json={ "jsonrpc": "2.0", "method": "eth_sendRawTransaction", "params": [raw_tx], "id": 1, }, ) response.raise_for_status() print(response.json()) ``` The builder returns a JSON-RPC result with the transaction hash: ```json theme={null} { "jsonrpc": "2.0", "result": "0xa1a46dff1438f59eb332adb778c6b67fd9b8d3ad39968047bc8ad72fe25677ec", "id": 1 } ``` The `result` is your transaction hash. Track it on a block explorer to confirm it landed in the target block. Can't tolerate a same-block miss? The `/quote` endpoint returns ready `swapWithFallback` calldata that tries BopAMM first and settles via RFQ if the BopAMM leg can't land. See [Falling back to RFQ](/bopamm/guides/falling-back-to-rfq). ## Next steps Add optional integrator fees to router calldata or `/quote`. Use push-payment `swap` or callback settlement directly against `BopAmm`. Get ready `swapWithFallback` calldata from the `/quote` endpoint. # Brand Kit Source: https://docs.bebop.xyz/brand-kit Bebop brand colors, logos, and usage guidelines. Brand assets for partners and integrators. Reference the official color palette. ## Color palette Bebop's palette has twelve colors split into two tiers. The primary row is used across the core brand, and the secondary row supports illustrations, accents, and UI states. ### Primary
#171717
Black
#00E64F
Green
#D4FF00
Yellow
#F83DDA
Pink
#8B13CC
Purple
#2F50FF
Dark Blue
### Secondary
#6A6A6A
Grey
#004618
Dark Green
#FF8300
Orange
#D80500
Red
#FF8CFF
Light Pink
#35F3FF
Light Blue
## Logos Use the appropriate variant depending on the background. Do not modify, recolor, or distort the logo.
Bebop brandmark, white Bebop horizontal logo, white Bebop wordmark, white
Bebop vertical logo, white
Dark background
Bebop brandmark, black Bebop horizontal logo, black Bebop wordmark, black
Bebop vertical logo, black
Light background
[Download](https://bebop-public-images.s3.eu-west-2.amazonaws.com/Bebop_Logo.zip). ## Usage guidelines When using Bebop brand assets, please keep the following in mind: always maintain clear space around the logo, never place it on visually busy backgrounds, and use the correct color variant for the surface it sits on. If you need additional formats or have questions, reach out at [hello@bebop.xyz](mailto:hello@bebop.xyz). # API Overview Source: https://docs.bebop.xyz/build Navigate the Bebop API suite and start integrating. Bebop exposes five APIs organized into three categories, pick the one that matches what you need. ## Trading Execute swaps on behalf of your users. Request firm quotes from private market makers. Guaranteed execution, guaranteed fill, single and multi-token trades. Coordinated, oracle-priced AMM liquidity from Bebop's market maker network. Solver auction across multiple liquidity sources. Broader token coverage with slippage tolerance. Earn revenue on every trade by adding a partner fee to your quote requests. ## Market data Real-time streaming prices via WebSocket. Use for pre-trade discovery, routing decisions, or indicative pricing in your UI. ## Trade history Look up historical trades and transaction details across all supported chains. # Authentication Source: https://docs.bebop.xyz/core-concepts/authentication How to obtain and use an API key for Bebop's trading and data APIs. All production usage of Bebop's APIs requires an API key. Without one, trading endpoints return widened demo-mode quotes and some endpoints are inaccessible entirely. ## Obtaining an API Key Request an API key through the [Bebop support page](/support). The team will provision a key and send it to you directly. ## Passing Your API Key For HTTP requests (RFQ, Aggregation, Trade History), authenticate with the `Authorization` header using your API key as a Bearer token: ```python theme={null} YOUR_API_KEY = "YOUR_API_KEY" headers = {"Authorization": f"Bearer {YOUR_API_KEY}"} ``` With curl: ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" ... ``` For WebSocket connections (Price API), send the `Authorization` header with your API key as a Bearer token on the connection handshake: ```python theme={null} import websockets ws_url = "wss://api.bebop.xyz/pmm/ethereum/v3/pricing" async with websockets.connect( ws_url, additional_headers={"Authorization": f"Bearer {YOUR_API_KEY}"}, ) as ws: ... ``` ## What Requires Authentication | API | Without key | With key | | - | - | - | | **Price API** | Cannot connect | Full access to real-time price streams | | **RFQ API** | Demo mode - quotes are widened, heavily rate limited and not suitable for production | Firm, tight quotes ready for execution | | **Aggregation API** | Demo mode - quotes are widened, heavily rate limited and not suitable for production | Firm, tight quotes ready for execution | | **Trade History API** | Public lookups by wallet address only | Full access - see all trades attributed to your integration | Demo mode is useful for testing your integration flow end-to-end before going live, but the prices returned are not competitive. ## Rate Limits Rate limits apply per API key. If you have concerns about throughput for your use case, reach out via the [support page](/support). ## Best Practices Keep your API key secret. Do not expose it in client-side code, public repositories, or browser network requests. All Bebop API calls should be made from your backend. If you suspect your key has been compromised, contact the Bebop team immediately via the [support page](/support) to rotate it. # Transaction Submission Source: https://docs.bebop.xyz/core-concepts/execution-modes Sign and broadcast transactions from the public RFQ and Aggregation APIs. The public RFQ and Aggregation APIs use client-submitted settlement. The quote APIs do not use an execution mode selector. ## EVM transactions EVM quotes return a ready-to-broadcast `tx` object. Your integration signs the transaction and broadcasts it through your RPC provider. The transaction sender pays the network fee. **EVM flow:** Quote → Approve → Sign `tx` → Broadcast → Confirm Use the `Standard` approval type. Approve the `approvalTarget` from the quote before you broadcast the transaction. ## Solana RFQ transactions Solana RFQ quotes return `solana_tx` and `blockhash`. Your integration signs `solana_tx` and broadcasts it before the blockhash expires. **Solana flow:** Quote → Sign `solana_tx` → Broadcast → Confirm See the [RFQ API quickstart](/rfq-api/quickstart) and [Aggregation API quickstart](/aggregation-api/quickstart) for complete examples. # Monetization Source: https://docs.bebop.xyz/core-concepts/monetization Add a partner fee to quote requests and collect revenue on every trade. This guide covers how to include a partner fee in your quote requests. Fee collection works differently depending on which API you use. For business context - fee models, payment terms, and practical considerations - see [Monetize](/monetize). If you've agreed on a **flat fee** with Bebop, fees are applied automatically. You do not need to pass the `fee` parameter. ## How fees work Pass `fee` (in basis points) on any quote request. One basis point is 0.01%, so `fee=25` means 0.25%. The fee is deducted from the buy side of the trade - the taker receives slightly less of the buy token. The taker's sell amount stays the same. | Parameter | Type | Range | Description | | - | - | - | - | | `fee` | integer | 0-500 (0%-5%) | Partner fee in basis points. Deducted from the buy side of the trade. | | `fee_recipient` | string | - | Wallet address that receives the fee on-chain. Required when `fee` is set (Aggregation API only). | The two APIs differ in how fees are collected: | | Aggregation API | RFQ API | | - | - | - | | **Collection** | On-chain, atomic | Off-chain, monthly | | **Parameters** | `fee` + `fee_recipient` | `fee` | | **Payout** | Instant to your wallet | Monthly invoice via Bebop | | **Tracking** | On-chain transfers | [Trade History API](/trade-history-api/introduction) | ## RFQ API The RFQ API tracks fees off-chain. Pass `fee` (bps) on the quote request. Fees are converted to the chain's native token and attributed to your integration via your API key. ```python theme={null} import httpx resp = httpx.get( "https://api.bebop.xyz/pmm/ethereum/v3/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", "taker_address": "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693", "fee": 25, # 0.25% }, headers={"Authorization": "Bearer YOUR_API_KEY"}, ) quote = resp.json() # The buyTokens amount already reflects the 25 bps deduction. print(quote) ``` ### Fee reconciliation Use the [Trade History API](/trade-history-api/quickstart) to monitor trades attributed to your integration at any time. Authenticate with your API key and the results are scoped to your integration. See [Authentication](/core-concepts/authentication) for details. For a complete walkthrough including pagination, see the [Trade History quickstart](/trade-history-api/quickstart). ## Aggregation API The Aggregation API collects fees atomically as part of the trade transaction. Pass `fee` (bps) and `fee_recipient` (your wallet address) on the quote request. The settlement contract transfers the fee directly to the recipient on-chain when the trade settles. There is no reconciliation step - you receive the fee in your wallet with every trade. ```python theme={null} import httpx resp = httpx.get( "https://api.bebop.xyz/jam/ethereum/v2/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", "taker_address": "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693", "fee": 25, # 0.25% "fee_recipient": "0xYourWalletAddress", }, ) quote = resp.json() # The buyAmounts already reflects the 25 bps deduction. # The fee will be transferred to fee_recipient on settlement. print(quote) ``` # Settlement & Smart Contracts Source: https://docs.bebop.xyz/core-concepts/settlement-smart-contracts Bebop's on-chain settlement architecture - contracts, approvals, and how trades are executed. All Bebop trades settle on-chain through audited smart contracts. Bebop never takes custody of user funds - the settlement contracts verify signatures, enforce price minimums, and atomically transfer tokens between parties. ## Settlement Architecture Bebop uses separate contract systems for each API: ### RFQ API The RFQ API can use the router or the settlement contract. The route and integration determine the contract: | Contract | Address | Purpose | | - | - | - | | BebopRouter | `0xBeb0009ACa35087ce7cCF11637E24dd1Aad3bf2A` | Routes eligible RFQ trades | | BebopSettlement | `0xbbbbbBB520d69a9775E85b458C58c648259FAD5F` | Settles RFQ trades directly | An EVM one-to-one quote can target either contract. Always use `approvalTarget`, `settlementAddress`, and `tx.to` from the quote response. ### Aggregation API The Aggregation API uses a two-contract architecture: | Contract | Address | Purpose | | - | - | - | | Balance Manager (`approvalTarget`) | `0xC5a350853E4e36b73EB0C24aaA4b8816C9A3579a` | Holds token approvals and manages taker balances during settlement | | JamSettlement (`settlementAddress`) | `0xbeb0b0623f66bE8cE162EbDfA2ec543A522F4ea6` | Executes the swap - verifies signatures, enforces minimums, transfers tokens | These are two distinct contracts. Takers must approve the **balance manager**, not the settlement contract. The quote response handles this for you via the `approvalTarget` field - see [Token Approvals](/core-concepts/token-approvals) for the full flow. ### Chain-Specific Addresses The addresses above apply to all supported EVM chains except zkSync Era (chain ID 324), which uses different contract addresses. Always use the values from the quote response rather than hardcoding. See [Supported Chains](/supported-chains) for the full list of networks. ## How Settlement Works ### RFQ and Aggregation transactions For EVM, a quote includes the complete settlement transaction in `tx`. For Solana, an RFQ quote includes `solana_tx` and `blockhash`. You sign and broadcast the returned transaction. The applicable settlement program checks the trade terms and transfers the tokens atomically. If any condition fails, the entire transaction reverts - no partial state changes, no stuck funds, the contracts enforce the same guarantees and Bebop never takes custody of your tokens. ## Approvals Token approvals target the `approvalTarget` address from the quote response. The general pattern is the same across both APIs - see [Token Approvals](/core-concepts/token-approvals) for the full check-and-approve flow. | Approval type | Use | | - | - | | `Standard` | Public RFQ and Aggregation transactions | For direct EVM trades, call ERC-20 `approve()` before you broadcast the settlement transaction. # Token Approvals Source: https://docs.bebop.xyz/core-concepts/token-approvals Set up ERC-20 token approvals so Bebop's router and settlement contracts can execute your trades. Before settlement, the applicable contract needs permission to move your sell tokens. Call `approve()` on the ERC-20 token contract for the required amount. This applies to both the RFQ API (router and settlement contracts) and Aggregation API (balance manager). ## Which Contract to Approve Every quote response includes an `approvalTarget` field - this is the address you approve. Always use this value rather than hardcoding. **Never hardcode the approval address.** Always read `approvalTarget` from the quote response. RFQ API quotes may return the router contract, settlement contract, or Permit2, depending on the flow. The Aggregation API uses a separate balance manager contract for approvals - it is not the same as the settlement contract. For Aggregation API, approving the settlement contract will lead to loss of funds. ## Check Existing Allowance Before approving, check whether the token already has sufficient allowance. This avoids unnecessary gas spend on redundant approval transactions. ```python theme={null} from web3 import Web3 ERC20_ABI = [ { "constant": True, "inputs": [ {"name": "_owner", "type": "address"}, {"name": "_spender", "type": "address"}, ], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "type": "function", }, { "constant": False, "inputs": [ {"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}, ], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "type": "function", }, ] w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com")) def check_allowance(owner: str, token_address: str, spender: str) -> int: token = w3.eth.contract( address=Web3.to_checksum_address(token_address), abi=ERC20_ABI, ) return token.functions.allowance( Web3.to_checksum_address(owner), Web3.to_checksum_address(spender), ).call() ``` ## Approve if Needed If the current allowance is less than the trade amount, submit an approval transaction. Most integrators approve the maximum amount (`2^256 - 1`) so they only need to do this once per token. ```python theme={null} from eth_account import Account PRIVATE_KEY = "0x" def ensure_allowance(account, token_address: str, spender: str, required: int): current = check_allowance(account.address, token_address, spender) if current >= required: return # already approved token = w3.eth.contract( address=Web3.to_checksum_address(token_address), abi=ERC20_ABI, ) tx = token.functions.approve( Web3.to_checksum_address(spender), 2**256 - 1, # max approval ).build_transaction({ "from": account.address, "nonce": w3.eth.get_transaction_count(account.address), "gasPrice": w3.eth.gas_price, }) signed = w3.eth.account.sign_transaction(tx, private_key=PRIVATE_KEY) tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction) w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) ``` ## Using It with Quotes After receiving a quote from either API, check and approve before signing and broadcasting: ```python theme={null} quote = response.json() sell_token = list(quote["sellTokens"].keys())[0] sell_amount = int(quote["sellTokens"][sell_token]["amount"]) approval_target = quote["approvalTarget"] account = Account.from_key(PRIVATE_KEY) ensure_allowance(account, sell_token, approval_target, sell_amount) # Now proceed to sign and broadcast... ``` ## Approval Strategies **Max approval (recommended for programmatic integrators):** Approve `2^256 - 1` once per token. Saves gas on subsequent trades since you never need to re-approve. This is the standard approach for solvers and aggregators. **Exact approval:** Approve only the exact trade amount each time. More conservative, but costs gas on every trade. Some compliance-sensitive integrations prefer this. ## Approval type The public RFQ and Aggregation APIs use `approval_type=Standard`. For BebopRouter trades, see [Permit2 for self-execution](/rfq-api/guides/router-migration-guide#permit2-self-execution). Always approve the `approvalTarget` from the current quote. # FAQ Source: https://docs.bebop.xyz/faq Frequently asked questions about Bebop. Visit the Bebop Help Center for answers to common questions about trading, integrations, and supported chains. help.bebop.xyz # Home Source: https://docs.bebop.xyz/home Institutional-grade DeFi liquidity infrastructure, ready for your next product.

Build with Bebop

Institutional-grade DeFi liquidity infrastructure, ready for your next product.

Learn how Bebop works, explore use cases, and find the right integration path. Core concepts, API quickstarts, guides, and reference documentation.
## Get in touch Questions, partnerships, or integration support. Follow us on X for updates. Connect with us on LinkedIn.
# How Bebop Works Source: https://docs.bebop.xyz/how-bebop-works Two liquidity models, one platform - understand how Bebop works and choose the right integration. ## Two Liquidity Models, One Platform Bebop operates two distinct liquidity networks, each optimized for different execution requirements: Professional trading firms that quote directly from their own on-chain inventory. They provide guaranteed execution and guaranteed fill - what you see is what you get. Orders are automatically split across multiple market makers for optimal pricing on large trades. Algorithmic agents that compete to find the best execution path across all available decentralized liquidity sources. They optimize routing in real-time to deliver the best possible price for any token pair, including long-tail assets. Both models use request-for-quote architecture. You request a price, and the selected integration path submits settlement on-chain. Public RFQ and Aggregation API clients broadcast the returned transaction. Bebop never takes custody of user tokens. The key difference is **who provides liquidity and how**: * **Market Makers:** Professional firms quote from their own on-chain inventory with firm prices * **Solvers:** Algorithms route through decentralized liquidity sources ## Choose Your Integration | | **RFQ API - Market Maker Liquidity** | **Aggregation API - Solver Liquidity** | | - | - | - | | **Execution model** | Firm, guaranteed pricing | Best-effort pricing; solvers optimize routing across all available liquidity | | **Settlement** | On-chain via audited smart contracts; Bebop never holds custody | On-chain via audited smart contracts; Bebop never holds custody | | **Optimal for** | Supported token pairs where guaranteed pricing and fills matter most | Long-tail tokens, unusual pairs, or when maximizing token coverage is priority | | **Trade-off** | Limited to tokens where market makers actively provide liquidity | Pricing subject to on-chain slippage based on solver's execution path and your preferences | **Use RFQ API when:** * You need guaranteed fills at guaranteed prices * You are trading supported pairs at size * Speed and reliability are critical **Use Aggregation API when:** * You need broad token coverage including long-tail assets * You want to tap into all available on-chain liquidity sources * You prefer configuring slippage limits to access wider liquidity **Use both when:** * You want to automatically route each trade to its optimal liquidity source * You need both guaranteed execution and fill for core pairs and broad coverage for everything else Still unsure? [Get in touch with our team](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_how_bebop_works). # Monetize Source: https://docs.bebop.xyz/monetize Earn revenue on every trade your users make through Bebop. Every trade that flows through Bebop can generate revenue for your product. There are two ways to collect fees - choose whichever fits your workflow. ## Two approaches Tell Bebop your desired fee rate and we apply it automatically to every quote under your API key. No code changes required - just agree on a rate and it takes effect immediately. Pass a `fee` parameter (in basis points) on each quote request. This gives you full control - vary the fee by token pair, chain, trade size, or user tier. See the [implementation guide](/core-concepts/monetization) for details. ## How it works Either agree on a flat fee with Bebop, or add the `fee` parameter to your quote requests for per-trade control. The fee is deducted from the buy side of the trade - the taker receives slightly less of the buy token. The taker's sell amount stays the same. For convenience, fees are converted to the chain's native token. How you receive fees depends on the API. With the **Aggregation API**, fees are transferred directly to your wallet on-chain as part of each trade - no invoicing required. With the **RFQ API**, monitor your fee data via the [Trade History API](/trade-history-api/introduction), reconcile monthly, and invoice Bebop. ## Works across all trading APIs The fee parameter is available on both the [RFQ API](/rfq-api/introduction) and the [Aggregation API](/aggregation-api/introduction), so you can monetize from day one. The fee collection mechanism differs between APIs - see the [implementation guide](/core-concepts/monetization) for details. ## Practical considerations Most integrators charge between 5 and 50 bps depending on their product and audience. Higher fees widen the quoted spread, which can make your pricing less competitive if your users compare across venues. Starting lower and adjusting based on volume data is a common approach. Ready to add fees? The technical guide covers the API parameter and code examples. # Pricing Source: https://docs.bebop.xyz/price-api/api-reference/websockets/pricing # Pricing Unlisted Pairs Source: https://docs.bebop.xyz/price-api/guides/pricing-unlisted-pairs Estimate prices for token pairs that aren't directly quoted by constructing synthetic rates through a common quote token. Not every token pair has a direct price in the stream. If you need a price for WETH/WBTC but only WETH/USDC and WBTC/USDC are available, you can construct a **synthetic pair** by routing through the common quote token. This is useful for pre-trade estimation - checking whether a trade is worth pursuing before requesting a firm quote from the RFQ API. **Use case:** You're a solver or aggregator routing a WETH/WBTC swap. The Price API stream doesn't have a direct WETH/WBTC pair, but it does have WETH/USDC and WBTC/USDC. Instead of requesting a firm quote blind, you estimate the synthetic price from streamed depth to decide if Bebop is competitive for this route. ## How It Works Say you want an indicative price for buying WBTC with WETH, but the stream only has WETH/USDC and WBTC/USDC. You construct the synthetic rate in two legs: 1. **Sell WETH for USDC** - use the WETH/USDC bids (you're selling the base) 2. **Buy WBTC with USDC** - use the WBTC/USDC asks (you're buying the base) The effective WETH/WBTC price is the ratio of the two VWAP estimates. Using [VWAP on each leg](/price-api/guides/vwap-estimation) rather than top-of-book gives you a size-aware synthetic price, since tier cadence (the size available at each level) often differs between pairs. ### Step-by-Step Breakdown Build a map of all pairs in the stream. For the two base tokens you care about, find quote tokens they both share using `find_common_quotes`. If multiple common quotes exist, prefer the one with the deepest liquidity on both legs. Stablecoins like USDC tend to have the most depth. Use the [VWAP algorithm](/price-api/guides/vwap-estimation) on each leg independently. For a buy, consume bids on leg 1 (selling your input token) and asks on leg 2 (buying your target token). Divide leg 1 VWAP by leg 2 VWAP. This gives you the effective cross rate at your target trade size. ## Finding the Common Quote Token The Price API streams all available pairs on the network. To find routing opportunities, scan the stream for pairs that share a common quote token. Common quote tokens vary by chain - look at what's actually in the stream rather than hardcoding assumptions. On most EVM chains, stablecoins like USDC tend to appear frequently as quote tokens, but the stream is the source of truth. ```python theme={null} def find_common_quotes( pairs: dict[tuple[str, str], dict], base_a: str, base_b: str, ) -> list[str]: """ Find quote tokens shared between two base tokens. Args: pairs: Dict mapping (base_addr, quote_addr) to depth data. base_a: Address of the first token (e.g. WETH). base_b: Address of the second token (e.g. WBTC). Returns: List of quote token addresses that both base tokens are paired with. """ quotes_a = {q for (b, q) in pairs if b == base_a} quotes_b = {q for (b, q) in pairs if b == base_b} return list(quotes_a & quotes_b) ``` ## Calculating the Synthetic Price Once you've identified a common quote token, estimate the VWAP on each leg and combine them. ```python theme={null} def estimate_synthetic_price( leg1_levels: list[tuple[float, float]], leg2_levels: list[tuple[float, float]], target_notional: float, direction: str, # "buy" or "sell" (relative to the synthetic pair) ) -> tuple[float, float, float]: """ Estimate a synthetic price through a common quote token using VWAP on each leg. For buying base_b with base_a (e.g. buy WBTC with WETH): - Leg 1: sell base_a for quote (use bids) - Leg 2: buy base_b with quote (use asks) For selling base_b for base_a (e.g. sell WBTC for WETH): - Leg 1: sell base_b for quote (use bids) - Leg 2: buy base_a with quote (use asks) Args: leg1_levels: Depth levels for the first leg. leg2_levels: Depth levels for the second leg. target_notional: Trade size in quote token terms (e.g. USDC). direction: "buy" or "sell" relative to the synthetic pair. Returns: (synthetic_price, leg1_vwap, leg2_vwap) """ if direction == "buy": # Leg 1: sell base_a -> quote (consume bids) leg1_vwap, leg1_unfilled = estimate_vwap(leg1_levels, target_notional, "sell") # Leg 2: buy base_b <- quote (consume asks) leg2_vwap, leg2_unfilled = estimate_vwap(leg2_levels, target_notional, "buy") else: # Leg 1: sell base_b -> quote (consume bids) leg1_vwap, leg1_unfilled = estimate_vwap(leg1_levels, target_notional, "sell") # Leg 2: buy base_a <- quote (consume asks) leg2_vwap, leg2_unfilled = estimate_vwap(leg2_levels, target_notional, "buy") if leg1_vwap == 0 or leg2_vwap == 0: return 0.0, 0.0, 0.0 synthetic_price = leg1_vwap / leg2_vwap return synthetic_price, leg1_vwap, leg2_vwap ``` ## Full Example Combining synthetic pair estimation with the Price API stream from the [Quickstart](/price-api/quickstart): ```python theme={null} import asyncio import httpx import websockets from bebop_pb2 import BebopPricingUpdate # # type: ignore BASE_A = "WETH" # token you're selling BASE_B = "WBTC" # token you're buying TARGET_NOTIONAL = 100_000 # estimate price for $100k trade NETWORK = "ethereum" API_KEY = "" WSS_URL = ( f"wss://api.bebop.xyz/pmm/{NETWORK}/v3/pricing" f"?format=protobuf" f"&expiry_type=standard" ) def address_to_hex(b: bytes) -> str: return "0x" + b.hex() def to_levels(flat: list[float]) -> list[tuple[float, float]]: it = iter(flat) return list(zip(it, it, strict=True)) def estimate_vwap( levels: list[tuple[float, float]], target_notional: float, intent: str ) -> tuple[float, float]: sorted_levels = sorted(levels, key=lambda lv: lv[0], reverse=(intent == "sell")) remaining = target_notional total_base = 0.0 total_quote = 0.0 for price, size in sorted_levels: if remaining <= 0: break level_notional = price * size fill_notional = min(level_notional, remaining) fill_base = fill_notional / price total_quote += fill_notional total_base += fill_base remaining -= fill_notional if total_base == 0: return 0.0, target_notional return total_quote / total_base, remaining def find_common_quotes( pairs: dict[tuple[str, str], dict], base_a: str, base_b: str ) -> list[str]: quotes_a = {q for (b, q) in pairs if b == base_a} quotes_b = {q for (b, q) in pairs if b == base_b} return list(quotes_a & quotes_b) def estimate_synthetic_price( leg1_levels: list[tuple[float, float]], leg2_levels: list[tuple[float, float]], target_notional: float, direction: str, ) -> tuple[float, float, float]: if direction == "buy": leg1_vwap, _ = estimate_vwap(leg1_levels, target_notional, "sell") leg2_vwap, _ = estimate_vwap(leg2_levels, target_notional, "buy") else: leg1_vwap, _ = estimate_vwap(leg1_levels, target_notional, "sell") leg2_vwap, _ = estimate_vwap(leg2_levels, target_notional, "buy") if leg1_vwap == 0 or leg2_vwap == 0: return 0.0, 0.0, 0.0 return leg1_vwap / leg2_vwap, leg1_vwap, leg2_vwap # Resolve token addresses resp = httpx.get(f"https://api.bebop.xyz/pmm/{NETWORK}/v3/tokenlist", timeout=10.0) tokens = {t["symbol"]: t for t in resp.json().get("tokens", {})} addr_a = tokens[BASE_A]["address"].lower() addr_b = tokens[BASE_B]["address"].lower() async def main(): async with websockets.connect( WSS_URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}, ping_interval=20, ping_timeout=10, max_size=2**21, ) as ws: print(f"Connected - looking for synthetic {BASE_A}/{BASE_B} via common quote\n") async for blob in ws: update = BebopPricingUpdate() update.ParseFromString(blob) # Build pair map from this snapshot pair_map: dict[tuple[str, str], dict] = {} for pair in update.pairs: base_hex = address_to_hex(pair.base).lower() quote_hex = address_to_hex(pair.quote).lower() pair_map[(base_hex, quote_hex)] = { "bids": to_levels(list(pair.bids)), "asks": to_levels(list(pair.asks)), } # Find common quote tokens common = find_common_quotes(pair_map, addr_a, addr_b) if not common: continue quote_addr = common[0] # use first common quote leg1 = pair_map.get((addr_a, quote_addr)) leg2 = pair_map.get((addr_b, quote_addr)) if not leg1 or not leg2: continue synthetic, vwap_a, vwap_b = estimate_synthetic_price( leg1["bids"], leg2["asks"], TARGET_NOTIONAL, "buy" ) if synthetic > 0: print( f" {BASE_A}/{BASE_B} synthetic (via common quote): {synthetic:.6f}\n" f" Leg 1 ({BASE_A}/quote) VWAP: {vwap_a:.2f}\n" f" Leg 2 ({BASE_B}/quote) VWAP: {vwap_b:.2f}\n" ) asyncio.run(main()) ``` Example output for a \$100,000 WETH/WBTC synthetic estimate: ```json theme={null} { "pair": "WETH/WBTC", "target_notional": 100000, "synthetic_price": 0.031427, "leg1": { "pair": "WETH/quote", "vwap": 2327.58 }, "leg2": { "pair": "WBTC/quote", "vwap": 74062.19 } } ``` Synthetic prices are **indicative estimates** based on streamed depth. The actual execution price from a firm quote may differ. ## Key Considerations * **Use VWAP on each leg.** Top-of-book prices can be misleading because tier cadence (the size available at each level) often differs between pairs. VWAP gives you a size-aware estimate. * **Check for sufficient depth on both legs.** If either leg returns unfilled notional, the synthetic estimate is unreliable at that size. * **Pick the deepest routing token.** When multiple common quotes exist, prefer the one with the most liquidity on both sides. * **The stream is the source of truth for available pairs.** Common quote tokens vary by chain - discover them from the data rather than hardcoding. # Estimating VWAP Source: https://docs.bebop.xyz/price-api/guides/vwap-estimation Estimate the indicative execution price for a given trade size using the Price API stream - without requesting a firm quote. Solvers and aggregators often need to estimate what price they'd get for a specific trade size before committing to a firm RFQ quote. The Price API stream provides enough depth data to calculate a **Volume-Weighted Average Price (VWAP)** locally, giving you a reliable indicative price at any size. **Use case:** You're a solver or aggregator evaluating whether to bid on an intent. Instead of requesting a firm quote (which has rate limits and expiry), you estimate the execution price from the live stream to decide if the trade is worth pursuing. ## How It Works The Price API streams order book levels as `(price, size)` pairs, sorted best-first. To estimate the execution price for a target trade size, you walk through these levels from best to worst, accumulating volume until you've filled the target amount. The VWAP is the notional-weighted average price across all levels you'd consume. ## The Algorithm ### Step-by-Step Breakdown For a **buy**, sort asks lowest-first (cheapest prices first). For a **sell**, sort bids highest-first (best bid prices first). For each level, calculate the notional value (`price × size`). Take the lesser of the level's notional and your remaining target - this handles partial fills on the last level. Track total base tokens filled and total quote spent. The ratio gives you the VWAP. If `remaining > 0` after exhausting all levels, the stream doesn't have enough depth for your size. You may want to fall back to a firm quote or split across sources. ```python theme={null} def estimate_vwap( levels: list[tuple[float, float]], target_notional: float, intent: str, # "buy" or "sell" ) -> tuple[float, float, float]: """ Estimate the VWAP for a target notional trade size. Args: levels: Price levels as (price, size) tuples from the stream. Bids are highest-first, asks are lowest-first. target_notional: The total notional (in quote terms) you want to trade. intent: "buy" (you're buying base, consuming asks) or "sell" (you're selling base, consuming bids). Returns: (vwap, unfilled) - vwap: the effective execution price - unfilled: remaining notional that couldn't be filled (0 if fully filled) """ # Sort: cheapest first for buys, most expensive first for sells sorted_levels = sorted(levels, key=lambda l: l[0], reverse=(intent == "sell")) remaining = target_notional total_base = 0.0 total_quote = 0.0 for price, size in sorted_levels: if remaining <= 0: break level_notional = price * size fill_notional = min(level_notional, remaining) fill_base = fill_notional / price total_quote += fill_notional total_base += fill_base remaining -= fill_notional if total_base == 0: return 0.0, target_notional vwap = total_quote / total_base return vwap, remaining ``` ## Full Example Combining the VWAP estimation with the Price API stream from the [Quickstart](/price-api/quickstart): ```python theme={null} import asyncio import httpx import websockets from bebop_pb2 import BebopPricingUpdate # type: ignore NETWORK = "ethereum" API_KEY = "" WSS_URL = ( f"wss://api.bebop.xyz/pmm/{NETWORK}/v3/pricing" f"?format=protobuf" f"&expiry_type=standard" ) PAIR = "WETH/USDC" TARGET_NOTIONAL = 100_000 # estimate price for $100k trade def address_to_hex(b: bytes) -> str: return "0x" + b.hex() def to_levels(flat: list[float]) -> list[tuple[float, float]]: it = iter(flat) return list(zip(it, it, strict=True)) def estimate_vwap( levels: list[tuple[float, float]], target_notional: float, intent: str ) -> tuple[float, float]: sorted_levels = sorted(levels, key=lambda lv: lv[0], reverse=(intent == "sell")) remaining = target_notional total_base = 0.0 total_quote = 0.0 for price, size in sorted_levels: if remaining <= 0: break level_notional = price * size fill_notional = min(level_notional, remaining) fill_base = fill_notional / price total_quote += fill_notional total_base += fill_base remaining -= fill_notional if total_base == 0: return 0.0, target_notional return total_quote / total_base, remaining # Resolve token addresses resp = httpx.get(f"https://api.bebop.xyz/pmm/{NETWORK}/v3/tokenlist", timeout=10.0) tokens = {t["symbol"]: t for t in resp.json().get("tokens", {})} base_symbol, quote_symbol = PAIR.split("/") base_addr = tokens[base_symbol]["address"].lower() quote_addr = tokens[quote_symbol]["address"].lower() async def main(): async with websockets.connect( WSS_URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}, ping_interval=20, ping_timeout=10, max_size=2**21, ) as ws: print(f"Connected - estimating VWAP for {PAIR} at ${TARGET_NOTIONAL:,}\n") async for blob in ws: update = BebopPricingUpdate() update.ParseFromString(blob) for pair in update.pairs: if ( address_to_hex(pair.base).lower() != base_addr or address_to_hex(pair.quote).lower() != quote_addr ): continue bids = to_levels(list(pair.bids)) asks = to_levels(list(pair.asks)) if not bids or not asks: continue # Estimate VWAP for buying $100k of WETH buy_vwap, buy_unfilled = estimate_vwap(asks, TARGET_NOTIONAL, "buy") # Estimate VWAP for selling $100k of WETH sell_vwap, sell_unfilled = estimate_vwap(bids, TARGET_NOTIONAL, "sell") print( f" BUY ${TARGET_NOTIONAL:>8,} " f"vwap: {buy_vwap:.2f}" f'{" ⚠ unfilled: " + f"${buy_unfilled:,.0f}" if buy_unfilled > 0 else ""}\n' f" SELL ${TARGET_NOTIONAL:>8,} " f"vwap: {sell_vwap:.2f}" f'{" ⚠ unfilled: " + f"${sell_unfilled:,.0f}" if sell_unfilled > 0 else ""}\n' ) asyncio.run(main()) ``` Example output for a \$100,000 WETH/USDC estimate: ```json theme={null} { "pair": "WETH/USDC", "target_notional": 100000, "buy": { "vwap": 2330.0, "unfilled": 0.0 }, "sell": { "vwap": 2328.39, "unfilled": 0.0 } } ``` These are **indicative** estimates based on streamed depth. The actual execution price from a firm quote may differ due to market maker inventory changes, timing, and quote-specific parameters. ## Key Considerations * **Check for sufficient depth.** If `unfilled > 0` after exhausting all levels, the stream doesn't have enough liquidity for your trade size. Fall back to a firm quote or split across sources. * **VWAP diverges from top-of-book at size.** For small trades the top level is a reasonable proxy. For larger sizes, the VWAP will be meaningfully worse - that's the whole point of estimating it. * **Tier cadence varies between pairs.** Two pairs with the same top-of-book price can have very different depth profiles. Always estimate at your actual trade size rather than assuming uniform depth. * **Stream prices update frequently.** Re-estimate on each new message rather than caching stale VWAP values. # Introduction Source: https://docs.bebop.xyz/price-api/introduction Real-time streaming indicative prices from Bebop's market makers via WebSocket. The Price API streams real-time indicative prices from Bebop's market makers over WebSocket. Each message contains a full snapshot of pricing across all supported pairs on a network, encoded as Protocol Buffers for compact, low-latency delivery. ## When to Use * You need **real-time indicative prices** for pre-trade estimation before requesting a firm quote from the [RFQ API](/rfq-api/introduction) * You are building a **solver or aggregator** that evaluates Bebop liquidity continuously without consuming quote rate limits * You want a **live order book view** of Bebop's aggregated market maker depth across all pairs on a network ## At a Glance | | | | - | - | | **Transport** | WebSocket | | **Authentication** | API key | | **Complexity** | Medium - protobuf decoding + connection management | ## How It Works | Step | Action | You send | You get back | | - | - | - | - | | 1 | Connect | WebSocket URL + API key + expiry type | Connection established | | 2 | Receive messages | - | Protobuf-encoded price snapshots for all pairs | | 3 | Decode | Protobuf message → language bindings | Bid/ask depth levels per pair, ordered best-first | See the [Reference](/price-api/reference) for expiry behavior and stream details. ## Key Endpoints | Endpoint | Purpose | | - | - | | `wss://api.bebop.xyz/pmm/{network}/v3/pricing` | Streaming price connection | | `GET /pmm/{network}/v3/tokenlist` | Resolve token symbols to contract addresses for pair matching | ## Next Steps Connect, decode, and process the pricing stream in 10-15 minutes. Estimate execution prices for specific trade sizes without requesting a firm quote. # Quickstart Source: https://docs.bebop.xyz/price-api/quickstart Connect to the Price API and start receiving real-time market maker quotes via WebSocket. The Price API streams real-time indicative prices from Bebop's market makers over WebSocket using protobuf-encoded messages. This guide walks you through connecting, decoding, and processing the stream. **What you'll build:** A WebSocket client that receives and decodes real-time pricing data. **Time required:** 10-15 minutes **Prerequisites:** Python 3.10+, [uv](https://docs.astral.sh/uv/) (or pip), and an API key from Bebop. ## 1. Set Up Protobuf The Price API encodes messages using Protocol Buffers for compact, low-latency delivery. You'll need to generate Python bindings from the schema. ### Define the Schema Create `bebop.proto`: ```protobuf theme={null} syntax = "proto3"; package bebop; message PriceUpdate { optional bytes base = 1; optional bytes quote = 2; optional uint64 last_update_ts = 3; repeated float bids = 4 [packed = true]; repeated float asks = 5 [packed = true]; } message BebopPricingUpdate { repeated PriceUpdate pairs = 1; } ``` Each `PriceUpdate` contains: | Field | Description | | - | - | | `base` | Base token contract address (raw bytes) | | `quote` | Quote token contract address (raw bytes) | | `last_update_ts` | Timestamp of the last price update (milliseconds) | | `bids` | Flat array of floats: `[price₁, size₁, price₂, size₂, ...]` | | `asks` | Flat array of floats: `[price₁, size₁, price₂, size₂, ...]` | ### Generate Python Bindings Install the protobuf compiler tools and generate the Python module: ```bash uv theme={null} uv pip install grpcio-tools protobuf python -m grpc_tools.protoc \ --proto_path=. \ --python_out=. \ bebop.proto ``` ```bash pip theme={null} pip install grpcio-tools protobuf python -m grpc_tools.protoc \ --proto_path=. \ --python_out=. \ bebop.proto ``` This produces `bebop_pb2.py` - the module you'll import to decode messages. ## 2. Connect to the WebSocket The pricing stream is available at: ``` wss://api.bebop.xyz/pmm/{network}/v3/pricing ``` ### Connection Parameters | Parameter | Required | Description | | - | - | - | | `format` | Yes | Set to `protobuf` | | `expiry_type` | No | `short` or `standard` (default) - controls quote expiry window | | `pair` | No | Repeatable - one `BASE/QUOTE` address pair per parameter (max 25) to stream only those pairs. See [Reference](/price-api/reference). | Authenticate by sending your API key as a Bearer token in the `Authorization` header on the connection handshake. ### Open the Connection ```python theme={null} import asyncio import websockets from bebop_pb2 import BebopPricingUpdate # type: ignore NETWORK = "ethereum" API_KEY = "" WSS_URL = ( f"wss://api.bebop.xyz/pmm/{NETWORK}/v3/pricing" f"?format=protobuf" f"&expiry_type=standard" ) async def main(): async with websockets.connect( WSS_URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}, ping_interval=20, ping_timeout=10, max_size=2**21, ) as ws: print(f"Connected to pricing stream on {NETWORK}") async for blob in ws: update = BebopPricingUpdate() update.ParseFromString(blob) print(f"Received {len(update.pairs)} pairs") asyncio.run(main()) ``` ## 3. Decode the Price Data Each message contains updates for multiple trading pairs. To extract pricing for a specific pair, match on the `base` and `quote` addresses. ### Resolve Token Addresses (optional) First, look up the contract addresses for the tokens you want: ```python theme={null} async def fetch_token_list(network: str) -> dict: url = f"https://api.bebop.xyz/pmm/{network}/v3/tokenlist" resp = httpx.get(url, timeout=10.0) resp.raise_for_status() return resp.json().get("tokens", {}) tokens_raw = asyncio.run(fetch_token_list(NETWORK)) tokens = {t["symbol"]: t for t in tokens_raw} # Example: WETH/USDC base_addr = tokens["WETH"]["address"].lower() quote_addr = tokens["USDC"]["address"].lower() ``` ### Parse Bids and Asks The `bids` and `asks` fields are flat float arrays encoding `(price, size)` pairs: ```python theme={null} def address_to_hex(b: bytes) -> str: """Convert raw protobuf bytes to a hex address.""" return "0x" + b.hex() def to_levels(flat: list[float]) -> list[tuple[float, float]]: """Unpack flat [price, size, price, size, ...] into level tuples.""" it = iter(flat) return list(zip(it, it, strict=True)) async def stream_prices(pair_base: str, pair_quote: str): async with websockets.connect( WSS_URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}, ping_interval=20, ping_timeout=10, max_size=2**21, ) as ws: async for blob in ws: update = BebopPricingUpdate() update.ParseFromString(blob) for pair in update.pairs: base_hex = address_to_hex(pair.base) quote_hex = address_to_hex(pair.quote) if base_hex.lower() != pair_base or quote_hex.lower() != pair_quote: continue bids = to_levels(list(pair.bids)) asks = to_levels(list(pair.asks)) if not bids or not asks: continue best_bid = bids[0][0] best_ask = asks[0][0] mid_price = (best_bid + best_ask) / 2 spread_bps = (best_ask - best_bid) / mid_price * 10_000 print( f"WETH/USDC mid: {mid_price:.2f} " f"spread: {spread_bps:.1f} bps " f"bid levels: {len(bids)} " f"ask levels: {len(asks)}" ) asyncio.run(stream_prices(base_addr, quote_addr)) ``` ### Understanding the Levels Bids are ordered best (highest) first, asks are ordered best (lowest) first. Each level represents a price point with available size in base token units. For example, if `bids = [2450.50, 1.5, 2449.80, 3.0]`, that means: | Level | Price | Size | | - | - | - | | 1 | 2450.50 | 1.5 WETH | | 2 | 2449.80 | 3.0 WETH | ## 4. Full Example Putting it all together - a complete script that connects, resolves a trading pair, and prints live pricing: ```python theme={null} import asyncio import httpx import websockets from bebop_pb2 import BebopPricingUpdate # type: ignore NETWORK = "ethereum" API_KEY = "" WSS_URL = ( f"wss://api.bebop.xyz/pmm/{NETWORK}/v3/pricing" f"?format=protobuf" f"&expiry_type=standard" ) PAIR = "WETH/USDC" def address_to_hex(b: bytes) -> str: return "0x" + b.hex() def to_levels(flat: list[float]) -> list[tuple[float, float]]: it = iter(flat) return list(zip(it, it, strict=True)) # 1. Resolve token addresses resp = httpx.get(f"https://api.bebop.xyz/pmm/{NETWORK}/v3/tokenlist", timeout=10.0) tokens = {t["symbol"]: t for t in resp.json().get("tokens", {})} base_symbol, quote_symbol = PAIR.split("/") base_addr = tokens[base_symbol]["address"].lower() quote_addr = tokens[quote_symbol]["address"].lower() # 2. Connect to the pricing stream async def main(): async with websockets.connect( WSS_URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}, ping_interval=20, ping_timeout=10, max_size=2**21, ) as ws: print(f"Connected - streaming {PAIR} on {NETWORK}\n") async for blob in ws: update = BebopPricingUpdate() update.ParseFromString(blob) for pair in update.pairs: if ( address_to_hex(pair.base).lower() != base_addr or address_to_hex(pair.quote).lower() != quote_addr ): continue bids = to_levels(list(pair.bids)) asks = to_levels(list(pair.asks)) if not bids or not asks: continue best_bid = bids[0][0] best_ask = asks[0][0] mid = (best_bid + best_ask) / 2 spread = (best_ask - best_bid) / mid * 10_000 print( f"{PAIR} mid: {mid:.2f} " f"spread: {spread:.1f} bps " f"best bid: {best_bid:.2f} " f"best ask: {best_ask:.2f} " f"levels: {len(bids)}b / {len(asks)}a" ) asyncio.run(main()) ``` **Prices are indicative.** The Price API streams real-time market data for monitoring and pre-trade analysis. To get firm, executable quotes, use the [RFQ API](/rfq-api/introduction). ## Next Steps Execute trades against the prices you're streaming. Message types, connection limits, and coverage details. # Reference Source: https://docs.bebop.xyz/price-api/reference Key facts, message types, schemas, and coverage details for the Price API. ## Pricing Behavior The stream publishes indicative prices for direct RFQ transactions. Request a firm quote before you execute a trade. A direct EVM quote includes a ready-to-broadcast `tx` object. Broadcast it before its expiry time. ## Quote Expiry The `expiry_type` parameter controls the target expiry window for streamed prices. | Expiry type | Window | Best for | | - | - | - | | `standard` | \~60-75s, depending on the chain | Most integrations | | `short` | \~5s on Ethereum, \~3s on other chains | Low-latency execution infrastructure | Short expiry quotes offer better prices because the market maker's risk window is smaller. However, they require fast execution infrastructure - you need to sign and broadcast within a single block. If you request `short` expiry quotes from the RFQ API, subscribe to the `short` expiry pricing stream to get matching indicative prices. Mixing expiry types between the stream and firm quotes will give you inaccurate pre-trade estimates. ## Requesting Specific Pairs By default the stream sends every pair on the network. To receive only the pairs you care about, pass one `pair` query parameter per pair: ``` wss://api.bebop.xyz/pmm/ethereum/v3/pricing?format=protobuf&pair=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&pair=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xdAC17F958D2ee523a2206206994597C13D831ec7 ``` Each message then contains only your requested pairs, in the same schema as the full stream. Use this when you track a fixed set of pairs and want smaller messages instead of filtering the full snapshot client-side. Pair filtering works with both `format=json` and `format=protobuf`, and combines with the other connection parameters (`gasless`, `expiry_type`, `fast`, `show_makers`). Pair filtering must be enabled for your API key. Without it the connection is rejected with `Invalid credentials` - request access via the [support page](/support). ### Limitations | Limit | Detail | | - | - | | **Max 25 pairs** | A connection accepts up to 25 `pair` parameters. More than that rejects the connection. | | **Addresses, not symbols** | Each `pair` must use contract addresses as `BASE/QUOTE` (e.g. `0xC02a.../0xA0b8...`). Resolve symbols via `GET /pmm/{network}/v3/tokenlist`. | | **Known tokens only** | Both tokens must exist in the token list for that network, otherwise the connection is rejected. | | **Base and quote must differ** | Pairs like `WETH/WETH` are rejected. | | **Duplicates are ignored** | Repeating the same pair (in any casing) counts once. | An invalid `pair` value rejects the connection with a reason such as `Pairs must use the BASE/QUOTE address format`, `Pairs must contain valid token addresses`, `Pair tokens must exist in the token list`, `Pair tokens must be different`, or `A maximum of 25 pairs is supported`. # Privacy Policy Source: https://docs.bebop.xyz/privacy Bebop privacy policy. PDF document # Quote Source: https://docs.bebop.xyz/rfq-api/api-reference/quote /specs/rfq-api.json get /v3/quote Get a firm quote and a ready-to-broadcast settlement transaction. Broadcast tx on EVM or solana_tx on Solana before expiry. # Supported Chains Source: https://docs.bebop.xyz/rfq-api/api-reference/supported-chains /specs/rfq-api.json get /chains Returns a mapping of chain names to chain IDs supported by the RFQ API. # Token Info Source: https://docs.bebop.xyz/rfq-api/api-reference/token-info /specs/rfq-api.json get /v3/token-info Returns detailed metadata for all tokens available for trading on Bebop, including price, decimals, and display information. Use /tokenlist for the standard token list format. # Token List Source: https://docs.bebop.xyz/rfq-api/api-reference/token-list /specs/rfq-api.json get /v3/tokenlist This endpoint will return all tokens available for trading on Bebop PMM, in tokenlist format, with extra info. # Best Practices Source: https://docs.bebop.xyz/rfq-api/guides/best-practices Recommended patterns for RFQ API integrators. This page is the operational manual for integrating the RFQ API. Every rule here exists because a real maker has either de-prioritised, denylisted, or widened quotes for an integrator that violated it. Bebop's liquidity providers are professional desks with their own risk systems. They observe per-source flow and reject patterns that look adversarial. If your integration follows these six rules, you'll have stable access to the full liquidity surface. If it doesn't, you'll see degrading fill rates, sporadic `InsufficientLiquidity` errors, and eventually maker-level bans. ## 1. Don't spam Use the [Price API stream](/price-api/quickstart) for route estimation and sizing. Only call `/quote` once the stream tells you the trade is worth pursuing. The stream gives you the full depth in real time, while the `/quote` endpoint is rate-limited per chain and per key precisely *because* it costs makers something to produce a firm quote. ```python theme={null} # Anti-pattern: blind RFQ for every intent quote = httpx.get(QUOTE_URL, params=...) # consumes RFQ rate limit even when no liquidity exists # Recommended: pre-filter via the stream if stream_depth_for(pair) >= intent.size: quote = httpx.get(QUOTE_URL, params=...) ``` See [Estimating VWAP](/price-api/guides/vwap-estimation) for the canonical pattern. ## 2. Don't cherry-pick quotes A quote is valid for a specific moment in time. Caching a quote and re-evaluating it later, then executing only when the cached quote becomes more profitable than the live market, is **toxic flow**. **Recommended pattern**: request a quote at the moment you intend to execute. Use the live Price API stream for everything earlier in your decision pipeline. ## 3. Don't slice, use partial fills instead Slicing means breaking a single fill intent into multiple smaller RFQ requests. There are two flavours of this, both of which makers detect and react to: * **Multiple quotes for the same fill**: requesting 5 × `1M USDC → WETH` quotes when you intend to fill `5M USDC → WETH` once. * **Multiple quotes for the same execution**: requesting two independent `10M USDC → WETH` quotes at the same time and executing both - whether bundled into one transaction or split across consecutive blocks. Each quote was priced against the maker's full inventory. The first fills; the second reverts on-chain once that inventory is drawn down. Blocks land seconds apart and the maker's liquidity does not replenish in that window, so submitting across separate blocks doesn't help. Requesting both quotes at once is slicing regardless of how you submit them. If you need flexibility on the fill amount, use [Partial Fills](/rfq-api/guides/partial-fills) instead. A 5M quote can be filled at 4.5M against the same firm price - that's not slicing, that's the supported partial-fill mechanic. Another common case for partial fills is topping up an AMM leg. If you route part of a trade through an AMM and add maker liquidity on top, the AMM's real `amountOut` isn't known until the swap executes - slippage, MEV and pool state move it. Request a quote for the maximum you may need, then partial-fill down to what the AMM actually returned. You get the same firm price regardless of the fill amount, avoiding a second round trip on the critical path. ```python theme={null} # Other required quote parameters are omitted from this sizing example. # Anti-pattern: 5 small quotes for one intent for amount in [1_000_000] * 5: quote = httpx.get(QUOTE_URL, params={"sell_amounts": str(amount)}) # Recommended: 1 quote, optional partial fill quote = httpx.get(QUOTE_URL, params={"sell_amounts": "5000000"}) # Fill any amount up to the quote at the firm price. ``` ## 4. Don't probe unsupported tokens Requesting quotes for tokens that aren't supported on a given chain costs maker compute and signals an unfiltered taker. Use the `/tokens` endpoint with the `isAvailable` flag as the canonical liquidity check before requesting a quote. ```python theme={null} import httpx NETWORK = "ethereum" tokens = httpx.get(f"https://api.bebop.xyz/pmm/{NETWORK}/v3/tokens").json() available = {addr for addr, t in tokens["tokens"].items() if t.get("isAvailable")} if sell_token in available and buy_token in available: quote = httpx.get(QUOTE_URL, params=...) ``` You can request a quote for any supported token against any other supported token.. ## 5. Firm quote means 0% slippage A Bebop quote is **executable at the price you get until expiry**. This matters when comparing Bebop quotes to AMM quotes in a routing decision. Don't apply an "expected slippage" adjustment to the Bebop quote that you'd normally apply to a Uniswap quote. | Source | Returned price | What you actually receive | | - | - | - | | Bebop RFQ | Firm | Same as returned price (pre-execution) | | AMM (Uniswap, etc.) | Mid-price estimate | Returned price minus slippage cost | ## 6. Pass origin so we can identify legitimate flow The following four fields tell us who is really behind a quote request. Send the ones that match your integration shape - they feed abuse-prevention and market-maker reputation, so accuracy matters. | Field | Send when | Value | | - | - | - | | `taker_address` | Always (required) | The address that supplies the sell tokens during settlement. | | `origin_address` | Your taker is not the end-user's own wallet | The real end-user's EOA. | | `origin_target` | The swap is executed by a contract on the user's side | The to address of the resulting transaction. | | `origin_source` | You aggregate flow from multiple upstream sources | A stable identifier for the sub-source the request came from. | **If you're a direct integrator** - the `taker_address` is a real user wallet and there is no contract of yours in between: you don't need any of the `origin_*` fields. **If you route through your own contract** - `taker_address` is your settlement/router contract, so Bebop can't see the real user from it. Send `origin_address` = the end-user's EOA. Depending on your integration profile, Bebop may require `origin_address` on every request; if that applies to you we'll tell you, and requests without it will be rejected. **If a contract executes the swap on the user's side** - additionally send `origin_target` = the `to` address of the resulting transaction (the contract that executes the swap). This is specifically the transaction target, not just any intermediate in your call path. Bebop screens this contract before forwarding the request. **If you aggregate flow from multiple upstream sources** - additionally send `origin_source` = a stable, consistent identifier for the specific sub-source a request came from (e.g. one value for your own UI, a distinct value per downstream partner). This lets Bebop and market makers manage reputation per sub-source rather than treating your entire integration as one bucket - so one bad downstream partner doesn't taint your good flow. It's a free-text string; keep the values stable over time. **Send the true end-user EOA.** Inaccurate values simultaneously blind reputation tracking and weaken address screening, which degrades the quality of liquidity you receive and can get your integration de-prioritized. ## Summary checklist Before going live, confirm: * You connect to the Price API stream and use it as the pre-quote filter * You never cache a quote between issuance and execution * You request one quote per intended fill (use partial fills for flex sizing) * You only execute one quote for the same fill (token pair) per transaction * You only request quotes for tokens with `isAvailable=true` * You compare Bebop's firm quote to AMM quotes net of expected AMM slippage * You pass the real end-user address Following all these recommendations materially reduces the chance of being deny-listed and is the difference between a stable integration and one that quietly degrades over weeks. # Multi-Token Trades Source: https://docs.bebop.xyz/rfq-api/guides/multi-token-trades Swap multiple tokens in a single atomic transaction. Bebop supports three trade structures. Each structure settles atomically in one transaction: Multi-token RFQ trades are available on EVM chains. Solana RFQ supports one-to-one trades only. | Mode | Example | `type` in response | | - | - | - | | One-to-one | WETH → USDC | `121` | | Many-to-one | (WETH + WBTC) → USDC | `M21` | | One-to-many | WETH → (USDT + USDC + PYUSD) | `12M` | Use multi-token trades to rebalance a portfolio, consolidate stablecoins, or distribute one asset into multiple tokens. ## How It Differs from Single-Token Trades The API interface is nearly identical. The differences are: **Request:** Pass comma-separated token addresses and amounts instead of single values. **Response:** The `onchainOrderType` is `MultiOrder` or `AggregateOrder` instead of `SingleOrder`. The API returns `MultiOrder` when a single maker fills the trade, or `AggregateOrder` when multiple makers are involved. ## 1. Request a Multi-Token Quote Separate multiple token addresses and amounts with commas. The order of amounts must match the order of token addresses. ### Many-to-one: sell USDC + DAI → buy USDT ```python theme={null} import httpx NETWORK = "ethereum" # Many-to-one: sell USDC + DAI -> buy USDT resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "sell_tokens": ",".join( [ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI ] ), "buy_tokens": "0xdAC17F958D2ee523a2206206994597C13D831ec7", # USDT "sell_amounts": ",".join( [ "100000000", # 100 USDC (6 decimals) "100000000000000000000", # 100 DAI (18 decimals) ] ), "taker_address": "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693", }, ) resp.raise_for_status() quote = resp.json() print( f'Quote type: {quote.get("type")}, order type: {quote.get("onchainOrderType")}' ) print(quote) ``` ### One-to-many: sell WETH → buy USDT + USDC + PYUSD For one-to-many, specify a single `sell_tokens` / `sell_amounts` and comma-separated `buy_tokens`. Use `buy_amounts` instead of `sell_amounts` to control how much of each output token you want: ```python theme={null} resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH "buy_tokens": ",".join([ "0xdAC17F958D2ee523a2206206994597C13D831ec7", # USDT "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "0x6c3ea9036406852006290770BEdFcAbA0e23A0e8", # PYUSD ]), "buy_amounts": ",".join([ "500000000", # 500 USDT "500000000", # 500 USDC "500000000", # 500 PYUSD ]), "taker_address": "0xYourWalletAddress", }, ) quote = resp.json() ``` ## 2. Understand the Response The response uses multiple entries in `sellTokens` or `buyTokens`. It also includes a complete settlement transaction: ```json theme={null} { "type": "M21", "onchainOrderType": "AggregateOrder", "sellTokens": { "0xA0b8...": {"amount": "100000000", "symbol": "USDC"}, "0x6B17...": {"amount": "100000000000000000000", "symbol": "DAI"} }, "buyTokens": { "0xdAC1...": {"amount": "198898842", "symbol": "USDT"} }, "tx": { "to": "0xbbbbbB...AD5F", "value": "0x0", "data": "0x4dcebcba...", "from": "0x5Bad...bBcB6" } } ``` The API selects `MultiOrder` or `AggregateOrder` from the maker composition. You do not need to process this distinction. ## 3. Sign and Broadcast Sign and broadcast the returned `tx` object before the quote expires. ```python theme={null} import httpx from eth_account import Account from web3 import Web3 PRIVATE_KEY = "0x" RPC_URL = "https://eth.llamarpc.com" NETWORK = "ethereum" # --- 1. Request a multi-token quote --- w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "sell_tokens": ",".join( [ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "0x6B175474E89094C44Da98b954EedeAC495271d0F", # DAI ] ), "buy_tokens": "0xdAC17F958D2ee523a2206206994597C13D831ec7", # USDT "sell_amounts": ",".join( [ "100000000", # 100 USDC "100000000000000000000", # 100 DAI ] ), "taker_address": account.address, }, ) quote = resp.json() # --- 2. Approve each sell token for quote["approvalTarget"], if necessary --- # See the Token Approvals guide for the complete allowance flow. # --- 3. Sign and broadcast --- tx = quote["tx"] if tx["from"].lower() != account.address.lower(): raise ValueError("The quote taker does not match the signing account") tx["nonce"] = w3.eth.get_transaction_count(account.address) tx["chainId"] = quote["chainId"] signed_tx = w3.eth.account.sign_transaction(tx, account.key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) print(f"Multi-token trade submitted: {tx_hash.hex()}") ``` The API selects the order type automatically. Broadcast the returned transaction without changing its settlement calldata. # Partial Fills Source: https://docs.bebop.xyz/rfq-api/guides/partial-fills Execute only a portion of a quoted order - useful when combining RFQ liquidity with other sources. Partial fills let you execute only a portion of a quoted order size. This is useful when combining RFQ liquidity with other sources (e.g. AMM fallback), or when market conditions change and you only need part of the originally requested size. Partial fills are available on EVM chains. Solana RFQ quotes do not include `partialFillOffset`. ## How It Works The quote response includes a `partialFillOffset` field that tells you where in the transaction calldata to modify the fill amount. By default, quotes are filled fully - to partially fill, you replace the taker amount at that offset with your desired amount. Partial fill amounts must be less than the original `taker_amount` in the quote, specified in base units (same as the original quote), and encoded as a 64-character hex string (32 bytes, zero-padded). ## Understanding the Calldata Structure The `partialFillOffset` is a word index (0-indexed) counting 32-byte segments in the encoded calldata. Each word is 32 bytes = 64 hex characters. ``` 0x // Hex prefix (2 characters) 4dcebcba // Function selector (8 characters) [word 0] // First 32-byte word (64 characters) [word 1] // Second 32-byte word (64 characters) ... [word N] // filledTakerAmount (64 characters) ← partialFillOffset = N ``` The position in the hex string is: ``` position = 10 + (offset × 64) ``` where `10` accounts for the `0x` prefix (2 chars) and the 4-byte function selector (8 chars). ## Step by Step ### 1. Request a Quote ```python theme={null} import httpx from eth_account import Account NETWORK = "ethereum" PRIVATE_KEY = "0x" account = Account.from_key(PRIVATE_KEY) resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", # 1 WETH "taker_address": account.address, }, ) resp.raise_for_status() quote = resp.json() ``` Key fields in the response: | Field | Description | | - | - | | `sellTokens..amount` | Originally requested amount | | `tx.data` | Original calldata | | `partialFillOffset` | Word index where `filledTakerAmount` lives in the calldata | ### 2. Calculate Your Partial Fill Amount Decide how much of the original order you want to fill. The amount must be in base units and less than the original `taker_amount`: ```python theme={null} sell_token = list(quote["sellTokens"].keys())[0] original_amount = int(quote["sellTokens"][sell_token]["amount"]) # Fill 50% of the original order fill_amount = original_amount // 2 ``` ### 3. Modify the Calldata Replace the fill amount in the transaction calldata at the position indicated by `partialFillOffset`: ```python theme={null} def apply_partial_fill(tx_data: str, offset: int, fill_amount: int) -> str: pos = 10 + offset * 64 return ( tx_data[:pos] + f"{fill_amount:064x}" + tx_data[pos + 64:] ) modified_data = apply_partial_fill( quote["tx"]["data"], quote["partialFillOffset"], fill_amount, ) ``` ### 4. Sign and Broadcast From here, follow the standard [RFQ quickstart](/rfq-api/quickstart). Use the **modified** calldata when you sign and broadcast the transaction: ```python theme={null} from web3 import Web3 RPC_URL = "https://eth.llamarpc.com" w3 = Web3(Web3.HTTPProvider(RPC_URL)) # Broadcast with the modified calldata tx = quote["tx"] if tx["from"].lower() != account.address.lower(): raise ValueError("The quote taker does not match the signing account") tx["data"] = modified_data tx["nonce"] = w3.eth.get_transaction_count(account.address) tx["chainId"] = quote["chainId"] signed_tx = w3.eth.account.sign_transaction(tx, account.key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) print(f"Partial fill transaction: {tx_hash.hex()}") ``` ## Full Example ```python theme={null} import httpx from eth_account import Account from web3 import Web3 PRIVATE_KEY = "0x" NETWORK = "ethereum" RPC_URL = "https://eth.llamarpc.com" FILL_PERCENT = 50 # Fill 50% of the quoted amount # --- 1. Request a quote for the signing account --- w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) quote_resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", # 1 WETH "taker_address": account.address, }, ) quote_resp.raise_for_status() quote = quote_resp.json() sell_token = list(quote["sellTokens"].keys())[0] original_amount = int(quote["sellTokens"][sell_token]["amount"]) buy_token = list(quote["buyTokens"].values())[0] print( f'Quote: sell 1 WETH -> buy ~{int(buy_token["amount"]) / 10 ** buy_token["decimals"]:.2f} USDC' ) # --- 2. Calculate partial fill amount --- fill_amount = original_amount * FILL_PERCENT // 100 print(f"Partial fill: {FILL_PERCENT}% -> {fill_amount} base units") # --- 3. Splice the calldata --- def apply_partial_fill(tx_data: str, offset: int, amount: int) -> str: pos = 10 + offset * 64 return tx_data[:pos] + f"{amount:064x}" + tx_data[pos + 64 :] modified_data = apply_partial_fill( quote["tx"]["data"], quote["partialFillOffset"], fill_amount ) # --- 4. Sign and broadcast with modified calldata --- tx = quote["tx"] if tx["from"].lower() != account.address.lower(): raise ValueError("The quote taker does not match the signing account") tx["data"] = modified_data tx["nonce"] = w3.eth.get_transaction_count(account.address) tx["chainId"] = quote["chainId"] signed_tx = w3.eth.account.sign_transaction(tx, account.key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) print(f"Partial fill submitted: {tx_hash.hex()}") ``` # Migrating to the BebopRouter contract Source: https://docs.bebop.xyz/rfq-api/guides/router-migration-guide Update a direct RFQ integration to use the BebopRouter smart contract. Bebop has introduced the **BebopRouter** `0xBeb0009ACa35087ce7cCF11637E24dd1Aad3bf2A` contract, which now wraps BebopSettlement extending its functionality. ## Do you need to migrate? With `Standard` approvals, no changes are required if your integration already reads these fields from the quote response. Confirm both: * **Approvals.** You approve the `approvalTarget` from the quote response, not a hardcoded address. See [Token approvals](/core-concepts/token-approvals). * **Broadcast.** You send the returned `tx` object as-is; `tx.to` already targets the correct contract. If you hardcoded the BebopSettlement address anywhere (for approvals or as the transaction target), switch to reading `approvalTarget` and `tx` from the quote response. ## Reading the calldata With `Standard` approvals, Bebop returns calldata ready to broadcast in `tx.data`. It calls the router's `swap` function: ```solidity theme={null} function swap( int256 exactAmount, BebopRouterOrder calldata order, bytes calldata extraInfo, bytes calldata routerSignature, bytes calldata bebopPmmCalldata, Hook[] calldata hooks ) ``` | Parameter | Description | | - | - | | `exactAmount` | Fill mode. `> 0`: exactIn, you send exactly this amount of `fromToken`. `< 0`: exactOut, you want exactly `\|exactAmount\|` of `toToken` after fees. `== 0`: use whatever `fromToken` balance the router holds. | | `order` | The signed order struct. `fromAmount` / `toAmount` define the quote ratio used for scaling. | | `routerSignature` | The router signer's EIP-712 signature over the order. | | `bebopPmmCalldata` | The raw `BebopSettlement.swapSingle` / `swapAggregate` calldata the router wraps. | For a concrete reference, see this [example transaction](https://etherscan.io/tx/0x071db51c478baf95d51fb69c4c80e1e8fcc90f68fcce94b0b6ce8e51321dd810). ## Permit2 self-execution BebopRouter supports Permit2 self-execution for ERC-20 sell tokens. Add `gasless=false&approval_type=Permit2` to your quote request. Set `taker_address` to the user who owns the tokens, not your wrapper contract. If needed, approve `quote.approvalTarget` on the sell token for at least `quote.toSign.permitted.amount`. For this flow, `approvalTarget` is the Permit2 contract. Have the taker sign `quote.toSign` as the EIP-712 message using the [Permit2 witness types](https://github.com/bebop-dex/bebop-rfqa/blob/cde98215e8522c36f410c4c75649a71208cb1f5b/test/BebopRouter.test.ts#L589-L617). Use the domain `{ name: "Permit2", chainId: quote.chainId, verifyingContract: quote.approvalTarget }` without a `version`. Keep `spender` as returned: it is BebopRouter, not your wrapper. The returned `tx.data` calls `settle` with an empty `userSignature`. With an ethers v6 `Interface` for [BebopRouter](https://github.com/bebop-dex/bebop-rfqa/blob/cde98215e8522c36f410c4c75649a71208cb1f5b/contracts/BebopRouter.sol), replace that final argument: ```javascript theme={null} const args = routerInterface .decodeFunctionData("settle", quote.tx.data) .toArray(true) args[6] = userSignature quote.tx.data = routerInterface.encodeFunctionData("settle", args) ``` Simulate the completed transaction before submission. Then broadcast the updated `quote.tx`, or call `quote.tx.to` from your wrapper with the updated calldata. # Short Expiry Source: https://docs.bebop.xyz/rfq-api/guides/short-expiry Request quotes with shorter expiry windows for latency-sensitive integrations. Short expiry quotes have a narrower validity window than standard quotes. Because the market maker's risk window is smaller, short expiry quotes offer tighter pricing. The trade-off is that the transaction must be **included in a block** before the quote expires, not just signed and broadcast. ## Expiry Windows by Chain | Chain | Short expiry | Standard expiry | | - | - | - | | Ethereum | 5s | 75s | | Arbitrum | 3s | 60s | | Base | 3s | 60s | | BSC | 3s | 60s | | Polygon | N/A | 60s | | Optimism | N/A | 60s | | Hyperevm | N/A | 60s | | Avalanche | N/A | 60s | | Solana | N/A | 90s | ## Requesting Short Expiry Quotes Pass `expiry_type=short` when calling `/v3/quote`: ```bash theme={null} curl "https://api.bebop.xyz/pmm/ethereum/v3/quote?\ buy_tokens=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\ sell_tokens=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&\ sell_amounts=1000000000000000000&\ taker_address=0xYOUR_ADDRESS&\ expiry_type=short" \ -H "Authorization: Bearer YOUR_API_KEY" ``` The returned quote will have a shorter validity window matching the chain-specific durations above. Your transaction must be included in a block within that window. Signing and broadcasting alone is not enough. If your transaction doesn't land in the target block, you may need to increase your gas price. Note that even a higher fee may not guarantee inclusion if the block is full (e.g. due to MEV bundles). ## Using with the Price API If you use the [Price API](/price-api/introduction) for indicative pricing before requesting firm quotes, make sure the expiry types match. Subscribe to the `short` expiry stream by including `expiry_type=short` in the WebSocket URL, so the indicative prices you receive reflect the same pricing that `/v3/quote` will return. Mixing expiry types between the Price API stream and firm RFQ quotes will give you inaccurate pre-trade estimates. See [Quote Expiry](/price-api/reference#quote-expiry) for details. ## Trade-offs | | Short expiry | Standard expiry | | - | - | - | | **Pricing** | Tighter spreads | Slightly wider spreads | | **Execution window** | Transaction must be included in a block before expiry; may require aggressive gas pricing | More time to manage signing and submission | | **Best for** | Latency-sensitive integrations | Most integrators | # Speed-Optimised Quotes Source: https://docs.bebop.xyz/rfq-api/guides/speed-optimized-quotes Get faster quotes from a latency-optimised subset of market makers for time-sensitive integrations. Speed-optimised mode is served from a Tokyo-based backend and produces quotes in **under 100 ms**. Colocating in Tokyo minimises end-to-end latency; clients in other regions should add the network round-trip to Tokyo to estimate their observed response time. The mode is backed by a subset of market makers that support low-latency responses, and requires enablement by the Bebop team. [Contact support](/support) to get access and learn more about configuration options. # Introduction Source: https://docs.bebop.xyz/rfq-api/introduction Access institutional market maker liquidity with guaranteed execution and guaranteed fill. The RFQ API connects you to Bebop's network of private market makers who quote directly from their own on-chain inventory. Every quote is firm - guaranteed execution and guaranteed fill. ## When to Use * You need **firm pricing** with guaranteed execution and guaranteed fill - no slippage, no solver auction, no price uncertainty * You are trading **major token pairs** where private market maker liquidity offers tighter spreads than on-chain routing * You want **atomic multi-token swaps on EVM** (e.g., sell 3 tokens for 1 or buy 1 token with 3 tokens) settled in a single transaction ## At a Glance | | | | - | - | | **Transport** | REST | | **Authentication** | API key | | **Signing** | Sign the transaction returned by the quote | | **On-chain tx** | EVM uses `tx`; Solana uses `solana_tx` and `blockhash` | | **Complexity** | Medium - transaction signing + token approvals | ## How It Works Direct RFQ API clients broadcast the returned transaction for on-chain settlement. | Step | Action | You send | You get back | | - | - | - | - | | 1 | Request a quote | Token pair, amount | Firm price and a settlement transaction | | 2 | Approve the sell token on EVM | `approvalTarget` and amount | Token allowance | | 3 | Broadcast on-chain | Signed EVM or Solana transaction | On-chain settlement | See [Execution](/core-concepts/execution-modes) for the transaction flow. ## Key Endpoints | Endpoint | Purpose | | - | - | | `GET /pmm/{network}/v3/quote` | Request a firm quote for a token swap | ## Next Steps Make your first trade in 10-15 minutes. Learn about partial fills, multi-token trades, and quote expiry. # Quickstart Source: https://docs.bebop.xyz/rfq-api/quickstart Make your first trade on Bebop using the RFQ API - from quote request to settlement. This guide shows how to discover supported assets, request a quote, approve tokens, and broadcast the settlement transaction. **What you'll build:** A complete trade flow from quote request to settlement. **Time required:** 10-15 minutes **Prerequisites:** Basic understanding of EVM wallets and token approvals. The trading example below uses EVM. Solana RFQ quotes return `solana_tx` and `blockhash` instead of `tx`. ## 1. Discover Supported Chains and Tokens Before requesting quotes, identify which chains and tokens Bebop supports. This information changes as new assets are added, so your integration should refresh it regularly. ### Get Supported Chains Retrieve the supported blockchains: ```text theme={null} GET /pmm/chains ``` ```bash bash theme={null} curl --get https://api.bebop.xyz/pmm/chains ``` ```python python theme={null} import httpx resp = httpx.get("https://api.bebop.xyz/pmm/chains") data = resp.json() print(data) ``` Response (abridged): ```json theme={null} { "ethereum": 1, "polygon": 137, "arbitrum": 42161, "optimism": 10, "base": 8453, "bsc": 56, "hyperevm": 999, "avalanche": 43114, "solana": 2, ... } ``` Use these network names in subsequent API calls (e.g., `/pmm/ethereum/v3/quote`). ### Get Tokens for a Chain Once you know which chain you want to trade on, retrieve the list of supported tokens: ```text theme={null} GET /pmm/{network}/v3/tokens ``` ```bash bash theme={null} curl --get https://api.bebop.xyz/pmm/ethereum/v3/tokens ``` ```python python theme={null} import httpx resp = httpx.get("https://api.bebop.xyz/pmm/ethereum/v3/tokens") data = resp.json() tokens = data.get("tokens", {}) # dict: ticker -> token info for ticker, t in list(tokens.items())[:5]: info = t.get("chainInfo") or [{}] chain = info[0] addr = chain.get("contractAddress", "") print(f'{ticker} ({addr[:10]}...) decimals={chain.get("decimals")}') ``` Response (abridged): ```json theme={null} { "tokens": { "EIGEN": { "name": "Eigenlayer", "ticker": "EIGEN", "availability": { "isAvailable": true, "canBuy": true, "canSell": true }, "priceUsd": 0.231681, "rfqaSupported": false, "cid": "eigenlayer", "displayDecimals": 1, "colour": "#FFFFFF", "tags": [], "iconUrl": "https://bebop-public-images.s3.eu-west-2.amazonaws.com/1-0xec53bF9167f50cDEB3Ae105f56099aaaB9061F83.svg", "chainInfo": [ { "chainId": 1, "contractAddress": "0xec53bF9167f50cDEB3Ae105f56099aaaB9061F83", "decimals": 18 } ] }, ... }, "metadata": { "lastUpdate": "1784791364.774951" } } ``` Key fields: | Field | Description | | - | - | | `ticker` | Human-readable token symbol | | `chainInfo.contractAddress` | Token contract address - use this in all subsequent requests | | `chainInfo.decimals` | For converting human amounts to base units (e.g., 1 WETH = 1 × 10¹⁸ base units) | | `availability.canBuy/canSell` | Whether liquidity is available in either direction | **Caching recommendation:** Refresh tokens at least once daily. Token availability can change due to liquidity conditions or new listings. ## 2. Request a Quote Quotes return the exact price and settlement parameters for your trade. Regular quotes expire typically within 60-75 seconds (depending on the blockchain), so request them when you're ready to execute. ### Authentication Unauthenticated requests receive significantly worse prices and are heavily rate limited. You can try the API in demo mode, however, we encourage you to [request an API key](/core-concepts/authentication) to get production-grade pricing and higher limits. ### Basic Quote Request ```text theme={null} GET /pmm/{chain}/v3/quote ``` Required parameters: | Parameter | Description | Example | | - | - | - | | `sell_tokens` | Token(s) you're selling (contract address) | `0xC02a...` (WETH) | | `buy_tokens` | Token(s) you're buying | `0xA0b8...` (USDC) | | `sell_amounts` OR `buy_amounts` | Amount in base units. Use one, not both. | `1000000000000000000` (1 WETH) | | `taker_address` | Address that supplies the sell tokens | `0xYourWalletAddress` | | `receiver_address` | Address to receive bought tokens (if different from taker) | `taker_address` | **Direct wallet flow:** Use the sending wallet as `taker_address`. `receiver_address` receives the bought tokens and defaults to `taker_address`. * Use `sell_amounts` when you know exactly how much you want to sell (e.g., "Sell 1 WETH") * Use `buy_amounts` when you know exactly how much you want to receive (e.g., "Buy 5000 USDC") ```text theme={null} base_units = human_amount × 10^decimals ``` For example: 1.5 WETH (18 decimals) = 1.5 × 10¹⁸ = `1500000000000000000` Example - selling 0.01 WETH for USDC on Ethereum: ```bash bash theme={null} curl https://api.bebop.xyz/pmm/ethereum/v3/quote \ --get \ --data-urlencode "sell_tokens=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" \ --data-urlencode "buy_tokens=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" \ --data-urlencode "sell_amounts=10000000000000000" \ --data-urlencode "taker_address=0x5Bad996643a924De21b6b2875c85C33F3c5bBcB6" ``` ```python python theme={null} import httpx from web3 import Web3 NETWORK = "ethereum" URL = f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote" buy_token = Web3.to_checksum_address( "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" ) # USDC sell_token = Web3.to_checksum_address( "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" ) # WETH sell_token_decimals = 18 sell_amounts = 0.01 params = { "buy_tokens": buy_token, "sell_tokens": sell_token, "sell_amounts": int(sell_amounts * 10**sell_token_decimals), "taker_address": "0x5Bad996643a924De21b6b2875c85C33F3c5bBcB6", } response = httpx.get(URL, params=params) data = response.json() print(data) ``` ### Understanding the Response The response below shows the fields that you need for the direct transaction flow. ```json theme={null} { "requestId": "dd78b459-2dcf-4478-9393-5e9d10e1d573", "type": "121", "status": "SIG_SUCCESS", "quoteId": "121-897150427680310661818327515657942468...", "chainId": 1, "approvalType": "Standard", "nativeToken": "ETH", "taker": "0x5Bad99...BcB6", "receiver": "0x5Bad99...BcB6", "expiry": 1773827371, "slippage": 0.0, "buyTokens": { "0xA0b869...eB48": { "amount": "23197992", "decimals": 6, "priceUsd": 0.999863, "symbol": "USDC", "minimumAmount": "23197992", "price": 0.0004310717927655118, "priceBeforeFee": 0.00042891596855834495, "amountBeforeFee": "23314591", "deltaFromExpected": -0.005043930479229898 } }, "sellTokens": { "0xC02aaA...6Cc2": { "amount": "10000000000000000", "decimals": 18, "priceUsd": 2331.24, "symbol": "WETH", "price": 2319.7992, "priceBeforeFee": 2331.4590113330582 } }, "settlementAddress": "0xbbbbbB...AD5F", "approvalTarget": "0xbbbbbB...AD5F", "priceImpact": -0.005043930479229898, "partnerFee": { "0xEeeeeE...EEeE": "50000000000000" }, "warnings": [], "info": "You are using Bebop's public API. For hi...", "tx": { "to": "0xbbbbbB...AD5F", "value": "0x0", "data": "0x4dcebcba000000000000000000000000000000...", "from": "0x5Bad99...BcB6", "gas": 91793, "gasPrice": 154958393 }, "makers": [ "🦙" ], "onchainOrderType": "SingleOrder", "partialFillOffset": 12 } ``` Key items to note: | Field | Description | | - | - | | `quoteId` | Quote identifier for logs and support requests | | `expiry` | Unix timestamp - order is invalid after this time | | `buyTokens` | Tokens you'll receive, keyed by contract address. `amount` is the guaranteed fill amount (no slippage). | | `approvalTarget` | Contract that needs token approval (if not already approved). | | `tx` | Complete transaction that you sign and broadcast | **Important.** Make sure `approvalTarget` is always picked up from the quote response, not hardcoded. You may receive different targets for different quotes depending on how the trade is handled. As of June 2026, you may see either of the following two contracts in the quote response: * router contract: `0xBeb0009ACa35087ce7cCF11637E24dd1Aad3bf2A` * settlement contract: `0xbbbbbBB520d69a9775E85b458C58c648259FAD5F` See [Smart Contracts](/core-concepts/settlement-smart-contracts) and [Token Approvals guide](/core-concepts/token-approvals) for more information. ## 3. Approve the Sell Token Approve the current quote's `approvalTarget` for the sell amount. Confirm the approval transaction before settlement. See [Token Approvals](/core-concepts/token-approvals) for the allowance check and approval code. ## 4. Sign and Broadcast The `/v3/quote` response includes a `tx` object that is ready to submit. For Permit2 trades through BebopRouter, follow the [Permit2 self-execution guide](/rfq-api/guides/router-migration-guide#permit2-self-execution) before broadcasting. Partial fills are also supported. See the [Partial Fills guide](/rfq-api/guides/partial-fills) for details. Add your `nonce` and `chainId`. Then sign and broadcast the transaction before the quote expires. ```python theme={null} import httpx from eth_account import Account from web3 import Web3 PRIVATE_KEY = "0x" RPC_URL = "https://eth.llamarpc.com" NETWORK = "ethereum" w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) # --- 1. Request a quote for the signing account --- response = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "sell_amounts": 1000000000000000000, "taker_address": account.address, }, ) response.raise_for_status() quote = response.json() # --- 2. Approve quote["approvalTarget"] before settlement, if necessary --- # See the Token Approvals guide for the complete allowance flow. # --- 3. Sign and broadcast the settlement transaction --- tx = quote["tx"] if tx["from"].lower() != account.address.lower(): raise ValueError("The quote taker does not match the signing account") tx["nonce"] = w3.eth.get_transaction_count(account.address) tx["chainId"] = quote["chainId"] signed_tx = w3.eth.account.sign_transaction(tx, account.key) tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction) print(f"Transaction: {tx_hash.hex()}") receipt = w3.eth.wait_for_transaction_receipt(tx_hash) if receipt["status"] != 1: raise RuntimeError("Settlement transaction reverted") print(f'Settled in block {receipt["blockNumber"]}') ``` Key points: | Step | What happens | | - | - | | Sign | Sign the transaction using your private key | | Broadcast | Submit the transaction on-chain via your RPC provider | | Settlement | Tokens arrive at your `receiver` address in the same transaction | ## Next Steps Ready to explore more? Dive into the guides: Manage token approvals for trading. Combine market maker and AMM liquidity. Swap multiple tokens in a single transaction. # Support Source: https://docs.bebop.xyz/support Get in touch with the Bebop team. ## Talk to us Once we receive your submission, we will set up a dedicated Telegram channel for ongoing communication. Choose the form that best describes your use case. Building a product on top of Bebop's APIs Interested in providing liquidity through Bebop Request access to the Block Oracle Priced AMM ## General inquiries Partnerships, questions, or general feedback Anything that doesn't fit the categories above ## Live chat For real-time help, use the chat widget on [bebop.xyz](https://bebop.xyz) - look for the chat icon in the bottom-right corner of the page. # Chains Availability Source: https://docs.bebop.xyz/supported-chains Supported blockchains across Bebop's RFQ and Aggregation APIs. | VM | Chain | Chain ID | RFQ API | Aggregation API | | - | - | - | - | - | | EVM | Arbitrum | 42161 | ✓ | ✓ | | EVM | Avalanche | 43114 | | ✓ | | EVM | BNB Chain | 56 | ✓ | ✓ | | EVM | Base | 8453 | ✓ | ✓ | | EVM | Ethereum | 1 | ✓ | ✓ | | EVM | HyperEVM | 999 | ✓ | ✓ | | EVM | Optimism | 10 | ✓ | ✓ | | EVM | Polygon | 137 | ✓ | ✓ | | EVM | Robinhood | 4663 | ✓ | | Chain support changes over time. To fetch the latest programmatically: ```bash theme={null} # RFQ API chains curl https://api.bebop.xyz/pmm/chains # Aggregation API chains curl https://api.bebop.xyz/jam/chains ``` # Terms of Service Source: https://docs.bebop.xyz/terms Bebop terms of service. PDF # Trade by Tx Hash Source: https://docs.bebop.xyz/trade-history-api/api-reference/trade-by-tx-hash /specs/trade-history-api.json get /v2/tx/{tx_hash} Retrieve trade details for a specific transaction hash. Returns the trade with token metadata and pricing. # Trades Source: https://docs.bebop.xyz/trade-history-api/api-reference/trades /specs/trade-history-api.json get /v2/trades Retrieve trade history for a wallet address or partner source within a given time range. Returns trade details with token metadata and pricing. # Introduction Source: https://docs.bebop.xyz/trade-history-api/introduction Look up historical trades and transaction details across all Bebop-supported chains. The Trade History API gives you read-only access to every trade executed through Bebop. Look up trades by wallet address or inspect individual transactions - all from a single endpoint that aggregates across every supported chain. ## When to Use * You need **post-trade analytics** - reconciling fills, tracking volumes, or reviewing trades attributed to your integration * You want to **inspect any Bebop transaction across all supported chains** from a single endpoint, without querying each chain separately ## At a Glance | | | | - | - | | **Transport** | REST | | **Authentication** | Optional (required to see your integration's trades) | | **Base URL** | `https://api.bebop.xyz/history/v2/` | | **Complexity** | Low - standard REST GET requests | ## How It Works | Step | Action | You send | You get back | | - | - | - | - | | 1 | Query trades | Wallet address → `GET /history/v2/trades` | Paginated list of trades with token + USD details | | 2 | Inspect a transaction | Tx hash → `GET /history/v2/tx/{tx_hash}` | Full trade breakdown: tokens, amounts, gas, type | To see trades attributed to your integration, [authenticate with your API key](/core-concepts/authentication). The results are scoped to your integration automatically. ## Key Endpoints | Endpoint | Purpose | | - | - | | `GET /history/v2/trades` | Query trades by wallet address with time range and pagination | | `GET /history/v2/tx/{tx_hash}` | Look up a specific transaction by hash | ## Next Steps Fetch your first trade history in a few minutes. # Quickstart Source: https://docs.bebop.xyz/trade-history-api/quickstart Fetch trade history from Bebop - from wallet lookups to individual transaction details. This guide walks you through the two Trade History API endpoints. You'll query trades for a wallet address and look up a specific transaction by hash. **What you'll build:** Scripts that fetch and display trade history from Bebop. **Time required:** 5 minutes **Prerequisites:** None for public wallet lookups. To fetch trades attributed to your own integration, [authenticate with your API key](/core-concepts/authentication). ## 1. Look up trades for a wallet Retrieve all trades for a given wallet address. The API returns trades across all Bebop-supported chains in a single response. ``` GET /history/v2/trades ``` | Parameter | Required | Type | Description | | - | - | - | - | | `wallet_address` | Yes | string | Wallet address to look up | | `start` | No | integer | Start of time range (UNIX timestamp in nanoseconds) | | `end` | No | integer | End of time range (UNIX timestamp in nanoseconds) | | `size` | No | integer | Number of trades to return per chain, between 1 and 500 (default: 500) | ```bash bash theme={null} NOW_NS=$(date +%s)000000000 THIRTY_DAYS_NS=$((30 * 24 * 60 * 60))000000000 START_NS=$((NOW_NS - THIRTY_DAYS_NS)) curl --get "https://api.bebop.xyz/history/v2/trades" \ --data-urlencode "wallet_address=0xcaBD7845e4E51069E87a62d0A29064782134124C" \ --data-urlencode "start=$START_NS" \ --data-urlencode "end=$NOW_NS" \ --data-urlencode "size=5" ``` ```python python theme={null} import time import httpx now_ns = int(time.time() * 1_000_000_000) thirty_days_ns = 30 * 24 * 60 * 60 * 1_000_000_000 resp = httpx.get( "https://api.bebop.xyz/history/v2/trades", params={ "wallet_address": "0xcaBD7845e4E51069E87a62d0A29064782134124C", "start": now_ns - thirty_days_ns, "end": now_ns, "size": 5, }, ) data = resp.json() for trade in data.get("results", []): sell = ( list(trade.get("sellTokens", {}).values())[0] if trade.get("sellTokens") else {} ) buy = ( list(trade.get("buyTokens", {}).values())[0] if trade.get("buyTokens") else {} ) print( f'{sell.get("symbol", "?")} -> {buy.get("symbol", "?")} ' f'${trade.get("volumeUsd", 0):.2f} ({trade.get("timestamp", "")})' ) ``` ### Understanding the response This abridged response shows the fields used by most integrations. See the API reference for the complete historical schema. ```json theme={null} { "results": [ { "chain_id": 42161, "txHash": "0x56927ccf...9c05", "status": "Success", "type": "121", "taker": "0xcaBD78...124C", "receiver": "0xcaBD78...124C", "sellTokens": { "0xEeeeeE...EEeE": { "amount": "9706350785296284", "amountUsd": 20.03361683032797 } }, "buyTokens": { "0xaf88d0...5831": { "amount": "20057981", "amountUsd": 20.056035375843003 } }, "volumeUsd": 20.056035375843003, "gasFeeUsd": 0.01292311262430648, "timestamp": "2026-02-25 19:44:34Z", "route": "JAM" } ], "metadata": { "timestamp": "2026-03-18 09:49:08", "results": 1, "tokens": { "42161": { "0xEeeeeE...EEeE": { "name": "Ethereum", "symbol": "ETH", "decimals": 18, "displayDecimals": 5, "icon": "https://bebop-public-images.s3.eu-west-2..." }, "0xaf88d0...5831": { "name": "USDC", "symbol": "USDC", "decimals": 6, "displayDecimals": 2, "icon": "https://bebop-public-images.s3.eu-west-2..." } } } } } ``` | Field | Type | Description | | - | - | - | | `results` | array | Array of trade objects, newest first | | `results[].chain_id` | integer | Chain ID where the trade settled | | `results[].txHash` | string | Transaction hash | | `results[].status` | string | `Success`, etc. | | `results[].type` | string | Trade type (see [trade types](#trade-types) below) | | `results[].taker` | string | Address that sent the sell tokens | | `results[].receiver` | string | Address that received the buy tokens | | `results[].sellTokens` | object | Map of contract address to token info (`amount`, `amountUsd`, `symbol`) | | `results[].buyTokens` | object | Map of contract address to token info (`amount`, `amountUsd`, `symbol`) | | `results[].volumeUsd` | number | Trade volume in USD (excluding gas) | | `results[].gasFeeUsd` | number | Gas fee in USD at the time of the trade | | `results[].timestamp` | string | When the trade occurred | | `results[].route` | string | Which Bebop API executed the trade (`JAM` or `PMM`) | | `nextAvailableTimestamp` | integer or null | Nanosecond timestamp for pagination. `null` when all trades have been returned. | | `metadata.timestamp` | string | Current server timestamp | | `metadata.results` | integer | Number of trade objects in this response | ### Trade types | Type | Description | | - | - | | `121` | Single swap - one token in, one token out | | `12M` | One token in, multiple tokens out (exact amounts per token) | | `M21` | Multiple tokens in, one token out (exact amounts per token) | | `12MPercentages` | One token in, multiple tokens out (percentage ratios across output tokens) | | `M21Percentages` | Multiple tokens in, one token out (percentage ratios across input tokens) | ### Filtering by time range Narrow results to a specific window using `start` and `end` parameters. Timestamps are in **nanoseconds**, not seconds or milliseconds. For example, `1680303600000000000` corresponds to `2023-03-31T21:00:00Z`. Passing a millisecond timestamp will return no results. ```python theme={null} import time import httpx # Last 30 days now_ns = int(time.time() * 1_000_000_000) thirty_days_ns = 30 * 24 * 60 * 60 * 1_000_000_000 resp = httpx.get( "https://api.bebop.xyz/history/v2/trades", params={ "wallet_address": "0xcaBD7845e4E51069E87a62d0A29064782134124C", "start": now_ns - thirty_days_ns, "end": now_ns, "size": 100, }, ) data = resp.json() print(f'Fetched {len(data.get("results", []))} trades in last 30 days') ``` ### Pagination A single response returns at most 500 trades per chain (the `size` cap). If `nextAvailableTimestamp` is not `null`, repeat the request using that value as your new `end` parameter: ```python theme={null} import time import httpx now_ns = int(time.time() * 1_000_000_000) thirty_days_ns = 30 * 24 * 60 * 60 * 1_000_000_000 all_trades = [] end = now_ns while True: resp = httpx.get( "https://api.bebop.xyz/history/v2/trades", params={ "wallet_address": "0xcaBD7845e4E51069E87a62d0A29064782134124C", "start": now_ns - thirty_days_ns, "end": end, "size": 100, }, ) data = resp.json() all_trades.extend(data.get("results", [])) next_ts = data.get("nextAvailableTimestamp") if next_ts is None: break end = next_ts print(f"Fetched {len(all_trades)} trades") ``` ## 2. Look up a specific transaction Retrieve details for a single transaction hash. The API searches across all chains automatically. ``` GET /history/v2/tx/{tx_hash} ``` | Parameter | Required | Type | Description | | - | - | - | - | | `tx_hash` | Yes | string | Transaction hash (66-character hex string starting with `0x`) | ```bash bash theme={null} curl "https://api.bebop.xyz/history/v2/tx/0x8f4adfc8aa60711c464194a2d297f823e7f54fadc3995ea1844d6731fdaf38ee" ``` ```python python theme={null} import httpx tx_hash = "0x8f4adfc8aa60711c464194a2d297f823e7f54fadc3995ea1844d6731fdaf38ee" resp = httpx.get(f"https://api.bebop.xyz/history/v2/tx/{tx_hash}") data = resp.json() # The response wraps results in a list results = data.get("results", [data]) trade = results[0] if results else data sell = ( list(trade.get("sellTokens", {}).values())[0] if trade.get("sellTokens") else {} ) buy = list(trade.get("buyTokens", {}).values())[0] if trade.get("buyTokens") else {} print( f'Chain {trade.get("chain_id", "?")}: {sell.get("symbol", "?")} -> {buy.get("symbol", "?")}' ) print( f'Volume: ${trade.get("volumeUsd", 0):.2f} Gas: ${trade.get("gasFeeUsd", 0):.4f}' ) ``` The response is a single trade object with the same schema as the items in the `/trades` array. ### Errors | Code | Detail | Reason | | - | - | - | | 400 | `Invalid tx hash: {tx_hash}.` | Not a valid 66-character hex string | | 404 | `Tx hash not found: {tx_hash}.` | Transaction was not executed through Bebop | | 500 | `Something went wrong, try again later.` | Server error | # Aggregators Source: https://docs.bebop.xyz/use-cases/aggregators Deliver best-in-class swap execution by accessing professional market maker depth that direct DEX routing can't match. Aggregators route user swaps across multiple liquidity sources to find the best execution. Integrate once to access deep institutional liquidity alongside your existing DEX routes, improving fill rates and pricing for large trades where others fall short. [**BopAMM**](/bopamm/introduction) is now generally available. Add a new oracle-priced execution venue to your routing alongside RFQ. [Request access](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_aggregators_bopamm\&utm_medium=docs\&utm_campaign=bopamm_access). ## Key Benefits * **Single integration, many sources:** instantly unlock multiple market makers instead of managing individual partnerships * **Guaranteed execution and guaranteed fill:** High fill rates with firm pricing * **Quotes arrive quickly** - keep your routing competitive * **Comprehensive coverage:** Deep liquidity across hundreds of tokens and multiple chains * **Your fees priced in** - simplified monetization with no overhead ## Recommended APIs Get firm quotes from institutional market makers with guaranteed execution and guaranteed fill. Stream real-time indicative prices for pre-trade routing decisions. ## Ready For Integration? Start building with Bebop today. [Request API access](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_aggregators) for production-grade pricing, full rate limits, and technical support from our team. # Consumer Apps & Wallets Source: https://docs.bebop.xyz/use-cases/consumer Ship reliable swaps your users can trust - guaranteed prices, guaranteed fills, and broad token coverage across all major chains. Consumer wallets and apps need swaps that just work. Failed transactions, price slippage, and reverts destroy user trust. Bebop delivers guaranteed prices and guaranteed fills from professional market makers, so your users get the price they see every time - no surprises, no failed swaps. ## Key Benefits * **No failed swaps** - guaranteed price and guaranteed fill on every trade * **All important chains covered** with consistent reliability across networks * **Quote optimized for speed or price** - your choice depending on the user experience you want to deliver * **Wide range of tokens covered**, including RWA tokens and stablecoins * **Optional fee collection embedded** directly in quotes - simplified monetization with no overhead ## Recommended APIs Get firm quotes from institutional market makers with guaranteed execution and guaranteed fill. Access solver auction liquidity for broad token coverage including long-tail assets. ## Ready For Integration? Start building with Bebop today. [Request API access](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_consumer) for production-grade pricing, full rate limits, and technical support from our team. # Cross-Chain Aggregators Source: https://docs.bebop.xyz/use-cases/cross-chain-aggregators Source efficient liquidity on both source and destination chains for seamless cross-chain swaps. Cross-chain aggregators enable asset transfers across chains by sourcing liquidity on both source and destination sides. Bebop delivers firm price guarantees and deep liquidity across multiple chains, eliminating destination-side slippage uncertainty while providing a consistent integration across all supported networks. ## Key Benefits * **Deep liquidity** with fill guarantees across chains * **Firm price guarantees** eliminate source and destination-side slippage uncertainty * **Multi-chain coverage** with consistent reliability ## Recommended APIs Get firm quotes from institutional market makers with guaranteed execution and guaranteed fill. Access solver auction liquidity for broad token coverage across chains. ## See It In Production How Bebop became critical infrastructure for Rhino's crosschain swaps. ## Ready For Integration? Start building with Bebop today. [Request API access](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_cross_chain_aggregators) for production-grade pricing, full rate limits, and technical support from our team. # Institutional Apps & Wallets Source: https://docs.bebop.xyz/use-cases/institutional Exceptional firm pricing for larger trades with custom routing and multi-token portfolio rebalancing. Institutional applications and wallets have unique requirements - larger trade sizes, specific counterparty preferences, and complex multi-asset workflows. Bebop provides exceptional quality firm pricing for larger trades, with the flexibility to customize routing and execute sophisticated portfolio operations in a single transaction. ## Key Benefits * **Exceptional firm pricing for larger trades** - deep market maker liquidity that scales with order size * **Custom routing to selected market makers** - choose your preferred market makers or route to a known set of counterparties * **Multi-token trades for portfolio rebalancing** - swap multiple tokens in a single atomic transaction, ideal for one-click rebalancing or basket trades * **Guaranteed execution and guaranteed fill** - firm prices, no reverts, no failed transactions ## Recommended APIs Get firm quotes from institutional market makers with guaranteed execution and guaranteed fill. ## See It In Production How Bebop's RFQ network became a key piece of Definitive's advanced execution engine. ## Ready For Integration? Start building with Bebop today. [Request API access](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_institutional) for production-grade pricing, full rate limits, and technical support from our team. # Liquidators Source: https://docs.bebop.xyz/use-cases/liquidators Execute time-critical liquidations with maximum speed and competitive pricing to capture liquidation fees while protecting protocol solvency. Liquidators need instant execution at competitive prices to profitably close underwater positions. Bebop delivers firm quotes with guaranteed prices and guaranteed fills for large liquidation sizes, with ultra-fast response times that let you compete effectively for liquidation fees. [**BopAMM**](/bopamm/introduction) is now generally available. Liquidate against firm oracle-priced quotes with predictable execution at size. [Request access](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_liquidators_bopamm\&utm_medium=docs\&utm_campaign=bopamm_access). ## Key Benefits * **Instant deep liquidity** for large positions - orders automatically split across multiple market makers when needed * **Guaranteed price and fill** - firm quotes eliminate slippage risk during volatile liquidations * **Ultra-fast quotes** with simplified routing offering 100ms quote response times * **Broad token support** for diverse collateral types ## Recommended APIs Get firm quotes from institutional market makers with guaranteed execution and guaranteed fill. ## Ready For Integration? Start building with Bebop today. [Request API access](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_liquidators) for production-grade pricing, full rate limits, and technical support from our team. # Market Makers Source: https://docs.bebop.xyz/use-cases/market-makers Access high-quality order flow across multiple client segments while managing risk efficiently through flexible quoting infrastructure. **Interested in providing liquidity?** Review the requirements below and [complete our Market Maker Application](https://survey.typeform.com/to/QsnrTOAz?utm_source=docs_discover_market_makers). Market makers source profitable order flow while managing risk across multiple venues. Bebop aggregates demand from a diverse set of organizations, giving you access to high-quality flow through a single integration with full control over your quoting parameters. [**BopAMM**](/bopamm/introduction) is now generally available. Provide liquidity through coordinated oracle updates and share the cost of refreshes across participants. [Request access](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_market_makers_bopamm\&utm_medium=docs\&utm_campaign=bopamm_access). ## Key Benefits * **Integration with the largest RFQ platform on EVM** * **Diversified order flow:** Tap into demand from a diverse set of on-chain actors - wallets, aggregators, solvers, and institutional apps * **Multi-chain presence** - provide liquidity across all supported networks from a single integration * **Firm quote model** - you control your pricing, spreads, and risk exposure * **Direct RFQ execution** - provide firm quotes for taker-submitted transactions * **Transparent metrics:** Monitor fill rates and other trading stats to optimize your quoting strategy ## What We Look For We partner with teams who can provide: * **Active liquidity:** Consistent quoting with competitive spreads across your supported pairs * **Reliable infrastructure:** Low latency systems and high uptime ## Get Started [Complete our Market Maker Application](https://survey.typeform.com/to/QsnrTOAz?utm_source=docs_discover_market_makers) and the team will walk you through the integration process. # RWA & Stablecoin Issuers Source: https://docs.bebop.xyz/use-cases/rwa-issuers Enable liquidity for your asset with capital efficiency and full distribution across DeFi. Liquidity is a core component of any asset, and it needs to be available in size. Traders want tight spreads, vault curators require availability of liquidations, and consistent volumes attract attention that compounds into further growth. Pool-based liquidity is expensive: capital sits idle and usually requires ongoing incentives to attract and rebalance provisioning. Bebop's RFQ infrastructure delivers the same on-chain availability with an order of magnitude less capital, and plugs your asset into distribution across DeFi from day one. ## Liquidity Models Choose the operating model that fits your team: * **Protocol-owned liquidity** - become the market maker yourself and retain full on-chain ownership of inventory, pricing, and spread capture. * **Third-party market makers** - contract with one or more makers in Bebop's network to price and manage inventory on your behalf. ## Key Benefits * **Capital efficiency** - an order of magnitude cheaper than funding and managing AMM pools, with no idle capital or rebalancing overhead * **Instant distribution** across aggregators, solvers, liquidators, and wallets integrated with Bebop from day one * **Support for very large sizes** at guaranteed price with guaranteed fill, suitable for trading and liquidations * **Full control of pricing and size** - you set spreads, inventory availability, and which chains you support * **Embed fees or rebates** directly into any transaction through the RFQ flow ## How It Works You or a contracted market maker price and commit inventory. No AMM pool is required to make your asset available on-chain. Solvers, aggregators, and liquidators already integrated with Bebop automatically gain access to your liquidity. Users swap hundreds of tokens into your asset, and vice versa, across every connected application. ## Recommended APIs Provide firm quotes to the entire Bebop distribution network with full control of pricing and size. ## See It In Production How Monerium and Bebop bring scalable euro liquidity to DeFi. ## Get Started [Contact us](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_rwa_issuers) to discuss bringing your asset onto Bebop's RFQ network. # Solvers Source: https://docs.bebop.xyz/use-cases/solvers Access institutional market maker liquidity to win auctions through superior pricing and execution speed. Solvers compete in intent-based auctions by sourcing the best execution paths for user orders. Our RFQ platform gives you direct access to institutional level market maker liquidity that consistently outperforms public DEX paths at size. [**BopAMM**](/bopamm/introduction) is now generally available. Execute against coordinated, oracle-priced maker quotes with onchain settlement guarantees. [Request access](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_solvers_bopamm\&utm_medium=docs\&utm_campaign=bopamm_access). ## Key Benefits * **Access deeper liquidity** with large orders automatically split across multiple market makers for optimal pricing * **Get better pricing** with short expiry quotes for auction competitiveness * **Win time sensitive auctions** with simplified quoting process getting a price in 100ms * **Improve fill rates** with partial fill support - combine AMM and market maker liquidity seamlessly * **Reduce overhead** with fees priced directly into quotes, eliminating calculation complexity * **Optimize pre-auction** using our real-time indicative prices for efficient path computation * **Scale operations** with batch quoting for multiple intents simultaneously ## Recommended APIs Get firm quotes from institutional market makers with guaranteed execution and guaranteed fill. Stream real-time indicative prices for pre-auction path computation and efficient routing. ## See It In Production How offchain liquidity helps a solver consistently outbid the competition. ## Ready For Integration? Start building with Bebop today. [Request API access](https://survey.typeform.com/to/tmPax8Fu?utm_source=docs_discover_solvers) for production-grade pricing, full rate limits, and technical support from our team. # Welcome Source: https://docs.bebop.xyz/welcome Institutional RFQ infrastructure for decentralized finance. Bebop delivers **private market maker liquidity** with guaranteed pricing and execution alongside **solver auction liquidity** for comprehensive token coverage - making professional-grade execution accessible to any application. ## Who integrates Bebop Reliable swaps with guaranteed execution and fill Deep liquidity for large trades with custom routing Professional market maker depth in your routing Win auctions with institutional liquidity and speed Firm pricing on source and destination chains Instant execution for time-critical liquidations Primary and secondary market infrastructure High-quality order flow across all supported chains ## See it in production * **[Built for the Desk: Powering institutional-grade execution for Definitive](https://bebop.xyz/case-studies/definitive)** - How Bebop's RFQ network became a key piece of Definitive's advanced execution engine. * **[Winning the Batch: Fractal's offchain liquidity edge on Cowswap](https://bebop.xyz/case-studies/fractal)** - How offchain liquidity helps a solver consistently outbid the competition. * **[Zero Failed Swaps: Powering Rhino's Cross-Chain Onboarding](https://bebop.xyz/case-studies/rhino)** - How Bebop became critical infrastructure for Rhino's crosschain swaps. * **[Issuer-led liquidity: Monerium's formula for onchain Euro execution](https://bebop.xyz/case-studies/monerium)** - How Monerium and Bebop bring scalable euro liquidity to DeFi.