TypeScript SDK
TypeScript SDK Integration
Section titled “TypeScript SDK Integration”Cerebrus Pulse takes payment over x402 v2. An unpaid request gets 402 Payment Required with the terms
in the base64 PAYMENT-REQUIRED header; the client pays by retrying with a PAYMENT-SIGNATURE header and
gets the data plus a PAYMENT-RESPONSE receipt. The v2 fetch client, @x402/fetch, does all of that for you.
Installation
Section titled “Installation”npm install @x402/fetch @x402/evm viemnpm install @x402/fetch @x402/svm @solana/kit @scure/baseThe examples were checked against @x402/fetch, @x402/evm and @x402/svm 2.27.0. They are ES modules
(top-level await), for Node.js 18 or later.
wrapFetchWithPaymentFromConfig wraps the native fetch. It makes the request, reads the 402 terms, signs a
USDC payment on a network you registered, and retries. The rest of your code just calls fetchWithPayment.
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";import { ExactEvmScheme } from "@x402/evm";import { privateKeyToAccount } from "viem/accounts";
// A dedicated, low-balance wallet. Read the key from the environment, never from source.const account = privateKeyToAccount(process.env.BASE_WALLET_PRIVATE_KEY as `0x${string}`);
export const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, { schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }], // Base mainnet spendControls: { maxAmountPerPayment: "$0.10" }, // refuse any single payment above $0.10});
export const BASE_URL = "https://api.cerebruspulse.xyz";import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";import { ExactSvmScheme } from "@x402/svm";import { createKeyPairSignerFromBytes } from "@solana/kit";import { base58 } from "@scure/base";
// A dedicated, low-balance wallet: its base58-encoded secret key, from the environment.const signer = await createKeyPairSignerFromBytes( base58.decode(process.env.SOLANA_WALLET_PRIVATE_KEY!),);
export const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, { // Solana mainnet schemes: [{ network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", client: new ExactSvmScheme(signer) }], spendControls: { maxAmountPerPayment: "$0.10" }, // refuse any single payment above $0.10});
export const BASE_URL = "https://api.cerebruspulse.xyz";Register both schemes to let the client pay on whichever rail the 402 offers first (Base is listed first).
Making Requests
Section titled “Making Requests”// Pulse — multi-timeframe technical analysisexport async function getPulse(coin: string, timeframes = "1h,4h") { const resp = await fetchWithPayment(`${BASE_URL}/pulse/${coin}?timeframes=${timeframes}`); return resp.json();}
// Sentiment — a bucketed market sentiment labelexport async function getSentiment() { const resp = await fetchWithPayment(`${BASE_URL}/sentiment`); return resp.json();}
// Funding — funding rate statistics over 1-168 hoursexport async function getFunding(coin: string, lookbackHours = 24) { const resp = await fetchWithPayment(`${BASE_URL}/funding/${coin}?lookback_hours=${lookbackHours}`); return resp.json();}
// Bundle — /pulse, 24h /funding and /sentiment in one requestexport async function getBundle(coin: string, timeframes = "1h,4h") { const resp = await fetchWithPayment(`${BASE_URL}/bundle/${coin}?timeframes=${timeframes}`); return resp.json();}
// Free endpoints — no payment needed, use plain fetchexport async function getCoins(): Promise<{ coins: string[] }> { const resp = await fetch(`${BASE_URL}/coins`); return resp.json();}The payment receipt
Section titled “The payment receipt”A paid response carries the settlement receipt in the PAYMENT-RESPONSE header:
import { decodePaymentResponseHeader } from "@x402/fetch";
const resp = await fetchWithPayment(`${BASE_URL}/pulse/BTC`);const receipt = resp.headers.get("PAYMENT-RESPONSE");if (receipt) console.log(decodePaymentResponseHeader(receipt)); // transaction, network, payerWorking with the Response
Section titled “Working with the Response”The shapes below follow what the engine returns; see GET /pulse for every field.
type TrendLabel = "bullish" | "weak_bullish" | "neutral" | "weak_bearish" | "bearish" | "insufficient_data";
interface PulseResponse { coin: string; market_symbol: string; // Hyperliquid's name for the market (e.g. "kPEPE") price_unit: string; // prices are per this many tokens (e.g. "1000 PEPE") price: { current?: number }; timeframes: Record<string, { indicators: { rsi_14: number | null; rsi_zone: string; trend: { direction: -1 | 0 | 1; label: TrendLabel; ema_stack: string }; bollinger: { position_pct?: number; width_pct?: number }; zscore_100: number | null; }; last_closed_candle_time?: string; stale?: boolean; } | { error: string }>; confluence: { score: number; // bullish vote share on the primary timeframe: 1 = all bullish, 0.5 = neutral, 0 = all bearish bias: "bullish" | "bearish" | "neutral" | "unknown"; primary_timeframe: string | null; }; regime: { current: string; as_of: number; stale?: boolean }; derivatives: { funding_rate?: number; // hourly rate funding_annualized_pct?: number; open_interest?: number; // in contracts (price_unit), not USD }; meta: { execution_ms: number; warning?: string; warnings?: string[] };}
async function analyzeMarket(coin: string) { const data: PulseResponse = await getPulse(coin);
console.log(`${coin} @ $${data.price.current?.toLocaleString()} | regime: ${data.regime.current}`); console.log(`Confluence: ${data.confluence.bias} (bullish share ${data.confluence.score})`);
for (const [tf, analysis] of Object.entries(data.timeframes)) { if ("error" in analysis) { console.log(` [${tf}] ${analysis.error}`); continue; } const { rsi_14, rsi_zone, trend } = analysis.indicators; console.log(` [${tf}] RSI: ${rsi_14 ?? "n/a"} (${rsi_zone}) | Trend: ${trend.label}`); }
if (data.meta.warning || data.meta.warnings?.length) { console.warn("Data warnings:", data.meta.warning, data.meta.warnings); }}Error Handling
Section titled “Error Handling”async function safePulse(coin: string) { try { const resp = await fetchWithPayment(`${BASE_URL}/pulse/${coin}`);
if (!resp.ok) { switch (resp.status) { case 400: console.error("Invalid input, e.g. a bad timeframe (not charged)"); break; case 402: console.error("Payment not accepted, e.g. not enough USDC (nothing settled)"); break; case 404: console.error(`Unknown coin: ${coin} (GET /coins lists them)`); break; case 429: console.error("Rate limited: wait and retry"); break; case 503: console.error(`Unavailable; retry after ${resp.headers.get("Retry-After")} s`); break; case 504: console.error("Engine timeout (not charged): try again"); break; default: console.error(`Error ${resp.status}`); } return null; }
return resp.json(); } catch (err) { // Also thrown when a payment would exceed spendControls console.error("Request failed:", err); return null; }}Multi-Coin Dashboard
Section titled “Multi-Coin Dashboard”async function dashboard() { const { coins } = await getCoins(); // free console.log(`Scanning ${coins.length} coins...\n`);
// Paid: five /pulse calls. One /screener call covers every coin instead. const results = await Promise.all( coins.slice(0, 5).map(async (coin) => { const data = await safePulse(coin); return data ? { coin, ...data.confluence } : null; }), );
// Highest bullish vote share first (1 = all bullish, 0 = all bearish) const sorted = results .filter((r): r is NonNullable<typeof r> => r !== null) .sort((a, b) => b.score - a.score);
console.log("Coin Bias Bullish share"); for (const r of sorted) console.log(`${r.coin.padEnd(9)}${r.bias.padEnd(11)}${r.score}`);}- Free endpoints (
/health,/coins,/demo/{coin}) don’t needfetchWithPayment; use regularfetch Promise.allbatches requests, but the gateway allows 3 concurrent requests and 12 per minute per IP- Check
/healthbefore a batch to avoid paying into a degraded gateway meta.warning(stale_data) andmeta.warnings(for examplestale_candles) flag old inputs/bundlereturns/pulse, 24h/fundingand/sentimentfor one coin in one request (one rate-limit slot instead of three); its page shows how its price compares