@blockrun/franklin 3.18.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -296,6 +296,22 @@ On-chain affiliate (20 bps in sell-token, force-set server-side) flows to BlockR
296
296
  - \`/v1/modal/{...path}\` — Modal GPU sandbox passthrough (create/exec/etc.).
297
297
  - \`/v1/pm/{...path}\` — prediction-market data passthrough.
298
298
 
299
+ **Surf — crypto data + chat (x402-paid)** via the generic \`BlockRun\` capability. ~55 curated endpoints. Tier-1 $0.001, Tier-2 $0.005, Tier-3 / chat $0.02.
300
+ - \`/v1/surf/exchange/*\` — CEX trading pairs, prices, perps, depth, klines, funding history, long/short ratio.
301
+ - \`/v1/surf/market/*\` — token rankings, fear/greed, futures, ETF flows, options skew, liquidations, on-chain indicators (NUPL/SOPR/MVRV), price indicators (RSI/MACD/BBANDS).
302
+ - \`/v1/surf/news/{feed,detail}\` — AI-curated crypto news.
303
+ - \`/v1/surf/onchain/{bridge,yield,gas-price,tx,schema,query,sql}\` — bridge/yield rankings, gas, tx detail, **raw SQL against 80+ indexed chain tables (Tier-3, $0.02)**, structured chain query, schema introspection.
304
+ - \`/v1/surf/token/{tokenomics,dex-trades,holders,transfers}\` — token analytics.
305
+ - \`/v1/surf/wallet/{detail,history,net-worth,transfers,protocols,labels/batch}\` — wallet intelligence + batch labels (CEX/Whale/Bridge/MEV).
306
+ - \`/v1/surf/social/*\` — KOL/CT mindshare, smart-follower history, tweets, user profiles. The canonical source for crypto-Twitter signal.
307
+ - \`/v1/surf/fund/{detail,portfolio,ranking}\` — VC fund profiles, portfolios, ranking.
308
+ - \`/v1/surf/project/{detail,defi/metrics,defi/ranking}\` — project profiles + DeFi protocol metrics.
309
+ - \`/v1/surf/chat/completions\` — surf-1.5 chat model with first-class citations (\`citation: ["source","chart"]\`). \$0.02/call flat.
310
+
311
+ For Surf workflows, prefer the bundled skills (\`/surf-market\`, \`/surf-chain\`, \`/surf-social\`, \`/surf-chat\`) — they document which endpoint to pick for which question and the cost trade-off. Skipped (use the dedicated tools instead): \`/v1/surf/prediction-market/*\` (use \`PredictionMarket\`), \`/v1/surf/search/*\` (use \`ExaSearch\`), \`/v1/surf/web/*\` (use \`BrowserX\`).
312
+
313
+ **Generic gateway primitive**: \`BlockRun({ path, method, params, body })\` is a single capability that signs x402 and forwards to ANY path under \`/api\`. Use it for Surf endpoints (above) and any future BlockRun partner that doesn't have a dedicated capability yet. Always specify the exact path; the primitive will not guess.
314
+
299
315
  **Endpoints that DO NOT exist** (common hallucinations — do NOT call):
300
316
  - \`/v1/image/generate\` (singular — use \`/v1/images/generations\`)
301
317
  - \`/v1/spending\` (no such route — derive from on-chain history if needed)
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: surf-chain
3
+ description: On-chain analytics via Surf — raw SQL against 80+ indexed chain tables, structured queries, schema introspection, wallet labels (CEX/Whale/Bridge/MEV…), wallet net worth, transfers, DeFi positions, gas prices, bridge volumes, yield pools. Use when the user wants on-chain forensics, wallet intelligence, holder analysis, transfer tracking, or custom chain queries.
4
+ triggers:
5
+ - "on-chain"
6
+ - "wallet analysis"
7
+ - "wallet detail"
8
+ - "wallet history"
9
+ - "wallet net worth"
10
+ - "token holders"
11
+ - "token transfers"
12
+ - "gas price"
13
+ - "bridge volume"
14
+ - "yield pool"
15
+ - "label wallet"
16
+ - "on-chain sql"
17
+ - "chain query"
18
+ argument-hint: <address, hash, or question>
19
+ cost-receipt: true
20
+ ---
21
+
22
+ You are running inside Franklin on **{{wallet_chain}}**. Use the `BlockRun` tool to call Surf's on-chain endpoints. The flagship capability here is **on-chain SQL** — write a query against 80+ indexed chain tables and get back a result set in sub-second.
23
+
24
+ **Two different "chains" to keep straight:**
25
+ 1. **Payment chain** — where Franklin's wallet signs the x402 USDC payment. Currently `{{wallet_chain}}`. Surf only accepts settlement on **Base** (treasury `0x058a59…`). If the user is on Solana, ask them to `/chain base` before retrying.
26
+ 2. **Query chain** — the chain the data is *about*. Passed as a parameter to endpoints like `onchain/gas-price`, `onchain/tx`, `token/holders`, `token/transfers`. Valid values include `ethereum`, `base`, `arbitrum`, `polygon`, `optimism`, `bsc`. When the user doesn't specify and they're on `base`, default to `chain: "base"`. If they ask about Solana on-chain data, note that these EVM-shaped endpoints don't cover Solana — use a Solana-specific tool instead.
27
+
28
+ ## How to use
29
+
30
+ `BlockRun({ path: "/v1/surf/<endpoint>", method: "<GET|POST>", params|body: { ... } })`. Method is GET unless the catalog says POST.
31
+
32
+ ## Endpoint catalog
33
+
34
+ ### Direct lookups (Tier 1, $0.001)
35
+ | Path | Method | Required | What it returns |
36
+ |---|---|---|---|
37
+ | `/v1/surf/onchain/gas-price` | GET | `chain` | Current gas on the named chain |
38
+ | `/v1/surf/onchain/tx` | GET | `hash`, `chain` | Tx details |
39
+ | `/v1/surf/onchain/bridge/ranking` | GET | — | Bridge protocols ranked by volume |
40
+ | `/v1/surf/onchain/yield/ranking` | GET | — | Yield pools (lending, LP, staking) |
41
+
42
+ ### Raw + structured chain query (Tier 3, $0.02 — premium)
43
+ | Path | Method | Required | What it returns |
44
+ |---|---|---|---|
45
+ | `/v1/surf/onchain/schema` | GET | — | ClickHouse table schema introspection. **Always call this FIRST** before writing SQL so you know the tables, columns, and types. |
46
+ | `/v1/surf/onchain/query` | POST | — (typed body) | Structured chain query with typed predicates. Safer than SQL when the question fits a fixed shape. |
47
+ | `/v1/surf/onchain/sql` | POST | body: `{ query: string }` | Raw SQL against 80+ indexed tables. Sub-second. Use for novel questions that the typed query can't express. |
48
+
49
+ ### Token analytics (Tier 2, $0.005)
50
+ | Path | Method | Required | What it returns |
51
+ |---|---|---|---|
52
+ | `/v1/surf/token/holders` | GET | `address`, `chain` | Top token holders with balances |
53
+ | `/v1/surf/token/transfers` | GET | `address`, `chain` | Token transfer history |
54
+
55
+ ### Wallet intelligence (Tier 2, $0.005)
56
+ | Path | Method | Required | What it returns |
57
+ |---|---|---|---|
58
+ | `/v1/surf/wallet/detail` | GET | `address` | Aggregated wallet profile across chains |
59
+ | `/v1/surf/wallet/history` | GET | `address` | Transaction history |
60
+ | `/v1/surf/wallet/net-worth` | GET | `address` | Net-worth time series |
61
+ | `/v1/surf/wallet/transfers` | GET | `address` | Transfer history |
62
+ | `/v1/surf/wallet/protocols` | GET | `address` | DeFi positions (Aave, Lido, Uniswap, etc.) |
63
+ | `/v1/surf/wallet/labels/batch` | GET | `addresses` (comma-sep) | Batch label lookup: CEX, Whale, Bridge, MEV, Contract, etc. |
64
+
65
+ ## How to choose
66
+
67
+ - **"What's gas on Base?"** → `onchain/gas-price` with `chain: "base"` ($0.001). One call, done.
68
+ - **"Look up this tx"** → `onchain/tx` with `hash` + `chain`.
69
+ - **"Who owns this token?"** → `token/holders` ($0.005). Pair with `wallet/labels/batch` on the top 20 to see which holders are CEXes vs whales.
70
+ - **"Profile this wallet"** → `wallet/detail` first ($0.005). If they want depth, follow up with `wallet/history`, `wallet/net-worth`, `wallet/protocols`.
71
+ - **"Is this address a CEX / whale / MEV bot?"** → `wallet/labels/batch` — cheapest forensic call.
72
+ - **"Bridge volume this week"** → `onchain/bridge/ranking` ($0.001).
73
+ - **"Best yield on USDC right now"** → `onchain/yield/ranking` ($0.001), filter for USDC.
74
+
75
+ ### On-chain SQL workflow
76
+
77
+ For novel chain questions (e.g. "all addresses that received >100 ETH from Tornado in 2025"):
78
+
79
+ 1. **Schema first** — `BlockRun({ path: "/v1/surf/onchain/schema", method: "GET" })` ($0.02). Note the tables, columns, types.
80
+ 2. **Try structured query** — `BlockRun({ path: "/v1/surf/onchain/query", method: "POST", body: { /* typed predicates */ } })` ($0.02) when the shape fits.
81
+ 3. **Raw SQL fallback** — `BlockRun({ path: "/v1/surf/onchain/sql", method: "POST", body: { query: "SELECT …" } })` ($0.02) for anything else.
82
+ 4. **Validate** — if SQL fails parse or returns empty unexpectedly, fix the query and re-run. Each retry is $0.02 — be deliberate.
83
+
84
+ ## Cost discipline
85
+
86
+ - Wallet/token reads are $0.005 each. If you need 5 lookups, expect $0.025.
87
+ - Tier-3 chain queries are $0.02/call. Plan the schema → query → result loop before firing; don't fire speculative SQL.
88
+ - Always include the cost in your summary back to the user.
89
+
90
+ ## The user asked
91
+
92
+ $ARGUMENTS
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: surf-chat
3
+ description: Crypto-native chat with citations via the surf-1.5 model. Different from a generic LLM call — surf-1.5 is grounded in live crypto data and returns first-class citations (source links + charts). Use when the user wants research-shaped answers about projects, tokens, market events, or DeFi protocols, with sources attached.
4
+ triggers:
5
+ - "surf chat"
6
+ - "crypto research"
7
+ - "ask surf"
8
+ - "with citations"
9
+ - "grounded answer"
10
+ argument-hint: <research question>
11
+ cost-receipt: true
12
+ ---
13
+
14
+ You are running inside Franklin on **{{wallet_chain}}**. The `surf-1.5` model is crypto-native: it knows current market data, projects, on-chain flow, social signal, and returns structured citations. Reach for it when the user wants a sourced answer rather than a vibes-based one.
15
+
16
+ **Chain note:** Surf currently settles x402 payments on **Base** only. If the user's active chain is `solana`, ask them to `/chain base` before calling — surf-1.5 is $0.02/call, so a failed payment retry isn't free.
17
+
18
+ ## How to use
19
+
20
+ Call:
21
+
22
+ ```
23
+ BlockRun({
24
+ path: "/v1/surf/chat/completions",
25
+ method: "POST",
26
+ body: {
27
+ model: "surf-1.5",
28
+ messages: [
29
+ { role: "user", content: "<the question>" }
30
+ ],
31
+ citation: ["source", "chart"]
32
+ }
33
+ })
34
+ ```
35
+
36
+ Cost: **flat $0.02 per call** (per-token billing is Phase 2 upstream).
37
+
38
+ ### Response shape
39
+
40
+ OpenAI-compatible, with two extras:
41
+ - `choices[0].message.content` — the text answer
42
+ - `choices[0].message.citations[]` — array of `{ type: "source" | "chart", url, title }`
43
+
44
+ When you report back to the user, **always include the citations** as a footer:
45
+
46
+ ```
47
+ [Answer text]
48
+
49
+ Sources:
50
+ 1. <title> — <url>
51
+ 2. <title> — <url>
52
+ ```
53
+
54
+ If `citations` is empty, mention that the answer is ungrounded.
55
+
56
+ ### Multi-turn
57
+
58
+ Pass previous `messages` back in for follow-up turns. Each turn is $0.02.
59
+
60
+ ## When to use surf-chat vs the main agent loop
61
+
62
+ - **Use surf-chat** when: the question is about market state, project research, narrative tracking, or "what happened to X in the last week" — anywhere fresh crypto context matters and you want citations to share with the user.
63
+ - **Use the main agent loop** (no surf-chat) when: the question is general reasoning, coding, planning, summarization of content already in scope, or anything where adding citations doesn't add value.
64
+
65
+ The wallet is funding the surf-chat call directly — be deliberate about reaching for it.
66
+
67
+ ## When to use surf-chat vs the data endpoints
68
+
69
+ - **surf-chat** is good when you don't know which endpoints to compose, or when the answer is qualitative ("what's the bull case for $X?").
70
+ - The **`/surf-market` / `/surf-chain` / `/surf-social` skills** are better when the question is structured ("price of BTC", "RSI on ETH", "wallet net worth") — they're $0.001–$0.005 each and you keep full control of the data.
71
+
72
+ If you can answer with one $0.001 call to a data endpoint, do that. Only escalate to surf-chat when the question needs synthesis across many endpoints or the user explicitly asks for cited research.
73
+
74
+ ## The user asked
75
+
76
+ $ARGUMENTS
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: surf-market
3
+ description: Crypto market data via Surf — prices, futures, ETFs, options, fear/greed, technical and on-chain indicators, token DEX flows, news, project profiles, VC fund holdings. Use when the user asks about market conditions, ranking tokens, futures positioning, technical signals, options skew, news context, or VC/fund intelligence.
4
+ triggers:
5
+ - "market overview"
6
+ - "fear greed"
7
+ - "futures"
8
+ - "etf flow"
9
+ - "options skew"
10
+ - "tokenomics"
11
+ - "rsi"
12
+ - "macd"
13
+ - "liquidations"
14
+ - "vc fund"
15
+ - "token ranking"
16
+ argument-hint: <symbol or question>
17
+ cost-receipt: true
18
+ ---
19
+
20
+ You are running inside Franklin on **{{wallet_chain}}**. Crypto market data lives behind the `BlockRun` tool, which signs USDC x402 payments from the user's wallet on every call. Pick the cheapest endpoint that answers the question.
21
+
22
+ **Chain note:** Surf currently settles x402 payments on **Base** only (treasury is `0x058a59…` on Base). If the user's active chain is `solana` and you hit a payment error, tell them to switch with `/chain base` before retrying — the request itself works, the wallet just needs to be on Base to sign the payment.
23
+
24
+ ## How to use
25
+
26
+ Call `BlockRun({ path: "/v1/surf/<endpoint>", method: "GET", params: { ... } })`. All endpoints below are GET unless noted. Required params must be supplied or you'll get a 400 (no charge).
27
+
28
+ ## Endpoint catalog
29
+
30
+ ### Exchange (CEX intelligence)
31
+ | Path | Tier | Required params | What it returns |
32
+ |---|---|---|---|
33
+ | `/v1/surf/exchange/markets` | $0.001 | — | Trading pairs catalog across major CEXes |
34
+ | `/v1/surf/exchange/price` | $0.001 | `pair` | Ticker price for a pair |
35
+ | `/v1/surf/exchange/perp` | $0.001 | `pair` | Perpetual contract snapshot |
36
+ | `/v1/surf/exchange/depth` | $0.005 | `pair` | Order book depth |
37
+ | `/v1/surf/exchange/klines` | $0.005 | `pair` | OHLCV candlesticks |
38
+ | `/v1/surf/exchange/funding-history` | $0.005 | `pair` | Perp funding rate history |
39
+ | `/v1/surf/exchange/long-short-ratio` | $0.005 | `pair` | Long/short positioning |
40
+
41
+ ### Market (broad-market intelligence)
42
+ | Path | Tier | Required params | What it returns |
43
+ |---|---|---|---|
44
+ | `/v1/surf/market/ranking` | $0.001 | — | Token ranking (market cap, volume, change) |
45
+ | `/v1/surf/market/fear-greed` | $0.001 | — | Fear & Greed index history |
46
+ | `/v1/surf/market/futures` | $0.001 | — | Futures market overview |
47
+ | `/v1/surf/market/price` | $0.001 | `symbol` | Token price history |
48
+ | `/v1/surf/market/etf` | $0.001 | `symbol` | Spot ETF flow history (BTC, ETH) |
49
+ | `/v1/surf/market/options` | $0.001 | `symbol` | Options skew / IV / volume |
50
+ | `/v1/surf/market/liquidation/exchange-list` | $0.005 | — | Liquidations by exchange |
51
+ | `/v1/surf/market/liquidation/order` | $0.005 | — | Large liquidation orders |
52
+ | `/v1/surf/market/liquidation/chart` | $0.005 | `symbol` | Liquidation chart over time |
53
+ | `/v1/surf/market/onchain-indicator` | $0.005 | `symbol`, `metric` | NUPL, SOPR, MVRV, Puell, NVT |
54
+ | `/v1/surf/market/price-indicator` | $0.005 | `indicator`, `symbol` | RSI, MACD, Bollinger, EMA |
55
+
56
+ ### News
57
+ | Path | Tier | Required params | What it returns |
58
+ |---|---|---|---|
59
+ | `/v1/surf/news/feed` | $0.001 | — | AI-curated crypto news feed |
60
+ | `/v1/surf/news/detail` | $0.001 | `id` | Full article by ID |
61
+
62
+ ### Project (DeFi protocols + project profiles)
63
+ | Path | Tier | Required params | What it returns |
64
+ |---|---|---|---|
65
+ | `/v1/surf/project/detail` | $0.001 | — | Aggregated project profile (token + DeFi + social) |
66
+ | `/v1/surf/project/defi/metrics` | $0.001 | `metric` | Per-protocol DeFi metrics (TVL, fees, revenue) |
67
+ | `/v1/surf/project/defi/ranking` | $0.001 | `metric` | DeFi protocol ranking |
68
+
69
+ ### Token (on-chain analytics)
70
+ | Path | Tier | Required params | What it returns |
71
+ |---|---|---|---|
72
+ | `/v1/surf/token/tokenomics` | $0.001 | — | Unlock schedule + vesting |
73
+ | `/v1/surf/token/dex-trades` | $0.005 | `address` | DEX trade history |
74
+
75
+ ### Fund (VC + treasury intelligence)
76
+ | Path | Tier | Required params | What it returns |
77
+ |---|---|---|---|
78
+ | `/v1/surf/fund/detail` | $0.001 | — | VC fund profile |
79
+ | `/v1/surf/fund/portfolio` | $0.001 | — | VC fund portfolio holdings |
80
+ | `/v1/surf/fund/ranking` | $0.001 | `metric` | Top VC funds ranking |
81
+
82
+ ## How to choose
83
+
84
+ - **"How's the market?"** → `market/fear-greed` + `market/ranking` (both $0.001). Cheap snapshot.
85
+ - **"What's BTC doing?"** → `market/price` for history, `exchange/price` for spot tick, `market/etf` for institutional flow.
86
+ - **"Show me liquidations."** → `market/liquidation/chart` for time series, `market/liquidation/order` for whale events.
87
+ - **"Technical signal on ETH"** → `market/price-indicator` with `indicator: "RSI"` (or MACD, BBANDS, EMA).
88
+ - **"On-chain health"** → `market/onchain-indicator` with `metric: "NUPL"` etc.
89
+ - **"Who holds this token / where is it traded?"** → `token/tokenomics` for supply schedule, `token/dex-trades` for flow.
90
+ - **"What VCs back this project?"** → `fund/portfolio` (filter by project).
91
+
92
+ ## Cost discipline
93
+
94
+ - Most read endpoints are Tier 1 ($0.001). Burn freely.
95
+ - Tier 2 ($0.005) endpoints carry depth, history, or fraud-signal data — use when the cheaper endpoint can't answer.
96
+ - Avoid speculative multi-endpoint scans. Pick the right endpoint for the question; if unsure, ask the user one clarifying question first.
97
+ - Report the cost on every call in your summary: "Pulled fear/greed history ($0.001). Index sits at 62 (greed)."
98
+
99
+ ## The user asked
100
+
101
+ $ARGUMENTS
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: surf-social
3
+ description: Crypto-Twitter / KOL intelligence via Surf — project mindshare time series, smart-follower history (high-signal accounts), social ranking, tweet + replies fetch, user profile / followers / posts / replies. Use when the user asks about KOL sentiment, project attention, smart money commentary, narrative momentum, or specific Twitter handles.
4
+ triggers:
5
+ - "kol sentiment"
6
+ - "mindshare"
7
+ - "smart followers"
8
+ - "crypto twitter"
9
+ - "ct sentiment"
10
+ - "twitter analysis"
11
+ - "social ranking"
12
+ - "tweet"
13
+ - "twitter handle"
14
+ argument-hint: <project, handle, or question>
15
+ cost-receipt: true
16
+ ---
17
+
18
+ You are running inside Franklin on **{{wallet_chain}}**. Use the `BlockRun` tool to call Surf's social endpoints. This is the canonical source for crypto-Twitter signal — mindshare scoring, KOL identification, and reply-graph analysis.
19
+
20
+ **Chain note:** Surf currently settles x402 payments on **Base** only. If the user's active chain is `solana` and you hit a payment error, ask them to `/chain base` before retrying. The social data itself is chain-agnostic.
21
+
22
+ ## How to use
23
+
24
+ `BlockRun({ path: "/v1/surf/<endpoint>", method: "GET", params: { ... } })`. All endpoints below are GET.
25
+
26
+ ## Endpoint catalog
27
+
28
+ ### Project-level signal (Tier 2, $0.005)
29
+ | Path | Required params | What it returns |
30
+ |---|---|---|
31
+ | `/v1/surf/social/detail` | — | Aggregated social analytics for a project |
32
+ | `/v1/surf/social/ranking` | — | Mindshare ranking across projects |
33
+ | `/v1/surf/social/smart-followers/history` | — | Smart-follower count history (high-signal accounts only) |
34
+ | `/v1/surf/social/mindshare` | `q`, `interval` | Mindshare time series for a project (`q` = ticker or name, `interval` = `1d` / `7d` / `30d`) |
35
+
36
+ ### Tweet-level (Tier 1, $0.001)
37
+ | Path | Required params | What it returns |
38
+ |---|---|---|
39
+ | `/v1/surf/social/tweets` | `ids` (comma-sep) | Fetch tweets by ID |
40
+ | `/v1/surf/social/tweet/replies` | `tweet_id` | Replies to a specific tweet |
41
+
42
+ ### User-level (Tier 1, $0.001)
43
+ | Path | Required params | What it returns |
44
+ |---|---|---|
45
+ | `/v1/surf/social/user` | `handle` | Twitter user profile |
46
+ | `/v1/surf/social/user/followers` | `handle` | Followers list |
47
+ | `/v1/surf/social/user/following` | `handle` | Following list |
48
+ | `/v1/surf/social/user/posts` | `handle` | User posts |
49
+ | `/v1/surf/social/user/replies` | `handle` | User replies |
50
+
51
+ ## How to choose
52
+
53
+ - **"What's the market saying about $X?"** → `social/mindshare` with `q: "X", interval: "7d"` ($0.005). Read the trend, not the absolute number.
54
+ - **"Who's the smart money following $X?"** → `social/smart-followers/history` ($0.005). Compare growth rate to baseline.
55
+ - **"Top projects by attention right now"** → `social/ranking` ($0.005).
56
+ - **"Is @handle a real player?"** → `social/user` then `social/user/followers` (look at follower-to-following ratio + which smart accounts follow them).
57
+ - **"What did @handle say recently?"** → `social/user/posts` ($0.001 each).
58
+ - **"Show me the reply storm under tweet X"** → `social/tweet/replies` ($0.001).
59
+
60
+ ## When NOT to use this skill
61
+
62
+ - Generic Twitter scraping or non-crypto sentiment → use `BrowserX` or a web search tool. Surf is curated for crypto-relevant accounts.
63
+ - Real-time tweet streaming → not supported; this is historical / batch reads.
64
+
65
+ ## Cost discipline
66
+
67
+ - User-level reads are cheap ($0.001). Free to fan out across 5–10 handles when profiling.
68
+ - Project-level signal is $0.005/call. One mindshare + one smart-followers call is usually enough to answer "is this thing real?".
69
+ - Always include the cost in your summary.
70
+
71
+ ## The user asked
72
+
73
+ $ARGUMENTS
@@ -0,0 +1,21 @@
1
+ /**
2
+ * BlockRun primitive — the generic x402-paid gateway capability.
3
+ *
4
+ * One tool, every BlockRun endpoint. Replaces the per-API hardcoded pattern
5
+ * (ImageGen, VideoGen, Phone tools, etc) for new integrations. Skills in
6
+ * src/skills-bundled/<name>/SKILL.md describe which paths to call for which
7
+ * user intents; this tool just signs the x402 payment and forwards.
8
+ *
9
+ * Why the indirection: BlockRun keeps shipping new partner APIs (Surf,
10
+ * Phone & Voice, future ML/data partners). Hardcoding each as a fresh
11
+ * CapabilityHandler means a Franklin npm release per partner and a bigger
12
+ * tool list for the LLM to reason about. This primitive plus markdown
13
+ * skill files decouples API expansion from agent releases — new partners
14
+ * ship as a new SKILL.md, no code change.
15
+ *
16
+ * Signing pattern mirrors src/tools/modal.ts and src/phone/client.ts; we
17
+ * deliberately keep the copy-paste rather than refactor those into a
18
+ * shared module (out of scope; would touch unrelated tools).
19
+ */
20
+ import type { CapabilityHandler } from '../agent/types.js';
21
+ export declare const blockrunCapability: CapabilityHandler;
@@ -0,0 +1,257 @@
1
+ /**
2
+ * BlockRun primitive — the generic x402-paid gateway capability.
3
+ *
4
+ * One tool, every BlockRun endpoint. Replaces the per-API hardcoded pattern
5
+ * (ImageGen, VideoGen, Phone tools, etc) for new integrations. Skills in
6
+ * src/skills-bundled/<name>/SKILL.md describe which paths to call for which
7
+ * user intents; this tool just signs the x402 payment and forwards.
8
+ *
9
+ * Why the indirection: BlockRun keeps shipping new partner APIs (Surf,
10
+ * Phone & Voice, future ML/data partners). Hardcoding each as a fresh
11
+ * CapabilityHandler means a Franklin npm release per partner and a bigger
12
+ * tool list for the LLM to reason about. This primitive plus markdown
13
+ * skill files decouples API expansion from agent releases — new partners
14
+ * ship as a new SKILL.md, no code change.
15
+ *
16
+ * Signing pattern mirrors src/tools/modal.ts and src/phone/client.ts; we
17
+ * deliberately keep the copy-paste rather than refactor those into a
18
+ * shared module (out of scope; would touch unrelated tools).
19
+ */
20
+ import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
21
+ import { loadChain, API_URLS, USER_AGENT } from '../config.js';
22
+ import { recordUsage } from '../stats/tracker.js';
23
+ import { logger } from '../logger.js';
24
+ const DEFAULT_TIMEOUT_MS = 30_000;
25
+ const MAX_TIMEOUT_MS = 120_000;
26
+ // ─── x402 payment signing (same shape as modal.ts / phone/client.ts) ──────
27
+ async function extractPaymentReq(response) {
28
+ let header = response.headers.get('payment-required');
29
+ if (!header) {
30
+ try {
31
+ const body = (await response.clone().json());
32
+ if (body.x402 || body.accepts)
33
+ header = btoa(JSON.stringify(body));
34
+ }
35
+ catch { /* not JSON, no header */ }
36
+ }
37
+ return header;
38
+ }
39
+ async function signPayment(response, chain, endpoint, resourceDescription) {
40
+ try {
41
+ const paymentHeader = await extractPaymentReq(response);
42
+ if (!paymentHeader)
43
+ return null;
44
+ if (chain === 'solana') {
45
+ const wallet = await getOrCreateSolanaWallet();
46
+ const paymentRequired = parsePaymentRequired(paymentHeader);
47
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
48
+ const secretBytes = await solanaKeyToBytes(wallet.privateKey);
49
+ const feePayer = details.extra?.feePayer || details.recipient;
50
+ const payload = await createSolanaPaymentPayload(secretBytes, wallet.address, details.recipient, details.amount, feePayer, {
51
+ resourceUrl: details.resource?.url || endpoint,
52
+ resourceDescription: details.resource?.description || resourceDescription,
53
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
54
+ extra: details.extra,
55
+ });
56
+ return {
57
+ headers: { 'PAYMENT-SIGNATURE': payload },
58
+ amountUsd: Number(details.amount) / 1_000_000,
59
+ };
60
+ }
61
+ else {
62
+ const wallet = getOrCreateWallet();
63
+ const paymentRequired = parsePaymentRequired(paymentHeader);
64
+ const details = extractPaymentDetails(paymentRequired);
65
+ const payload = await createPaymentPayload(wallet.privateKey, wallet.address, details.recipient, details.amount, details.network || 'eip155:8453', {
66
+ resourceUrl: details.resource?.url || endpoint,
67
+ resourceDescription: details.resource?.description || resourceDescription,
68
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
69
+ extra: details.extra,
70
+ });
71
+ return {
72
+ headers: { 'PAYMENT-SIGNATURE': payload },
73
+ amountUsd: Number(details.amount) / 1_000_000,
74
+ };
75
+ }
76
+ }
77
+ catch (err) {
78
+ logger.warn(`[franklin] BlockRun payment error: ${err.message}`);
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Pull the settlement tx hash from the gateway's X-Payment-Receipt
84
+ * header. The X-Payment-Response header doesn't carry the amount (only
85
+ * { success, transaction, network, payer }), so we don't parse it here
86
+ * — the amount comes from what signPayment authorized in the 402 retry.
87
+ */
88
+ function extractTxHash(response) {
89
+ return response.headers.get('x-payment-receipt');
90
+ }
91
+ async function callGateway(url, method, body, resourceDescription, abortSignal, timeoutMs) {
92
+ const start = Date.now();
93
+ const chain = loadChain();
94
+ const headers = {
95
+ 'Accept': 'application/json',
96
+ 'User-Agent': USER_AGENT,
97
+ };
98
+ if (method === 'POST')
99
+ headers['Content-Type'] = 'application/json';
100
+ const ctrl = new AbortController();
101
+ const onParentAbort = () => ctrl.abort();
102
+ abortSignal.addEventListener('abort', onParentAbort, { once: true });
103
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
104
+ try {
105
+ const payload = method === 'POST' && body !== undefined ? JSON.stringify(body) : undefined;
106
+ let response = await fetch(url, { method, signal: ctrl.signal, headers, body: payload });
107
+ let paidUsd = 0;
108
+ if (response.status === 402) {
109
+ const signed = await signPayment(response, chain, url, resourceDescription);
110
+ if (!signed) {
111
+ return {
112
+ ok: false, status: 402,
113
+ body: { error: 'payment signing failed' }, raw: '',
114
+ paidUsd: 0, txHash: null, latencyMs: Date.now() - start,
115
+ };
116
+ }
117
+ paidUsd = signed.amountUsd;
118
+ response = await fetch(url, {
119
+ method, signal: ctrl.signal,
120
+ headers: { ...headers, ...signed.headers },
121
+ body: payload,
122
+ });
123
+ }
124
+ const txHash = extractTxHash(response);
125
+ // If the gateway returned 4xx after we signed, settlement was skipped
126
+ // server-side (per the route's "Payment was NOT charged" pattern). Don't
127
+ // claim a paid amount the wallet didn't actually spend.
128
+ if (!response.ok)
129
+ paidUsd = 0;
130
+ const raw = await response.text().catch(() => '');
131
+ let parsed = {};
132
+ try {
133
+ parsed = raw ? JSON.parse(raw) : {};
134
+ }
135
+ catch { /* leave as {} */ }
136
+ return {
137
+ ok: response.ok, status: response.status, body: parsed, raw,
138
+ paidUsd, txHash, latencyMs: Date.now() - start,
139
+ };
140
+ }
141
+ finally {
142
+ clearTimeout(timer);
143
+ abortSignal.removeEventListener('abort', onParentAbort);
144
+ }
145
+ }
146
+ function buildUrl(path, params) {
147
+ const chain = loadChain();
148
+ const base = API_URLS[chain]; // ends in /api
149
+ const clean = path.startsWith('/') ? path : `/${path}`;
150
+ const url = `${base}${clean}`;
151
+ if (!params || Object.keys(params).length === 0)
152
+ return url;
153
+ const usp = new URLSearchParams();
154
+ for (const [key, value] of Object.entries(params)) {
155
+ if (value === undefined || value === null)
156
+ continue;
157
+ if (Array.isArray(value)) {
158
+ for (const v of value)
159
+ usp.append(key, String(v));
160
+ }
161
+ else {
162
+ usp.append(key, String(value));
163
+ }
164
+ }
165
+ const qs = usp.toString();
166
+ return qs ? `${url}?${qs}` : url;
167
+ }
168
+ function fmtUsd(n) {
169
+ if (n < 0.01)
170
+ return `$${n.toFixed(4)}`;
171
+ return `$${n.toFixed(2)}`;
172
+ }
173
+ export const blockrunCapability = {
174
+ spec: {
175
+ name: 'BlockRun',
176
+ description: 'Call any BlockRun gateway endpoint. Signs an x402 USDC payment from the user wallet, retries on HTTP 402, and returns the response. ' +
177
+ 'Use this for crypto data (Surf — markets, on-chain, social, chat), AI inference (chat / image / video / music), phone numbers and voice calls, ' +
178
+ 'prediction markets, DeFi data, and any other API exposed under https://blockrun.ai/marketplace. ' +
179
+ 'The path must start with "/v1/" or "/.well-known/". ' +
180
+ 'Bundled skills like /surf-market, /surf-chain, /surf-social, /surf-chat document which endpoints to call for common workflows — read those when you are unsure which path serves the user\'s question. ' +
181
+ 'Cost is wallet-charged automatically; the response includes the actual USD paid.',
182
+ input_schema: {
183
+ type: 'object',
184
+ properties: {
185
+ path: {
186
+ type: 'string',
187
+ description: 'API path under /api, starting with "/v1/" or "/.well-known/". E.g. "/v1/surf/market/fear-greed", "/v1/phone/numbers/list", "/v1/chat/completions".',
188
+ },
189
+ method: {
190
+ type: 'string',
191
+ enum: ['GET', 'POST'],
192
+ description: 'HTTP method. Default: POST if `body` is provided, otherwise GET.',
193
+ },
194
+ params: {
195
+ type: 'object',
196
+ description: 'Query-string parameters. Use for GETs. E.g. { symbol: "BTC" }.',
197
+ },
198
+ body: {
199
+ type: 'object',
200
+ description: 'JSON body. Use for POSTs. E.g. { model: "surf-1.5", messages: [...] }.',
201
+ },
202
+ timeoutMs: {
203
+ type: 'number',
204
+ description: `Optional client-side timeout in ms. Default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS}.`,
205
+ },
206
+ },
207
+ required: ['path'],
208
+ },
209
+ },
210
+ concurrent: true,
211
+ async execute(input, ctx) {
212
+ const raw = input;
213
+ const path = typeof raw.path === 'string' ? raw.path.trim() : '';
214
+ if (!path) {
215
+ return { output: 'Error: `path` is required (e.g. "/v1/surf/market/fear-greed").', isError: true };
216
+ }
217
+ if (!/^\/(v1|\.well-known)\//.test(path)) {
218
+ return {
219
+ output: `Error: path must start with "/v1/" or "/.well-known/". Got: ${path}`,
220
+ isError: true,
221
+ };
222
+ }
223
+ const params = (raw.params && typeof raw.params === 'object') ? raw.params : undefined;
224
+ const body = (raw.body && typeof raw.body === 'object') ? raw.body : undefined;
225
+ // Method resolution: explicit > inferred from body > default GET
226
+ const explicitMethod = typeof raw.method === 'string' ? raw.method.toUpperCase() : '';
227
+ const method = explicitMethod === 'POST' || explicitMethod === 'GET'
228
+ ? explicitMethod
229
+ : (body ? 'POST' : 'GET');
230
+ const timeoutMs = Math.min(Math.max(1_000, typeof raw.timeoutMs === 'number' ? raw.timeoutMs : DEFAULT_TIMEOUT_MS), MAX_TIMEOUT_MS);
231
+ const url = buildUrl(path, method === 'GET' ? params : undefined);
232
+ const resourceDescription = `BlockRun ${method} ${path}`;
233
+ const result = await callGateway(url, method, method === 'POST' ? body : undefined, resourceDescription, ctx.abortSignal, timeoutMs);
234
+ // Telemetry — show in the panel Audit tab regardless of success
235
+ try {
236
+ recordUsage(`BlockRun:${path}`, 0, 0, result.paidUsd, result.latencyMs);
237
+ }
238
+ catch { /* best-effort */ }
239
+ if (!result.ok) {
240
+ const detail = typeof result.body?.error === 'string'
241
+ ? result.body.error
242
+ : `HTTP ${result.status}`;
243
+ const fullOutput = result.raw || JSON.stringify(result.body, null, 2);
244
+ return {
245
+ output: `BlockRun ${method} ${path} failed: ${detail} (status ${result.status}). No charge if status is 4xx pre-payment.`,
246
+ fullOutput,
247
+ isError: true,
248
+ };
249
+ }
250
+ const head = `BlockRun ${method} ${path} → ${fmtUsd(result.paidUsd)}${result.txHash ? ` · tx ${result.txHash.slice(0, 10)}…` : ''} · ${result.latencyMs}ms`;
251
+ const payload = typeof result.body === 'object' ? JSON.stringify(result.body, null, 2) : String(result.body);
252
+ return {
253
+ output: `${head}\n${payload}`,
254
+ fullOutput: `${head}\n${payload}`,
255
+ };
256
+ },
257
+ };
@@ -32,6 +32,7 @@ import { base0xGaslessSwapCapability } from './zerox-gasless.js';
32
32
  import { defiLlamaProtocolsCapability, defiLlamaProtocolCapability, defiLlamaChainsCapability, defiLlamaYieldsCapability, defiLlamaPriceCapability, } from './defillama.js';
33
33
  import { predictionMarketCapability } from './prediction.js';
34
34
  import { modalCapabilities } from './modal.js';
35
+ import { blockrunCapability } from './blockrun.js';
35
36
  import { createTradingCapabilities } from './trading-execute.js';
36
37
  import { Portfolio } from '../trading/portfolio.js';
37
38
  import { RiskEngine } from '../trading/risk.js';
@@ -163,6 +164,7 @@ export const allCapabilities = [
163
164
  defiLlamaYieldsCapability,
164
165
  defiLlamaPriceCapability,
165
166
  predictionMarketCapability, // Polymarket / Kalshi / matching / smart money via Predexon
167
+ blockrunCapability, // Generic x402-paid gateway primitive — Surf, Phone, future partners (see /surf-* skills)
166
168
  // Modal GPU sandbox tools — registered but hidden by default (not in
167
169
  // CORE_TOOL_NAMES). Agent must `ActivateTool({names:["ModalCreate",...]})`
168
170
  // before they appear in its tool inventory. High-cost ($0.40/H100 create)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.18.0",
3
+ "version": "3.19.0",
4
4
  "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {