Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7184347
fix(errors): read the SDK's detail field, and stop calling a 501 a tr…
VickyXAI Sep 8, 2026
3a33f52
fix(markets): say sports/* is degraded upstream and that nothing was …
VickyXAI Sep 8, 2026
66db658
fix(price): answer equity price/history before the wallet is consulted
VickyXAI Sep 8, 2026
939868a
docs: stop promising equity quotes and sports routes; sync the schema…
VickyXAI Sep 8, 2026
5073d1f
0.48.1 — the error says whether money moved
VickyXAI Sep 8, 2026
5f5777c
docs: update project documentation for 0.48.1
VickyXAI Sep 8, 2026
dbb8a44
Merge remote-tracking branch 'origin/main' into fix/issue-132-not-cha…
VickyXAI Sep 9, 2026
267a099
fix(video,image): refuse a 402 far above the published rate before si…
VickyXAI Sep 9, 2026
98ce903
chore(verify): classify a substituted Solana product as a gateway bug…
VickyXAI Sep 9, 2026
1338df6
docs: the quote guard, and the context-cost card at 7%
VickyXAI Sep 9, 2026
be4d323
chore(verify): a different label is a substitution only when Solana i…
VickyXAI Sep 9, 2026
e8daa4f
fix(wallet): provision the Solana wallet keychain-aware, and never mi…
VickyXAI Sep 9, 2026
9dea539
test(safety): pin the rail in image-cost, tighten the confirm-spend g…
VickyXAI Sep 9, 2026
d86e16f
fix(polymarket): sign market orders at the previewed worst fill, vali…
VickyXAI Sep 9, 2026
260e84f
fix(surf,docs): Surf is retired and the tool says so before the walle…
VickyXAI Sep 9, 2026
334120d
fix(chat): price the two flagships the table missed, sweep the live c…
VickyXAI Sep 9, 2026
cc48213
fix(startup): the key scanner stops branding the documented env overr…
VickyXAI Sep 9, 2026
30124ea
fix(media): a job that is already paid for is never abandoned, and ne…
VickyXAI Sep 9, 2026
4168470
fix(phone): refuse paths outside phone/* and voice/* before reserving…
VickyXAI Sep 9, 2026
c9d079d
0.49.0 — the error says whether money moved, and thirty-seven audit f…
VickyXAI Sep 9, 2026
ab5e5e8
feat(surf)!: drop blockrun_surf — 19 tools, matching the published br…
VickyXAI Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
{
"name": "crypto-data",
"source": "./skills/crypto-data",
"description": "Use for any crypto data question — token prices, FX, commodities, stocks, OHLC history, DEX pairs, DeFi TVL and yields, on-chain SQL, wallet labels and net worth, social mindshare. Routes across five overlapping tools and says which one to use and which are free."
"description": "Use for any crypto data question — token prices, FX, commodities, stock ticker catalog, OHLC history, DEX pairs, DeFi TVL and yields, on-chain SQL, wallet labels and net worth, social mindshare. Routes across five overlapping tools and says which one to use and which are free."
},
{
"name": "surf",
"source": "./skills/surf",
"description": "Use when the user wants deep crypto data — on-chain SQL, CEX order books, wallet labels and net worth, social mindshare, news and unified search across exchange, on-chain, wallet, social and prediction endpoints."
"description": "Surf data endpoints were retired by the gateway on 2026-09-06 — this skill redirects each former Surf question to the tool that still serves it (blockrun_price, blockrun_defi, blockrun_markets, blockrun_dex, blockrun_rpc) and names what has no replacement yet."
},
{
"name": "rpc",
Expand Down
29 changes: 27 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,25 @@ concurrency:
cancel-in-progress: true

jobs:
test:
# package.json says `engines.node >=20.19` and the README carries the badge,
# but until this matrix existed every check ran on Node 22 only. A dependency
# reaching for a 22-only API (Promise.withResolvers, Set.prototype.union) would
# have shipped green and broken on the oldest Node the package claims to
# support. 20.19 is the floor: mock.module (which `npm test` needs) landed in
# 20.18, and vite 8 / rolldown (the pretest MCP Apps build) require ^20.19.
test-node:
name: test (node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ["20.19", "22"]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
node-version: ${{ matrix.node }}
cache: npm

- run: npm ci
Expand Down Expand Up @@ -58,3 +69,17 @@ jobs:
# a blockrun.ai deploy in progress cannot fail this repo's CI.
- name: Brand numbers
run: node scripts/sync-brand-numbers.mjs --check

# Branch protection on main requires a status check named exactly `test`.
# A matrix job reports one check per leg ("test (node 20.19)", "test (node
# 22)") and never one called `test`, so without this gate every PR would sit
# unmergeable waiting for a check that can no longer arrive. `if: always()`
# makes it run even when a leg fails, so the failure is reported as a red
# `test` rather than a check that never completes.
test:
needs: test-node
if: always()
runs-on: ubuntu-latest
steps:
- name: All Node versions passed
run: test "${{ needs.test-node.result }}" = "success"
149 changes: 149 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Smoke-test the built server via the MCP stdio handshake:
(printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}\n{"jsonrpc":"2.0","method":"notifications/initialized"}\n{"jsonrpc":"2.0","id":2,"method":"tools/list"}\n'; sleep 2) | node dist/index.js 2>/dev/null
```

Should return <!-- br:mcp.tools -->20<!-- /br:mcp.tools --> tools including `blockrun_surf` and any new one you add.
Should return <!-- br:mcp.tools -->19<!-- /br:mcp.tools --> tools including any new one you add.

To test locally with Claude Code, point it at your dev build:

Expand Down
74 changes: 39 additions & 35 deletions README.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions apps/order-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ interface Preview {
outcome?: string;
conditionId?: string;
bestQuote?: number | null;
/** Market orders: the limit the order will be signed at (buy: max, sell: min price per share). */
worstFillPrice?: number;
minSize?: number;
maxBetUsd?: number;
sessionSpentUsd?: number;
Expand Down Expand Up @@ -114,6 +116,11 @@ function renderPreview(p: Preview): void {
const grid = el("div", { class: "grid" },
kv(isBuy ? "You spend" : "You receive (est.)", usd(p.notionalUsd), true),
kv(priceLabel, `${prob} · ${Number.isFinite(priceVal) ? priceVal.toFixed(3) : "—"}`, true),
// Market orders are SIGNED at this bound (the server walks the book), so
// the fill can never be worse than the number shown here.
...(!isLimit && typeof p.worstFillPrice === "number"
? [kv(isBuy ? "Worst fill (signed max)" : "Worst fill (signed min)", `${(p.worstFillPrice * 100).toFixed(1)}¢ · ${p.worstFillPrice.toFixed(3)}`)]
: []),
kv("Shares", shares !== undefined ? `${isLimit ? "" : "≈ "}${shares.toFixed(4)}` : "—"),
kv("Max payout if right", isBuy && shares !== undefined ? usd(shares) : "—"),
kv("Per-order cap", el("span", {}, `${usd(p.notionalUsd)} of ${cap ? usd(cap) : "—"}`, el("div", { class: "meter" }, el("i", { style: `width:${capPct}%` })))),
Expand Down Expand Up @@ -169,7 +176,25 @@ function renderPreview(p: Preview): void {
let armed = false;
const disarm = () => { armed = false; place.textContent = `Place ${p.action} · ${usd(p.notionalUsd)}`; place.classList.remove("danger"); cancel.hidden = true; };
cancel.addEventListener("click", disarm);

// Every figure on this card — notional, shares, worst fill, the confirm
// label — describes the amount that was QUOTED. currentArgs() reads the
// field live, so an edited amount used to be submitted under the old
// label ("Confirm — sign & submit $5.00" placing $50). Placing is only
// allowed while the field still equals the quoted amount; a change disarms
// and disables Place until Re-quote renders a fresh card.
const quotedAmount = parseFloat(amountField.value);
const syncPlace = () => {
const stale = parseFloat(amountField.value) !== quotedAmount;
if (stale && armed) disarm();
place.disabled = stale;
place.title = stale ? "Amount changed — Re-quote first" : "";
if (stale) { note.className = "note"; note.textContent = "Amount changed — Re-quote first to refresh the price and notional before placing."; }
};
amountField.addEventListener("input", syncPlace);

place.addEventListener("click", async () => {
if (parseFloat(amountField.value) !== quotedAmount) { syncPlace(); return; }
if (!armed) {
armed = true;
place.textContent = `Confirm — sign & submit ${usd(p.notionalUsd)}`;
Expand Down
6 changes: 3 additions & 3 deletions assets/context-cost-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions assets/context-cost.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 7 additions & 7 deletions brand-numbers.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"$schema": "https://blockrun.ai/brand/numbers.schema.json",
"version": 1,
"models": {
"chatVisible": 76,
"totalVisible": 100,
"free": 7,
"freeWithheld": 26,
"chatVisible": 78,
"totalVisible": 102,
"free": 6,
"freeWithheld": 27,
"image": 9,
"video": 8,
"music": 1,
"speech": 5,
"soundfx": 1,
"withFallback": 34,
"withFallbackAllEntries": 73
"withFallback": 33,
"withFallbackAllEntries": 74
},
"clawrouter": {
"dimensions": 15,
Expand All @@ -21,7 +21,7 @@
"aliases": 259
},
"mcp": {
"tools": 20,
"tools": 19,
"contextTokens": 12900,
"contextTokensTrading": 5554,
"contextCutPct": 57
Expand Down
29 changes: 24 additions & 5 deletions docs/polymarket-trading-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ AI via x402. One self-custody identity: it pays for models in USDC on Base *and*
settles USDC-denominated bets on the world's largest prediction market.

> **Real money.** A confirmed order spends real **pUSD** (Polymarket's USDC-backed
> collateral) on Polygon. Every order, approval, redeem, and withdrawal is
> **confirm-gated** (dry-run unless you pass `confirm:true`) and **capped**
> (`POLYMARKET_MAX_BET_USD`, default **$25/order**). Start with ~$5 and a $1 test.
> collateral) on Polygon. Every order, approval, fund, redeem, and withdrawal is
> **confirm-gated** (dry-run unless you pass `confirm:true`). Orders are
> additionally **capped** (`POLYMARKET_MAX_BET_USD`, default **$25/order**;
> optional `POLYMARKET_MAX_SESSION_USD`); funding can be capped per call with
> the optional `POLYMARKET_MAX_FUND_USD` (unset = no cap). Start with ~$5 and a $1 test.

This flow is verified end to end on the live CLOB: an agent's own wallet created
its deposit vault, funded it gaslessly via x402, and placed a **real $1 market
Expand Down Expand Up @@ -199,7 +201,8 @@ blockrun_polymarket action:"redeem" condition_id:"0x..." confirm:true
blockrun_polymarket action:"withdraw" # dry-run (full balance)
blockrun_polymarket action:"withdraw" confirm:true # execute
# partial: amount_usd:5
# elsewhere: to_address:"0x..." (default: your own agent wallet on Base)
# elsewhere: to_address:"0x..." (default: your own agent wallet on Base;
# a custom address is flagged CUSTOM in the preview — read it twice)
```

**The loop that closes the story:** `sell` (before resolution) or `redeem`
Expand All @@ -216,6 +219,12 @@ x402 AI fees. Money in via x402, money out via withdraw — one wallet, full cir
never signs *above* your limit, a sell never *below*.
- **Market buy** takes `amount_usd` (dollars to spend); **market sell** takes
`size` (shares to sell). **Limit** takes `price` + `size`.
- **Market orders are signed at the previewed worst fill.** The dry-run walks
the live book and prints `worst fill ≤ X` (buy) / `worst fill ≥ X` (sell)
next to the best quote; that same `X` is the limit on the signed order, so a
book that thins between preview and confirm can only fill *less*, never
worse than what you saw. Est. sell proceeds are the walked total, not
size × best bid.
- **Order types:** `GTC` (rests, default for limits), `GTD` (good-till-`expires_at`),
`FOK` (fill-or-kill, default for market), `FAK` (fill-and-kill the rest).
- `min_order_size` and tick come from the live market — the dry-run shows both.
Expand All @@ -227,7 +236,17 @@ x402 AI fees. Money in via x402, money out via withdraw — one wallet, full cir
- **`confirm:true` is required** for every order / approval / fund / redeem /
withdraw. Without it: a dry-run preview, nothing signed.
- **Per-order cap** `POLYMARKET_MAX_BET_USD` (default $25) + optional cumulative
**`POLYMARKET_MAX_SESSION_USD`** (in-memory, per-`agent_id`).
**`POLYMARKET_MAX_SESSION_USD`** (in-memory, per-`agent_id`). These gate
orders only.
- **Optional per-call fund cap** `POLYMARKET_MAX_FUND_USD` — bounds a single
`fund` call (your own Base USDC → your own vault). Unset = no cap, which is
the default: funding is self-to-self and reversible via `withdraw`. `redeem`
and `withdraw` are confirm-gated but not capped.
- **`withdraw` labels the destination honestly.** The preview says
`(your agent wallet)` only when the money is going back to the wallet that
pays your x402 fees; a caller-supplied `to_address` is shown as
`⚠️ CUSTOM destination — NOT your agent wallet`, and a malformed or
checksum-mismatched address is refused before anything is signed.
- **Bets never draw from your x402 API budget** — different asset (pUSD vs Base
USDC), different wallet ledger. They can't corrupt each other.
- **Your private key never leaves the machine** and is never printed or logged.
Expand Down
16 changes: 7 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blockrun/mcp",
"version": "0.48.0",
"version": "0.49.0",
"mcpName": "io.github.BlockRunAI/blockrun-mcp",
"description": "BlockRun MCP Server - Give your AI agent web search, deep research, prediction markets, and crypto data. Pay per call from a USDC wallet (Solana or Base) or a BlockRun API key.",
"type": "module",
Expand Down Expand Up @@ -58,7 +58,7 @@
},
"dependencies": {
"@anthropic-ai/sdk": "^0.123.0",
"@blockrun/llm": "^3.14.3",
"@blockrun/llm": "^3.15.1",
"@modelcontextprotocol/sdk": "^1.0.0",
"@polymarket/builder-relayer-client": "^0.0.10",
"@polymarket/builder-signing-sdk": "1.0.0",
Expand Down
Loading
Loading