@blockrun/franklin 3.17.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.
- package/dist/agent/context.js +16 -0
- package/dist/panel/html.js +403 -0
- package/dist/panel/server.js +217 -6
- package/dist/phone/cache.d.ts +44 -0
- package/dist/phone/cache.js +74 -0
- package/dist/phone/client.d.ts +50 -0
- package/dist/phone/client.js +162 -0
- package/dist/skills-bundled/surf-chain/SKILL.md +92 -0
- package/dist/skills-bundled/surf-chat/SKILL.md +76 -0
- package/dist/skills-bundled/surf-market/SKILL.md +101 -0
- package/dist/skills-bundled/surf-social/SKILL.md +73 -0
- package/dist/tools/blockrun.d.ts +21 -0
- package/dist/tools/blockrun.js +257 -0
- package/dist/tools/index.js +2 -0
- package/package.json +1 -1
|
@@ -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
|
+
};
|
package/dist/tools/index.js
CHANGED
|
@@ -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