# 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. # Order Source: https://docs.bebop.xyz/aggregation-api/api-reference/order /specs/aggregation-api.json post /v2/order Submit a signed order for gasless execution. Requires a valid quote ID and an EIP-712 signature from the taker. Bebop submits the transaction on-chain and covers gas fees. # Order Status Source: https://docs.bebop.xyz/aggregation-api/api-reference/order-status /specs/aggregation-api.json get /v2/order-status Returns the current status of a previously submitted order. Possible statuses include Pending, Success, Failed, and Expired. # Quote Source: https://docs.bebop.xyz/aggregation-api/api-reference/quote /specs/aggregation-api.json get /v2/quote Get a quote for a token swap via solver competition. Solvers compete to offer the best price. By default returns a gasless quote that must be signed and submitted to /order. Set gasless=false to get a self-execution transaction. # 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** | EIP-712 (gasless) / none (self-execution) | | **On-chain tx** | Bebop submits (gasless) or you broadcast (self-execution) | | **Gasless** | Yes (default) | | **Complexity** | Medium - similar to RFQ, plus slippage configuration | ## How It Works The Aggregation API supports two execution modes. Gasless is the default and recommended for most integrations. Bebop handles on-chain submission. Your users sign a message but never pay gas. | Step | Action | You send | You get back | | ---- | ------------------- | ----------------------------------------------- | -------------------------------------------------- | | 1 | Request a quote | Token pair, amount | Best solver price, EIP-712 typed data (`JamOrder`) | | 2 | Sign the order | EIP-712 typed data โ†’ taker wallet | Signature | | 3 | Submit to Bebop | Signature โ†’ `POST /jam/{network}/v2/order` | Quote ID | | 4 | Poll for settlement | Quote ID โ†’ `GET /jam/{network}/v2/order-status` | Status: `Settled` | You broadcast the transaction yourself for direct on-chain settlement. | Step | Action | You send | You get back | | ---- | ------------------ | ----------------------------------- | ------------------------------ | | 1 | Request a quote | Token pair, amount, `gasless=false` | Best solver price, `tx` object | | 2 | Broadcast on-chain | `tx` object โ†’ broadcast | On-chain settlement | See [Execution Modes](/core-concepts/execution-modes) for a detailed comparison. ## 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 | | `toSign.buyAmounts` | The `minimumAmount` values - this is what you sign and what the contract enforces | The on-chain settlement reverts if the solver cannot deliver at least `buyAmounts`. Any amount above that minimum is surplus that benefits the taker. ## Key Endpoints | Endpoint | Purpose | | ------------------------------------ | -------------------------------------------- | | `GET /jam/{network}/v2/quote` | Request a quote through solver competition | | `POST /jam/{network}/v2/order` | Submit a signed order for gasless settlement | | `GET /jam/{network}/v2/order-status` | Poll settlement status | ## 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 to settlement. This guide walks you through making your first trade using the Aggregation API. You'll request a quote, sign an EIP-712 message, submit the order, and poll for settlement. **What you'll build:** A complete trade flow using solver auction liquidity. **Time required:** 10-15 minutes **Prerequisites:** Basic understanding 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 guaranteed fill. The Aggregation API runs a solver auction - multiple solvers compete to fill your order across all available on-chain liquidity. This gives you broader token coverage (including long-tail assets) at the cost of requiring 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 | | Slippage | None - guaranteed execution and fill | Configurable tolerance | | Execution | Self-exec or gasless | Self-exec or gasless | | Base URL | `/pmm/{network}/v3/` | `/jam/{network}/v2/` | ## 1. Request a Quote ``` GET /jam/{network}/v2/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` | Wallet executing the trade | `0xYourWalletAddress` | Optional parameters: | Parameter | Description | Default | | ------------------ | ---------------------------------------------------------- | -------------------- | | `gasless` | Set to `false` for self-execution | `true` | | `approval_type` | `Standard` or `Permit2` (gasless only) | `Standard` | | `slippage` | Max acceptable slippage (0-50%, up to 2 decimal places) | Determined by solver | | `receiver_address` | Address to receive bought tokens (if different from taker) | `taker_address` | `Permit2` approval type is only supported with gasless execution. Self-execution requires `Standard` approvals. **Swap and send:** `taker_address` signs the order; the bought tokens go to `receiver_address`. `receiver_address` is optional and defaults to `taker_address`. Works in both gasless and self-execution modes. * 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") Example - selling 1 WETH for USDC on Ethereum: ```python theme={null} import httpx NETWORK = "ethereum" resp = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "sell_amounts": "1000000000000000000", # 1 WETH (18 decimals) "taker_address": "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693", "gasless": "false", # omit for gasless (default) }, ) quote = resp.json() print(quote) ``` ### Understanding the Response The response structure depends on whether you requested gasless or self-execution mode. **Gasless response** - includes `toSign` (the EIP-712 message you must sign). No `tx` object since Bebop handles on-chain submission: ```json theme={null} { "requestId": "76c0e766-5894-4f20-8799-515f7f9fe0d3", "type": "121", "status": "Success", "quoteId": "dbddb28f-d459-4a7e-8d53-d29dc74946ab", "chainId": 1, "approvalType": "Standard", "nativeToken": "ETH", "taker": "0x5Bad99...BcB6", "receiver": "0x5Bad99...BcB6", "expiry": 1773827406, "slippage": 0.1, "gasFee": { "native": "0", "usd": 0.0 }, "buyTokens": { "0xA0b869...eB48": { "amount": "2330815183", "decimals": 6, "priceUsd": 0.999867, "symbol": "USDC", "minimumAmount": "2328484367", "price": 0.0004290344456710191, "priceBeforeFee": 0.0004290344456710191, "amountBeforeFee": "2330815183", "deltaFromExpected": -0.00045240758090680117 } }, "sellTokens": { "0xC02aaA...6Cc2": { "amount": "1000000000000000000", "decimals": 18, "priceUsd": 2331.56, "symbol": "WETH", "price": 2330.815183, "priceBeforeFee": 2330.815183 } }, "settlementAddress": "0xbeb0b0...4ea6", "approvalTarget": "0xC5a350...579a", "requiredSignatures": [], "priceImpact": -0.00045240758090680117, "warnings": [], "tx": { "chainId": 1, "from": "0x5Bad99...BcB6", "to": "0xbeb0b0...4ea6", "value": "0x0", "data": "0x2143d82c000000000000000000000000000000...", "gas": 906396 }, "hooksHash": "0x00000000...0000", "toSign": { "taker": "0x5Bad99...BcB6", "receiver": "0x5Bad99...BcB6", "expiry": 1773827406, "exclusivityDeadline": 1773827406, "nonce": "292252050346888230878638781342528652971", "executor": "0x000000...0000", "partnerInfo": "0", "sellTokens": [ "0xC02aaA...6Cc2" ], "buyTokens": [ "0xA0b869...eB48" ], "sellAmounts": [ "1000000000000000000" ], "buyAmounts": [ "2328484367" ], "hooksHash": "0x00000000...0000" }, "solver": "๐Ÿ†" } ``` **Self-execution response** - includes both a `tx` object (settlement calldata ready to broadcast) and `toSign`: ```json theme={null} { "quoteId": "121-174...", "sellTokens": { ... }, "buyTokens": { ... }, "toSign": { ... }, "tx": { "to": "0xbeb0...4ea6", "value": "0x0", "data": "0x4dcebcba...", "from": "0xYour...", "gas": 250000, "gasPrice": 161765683 } } ``` Key fields: | Field | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `quoteId` | Unique identifier - you'll need this for order submission and status polling | | `solver` | Name of the solver that won the auction | | `expiry` | Unix timestamp - the order is invalid after this time | | `buyTokens.minimumAmount` | Worst-case amount after slippage. The `amount` is the expected fill. | | `gasFee` | Estimated gas cost in native token and USD | | `approvalTarget` | **The contract you must approve.** Do not hardcode this address or confuse it with `settlementAddress`. Approving the wrong contract may lead to loss of funds. | | `toSign` | EIP-712 message fields. Present in both modes; used for gasless signing. | | `tx` | Ready-to-broadcast transaction (self-execution only) | ## 2. Approve Tokens Before the settlement contract can move your sell tokens, you need an ERC-20 approval on the `approvalTarget` address from the quote response. See the [Token Approvals guide](/core-concepts/token-approvals) for the full check-and-approve flow. ```python theme={null} from eth_account import Account from web3 import Web3 PRIVATE_KEY = "0x" RPC_URL = "https://eth.llamarpc.com" w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) sell_token = list(quote["sellTokens"].keys())[0] sell_amount = int(quote["sellTokens"][sell_token]["amount"]) approval_target = quote["approvalTarget"] # Check current allowance and approve if needed # See Token Approvals guide for full implementation ``` With `approval_type=Permit2` (gasless only), the approval goes to the Permit2 contract instead, and the taker signs a Permit2 message bundled with the order. This avoids needing a separate on-chain approval transaction for each new spender. ## 3. Sign & Submit The Aggregation API supports two execution modes. Choose the one that fits your integration: With self-execution (`gasless=false`), the quote response includes a `tx` object containing the complete settlement calldata. You broadcast the transaction yourself. ```python theme={null} # The quote response already contains the ready-to-broadcast tx tx = quote["tx"] # Add gas parameters 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, PRIVATE_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) print(f"Settled in block {receipt['blockNumber']}") ``` With self-execution, there's no separate EIP-712 signing step and no `/order` POST. The `tx` object contains everything needed for settlement. With gasless execution (default), you sign an EIP-712 message and POST it to `/order`. Bebop handles the on-chain submission. ### Sign the EIP-712 order ```python theme={null} from eth_account.messages import encode_typed_data JAM_ORDER_TYPES = { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}, ], "JamOrder": [ {"name": "taker", "type": "address"}, {"name": "receiver", "type": "address"}, {"name": "expiry", "type": "uint256"}, {"name": "exclusivityDeadline", "type": "uint256"}, {"name": "nonce", "type": "uint256"}, {"name": "executor", "type": "address"}, {"name": "partnerInfo", "type": "uint256"}, {"name": "sellTokens", "type": "address[]"}, {"name": "buyTokens", "type": "address[]"}, {"name": "sellAmounts", "type": "uint256[]"}, {"name": "buyAmounts", "type": "uint256[]"}, {"name": "hooksHash", "type": "bytes32"}, ], } typed_data = { "types": JAM_ORDER_TYPES, "domain": { "name": "JamSettlement", "version": "2", "chainId": quote["chainId"], "verifyingContract": quote["settlementAddress"], }, "primaryType": "JamOrder", "message": quote["toSign"], } signable = encode_typed_data(full_message=typed_data) signed = Account.sign_message(signable, private_key=PRIVATE_KEY) signature = signed.signature.hex() ``` ### Submit the order ``` POST /jam/{network}/v2/order ``` ```python theme={null} resp = httpx.post( f"https://api.bebop.xyz/jam/{NETWORK}/v2/order", json={ "quote_id": quote["quoteId"], "signature": signature, "sign_scheme": "EIP712", }, ) order = resp.json() print(f"Order submitted: {order['status']}") ``` Order request fields: | Field | Type | Description | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------- | | `quote_id` | string | The `quoteId` from the quote response | | `signature` | string | Your EIP-712 signature (hex) | | `sign_scheme` | string | `"EIP712"` (default) or `"EIP1271"` (smart contract wallets - the contract's `isValidSignature` is called on-chain) | | Field | Description | | --------------------- | -------------------------------------------------------------------------------------------------------- | | `taker` | Address that signs and owns the sell tokens | | `receiver` | Address that receives the buy tokens (often same as `taker`) | | `expiry` | Unix timestamp after which the order is invalid | | `exclusivityDeadline` | Timestamp until which only the winning solver can settle. After this, any solver can fill. | | `nonce` | Unique identifier for replay protection | | `executor` | Address authorized to settle the order (typically the settlement contract) | | `partnerInfo` | Encoded partner fee information (protocol fee bps, partner fee bps, partner address) | | `sellTokens` | Array of token addresses the taker is selling | | `buyTokens` | Array of token addresses the taker is buying | | `sellAmounts` | Array of sell amounts in base units, matching `sellTokens` order | | `buyAmounts` | Minimum buy amounts in base units, matching `buyTokens` order. These are the slippage-adjusted minimums. | | `hooksHash` | Keccak256 hash of pre/post-settlement hooks. `0x000...000` when no hooks are used. | ### Permit2 Signing Variant When using `approval_type=Permit2` with gasless execution, the signing structure changes. Instead of signing `JamOrder` directly, you sign a `PermitBatchWitnessTransferFrom` that wraps the order: ```python theme={null} PERMIT2_WITH_JAM_ORDER_TYPES = { "PermitBatchWitnessTransferFrom": [ {"name": "permitted", "type": "TokenPermissions[]"}, {"name": "spender", "type": "address"}, {"name": "nonce", "type": "uint256"}, {"name": "deadline", "type": "uint256"}, {"name": "witness", "type": "JamOrder"}, ], "TokenPermissions": [ {"name": "token", "type": "address"}, {"name": "amount", "type": "uint256"}, ], "JamOrder": [ # Same fields as standard JamOrder above ], } ``` The domain changes to the Permit2 contract: ```python theme={null} { "name": "Permit2", "chainId": quote["chainId"], "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3", } ``` The quote response `toSign` will contain `permitted`, `spender`, `nonce`, `deadline`, and `witness` (which is the `JamOrder` data) instead of the flat `JamOrder` fields. ## 4. Poll for Settlement Both execution modes support status polling via `/order-status`. ``` GET /jam/{network}/v2/order-status ``` ```python theme={null} import time while True: status_resp = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/order-status", params={"quote_id": quote["quoteId"]}, ) result = status_resp.json() print(f"Status: {result['status']}") if result["status"] in ("Confirmed", "Settled"): print(f"Tx: {result['txHash']}") if result.get("surplus"): print(f"Surplus captured: {result['surplus']}") break elif result["status"] == "Failed": print("Order failed") break time.sleep(2) ``` Order statuses: | Status | Meaning | | ----------- | ------------------------------------------------------------------------------ | | `Pending` | Bebop received the order and is preparing to submit | | `Success` | Solver accepted - transaction is being broadcast | | `Settled` | Transaction confirmed on-chain - tokens transferred | | `Confirmed` | Final success state - settlement fully confirmed | | `Failed` | Order failed (solver rejection, on-chain failure, expiry, or validation error) | The status response also includes: | Field | Description | | --------- | ---------------------------------------------------------------------- | | `txHash` | Transaction hash (available once broadcast) | | `amounts` | Actual token amounts received after settlement | | `surplus` | Price improvement the solver achieved beyond the quoted minimum amount | ## Full Example ```python theme={null} import time import httpx from eth_account import Account from web3 import Web3 # --- Config --- PRIVATE_KEY = "0x" NETWORK = "ethereum" RPC_URL = "https://eth.llamarpc.com" # --- 1. Request a quote (self-execution) --- taker_address = "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" resp = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "sell_amounts": "1000000000000000000", # 1 WETH "taker_address": taker_address, "gasless": "false", }, ) quote = resp.json() buy_token_addr = list(quote["buyTokens"].keys())[0] buy_token = quote["buyTokens"][buy_token_addr] print( f'Quote: sell 1 WETH -> buy ~{int(buy_token["amount"]) / 10 ** buy_token["decimals"]:.2f} USDC' ) w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) # --- 2. Approve tokens (see Token Approvals guide) --- # ensure_allowance(account, sell_token, quote["approvalTarget"], sell_amount) # --- 3. Broadcast the transaction --- tx = quote["tx"] 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, PRIVATE_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) print(f'Settled in block {receipt["blockNumber"]}') # --- 4. Poll for settlement confirmation --- while True: status_resp = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/order-status", params={"quote_id": quote["quoteId"]}, ) result = status_resp.json() print(f'Status: {result["status"]}') if result["status"] in ("Confirmed", "Settled"): print(f'Tx: {result["txHash"]}') if result.get("surplus"): print(f'Surplus captured: {result["surplus"]}') break elif result["status"] == "Failed": print("Order failed") break time.sleep(2) ``` ```python theme={null} import time import httpx from eth_account import Account from eth_account.messages import encode_typed_data # --- Config --- PRIVATE_KEY = "0x" NETWORK = "ethereum" # --- 1. Request a quote (gasless, the default) --- taker_address = "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" resp = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "sell_amounts": "1000000000000000000", # 1 WETH "taker_address": taker_address, }, ) quote = resp.json() buy_token_addr = list(quote["buyTokens"].keys())[0] buy_token = quote["buyTokens"][buy_token_addr] print( f'Quote: sell 1 WETH -> buy ~{int(buy_token["amount"]) / 10 ** buy_token["decimals"]:.2f} USDC' ) # --- 2. Approve tokens (see Token Approvals guide) --- # ensure_allowance(account, sell_token, quote["approvalTarget"], sell_amount) # --- 3. Sign the JAM order --- JAM_ORDER_TYPES = { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}, ], "JamOrder": [ {"name": "taker", "type": "address"}, {"name": "receiver", "type": "address"}, {"name": "expiry", "type": "uint256"}, {"name": "exclusivityDeadline", "type": "uint256"}, {"name": "nonce", "type": "uint256"}, {"name": "executor", "type": "address"}, {"name": "partnerInfo", "type": "uint256"}, {"name": "sellTokens", "type": "address[]"}, {"name": "buyTokens", "type": "address[]"}, {"name": "sellAmounts", "type": "uint256[]"}, {"name": "buyAmounts", "type": "uint256[]"}, {"name": "hooksHash", "type": "bytes32"}, ], } typed_data = { "types": JAM_ORDER_TYPES, "domain": { "name": "JamSettlement", "version": "2", "chainId": quote["chainId"], "verifyingContract": quote["settlementAddress"], }, "primaryType": "JamOrder", "message": quote["toSign"], } signable = encode_typed_data(full_message=typed_data) signed = Account.sign_message(signable, private_key=PRIVATE_KEY) signature = signed.signature.hex() # --- 4. Submit the order --- resp = httpx.post( f"https://api.bebop.xyz/jam/{NETWORK}/v2/order", json={ "quote_id": quote["quoteId"], "signature": signature, "sign_scheme": "EIP712", }, ) order = resp.json() print(f'Order submitted: {order["status"]}') # --- 5. Poll for settlement --- while True: status_resp = httpx.get( f"https://api.bebop.xyz/jam/{NETWORK}/v2/order-status", params={"quote_id": quote["quoteId"]}, ) result = status_resp.json() print(f'Status: {result["status"]}') if result["status"] in ("Confirmed", "Settled"): print(f'Tx: {result["txHash"]}') if result.get("surplus"): print(f'Surplus captured: {result["surplus"]}') break elif result["status"] == "Failed": print("Order failed") break time.sleep(2) ``` ## Next Steps Set up ERC-20 approvals before trading. Compare with the RFQ API for 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. # BopAMM Source: https://docs.bebop.xyz/bopamm-beta Apply for access to Bebop's Block Oracle Priced AMM closed beta. BopAMM (Block Oracle Priced AMM) is Bebop's new Ethereum execution primitive: a coordinated, oracle-priced AMM updated by participating market makers and built for best execution at size. The closed beta is now accepting applications from launch partners. The full design and rationale for Block Oracle Priced AMM. We'll review your application and reach out within a few business days. # 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": "0xB098882fA4BAC9E0A80d34728423C554ac922Ec1", "approvalTarget": "0xB098882fA4BAC9E0A80d34728423C554ac922Ec1", "requiredSignatures": [], "sellTokens": { "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { "amount": "1000000", "decimals": 6, "symbol": "USDC" } }, "buyTokens": { "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { "amount": "504024636724243", "minimumAmount": "501504513540621", "decimals": 18, "symbol": "WETH" } }, "tx": { "to": "0xB098882fA4BAC9E0A80d34728423C554ac922Ec1", "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. BopAMM is in closed beta. 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` | `0xB09AaA5614916d7AEb59C295C52c92ca82aDdD76` | Core ERC-20 pool for `quote`, push-payment `swap`, and `swapWithCallback`. | | `BopAmmRouter` | `0xB098882fA4BAC9E0A80d34728423C554ac922Ec1` | 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. Launch partners get early 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("0xB09AaA5614916d7AEb59C295C52c92ca82aDdD76") BOPAMM_ROUTER_ADDRESS = Web3.to_checksum_address("0xB098882fA4BAC9E0A80d34728423C554ac922Ec1") 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": "0xDa7AfeeD01fe625CF15d187a19f94B45f00b8C5F", "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. Closed beta - applications open. 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. # Execution Modes Source: https://docs.bebop.xyz/core-concepts/execution-modes Choose between self-execution and gasless trading across Bebop's APIs. Both the RFQ API and Aggregation API support two execution modes, controlled by the `gasless` query parameter on the quote request. ## Self-Execution (`gasless=false`) You broadcast the settlement transaction yourself. The quote response includes a `tx` object with the complete calldata. **Flow:** Quote โ†’ Approve โ†’ Sign & Broadcast `tx` **Best for:** Solvers, aggregators, liquidators, and any integration that already manages its own transaction submission pipeline. **Key properties:** * You pay gas * No last look - once the solver/maker has provided the quote, the `tx` is ready to execute * Implies `Standard` approvals * No `/order` POST needed ## Gasless (`gasless=true`, default) Bebop submits the settlement transaction on your behalf. You sign an EIP-712 message and POST it to the `/order` endpoint. The user never pays gas. **Flow:** Quote โ†’ Approve โ†’ Sign EIP-712 โ†’ POST `/order` โ†’ Poll `/order-status` **Best for:** Wallets, super-apps, and consumer-facing products where users shouldn't think about gas. **Key properties:** * Bebop pays gas * Supports both `Standard` and `Permit2` approval types * Requires EIP-712 signature and `/order` submission ## Comparison | | Self-execution | Gasless (default) | | --------------- | ---------------------------- | -------------------------- | | `gasless` param | `false` | `true` (default) | | Who submits tx | You broadcast via your RPC | Bebop submits on-chain | | Gas cost | Paid by taker | Paid by Bebop | | Token approvals | Standard only | Standard or Permit2 | | Signing | Standard transaction signing | EIP-712 signature required | ## API-Specific Behavior While both APIs share the same execution modes, there are differences in how the modes work: ### RFQ API In gasless mode, market makers retain **last look** - they can reject a quote before settlement. This allows tighter pricing but means your integration should handle `Failed` statuses gracefully. In self-execution mode, quotes are firm once the `tx` object is returned. See the [RFQ Gasless Execution guide](/rfq-api/guides/gasless-execution) for the full RFQ-specific gasless flow including last look handling. ### Aggregation API The solver auction determines pricing in both modes. In self-execution, the `tx` object contains the winning solver's settlement calldata. In gasless mode, you sign a `JamOrder` EIP-712 message. See the [Aggregation API Quickstart](/aggregation-api/quickstart) for both flows with complete code 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 uses either the router or the settlement contract depending on the type of the trade: | Contract | Address | Purpose | | --------------- | -------------------------------------------- | ------------------------------------------ | | BebopRouter | `0xBeb0009ACa35087ce7cCF11637E24dd1Aad3bf2A` | Used for one-to-one token swaps | | BebopSettlement | `0xbbbbbBB520d69a9775E85b458C58c648259FAD5F` | Used for many-to-one and one-to-many swaps | The `approvalTarget` and `settlementAddress` in quote responses would point to either of those. ### 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 When you request a quote, Bebop returns an [EIP-712](/core-concepts/execution-modes#eip-712-signing) typed message representing the exact trade terms - tokens, amounts, and price minimums. You sign this message to authorize the trade. The settlement contract then verifies your signature on-chain, checks that the execution price meets the agreed minimums, and atomically transfers tokens between you and the maker(s) in a single transaction. 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 | Description | Compatible With | | ------------- | --------------------------------------------------------- | -------------------------- | | Standard | ERC-20 `approve()` on the token contract | Self-execution and gasless | | Permit2 | Approval bundled into the signed message (no on-chain tx) | Gasless only | ## EIP-712 Domains Each API uses its own EIP-712 domain for signature verification: | API | Domain Name | Version | | --------------- | ----------------- | ------- | | RFQ API | `BebopRouter` | `1` | | RFQ API | `BebopSettlement` | `2` | | Aggregation API | `JamSettlement` | `2` | The `verifyingContract` is always the `settlementAddress` from the quote response. The `chainId` is the chain the trade executes on. See the [RFQ gasless execution guide](/rfq-api/guides/gasless-execution#eip-712-order-type-schemas) and the [Aggregation API quickstart](/aggregation-api/quickstart#sign-the-eip-712-order) for the full EIP-712 type schemas. # 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 Bebop can settle a trade, the contract handling it needs permission to move your sell tokens. This is standard ERC-20 behavior - you call `approve()` on the token contract, authorizing the settlement contract to transfer up to a specified 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 either the router contract or the settlement contract, depending on how the trade is handled. 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. ## Permit2 In gasless mode (`gasless=true`), you can use `approval_type=Permit2` to replace the per-contract on-chain approval with a one-time approval of the [Permit2 contract](https://github.com/Uniswap/permit2). Token spending is then authorized via off-chain EIP-712 signatures rather than separate `approve()` transactions for each settlement contract. With `approval_type=Standard` (the default for gasless), the user must have approved the settlement contract beforehand - the same as self-execution. The Permit2 signing flow differs between APIs: * **Aggregation API:** The order is wrapped inside a `PermitBatchWitnessTransferFrom` message - you sign one combined message with the Permit2 domain. See the [Aggregation API quickstart](/aggregation-api/quickstart#permit2-signing-variant). * **RFQ API:** Order signing is unchanged (`BebopSettlement` domain, same types). You generate a **separate** `PermitBatch` signature and include it in the `/order` POST body. See the [RFQ gasless guide](/rfq-api/guides/gasless-execution#using-permit2-approvals). Permit2 is supported in gasless mode on both the RFQ API and Aggregation API. It is not compatible with self-execution on either API. # 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 requests-for-quote architecture: you specify what you want to trade, market makers compete to fulfill it, you sign the quote, submit the order, and settlement happens on-chain without Bebop taking custody. 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"&gasless=false" 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"&gasless=false" 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 + pricing mode | 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 a detailed pricing mode and expiry comparison. ## 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` | | `gasless` | No | `false` for self-execution quotes (default), `true` for gasless quotes | | `expiry_type` | No | `short` or `standard` (default) - controls quote expiry window | 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"&gasless=false" 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"&gasless=false" 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 Modes The stream supports two pricing modes, controlled by the `gasless` parameter on the WebSocket URL: | Mode | Parameter | Settlement | Last look | Pricing | | ------------------ | --------------- | ----------------------------------- | ---------------------------------------- | ---------------------- | | **Self-execution** | `gasless=false` | You submit the transaction on-chain | No - quotes are firm once signed | Slightly wider spreads | | **Gasless** | `gasless=true` | Bebop submits on-chain for you | Yes - maker can reject before settlement | Tighter spreads | **Self-execution** is the default and recommended mode for solvers and aggregators. Since there's no last look, the price you see is firm - but spreads are slightly wider to compensate. **Gasless** is designed for wallets and super-apps where gasless UX matters. Market makers retain last look, meaning they can reject a quote before settlement. In return, they offer tighter pricing. **Choosing the right mode:** If you're a solver or aggregator executing trades yourself, use self-execution. If you're building a wallet where users shouldn't pay gas, use gasless. Make sure your RFQ API quotes match the stream you're subscribed to. ## Quote Expiry The `expiry_type` parameter controls how long quotes remain valid. Short expiry is only available for self-execution - since gasless quotes are submitted by the maker, the taker doesn't control submission timing. | Expiry type | Availability | Window | Best for | | ----------- | -------------------------- | -------------------------------------- | ------------------------------------------------------------------------------- | | `standard` | Self-execution and gasless | \~60-75s (varies by chain) | Most integrators - easier to manage, more time to sign and submit | | `short` | Self-execution only | \~5s on Ethereum, \~3s on other chains | Latency-sensitive solvers - must submit by end of block, but pricing is tighter | 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. # Privacy Policy Source: https://docs.bebop.xyz/privacy Bebop privacy policy. PDF document # Order Source: https://docs.bebop.xyz/rfq-api/api-reference/order /specs/rfq-api.json post /v3/order This endpoint provides a simple order placement mechanism for quotes retrieved from [/quote](#/v3/v3_quote_v3_quote_post) endpoint. You will be required to sign the quote object using an EOA private key and POST this signature along with the quote ID to place an order. Bebop submits order on chain having received maker and taker signatures. This means that Bebop pays the network (gas) fees as they are already included in the price. # Order Status Source: https://docs.bebop.xyz/rfq-api/api-reference/order-status /specs/rfq-api.json get /v3/order-status Returns the status of a previously submitted order. Possible statuses include Pending, Success, Failed, and Expired. # Quote Source: https://docs.bebop.xyz/rfq-api/api-reference/quote /specs/rfq-api.json get /v3/quote ## Overview Get a quote for the requested tokens and amounts. This endpoint allows you to request quotes for one-to-one trades as well as multi token trades (one-to-many and many-to-one). ## Executing the quote By default this is a gasless quote and will need to be submitted to [`/order`](#/v3/post_order_v3_order_post) Specifying `gasless=false` to the API will return `tx` that can be used to self execute. You may also specify `skip_validation` for use cases where validations are a constraint. # 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} # 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 will sign the order. | | `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. # Gasless Execution Source: https://docs.bebop.xyz/rfq-api/guides/gasless-execution Let Bebop submit transactions on behalf of your users for a gas-free swap experience. In gasless mode, the user signs a quote and Bebop submits the settlement transaction on-chain. The user never pays gas - making it ideal for wallets and super-apps where UX simplicity matters. The trade-off: market makers retain **last look**, meaning they can reject a quote before settlement. In return, pricing is tighter than self-execution. **When to use gasless:** You're building a consumer-facing product where users shouldn't think about gas. For solvers and aggregators executing trades programmatically, see the [self-execution quickstart](/rfq-api/quickstart). ## How It Differs from Self-Execution | | Self-execution | Gasless (API default) | | --------------- | --------------------------------------- | ------------------------------------------ | | Quote request | `gasless=false` | `gasless=true` (default) + approval params | | Who submits tx | You broadcast via your RPC | Bebop submits on-chain | | Order endpoint | Not used - `tx` comes from `/v3/quote` | POST signature to `/v3/order` | | Last look | No - quote is firm once signed | Yes - maker can reject | | Gas cost | Paid by taker | Paid by Bebop | | Token approvals | Standard approve on settlement contract | Standard or Permit2 | ## 1. Request a Gasless Quote Add `gasless=true` and the relevant approval parameters to your quote request: ```python theme={null} import httpx NETWORK = "ethereum" URL = f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote" params = { "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", # WETH "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC "sell_amounts": "1000000000000000000", # 1 WETH "taker_address": "0xYourWalletAddress", "gasless": "true", } resp = httpx.get(URL, params=params) quote = resp.json() ``` ### Approval Parameters | Parameter | Description | | --------------- | ---------------------------------------------------------------------------- | | `gasless` | Set to `true` to enable gasless mode | | `approval_type` | `Standard` (default) or `Permit2` - controls how token approvals are handled | **Which approval type?** `Standard` (the default) requires the user to have already approved the settlement contract. `Permit2` removes the need for a separate approval transaction but adds a second signature step. For bundled approvals without a separate transaction, see [Using Permit2 Approvals](#using-permit2-approvals) below. ## 2. Sign the Quote Sign the EIP-712 typed data from the quote response - the same signing flow as self-execution: ```python theme={null} from eth_account import Account from eth_account.messages import encode_typed_data PRIVATE_KEY = "0x" typed_data = { "types": RFQ_EIP712_TYPES, # see below "domain": { "name": "BebopSettlement", "version": "2", "chainId": quote["chainId"], "verifyingContract": quote["settlementAddress"], }, "primaryType": quote["onchainOrderType"], "message": quote["toSign"], } signable = encode_typed_data(full_message=typed_data) signed = Account.sign_message(signable, private_key=PRIVATE_KEY) signature = signed.signature.hex() ``` The `RFQ_EIP712_TYPES` dictionary contains the `EIP712Domain` and all three order type definitions. The API selects the order type automatically based on the trade structure - use the `onchainOrderType` from the quote response as your `primaryType`. | Type | When used | Key differences | | ---------------- | ----------------------------- | ------------------------------------------------------------- | | `SingleOrder` | One-to-one trades | Scalar fields: `maker_address`, `taker_token`, `taker_amount` | | `MultiOrder` | Single maker, multiple tokens | Array fields: `taker_tokens[]`, `taker_amounts[]` | | `AggregateOrder` | Multiple makers | Nested arrays: `taker_tokens[][]`, `maker_addresses[]` | **SingleOrder** - simple one-to-one swaps (e.g. WETH โ†’ USDC): ```python theme={null} "SingleOrder": [ {"name": "partner_id", "type": "uint64"}, {"name": "expiry", "type": "uint256"}, {"name": "taker_address", "type": "address"}, {"name": "maker_address", "type": "address"}, {"name": "maker_nonce", "type": "uint256"}, {"name": "taker_token", "type": "address"}, {"name": "maker_token", "type": "address"}, {"name": "taker_amount", "type": "uint256"}, {"name": "maker_amount", "type": "uint256"}, {"name": "receiver", "type": "address"}, {"name": "packed_commands", "type": "uint256"}, ] ``` **MultiOrder** - single maker fills a multi-token trade: ```python theme={null} "MultiOrder": [ {"name": "partner_id", "type": "uint64"}, {"name": "expiry", "type": "uint256"}, {"name": "taker_address", "type": "address"}, {"name": "maker_address", "type": "address"}, {"name": "maker_nonce", "type": "uint256"}, {"name": "taker_tokens", "type": "address[]"}, {"name": "maker_tokens", "type": "address[]"}, {"name": "taker_amounts", "type": "uint256[]"}, {"name": "maker_amounts", "type": "uint256[]"}, {"name": "receiver", "type": "address"}, {"name": "commands", "type": "bytes"}, ] ``` **AggregateOrder** - multiple makers fill a multi-token trade: ```python theme={null} "AggregateOrder": [ {"name": "partner_id", "type": "uint64"}, {"name": "expiry", "type": "uint256"}, {"name": "taker_address", "type": "address"}, {"name": "maker_addresses", "type": "address[]"}, {"name": "maker_nonces", "type": "uint256[]"}, {"name": "taker_tokens", "type": "address[][]"}, {"name": "maker_tokens", "type": "address[][]"}, {"name": "taker_amounts", "type": "uint256[][]"}, {"name": "maker_amounts", "type": "uint256[][]"}, {"name": "receiver", "type": "address"}, {"name": "commands", "type": "bytes"}, ] ``` ## 3. Submit the Order Unlike self-execution, gasless orders are submitted to Bebop's `/v3/order` endpoint. Bebop handles the on-chain settlement: ```python theme={null} order_resp = httpx.post( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/order", json={ "quote_id": quote["quoteId"], "signature": f"0x{signature}", }, ) order = order_resp.json() tx_hash = order["txHash"] print(f"Bebop submitted transaction: {tx_hash}") ``` At this point Bebop has your signed order and will submit the settlement transaction on-chain. ## 4. Monitor Settlement Poll the order status endpoint to track progress: ```python theme={null} import time while True: status_resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/order-status", params={"quote_id": quote["quoteId"]}, ) status = status_resp.json() print(f"Status: {status['status']}") if status["status"] in ("Settled", "Confirmed", "Failed"): break time.sleep(2) ``` ### Order Statuses | Status | Meaning | | ----------- | -------------------------------------------------------------------------------------------- | | `Pending` | Bebop received the order and is preparing to submit | | `Success` | Maker accepted - transaction is being broadcast | | `Settled` | Transaction confirmed on-chain - tokens have been transferred | | `Confirmed` | Final success state - settlement fully confirmed | | `Failed` | Order failed. Covers last look rejections, on-chain failures, expiry, and validation errors. | The `order-status` response also includes `txHash` (when available) and `amounts` (received token amounts after settlement). **Last look rejections** are expected in gasless mode. When a maker rejects, the status will be `Failed`. Your integration should handle this gracefully - request a new quote and retry. The user's tokens are never at risk during a rejection. ## Full Example ```python theme={null} import time import httpx from eth_account import Account from eth_account.messages import encode_typed_data PRIVATE_KEY = "0x" NETWORK = "ethereum" # --- 1. Request a gasless quote --- taker_address = "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" quote_resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", "taker_address": taker_address, "gasless": "true", }, ) quote = quote_resp.json() buy_token = list(quote["buyTokens"].values())[0] print(f'Quote: sell 1 WETH for {buy_token["amount"]} {buy_token["symbol"]}') # --- 2. Sign the EIP-712 typed data --- RFQ_ORDER_TYPES = { "SingleOrder": [ {"name": "partner_id", "type": "uint64"}, {"name": "expiry", "type": "uint256"}, {"name": "taker_address", "type": "address"}, {"name": "maker_address", "type": "address"}, {"name": "maker_nonce", "type": "uint256"}, {"name": "taker_token", "type": "address"}, {"name": "maker_token", "type": "address"}, {"name": "taker_amount", "type": "uint256"}, {"name": "maker_amount", "type": "uint256"}, {"name": "receiver", "type": "address"}, {"name": "packed_commands", "type": "uint256"}, ], "MultiOrder": [ {"name": "partner_id", "type": "uint64"}, {"name": "expiry", "type": "uint256"}, {"name": "taker_address", "type": "address"}, {"name": "maker_address", "type": "address"}, {"name": "maker_nonce", "type": "uint256"}, {"name": "taker_tokens", "type": "address[]"}, {"name": "maker_tokens", "type": "address[]"}, {"name": "taker_amounts", "type": "uint256[]"}, {"name": "maker_amounts", "type": "uint256[]"}, {"name": "receiver", "type": "address"}, {"name": "commands", "type": "bytes"}, ], "AggregateOrder": [ {"name": "partner_id", "type": "uint64"}, {"name": "expiry", "type": "uint256"}, {"name": "taker_address", "type": "address"}, {"name": "maker_addresses", "type": "address[]"}, {"name": "maker_nonces", "type": "uint256[]"}, {"name": "taker_tokens", "type": "address[][]"}, {"name": "maker_tokens", "type": "address[][]"}, {"name": "taker_amounts", "type": "uint256[][]"}, {"name": "maker_amounts", "type": "uint256[][]"}, {"name": "receiver", "type": "address"}, {"name": "commands", "type": "bytes"}, ], } order_type = quote["onchainOrderType"] typed_data = { "types": { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}, ], order_type: RFQ_ORDER_TYPES[order_type], }, "domain": { "name": "BebopSettlement", "version": "2", "chainId": quote["chainId"], "verifyingContract": quote["settlementAddress"], }, "primaryType": order_type, "message": quote["toSign"], } signable = encode_typed_data(full_message=typed_data) signed = Account.sign_message(signable, private_key=PRIVATE_KEY) signature = signed.signature.hex() # --- 3. Submit the order to Bebop --- order_resp = httpx.post( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/order", json={"quote_id": quote["quoteId"], "signature": f"0x{signature}"}, ) order = order_resp.json() print(f'Order submitted - tx: {order["txHash"]}') # --- 4. Poll for settlement --- while True: status_resp = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/order-status", params={"quote_id": quote["quoteId"]}, ) status = status_resp.json() print(f'Status: {status["status"]}') if status["status"] in ("Settled", "Confirmed"): print(f'Trade settled! tx: {status.get("txHash")}') break elif status["status"] == "Failed": print("Order failed - request a new quote and retry.") break time.sleep(2) ``` **Dependencies:** `pip install httpx eth_account` (or `uv add httpx eth_account`) ## Using Permit2 Approvals With `approval_type=Standard`, the user must have approved the settlement contract before trading (a one-time on-chain transaction per token). Permit2 removes this requirement by using an off-chain signature for token approvals instead. This differs from the Aggregation API's Permit2 flow. The Aggregation API wraps the order inside `PermitBatchWitnessTransferFrom` - you sign a single combined message. The RFQ API keeps order signing unchanged and adds a **separate** `PermitBatch` signature for token approvals. ### How it works 1. **One-time setup:** Approve the Permit2 contract (`0x000000000022D473030F116dDEE9F6B43aC78BA3`) for your sell tokens. This is a standard ERC-20 `approve()` - the same kind of transaction you'd do for the settlement contract with Standard approvals, but targeting the Permit2 contract instead. 2. **Request a quote** with `approval_type=Permit2`: ```python theme={null} params = { "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_amounts": "1000000000000000000", "taker_address": taker_address, "gasless": "true", "approval_type": "Permit2", } ``` 3. **Sign the order** the same way as Standard - `BebopSettlement` domain, same order types. The `toSign` data is identical. 4. **Check `requiredSignatures`** in the quote response. If it contains token addresses, generate a `PermitBatch` signature: ```python theme={null} from web3 import Web3 PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3" PERMIT2_ABI = [ { "inputs": [ {"name": "owner", "type": "address"}, {"name": "token", "type": "address"}, {"name": "spender", "type": "address"}, ], "name": "allowance", "outputs": [ {"name": "amount", "type": "uint160"}, {"name": "expiration", "type": "uint48"}, {"name": "nonce", "type": "uint48"}, ], "stateMutability": "view", "type": "function", } ] w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com")) permit2 = w3.eth.contract( address=Web3.to_checksum_address(PERMIT2_ADDRESS), abi=PERMIT2_ABI ) required_sigs = quote.get("requiredSignatures", []) if required_sigs: settlement = quote["settlementAddress"] approvals_deadline = quote["expiry"] # use quote expiry token_nonces = [] token_addresses = [] details = [] for token_addr in required_sigs: amount, expiration, nonce = permit2.functions.allowance( Web3.to_checksum_address(taker_address), Web3.to_checksum_address(token_addr), Web3.to_checksum_address(settlement), ).call() token_nonces.append(nonce) token_addresses.append(token_addr) details.append({ "token": token_addr, "amount": 2**160 - 1, # max amount "expiration": approvals_deadline, "nonce": nonce, }) permit_typed_data = { "types": { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}, ], "PermitBatch": [ {"name": "details", "type": "PermitDetails[]"}, {"name": "spender", "type": "address"}, {"name": "sigDeadline", "type": "uint256"}, ], "PermitDetails": [ {"name": "token", "type": "address"}, {"name": "amount", "type": "uint160"}, {"name": "expiration", "type": "uint48"}, {"name": "nonce", "type": "uint48"}, ], }, "domain": { "name": "Permit2", "chainId": quote["chainId"], "verifyingContract": PERMIT2_ADDRESS, }, "primaryType": "PermitBatch", "message": { "details": details, "spender": settlement, "sigDeadline": approvals_deadline, }, } permit_signable = encode_typed_data(full_message=permit_typed_data) permit_signed = Account.sign_message(permit_signable, private_key=PRIVATE_KEY) permit2_signature = permit_signed.signature.hex() ``` 5. **POST to `/order`** with the additional `permit2` field: ```python theme={null} order_body = { "quote_id": quote["quoteId"], "signature": f"0x{signature}", } if required_sigs: order_body["permit2"] = { "signature": f"0x{permit2_signature}", "approvals_deadline": approvals_deadline, "token_addresses": token_addresses, "token_nonces": token_nonces, } order_resp = httpx.post( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/order", json=order_body, ) ``` On subsequent trades for the same token, `requiredSignatures` will be empty once the Permit2 allowance is active. In that case, no `permit2` field is needed in the POST body - it works the same as Standard. ### `permit2` Field Reference | Field | Type | Description | | -------------------- | ---------- | --------------------------------------------------------------------- | | `signature` | string | Hex-encoded `PermitBatch` EIP-712 signature | | `approvals_deadline` | integer | Unix timestamp - must match `sigDeadline` in the signed `PermitBatch` | | `token_addresses` | string\[] | Token addresses from `requiredSignatures` | | `token_nonces` | integer\[] | Permit2 nonces for each token (from `allowance()` call) | # 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 trading modes, all settled atomically in a single transaction: | 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` | Multi-token trades (many-to-one and one-to-many) are useful when rebalancing portfolios, consolidating stablecoin positions, or distributing a single asset into multiple tokens - all without paying gas for separate swaps. ## 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. **EIP-712 signing:** `MultiOrder` uses flat array types (`address[]`, `uint256[]`), while `AggregateOrder` uses nested arrays (`address[][]`, `uint256[][]`) with one entry per maker. ## 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", "gasless": "false", }, ) 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", "gasless": "false", }, ) quote = resp.json() ``` ## 2. Understand the Response The response structure is the same as single-token quotes, but with multiple entries in `sellTokens` or `buyTokens`: When a single maker fills the trade, the API returns `MultiOrder` with flat arrays: ```json theme={null} { "type": "M21", "onchainOrderType": "MultiOrder", "toSign": { "partner_id": 0, "expiry": 1772453661, "taker_address": "0x5Bad...bBcB6", "maker_address": "0xE873...", "maker_nonce": "297211...", "taker_tokens": ["0xA0b8...", "0x6B17..."], "maker_tokens": ["0xdAC1..."], "taker_amounts": ["100000000", "100000000000000000000"], "maker_amounts": ["198898842"], "receiver": "0x5Bad...bBcB6", "commands": "0x00000000" } } ``` When multiple makers are involved, the API returns `AggregateOrder` with nested arrays - one entry per maker: ```json theme={null} { "type": "M21", "onchainOrderType": "AggregateOrder", "toSign": { "partner_id": 0, "expiry": 1772453661, "taker_address": "0x5Bad...bBcB6", "maker_addresses": ["0xE873...", "0x51C7..."], "maker_nonces": ["297211...", "297211..."], "taker_tokens": [["0xA0b8..."], ["0x6B17..."]], "maker_tokens": [["0xdAC1..."], ["0xdAC1..."]], "taker_amounts": [["100000000"], ["100000000000000000000"]], "maker_amounts": [["99453999"], ["99444843"]], "receiver": "0x5Bad...bBcB6", "commands": "0x00000000" } } ``` Note the key structural differences: `MultiOrder` uses singular `maker_address` / `maker_nonce` and flat token/amount arrays, while `AggregateOrder` uses plural `maker_addresses` / `maker_nonces` and nested arrays where each outer index corresponds to a maker. ## 3. Sign and Broadcast The API returns `MultiOrder` or `AggregateOrder` depending on whether one or multiple makers fill the trade. For self-execution, this distinction doesn't matter - you broadcast the `tx` object directly. For gasless (EIP-712 signing), use the `onchainOrderType` from the response as your `primaryType`. ```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 --- taker_address = "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" 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": taker_address, "gasless": "false", }, ) quote = resp.json() # --- 2. Sign and submit --- w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) tx = quote["tx"] 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 - `SingleOrder`, `MultiOrder`, or `AggregateOrder` - based on the trade structure. Use the `onchainOrderType` from the quote response as your `primaryType`. See the [EIP-712 order type schemas](/rfq-api/guides/gasless-execution#eip-712-order-type-schemas) for the full type definitions. # 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 only supported for self-execution (`gasless=false`). Gasless quotes cannot be partially filled. ## 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 NETWORK = "ethereum" 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": "0xYourWalletAddress", "gasless": "false", }, ) 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, the flow is the same as the standard [self-execution quickstart](/rfq-api/quickstart) - use the **modified** calldata in the transaction, sign, and submit: ```python theme={null} from eth_account import Account from web3 import Web3 PRIVATE_KEY = "0x" RPC_URL = "https://eth.llamarpc.com" w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) # Submit with the modified calldata tx = quote["tx"] 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 self-execution quote --- taker_address = "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" 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": taker_address, "gasless": "false", }, ) 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 submit with modified calldata --- w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) tx = quote["tx"] 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 new router contract Source: https://docs.bebop.xyz/rfq-api/guides/router-migration-guide Learn how to migrate your integration to the new router contract. Bebop has introduced the **BebopRouter** `0xBeb0009ACa35087ce7cCF11637E24dd1Aad3bf2A` contract, which now wraps BebopSettlement extending its functionality. ## Do you need to migrate? If your integration already reads these fields from the quote response, no changes are required. 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 You don't build the router calldata yourself. Bebop returns it ready to broadcast in `tx.data`. If you want to decode it (for verification or logging), it's a call to 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). # 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. Short expiry is only available for **self-execution** (`gasless=false`). ## 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&\ gasless=false&\ 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 | | **Availability** | Self-execution only | Self-execution and gasless | | **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** (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** | EIP-712 | | **On-chain tx** | Bebop submits (gasless) or you broadcast (self-execution) | | **Gasless** | Yes (default) | | **Complexity** | Medium - EIP-712 signing + token approvals | ## How It Works The RFQ API supports two execution modes. Gasless is the default and recommended for most integrations. Bebop handles on-chain submission. Your users sign a message but never pay gas. | Step | Action | You send | You get back | | ---- | ------------------- | --------------------------------- | ------------------------------ | | 1 | Request a quote | Token pair, amount | Firm price, EIP-712 typed data | | 2 | Sign the order | EIP-712 typed data โ†’ taker wallet | Signature | | 3 | Submit to Bebop | Signature โ†’ `POST /v3/order` | Quote ID | | 4 | Poll for settlement | Quote ID โ†’ `GET /v3/order-status` | Status: `Settled` | You broadcast the transaction yourself for direct on-chain settlement. | Step | Action | You send | You get back | | ---- | ------------------ | --------------------------------------------- | ------------------------------------------- | | 1 | Request a quote | Token pair, amount, `gasless=false` | Firm price, EIP-712 typed data, `tx` object | | 2 | Sign the order | EIP-712 typed data โ†’ taker wallet | Signature | | 3 | Broadcast on-chain | Append signature to `tx` calldata โ†’ broadcast | On-chain settlement | See [Execution Modes](/core-concepts/execution-modes) for a detailed comparison. ## Key Endpoints | Endpoint | Purpose | | ------------------------------------ | ----------------------------------------------- | | `GET /pmm/{network}/v3/quote` | Request a firm quote for a token swap | | `POST /pmm/{network}/v3/order` | Submit a signed order for gasless settlement | | `GET /pmm/{network}/v3/order-status` | Poll settlement status by quote ID (both modes) | ## Next Steps Make your first trade in 10-15 minutes. Gasless execution, partial fills, multi-token trades, and more. # 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 walks you through making your first trade on Bebop using the RFQ API. You'll learn how to discover supported assets, request quotes, sign orders, and submit them for settlement. **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. ## 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` | Wallet that signs the order | `0xYourWalletAddress` | | `receiver_address` | Address to receive bought tokens (if different from taker) | `taker_address` | | `gasless` | Set to `false` for self-execution (default is `true`) | `false` | **Swap and send:** `taker_address` signs the order; the bought tokens go to `receiver_address`. `receiver_address` is optional and defaults to `taker_address`. Works in both gasless and self-execution modes. * 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 1 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=1000000000000000000" \ --data-urlencode "taker_address=0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" \ --data-urlencode "gasless=false" ``` ```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": "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693", "gasless": "false", } response = httpx.get(URL, params=params) data = response.json() print(data) ``` **Building a wallet or super-app?** Bebop also supports gasless execution where Bebop submits on-chain on behalf of your users. See the [Gasless guide](/rfq-api/guides/gasless-execution) for details. ### Understanding the Response ```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, "gasFee": { "native": "0", "usd": 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", "requiredSignatures": [], "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": [ "๐Ÿฆ™" ], "toSign": { "partner_id": 0, "expiry": 1773827371, "taker_address": "0x5Bad99...BcB6", "maker_address": "0xBEE321...a000", "maker_nonce": "3272802511230603750", "taker_token": "0xC02aaA...6Cc2", "maker_token": "0xA0b869...eB48", "taker_amount": "10000000000000000", "maker_amount": "23197992", "receiver": "0x5Bad99...BcB6", "packed_commands": "0" }, "onchainOrderType": "SingleOrder", "partialFillOffset": 12 } ``` Key items to note: | Field | Description | | ---------------- | ------------------------------------------------------------------------------------------------------- | | `quoteId` | Unique quote identifier for monitoring settlement | | `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). | | `toSign` | EIP-712 message fields you must sign | | `tx` | Ready-to-broadcast transaction - sign the EIP-712 typed data and submit on-chain | **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. Sign & Submit With self-execution, the `/v3/quote` response includes a `tx` object ready to submit. You add your `nonce` and `chainId`, sign the transaction, and broadcast on-chain. There's no separate order submission step. Partial fills are also supported. See the [Partial Fills guide](/rfq-api/guides/partial-fills) for details. The `tx` object from the quote response contains the complete settlement calldata. You just need to add your `nonce` and `chainId`, then sign and submit. For [gasless execution](/rfq-api/guides/gasless-execution), you sign EIP-712 typed data and POST to `/v3/order` instead. See the [EIP-712 order type schemas](/rfq-api/guides/gasless-execution#eip-712-order-type-schemas) for the full type definitions (`SingleOrder`, `MultiOrder`, `AggregateOrder`). ```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 quote --- taker_address = "0x2e7E7cc62919eAf4c502dAC34753cFc5A29e9693" response = httpx.get( f"https://api.bebop.xyz/pmm/{NETWORK}/v3/quote", params={ "buy_tokens": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "sell_tokens": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "sell_amounts": 1000000000000000000, "taker_address": taker_address, "gasless": "false", }, ) quote = response.json() # --- 2. Sign and submit the transaction --- w3 = Web3(Web3.HTTPProvider(RPC_URL)) account = Account.from_key(PRIVATE_KEY) tx = quote["tx"] 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) 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. Let Bebop submit on-chain for your users - ideal for wallets and super-apps. 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 Participating in Bebop's solver auctions Apply for the Block Oracle Priced AMM closed beta ## 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 | | --- | --------- | -------- | ------- | --------------- | | SVM | Solana | - | โœ“ | | | 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 | โœ“ | โœ“ | 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 ```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", "gasless": false } ], "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`) | | `results[].gasless` | boolean | Whether the trade was executed gaslessly | | `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-beta) is in closed beta and now accepting applications. Add a new oracle-priced execution venue to your routing alongside RFQ. [Apply](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_aggregators_bopamm\&utm_medium=docs\&utm_campaign=bopamm_beta). ## 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-beta) is in closed beta and now accepting applications. Liquidate against firm oracle-priced quotes with predictable execution at size. [Apply](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_liquidators_bopamm\&utm_medium=docs\&utm_campaign=bopamm_beta). ## 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-beta) is in closed beta and now accepting applications. Provide liquidity through coordinated oracle updates and share the cost of refreshes across participants. [Apply](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_market_makers_bopamm\&utm_medium=docs\&utm_campaign=bopamm_beta). ## 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 * **Flexible execution modes** - support self-execution and gasless flows depending on taker preference * **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. **Integrating liquidity or joining as a solver?** This page covers integration. To participate in Bebop solver auctions, [complete our solver application](https://survey.typeform.com/to/PwACeaHD?utm_source=docs_discover_solvers). 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-beta) is in closed beta and now accepting applications. Execute against coordinated, oracle-priced maker quotes with onchain settlement guarantees. [Apply](https://form.typeform.com/to/cYMBjLQy?utm_source=docs_uc_solvers_bopamm\&utm_medium=docs\&utm_campaign=bopamm_beta). ## 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.