Skip to main content
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 minutesPrerequisites: 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 APIAggregation API
Liquidity sourceDirect market maker quotesSolver auction across DEXs and aggregators
Token coverageMajor pairsBroad, including long-tail
SlippageNone - guaranteed execution and fillConfigurable tolerance
ExecutionSelf-exec or gaslessSelf-exec or gasless
Base URL/pmm/{network}/v3//jam/{network}/v2/

1. Request a Quote

GET /jam/{network}/v2/quote
Required parameters:
ParameterDescriptionExample
sell_tokensToken(s) you’re selling (contract address)0xC02a... (WETH)
buy_tokensToken(s) you’re buying0xA0b8... (USDC)
sell_amounts OR buy_amountsAmount in base units. Use one, not both.1000000000000000000 (1 WETH)
taker_addressWallet executing the trade0xYourWalletAddress
Optional parameters:
ParameterDescriptionDefault
gaslessSet to false for self-executiontrue
approval_typeStandard or Permit2 (gasless only)Standard
slippageMax acceptable slippage (0-50%, up to 2 decimal places)Determined by solver
receiver_addressAddress to receive bought tokens (if different from taker)taker_address
Permit2 approval type is only supported with gasless execution. Self-execution requires Standard approvals.
  • 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:
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:
{
  "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:
{
  "quoteId": "121-174...",
  "sellTokens": { ... },
  "buyTokens": { ... },
  "toSign": { ... },

  "tx": {
    "to": "0xbeb0...4ea6",
    "value": "0x0",
    "data": "0x4dcebcba...",
    "from": "0xYour...",
    "gas": 250000,
    "gasPrice": 161765683
  }
}
Key fields:
FieldDescription
quoteIdUnique identifier - you’ll need this for order submission and status polling
solverName of the solver that won the auction
expiryUnix timestamp - the order is invalid after this time
buyTokens.minimumAmountWorst-case amount after slippage. The amount is the expected fill.
gasFeeEstimated gas cost in native token and USD
approvalTargetThe contract you must approve. Do not hardcode this address or confuse it with settlementAddress. Approving the wrong contract may lead to loss of funds.
toSignEIP-712 message fields. Present in both modes; used for gasless signing.
txReady-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 for the full check-and-approve flow.
from eth_account import Account
from web3 import Web3

PRIVATE_KEY = "0x<your_private_key>"
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.
# 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.

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:
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:
{
    "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
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:
StatusMeaning
PendingBebop received the order and is preparing to submit
SuccessSolver accepted - transaction is being broadcast
SettledTransaction confirmed on-chain - tokens transferred
ConfirmedFinal success state - settlement fully confirmed
FailedOrder failed (solver rejection, on-chain failure, expiry, or validation error)
The status response also includes:
FieldDescription
txHashTransaction hash (available once broadcast)
amountsActual token amounts received after settlement
surplusPrice improvement the solver achieved beyond the quoted minimum amount

Full Example

import time

import httpx
from eth_account import Account
from web3 import Web3

# --- Config ---
PRIVATE_KEY = "0x<your_private_key_hex>"
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)

Next Steps

Token Approvals

Set up ERC-20 approvals before trading.

RFQ API Quickstart

Compare with the RFQ API for firm market maker pricing.