> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bebop.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

<Info>
  **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.
</Info>

## 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`      |

<Warning>
  `Permit2` approval type is only supported with gasless execution. Self-execution requires `Standard` approvals.
</Warning>

<Note>
  **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.
</Note>

<Accordion title="Choosing sell_amounts vs buy_amounts">
  * 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")
</Accordion>

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<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
```

<Tip>
  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.
</Tip>

## 3. Sign & Submit

The Aggregation API supports two execution modes. Choose the one that fits your integration:

<Tabs>
  <Tab title="Self-Execution">
    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.
  </Tab>

  <Tab title="Gasless">
    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) |

    <Accordion title="JamOrder field reference">
      | 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.                       |
    </Accordion>
  </Tab>
</Tabs>

### 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

<Tabs>
  <Tab title="Self-Execution">
    ```python theme={null}
    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)
    ```
  </Tab>

  <Tab title="Gasless">
    ```python theme={null}
    import time

    import httpx
    from eth_account import Account
    from eth_account.messages import encode_typed_data

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

## Next Steps

<CardGroup cols={2}>
  <Card title="Token Approvals" icon="key" href="/core-concepts/token-approvals">
    Set up ERC-20 approvals before trading.
  </Card>

  <Card title="RFQ API Quickstart" icon="bolt" href="/rfq-api/quickstart">
    Compare with the RFQ API for firm market maker pricing.
  </Card>
</CardGroup>
