Non-custodial RFQ and atomic swap settlement for AI agents. Escrow onchain, signed receipts.
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Use as a BUYER to accept a quote. Returns the exact createDeal transaction (with Bull's signed fee quote) for your wallet to send; Bull never holds your funds. Then call bull_attach_deal with the dealId from the DealCreated event. Safe to call again to refresh the fee quote. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
Your wallet address (the buyer or seller). Required.
Quote id.
"base" to pay in ETH (default) or "alt" to pay in the stablecoin.
EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call.
From the previous bull_* response. Leave empty on the first call.
This tool isn't marked read-only, so it may change something on the server or in an account you connected.
Pick your client. Values in capitals, like YOUR_TOKEN, are yours to fill in.
Run in a terminal
claude mcp add --transport http bull-otc https://www.bull.family/api/mcp
Use as a BUYER to accept a quote. Returns the exact createDeal transaction (with Bull's signed fee quote) for your wallet to send; Bull never holds your funds. Then call bull_attach_deal with the dealId from the DealCreated event. Safe to call again to refresh the fee quote. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| payAsset | string | "base" to pay in ETH (default) or "alt" to pay in the stablecoin. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"address"
],
"properties": {
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"payAsset": {
"type": "string",
"description": "\"base\" to pay in ETH (default) or \"alt\" to pay in the stablecoin."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use as a BUYER after sending createDeal: report the dealId. Bull checks it against the quote and returns the payment transactions (approve if needed, then fundPayment) to send from the buyer wallet. When the seller deposits, the swap settles in that transaction. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| dealId* | string | Deal number from the DealCreated event. |
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"dealId",
"address"
],
"properties": {
"dealId": {
"type": "string",
"description": "Deal number from the DealCreated event."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use as a BUYER to negotiate: propose a different total price (and optionally a different token amount) on a pending quote. Returns the stored counter. The seller can answer with bull_revise_quote. Works only while the quote is pending. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| price* | string | Your proposed total price in ETH. |
| tokens | string | Your proposed token amount (optional). |
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"price",
"address"
],
"properties": {
"price": {
"type": "string",
"description": "Your proposed total price in ETH."
},
"tokens": {
"type": "string",
"description": "Your proposed token amount (optional)."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use when you want to BUY a token in size (a block trade) without moving the market price: posts a private request for quotes on Bull, a non-custodial atomic OTC settlement layer. Returns an rfqId; sellers answer with sealed quotes only you can read. Optional maxPrice limit and webhook; set dryRun to only validate, idempotencyKey to make retries safe. Not for small market swaps (use a DEX) or paying for API calls (use x402). Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| note | string | Short note to sellers (optional). |
| chain | string | Chain name. Default: Robinhood Chain. |
| token* | string | Token contract address you want to buy. |
| dryRun | boolean | true = only check the request, nothing is created. Optional. |
| symbol | string | Token symbol (optional). |
| address* | string | Your wallet address (the buyer or seller). Required. |
| sizeUsd* | number | Size in USD (minimum 5 during beta). |
| webhook | string |
Use as a SELLER to submit the signed permit from bull_deposit_plan. Bull's relayer sends it and pays the gas; the swap settles in that transaction and you are paid in the same step. Returns the transaction hash. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| r* | string | Signature r. |
| s* | string | Signature s. |
| v* | number | Signature v. |
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
| permitDeadline* | string | message.deadline from the permit. |
Use as a SELLER after the buyer has paid into escrow: returns what to sign to deposit your tokens, either a gasless EIP-2612 permit (Bull's relayer pays gas) or direct approve + fundTokens transactions. Fails with not_ready until the buyer has funded. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"address"
],
"properties": {
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use to verify any escrow deal onchain: open, settled or cancelled, and whether buyer and seller have funded. Public, no signature needed. Returns the deal state read from the contract.
| Argument | Type | Description |
|---|---|---|
| dealId* | string | Deal number from the DealCreated event. |
{
"type": "object",
"required": [
"dealId"
],
"properties": {
"dealId": {
"type": "string",
"description": "Deal number from the DealCreated event."
}
}
}Use as a BUYER to read the sealed quotes on your own RFQ: seller, token amount, total price, counter state, deal status. Returns a list of quotes with quoteIds. Only the RFQ's buyer can read it. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| rfqId* | string | RFQ id. |
| address* | string | Your wallet address (the buyer or seller). Required. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"rfqId",
"address"
],
"properties": {
"rfqId": {
"type": "string",
"description": "RFQ id."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use to get proof of a completed trade: a signed receipt for a settled deal (buyer, seller, token, amounts, fee) that anyone can re-check onchain. Public. Fails with not_settled for deals that are still open.
| Argument | Type | Description |
|---|---|---|
| dealId* | string | Deal number. |
{
"type": "object",
"required": [
"dealId"
],
"properties": {
"dealId": {
"type": "string",
"description": "Deal number."
}
}
}Use first to see what Bull is: supported chains, escrow contract, fee tiers, minimum size and auth rules. Public. Bull is a non-custodial atomic OTC/RFQ settlement layer.
No arguments.
{
"type": "object",
"required": [],
"properties": {}
}Use when you SELL or hold a token and want to find buyers: lists open requests for quotes on Bull (token, size in USD, window, buyer's price limit). Returns rfqIds to answer with bull_send_quote. Public, no signature needed. Not for finding market prices.
| Argument | Type | Description |
|---|---|---|
| chain | string | Chain name. Default: Robinhood Chain. |
| token | string | Token contract address to filter by (optional). |
{
"type": "object",
"required": [],
"properties": {
"chain": {
"type": "string",
"description": "Chain name. Default: Robinhood Chain."
},
"token": {
"type": "string",
"description": "Token contract address to filter by (optional)."
}
}
}Use as BUYER or SELLER to check a quote and its onchain deal: status (pending, confirmed, funded, released), dealId, counter, who has funded. Poll this or use webhooks. Only the two parties can read it. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"address"
],
"properties": {
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use as a BUYER to reject a quote you do not want. Returns status rejected. Not needed for quotes that simply expire. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"address"
],
"properties": {
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use as a SELLER to change the price and/or token amount of your own pending quote, for example to meet the buyer's counter. Returns the new terms. The buyer then accepts with bull_accept_quote. Works only while the quote is pending. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| price | string | New total price in ETH (optional). |
| tokens | string | New token amount (optional). |
| address* | string | Your wallet address (the buyer or seller). Required. |
| quoteId* | string | Quote id. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
{
"type": "object",
"required": [
"quoteId",
"address"
],
"properties": {
"price": {
"type": "string",
"description": "New total price in ETH (optional)."
},
"tokens": {
"type": "string",
"description": "New token amount (optional)."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
}
}
}Use when you want to SELL tokens to a buyer who posted an RFQ: sends a sealed quote (token amount and total ETH price) that only the buyer sees. Returns a quoteId. The swap settles atomically in one escrow transaction, so you never deliver before you are paid. Not for listing prices publicly. Signed: the first call returns a message to sign; call again with the same arguments plus timestamp and signature.
| Argument | Type | Description |
|---|---|---|
| price* | string | Total price in ETH, e.g. "0.25". |
| rfqId* | string | RFQ id. |
| dryRun | boolean | true = only check the request, nothing is created. Optional. |
| payout | string | Wallet that receives the payment (optional). |
| tokens* | string | Token amount, e.g. "1000". |
| address* | string | Your wallet address (the buyer or seller). Required. |
| webhook | string | https URL that receives events: quote.countered, quote.accepted, quote.rejected, deal.buyer_funded, deal.settled (optional). |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
Use to check Bull's track record: number of settled deals, total volume, open RFQs and the latest settled deals, read from the escrow contract. Public, no signature needed.
No arguments.
{
"type": "object",
"required": [],
"properties": {}
}We read every tool's name, description and inputs the way the model will, looking for instructions hidden from you. A clean result means no warning signs, not a guarantee. Scanned Nov 11, 2026.
Takes a password, API key or token as an argument, so the secret passes through the model's context and chat logs.
Fix: Read credentials from the server's own configuration (environment, OAuth) instead of a tool argument.
No readOnlyHint / destructiveHint annotations, so clients can't tell a read from a write without asking.
Fix: Add annotations to each tool, e.g. { readOnlyHint: true } for tools that only read.
| https URL that receives events: quote.received, quote.revised, deal.settled (optional). |
| maxPrice | string | Your limit: the most you will pay in total, in ETH (optional). Quotes above it are refused. |
| signature | string | EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call. |
| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
| windowMin | number | How long the request stays open, in minutes: 15, 30, 60, 360 or 1440. Default 60. |
| idempotencyKey | string | Unique key (8-64 characters) so a retry never creates a duplicate. Optional. |
{
"type": "object",
"required": [
"token",
"sizeUsd",
"address"
],
"properties": {
"note": {
"type": "string",
"description": "Short note to sellers (optional)."
},
"chain": {
"type": "string",
"description": "Chain name. Default: Robinhood Chain."
},
"token": {
"type": "string",
"description": "Token contract address you want to buy."
},
"dryRun": {
"type": "boolean",
"description": "true = only check the request, nothing is created. Optional."
},
"symbol": {
"type": "string",
"description": "Token symbol (optional)."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"sizeUsd": {
"type": "number",
"description": "Size in USD (minimum 5 during beta)."
},
"webhook": {
"type": "string",
"description": "https URL that receives events: quote.received, quote.revised, deal.settled (optional)."
},
"maxPrice": {
"type": "string",
"description": "Your limit: the most you will pay in total, in ETH (optional). Quotes above it are refused."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
},
"windowMin": {
"type": "number",
"description": "How long the request stays open, in minutes: 15, 30, 60, 360 or 1440. Default 60."
},
"idempotencyKey": {
"type": "string",
"description": "Unique key (8-64 characters) so a retry never creates a duplicate. Optional."
}
}
}{
"type": "object",
"required": [
"quoteId",
"permitDeadline",
"v",
"r",
"s",
"address"
],
"properties": {
"r": {
"type": "string",
"description": "Signature r."
},
"s": {
"type": "string",
"description": "Signature s."
},
"v": {
"type": "number",
"description": "Signature v."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"quoteId": {
"type": "string",
"description": "Quote id."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
},
"permitDeadline": {
"type": "string",
"description": "message.deadline from the permit."
}
}
}| timestamp | string | From the previous bull_* response. Leave empty on the first call. |
| idempotencyKey | string | Unique key (8-64 characters) so a retry never creates a duplicate. Optional. |
{
"type": "object",
"required": [
"rfqId",
"tokens",
"price",
"address"
],
"properties": {
"price": {
"type": "string",
"description": "Total price in ETH, e.g. \"0.25\"."
},
"rfqId": {
"type": "string",
"description": "RFQ id."
},
"dryRun": {
"type": "boolean",
"description": "true = only check the request, nothing is created. Optional."
},
"payout": {
"type": "string",
"description": "Wallet that receives the payment (optional)."
},
"tokens": {
"type": "string",
"description": "Token amount, e.g. \"1000\"."
},
"address": {
"type": "string",
"description": "Your wallet address (the buyer or seller). Required."
},
"webhook": {
"type": "string",
"description": "https URL that receives events: quote.countered, quote.accepted, quote.rejected, deal.buyer_funded, deal.settled (optional)."
},
"signature": {
"type": "string",
"description": "EIP-191 personal_sign of `message` from the previous response. Leave empty on the first call."
},
"timestamp": {
"type": "string",
"description": "From the previous bull_* response. Leave empty on the first call."
},
"idempotencyKey": {
"type": "string",
"description": "Unique key (8-64 characters) so a retry never creates a duplicate. Optional."
}
}
}