About horizon-pulse
Horizon Pulse provides agents with pay-per-call endpoints for crypto markets, on-chain risk, and web processing, returning live JSON. It handles USDC payments through x402 and offers both local and hosted MCP access.
Key features
- 01Query prices, indicators, and funding rates
- 02Inspect DeFi yields and portfolio risk
- 03Retrieve on-chain transaction, balance, and gas data
- 04Fetch web text, proxy requests, and extract fields
- 05Pay per call in USDC through x402
Use cases
- 01Get crypto market and yield data for agents
- 02Assess asset allocation and risk for an EVM address
- 03Fetch and parse web pages on demand
- 04Call paid data endpoints from an MCP client
MCP support
The local stdio MCP server exposes live routes as tools and can pay x402 challenges using a configured buyer wallet. The hosted MCP uses streamable HTTP; payments through MCP are Base USDC only.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://horizonpulse.dev/mcpclaude mcp add --transport http horizon-pulse-horizon-pulse https://horizonpulse.dev/mcp{"mcpServers":{"horizon-pulse-horizon-pulse":{"type":"http","url":"https://horizonpulse.dev/mcp"}}}Open in Cursor{"servers":{"horizon-pulse-horizon-pulse":{"type":"http","url":"https://horizonpulse.dev/mcp"}}}Open in VS Code{"mcpServers":{"horizon-pulse-horizon-pulse":{"type":"http","url":"https://horizonpulse.dev/mcp"}}}{"mcpServers":{"horizon-pulse-horizon-pulse":{"serverUrl":"https://horizonpulse.dev/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
28This tool takes no parameters.
Details
From the project READMEHorizon Pulse
Pay-per-call crypto market pulse, signals, funding, yield rankings, portfolio risk, gas, plus public URL fetch → clean text, universal http proxy, and extract → structured fields for AI agents, monetized with the x402 protocol on Base mainnet and Solana mainnet (USDC, exact, same price on both).
This repo is a Next.js App Router service ready to deploy (e.g. Vercel) and push to:
https://github.com/horizon-pulse/horizon-pulse.git
Public host
| Role | URL |
|---|---|
| Canonical | https://horizonpulse.dev |
| Backup (Vercel) | https://horizon-pulse-seven.vercel.app |
Prefer horizonpulse.dev in agent docs, OpenAPI, and clients. The *.vercel.app URL remains a working fallback.
What it is
- Agents hit live paid HTTP endpoints (crypto routes frozen;
/api/fetch+/api/http+/api/extract+/api/x402-check+/api/bazaar-check+/api/market/search+/api/market/quote+/api/screenshot+/api/search+/api/pdfnon-crypto LIVE — see below). - Unpaid requests receive HTTP 402 with x402 v2 requirements: canonical wire is the
PAYMENT-REQUIREDheader, with oneacceptsentry per network: Base (CAIP-2eip155:8453, Base USDC, the treasury below) and Solana (CAIP-2solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp, USDC mint, the Solana payTo below). Seedocs/solana-rail.md. - Retry with an x402 v2
PAYMENT-SIGNATUREheader. The facilitator (Coinbase CDP on Base, PayAI on Solana) verifies + settles, then the route returns live data. - No stubbed prices or fake APYs: Coinbase Exchange (CoinGecko fallback) for spot; CoinGecko OHLC (Coinbase candles fallback) for signals; OKX for perpetual funding (Binance/Bybit are often geo-blocked on Vercel); DefiLlama for yield pools; public RPC
balanceOffor portfolio (real balances only);eth_feeHistory/eth_gasPricefor gas (real fees only);/api/fetchreturns best-effort cleaned text from a requested public URL (SSRF-safe, size/time capped);/api/httpis a raw universal proxy (filtered headers, text|base64 body, SSRF-safe) priced $0.01 for volume;/api/extractreturns best-effort structured page fields from a URL or HTML (SSRF-safe, size/time capped) priced $0.015. - Never invent metrics; never advertise routes that 404; never cite a cached balance —
/statusreads live USDCbalanceOfonpayTo. Honest: currentpayTois interim Coinbase-custodial (not Safe/multisig).
Catalog status (post first settlement)
| Fact | Value |
|---|---|
| Live paid routes | 30: see the table below (29 multi-network routes + the $1 verified brief, Base only) |
| First settle | /api/pulse $0.005 — tx 0xedbd1a51… |
| Crypto policy | Frozen — six crypto routes: no price changes |
| Fetch | Non-crypto LIVE: clean-text ($0.02) |
| Http | Non-crypto LIVE: universal proxy ($0.01 volume price) |
| Extract | Non-crypto LIVE: structured HTML ($0.015) |
Coming-soon / placeholder / 404 routes are not listed on /, /status, or this README.
Agent how-to (x402 v2)
GETa paid route with no payment → HTTP 402.- Read
PAYMENT-REQUIRED(v2) and pick oneacceptsentry. Confirmnetwork(eip155:8453for Base orsolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpfor Solana), USDC on that network, exact atomicamount, and that network'spayTo. - Sign and retry the same
GETwithPAYMENT-SIGNATURE(v2 primary; do not rely on legacyX-PAYMENTas the settle path). - On success: the facilitator settles on the network you paid on; response body is the live JSON for that route.
Optional: OPTIONS on a paid route returns discovery + the same PAYMENT-REQUIRED challenge without charging.
Agent integration
- Skill file:
/skill.md, plain markdown an agent can follow end to end (generated frompublic/openapi.jsonbylib/agent-skill.ts). - Guide:
/agents: pay flow, skill and MCP setup. - Local MCP server:
mcp/, stdio, every live route as a tool, pays the x402 challenge from a buyer wallet you configure, with per-call and per-session caps (quote-only without a key). Not published to npm; install from this repo. - Hosted MCP:
POST https://horizonpulse.dev/mcp(streamable HTTP) for MCP clients that can pay x402 themselves. Payments over MCP are USDC on Base only; the paid HTTP routes accept Base, Solana, Polygon and XRPL.
Treasury (payTo)
| Field | Value |
|---|---|
| payTo | 0x5b32c973596078a967562ca652761404f19be0e9 |
| Basename | horizonpulsebase.base.eth (resolves to the payTo above; a label only, the 402 carries the hex address) |
| Custody | Interim Coinbase-custodial Base address (owner-controlled). Not a Safe or multisig. |
| Upgrade path | Move later to a non-custodial Safe / multisig when ready — same catalog and prices. |
| USDC (Base) | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Network | base (CAIP-2 eip155:8453) |
| Facilitator | https://api.cdp.coinbase.com/platform/v2/x402 |
| Solana payTo | BjY98A6dS3GGLZdz2zHy8wK7XAwnQgNhCc66mfmBTRPz (operator-controlled; pinned in lib/solana-config.ts; payments credit its USDC token account) |
| USDC (Solana) | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
| Solana network | CAIP-2 solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp (exact only) |
| Solana facilitator | PayAI https://facilitator.payai.network |
Do not use the retired address 0xe16A1b12404cB2EbC6e783beCA6E2A9253c3dC7E.
PAY_TO may be overridden via env, but defaults to the address above. Agents should treat current payTo as the live settlement destination; custody shape may change later without changing route prices.
Endpoints
Paid (30 routes: 29 multi-network + the $1 verified brief, Base only; same list as /.well-known/x402):
| Route | Price | Auth |
|---|---|---|
GET /api/pulse | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/signals | $0.005 USDC (5000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/yield | $0.02 USDC (20000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/portfolio | $0.04 USDC (40000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/gas | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/funding | $0.003 USDC (3000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/fetch | $0.02 USDC (20000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET / POST /api/http | $0.01 USDC (10000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET / POST /api/extract | $0.015 USDC (15000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/x402-check | $0.01 USDC (10000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/x402-check/deep | $0.03 USDC (30000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/bazaar-check | $0.01 USDC (10000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/market/search | $0.005 USDC (5000 atomic), untested price | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/market/quote | $0.005 USDC (5000 atomic), untested price | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/tx-status | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/tokenized-stock | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/watch | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/balance | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/tx-guard | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET / POST /api/grocery/normalize | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET / POST /api/grocery/ingredient | $0.005 USDC (5000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/chain/head | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/token/meta | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/name/resolve | $0.002 USDC (2000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET / POST /api/batch | $0.01 USDC (10000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/discourse/split | $0.03 USDC (30000 atomic), untested price | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/screenshot | $0.02 USDC (20000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/search | $0.03 USDC (30000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/pdf | $0.02 USDC (20000 atomic) | x402 v2 (Base, Solana, Polygon, XRPL) |
GET /api/research/report/brief?q=… | $1 USDC (1000000 atomic): at least 2 claims confirmed in two sources as defined by the contract (two independent nodes, or two separate specification documents), or no charge (contract, terms) | x402 v2 (Base USDC only) |
Free (not paid APIs):
| Route | Auth |
|---|---|
GET /status | public HTML — live USDC balanceOf on payTo (honest RPC error if fetch fails) |
GET / | landing — live paid catalog + agent how-to |
GET /api/pulse
Real spot prices for BTC / ETH / SOL (Coinbase Exchange, CoinGecko fallback) with per-asset momentum, plus overall momentum / sentiment / direction (up/down/flat, a neutral description of the average 24h move; renamed from signal, see CHANGELOG.md).
runtime = 'nodejs'- Discovery: Bazaar extension +
outputSchema.input.discoverable: truein payment requirements
GET /api/signals
RSI(14), MACD(12,26,9), Bollinger bands from CoinGecko OHLC closes, plus OKX funding rates. Includes a methodology field.
runtime = 'nodejs'- Discoverable for agents
GET /api/yield
Ranked DeFi yield pools from the public DefiLlama yields API (https://yields.llama.fi/pools).
- Hard filter: TVL ≥ $10M; exclude non-finite APY and DefiLlama
outlierpools - Preference: keep stablecoin and/or single-asset (
exposure === "single") pools - Transparent ranking:
preferenceTierDESC (2 = stablecoin+single, 1 = either), then APY DESC, then TVL DESC - Every response includes a
methodologyobject (also on error bodies) - Price: $0.02 USDC (
20000atomic) runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9
GET /api/portfolio
On-chain portfolio snapshot for one EVM address (?address=0x…, required).
- Networks: Base + Ethereum mainnet (public RPCs; optional
BASE_RPC_URL/ETH_RPC_URLwith failover) - Tokens: native ETH; USDC, WETH, DAI on both chains; WBTC on Ethereum; cbBTC on Base (honest substitute — Base has no BitGo WBTC)
- USD marks via Coinbase public exchange rates (CoinGecko for the rest or on Coinbase failure); real balances only — if an RPC fails, that network is marked failed in
networks[]/warnings - Rule-based concentration score (0–100) and rule findings (largest asset weight vs 35%/50%, stablecoin share vs 15%/85%, largest chain share vs 85%, low native ETH gas balance; returned in the
suggestionsarray as plain findings, no instructions) with transparent formulas inmethodology - Price: $0.04 USDC (
40000atomic) runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9
GET /api/gas
Live gas snapshot for Base and Ethereum from public RPCs.
- Prefers
eth_feeHistory(last 20 blocks, reward percentiles 10/50/90):baseFeeGwei,priorityFeeGwei(p50),suggestedMaxFeeGwei(= 2×baseFee + priority) - Falls back to
eth_gasPricewhen feeHistory is unavailable - Transparent
timingHint: cheap / normal / expensive from percentile rank of the latest confirmed baseFee within the feeHistory window (<33 / 33–67 / >67); documented inmethodology— not a forecast - Optional ETH USD (Coinbase Exchange, CoinGecko fallback) for a simple 21k-gas transfer cost estimate (
simpleTransfer.costUsd) - Price: $0.01 USDC (
10000atomic) runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9
GET /api/funding
Live perpetual funding rates for BTC / ETH / SOL from OKX (same source as /api/signals; Vercel-friendly — not Binance/Bybit).
- Reuses
lib/okx.ts(fetchAllFunding) - Transparent
methodologyon every response (including errors) - Optional crowding hint from funding sign + magnitude only (rules: quiet / mild / elevated / extreme; side longs/shorts/neutral) — not a forecast
- Honesty: real OKX data only
- Price: $0.01 USDC (
10000atomic) — cheap poll product for agents runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9
GET /api/fetch
Fetch a public http(s) URL (?url=https://…, required) and return best-effort clean text/markdown.
- SSRF-safe: blocks localhost, private, link-local, and metadata IPs; re-checks each redirect hop
- Caps: ~200KB raw body, 8s timeout, max 3 redirects
- Preferred Content-Types:
text/html,text/plain,application/json,text/markdown - Cleaning is best-effort (strip script/style/chrome, collapse whitespace) — not a full readability engine
- Price: $0.02 USDC (
20000atomic) runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9
GET / POST /api/http
Universal agent HTTP proxy — call a public http(s) URL with optional method / headers / body; get back upstream status, filtered headers, and body (text or base64) plus contentType and elapsedMs.
- Price choice: $0.01 USDC (
10000atomic) for volume./api/fetchstays $0.02 (specialized clean-text). Prefer/api/httpfor raw proxy traffic. - Inputs:
url(required);method(GETdefault;GET|POST|HEAD|PUT|PATCH|DELETE); optional allowlistedheaders(no Cookie / hop-by-hop); optionalbodyfor POST/PUT/PATCH (size-capped) - GET query:
?url=&method=&headers=<json>; POST JSON body for full control including upstream body - SSRF-safe (shared guards with
/api/fetch): blocks private/link-local/metadata; http/https only; re-checks redirects - Caps: ~384KB response body, 64KB request body, 12s timeout, max 3 redirects
- Honesty: upstream 4xx/5xx returned in
statuswhen the hop succeeded; proxy failures use honest error codes runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET/POST → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9 - Bazaar discovery advertises GET; POST is paid identically
GET / POST /api/extract
URL or HTML → structured page fields for agents (title, description, canonical, language, links, images, headings, json-ld, text sample).
- Price choice: $0.015 USDC (
15000atomic) — between/api/http($0.01) and/api/fetch($0.02). Same payTo + Base USDC stack. - Inputs:
url(optional ifhtmlprovided) and/orhtml(size-capped). Prefer fetchingurlwith the same SSRF-safeassertSafePublicUrlas fetch/http. - GET query:
?url=&fields=<url-encoded json>; POST JSON{ url?, html?, fields? }for html payloads - Optional
fields: up to 20 named CSS selectors, e.g.{"title":"h1","links":{"selector":"a.story","attr":"href","all":true,"limit":5}}.attristext(default),html, or any attribute (href/src resolved to absolute URLs). Unmatched fields returnnullplusfieldErrors[name](no_match,bad_selector,attr_missing,time_budget,document_too_deep,eval_failed). If none match: 422no_fields_matched, not charged. Hard 2s selector budget;:has()at most once per selector; pages nested >256 levels are refused for fields. Deterministic selectors, no AI. - Lightweight regex parse (no cheerio). Empty fields omitted. Not a full browser DOM.
- Caps: ~200KB HTML, 8s timeout, max 3 redirects (same band as
/api/fetch) - No cookie jar. Private/localhost/link-local/metadata blocked.
runtime = 'nodejs',dynamic = 'force-dynamic'- Unpaid GET/POST → 402 +
PAYMENT-REQUIRED(v2,eip155:8453) ·payTo0x5b32c973596078a967562ca652761404f19be0e9 - Bazaar discovery advertises GET; POST is paid identically
GET /status
Public page showing the live USDC balance of the payTo address on Base (via public RPC / BASE_RPC_URL balanceOf) plus the live paid catalog and agent how-to.
- On RPC failure the page shows an honest error — it does not fall back to a cached balance. Custody note: interim Coinbase-custodial
payTo(not Safe/multisig); upgrade path later. - First settle noted as
/api/pulse$0.005 (0xedbd1a51…); six crypto routes frozen;/api/fetch+/api/http+/api/extractdeliberately LIVE.
Local development
Requires Node.js 20+ (tested on 20.19).
cd horizon-pulse
cp .env.example .env.local
# fill CDP_API_KEY_ID + CDP_API_KEY_SECRET for verify/settle
npm install
npm run dev
npm run build && npm start
Environment variables (Vercel / production)
Copy from .env.example:
| Variable | Required | Notes |
|---|---|---|
X402_NETWORK | recommended | base |
PAY_TO | optional | defaults to 0x5b32c973596078a967562ca652761404f19be0e9 |
CDP_API_KEY_ID | for settle | Coinbase Developer Platform |
CDP_API_KEY_SECRET | for settle | PKCS8 PEM (store safely; never commit) |
BASE_RPC_URL | optional | overrides default Base RPC for /status + /api/portfolio + /api/gas |
ETH_RPC_URL | optional | overrides default Ethereum RPC for /api/portfolio + /api/gas |
Without CDP keys:
- Unpaid GET and OPTIONS still return correct 402 / discovery with v2
PAYMENT-REQUIRED(payTo,eip155:8453, USDC amount) — no CDP required for discovery. - Requests that include
PAYMENT-SIGNATURE(or legacyX-PAYMENT) receive 503 untilCDP_API_KEY_ID+CDP_API_KEY_SECRETare set on Vercel (settlement path). - Do not expect live GET to 500 when CDP is missing; that was a prior bug fixed by gating
withX402behind payment + CDP credentials.
Stack
- Next.js 16 (App Router) + TypeScript
@x402/next+@x402/core+@x402/evm+@x402/extensions@coinbase/x402for CDP facilitator auth headersviemfor treasury USDCbalanceOfon Base, portfolio balances, and gas feeHistory on Base + Ethereum
Deploy notes
- Create the GitHub repo / remote
https://github.com/horizon-pulse/horizon-pulse.git. - Set Vercel env vars (above). Prefer PEM secret as a single line with
\nescapes if the UI is single-line. - Deploy from
main. Ensure functions use Node.js runtime (routes already setexport const runtime = 'nodejs'). - Smoke-test:
curl -i https://horizonpulse.dev/api/pulse(or/api/yield,/api/portfolio,/api/gas,/api/funding,/api/fetch?url=https://example.com,/api/http?url=https://example.com,/api/extract?url=https://example.com) should return 402 with payment requirements pointing at the newpayTo. - Confirm
/statusshows the treasury balance for0x5b32…e0e9.
Push (from this workspace)
This scaffold does not push for you (no credentials). From a machine with GitHub auth:
cd /workspace/horizon-pulse
git init
git add .
git commit -m "feat: Horizon Pulse x402 API with new Base treasury"
git branch -M main
git remote add origin https://github.com/horizon-pulse/horizon-pulse.git
git push -u origin main
If the remote already has history:
git remote add origin https://github.com/horizon-pulse/horizon-pulse.git
git fetch origin
git pull origin main --rebase # or merge, as appropriate
git push -u origin main
Reference agent (examples/)
Unpaid 402 discovery for the live paid routes listed in the script, plus optional paid single-route when SMOKE_PRIVATE_KEY is set:
cd horizon-pulse
node examples/reference-agent.mjs
# optional paid:
SMOKE_PRIVATE_KEY=0x... SMOKE_ROUTE=/api/pulse node examples/reference-agent.mjs
See examples/README.md. Uses @x402/core + @x402/evm from this package. Never commit secrets.
Paid smoke test (/api/pulse)
End-to-end check against production (or SMOKE_BASE_URL): unpaid 402 → sign EIP-3009 / x402 with a local key → paid 200 + on-chain USDC toward payTo.
cd horizon-pulse
npm install # if needed; uses existing @x402/* + viem
SMOKE_PRIVATE_KEY=0x... node scripts/smoke-pulse.mjs
# or: npm run smoke:pulse
Optional:
SMOKE_BASE_URL=https://horizonpulse.dev
# backup: https://horizon-pulse-seven.vercel.app
BASE_RPC_URL=https://mainnet.base.org
Never paste your private key in chat, commits, or screenshots. Export it only in your local shell (e.g. Mac mini). Without SMOKE_PRIVATE_KEY, the script exits with usage help (safe dry-run).
License
Private / as designated by the horizon-pulse org.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.