Skip to main content
Bebop supports three trading modes, all settled atomically in a single transaction:
ModeExampletype in response
One-to-oneWETH → USDC121
Many-to-one(WETH + WBTC) → USDCM21
One-to-manyWETH → (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

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:
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:
{
  "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:
{
  "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.
import httpx
from eth_account import Account
from web3 import Web3

PRIVATE_KEY = "0x<your_private_key_hex>"
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 for the full type definitions.