From f66007b87aa54713816d095fd53323d0bb7f6e49 Mon Sep 17 00:00:00 2001 From: Andrew Barnes Date: Mon, 18 May 2026 20:39:22 -0400 Subject: [PATCH] Add Satoshi API community example --- examples/community/README.md | 9 +- examples/community/satoshi-api/.env.example | 6 + examples/community/satoshi-api/README.md | 67 +++++++++ examples/community/satoshi-api/package.json | 19 +++ examples/community/satoshi-api/src/index.ts | 142 +++++++++++++++++++ examples/community/satoshi-api/tsconfig.json | 14 ++ pnpm-workspace.yaml | 3 +- 7 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 examples/community/satoshi-api/.env.example create mode 100644 examples/community/satoshi-api/README.md create mode 100644 examples/community/satoshi-api/package.json create mode 100644 examples/community/satoshi-api/src/index.ts create mode 100644 examples/community/satoshi-api/tsconfig.json diff --git a/examples/community/README.md b/examples/community/README.md index 20ce205..0f489e4 100644 --- a/examples/community/README.md +++ b/examples/community/README.md @@ -1,3 +1,8 @@ -# WIP +# Community Integrations -For community integrations \ No newline at end of file +Community-contributed examples for pairing Browserbase with external tools, +APIs, and services. + +| Example | Description | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| [satoshi-api](satoshi-api/) | Give a Browserbase Stagehand agent live Bitcoin fee intelligence before it acts on wallet, payment, exchange, or checkout pages. | diff --git a/examples/community/satoshi-api/.env.example b/examples/community/satoshi-api/.env.example new file mode 100644 index 0000000..5bcda1d --- /dev/null +++ b/examples/community/satoshi-api/.env.example @@ -0,0 +1,6 @@ +BROWSERBASE_API_KEY=your_browserbase_api_key +BROWSERBASE_PROJECT_ID=your_project_id +BROWSERBASE_MODEL=google/gemini-3-flash-preview + +SATOSHI_API_URL=https://bitcoinsapi.com +SATOSHI_API_KEY= diff --git a/examples/community/satoshi-api/README.md b/examples/community/satoshi-api/README.md new file mode 100644 index 0000000..d2fdcad --- /dev/null +++ b/examples/community/satoshi-api/README.md @@ -0,0 +1,67 @@ +# Satoshi API + Browserbase + +Give a Browserbase Stagehand agent live Bitcoin fee intelligence before it acts +on a payment, wallet, exchange, or checkout page. + +Satoshi API is a self-hostable Bitcoin REST API and hosted service at +`https://bitcoinsapi.com`. This example fetches a free fee recommendation from +Satoshi API, starts a Browserbase browser session, opens a live Bitcoin fee +page, and prints the fee-aware decision next to the Browserbase session ID. + +## Use Cases + +- Check whether a Bitcoin payment flow should send now or wait. +- Add fee context to wallet and checkout QA runs. +- Pair browser automation with x402-paid Bitcoin data for accountless agents. +- Store the Browserbase session replay next to the fee decision for audit logs. + +## Prerequisites + +- Node.js 18 or newer. +- A Browserbase API key. +- Optional: a free Satoshi API key for higher limits. + +## Setup + +```bash +cp .env.example .env +npm install +npm run start +``` + +The no-token quickstart uses: + +```text +GET https://bitcoinsapi.com/api/v1/fees/recommended +``` + +For x402 pay-per-call analysis, start with the paid route: + +```text +GET https://bitcoinsapi.com/api/v1/fees/now +``` + +## Environment Variables + +| Variable | Required | Description | +| ------------------------ | --------- | ---------------------------------------------------------------------- | +| `BROWSERBASE_API_KEY` | Yes | Browserbase API key used by Stagehand. | +| `BROWSERBASE_PROJECT_ID` | Sometimes | Browserbase project ID if your account or SDK setup requires it. | +| `BROWSERBASE_MODEL` | No | Model Gateway model name. Defaults to `google/gemini-3-flash-preview`. | +| `SATOSHI_API_URL` | No | Defaults to `https://bitcoinsapi.com`. | +| `SATOSHI_API_KEY` | No | Optional Satoshi API key for higher public endpoint limits. | + +## What The Example Does + +1. Fetches `GET /api/v1/fees/recommended` from Satoshi API. +2. Starts a Browserbase Stagehand session. +3. Opens `https://mempool.space/` for live browser context. +4. Prints the fee decision, page title, and Browserbase session ID. + +## Resources + +- Satoshi API: `https://bitcoinsapi.com` +- Satoshi API source: `https://github.com/Bortlesboat/bitcoin-api` +- Browserbase docs: `https://docs.browserbase.com` +- Browserbase x402 docs: + `https://docs.browserbase.com/integrations/x402/introduction` diff --git a/examples/community/satoshi-api/package.json b/examples/community/satoshi-api/package.json new file mode 100644 index 0000000..2464d1e --- /dev/null +++ b/examples/community/satoshi-api/package.json @@ -0,0 +1,19 @@ +{ + "name": "browserbase-satoshi-api-example", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "start": "tsx src/index.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@browserbasehq/stagehand": "^3.0.0", + "dotenv": "^17.2.3" + }, + "devDependencies": { + "@types/node": "^25.0.9", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + } +} diff --git a/examples/community/satoshi-api/src/index.ts b/examples/community/satoshi-api/src/index.ts new file mode 100644 index 0000000..db15af7 --- /dev/null +++ b/examples/community/satoshi-api/src/index.ts @@ -0,0 +1,142 @@ +import { Stagehand } from '@browserbasehq/stagehand'; +import 'dotenv/config'; + +type JsonObject = Record; + +interface SatoshiEnvelope { + data?: JsonObject; + meta?: JsonObject; + error?: unknown; +} + +function getString(value: unknown): string | undefined { + return typeof value === 'string' && value.trim().length > 0 + ? value + : undefined; +} + +function getNumber(value: unknown): number | undefined { + return typeof value === 'number' && Number.isFinite(value) + ? value + : undefined; +} + +function getObject(value: unknown): Record | undefined { + return value && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function getEstimate( + data: Record, + keys: string[] +): number | undefined { + const estimates = getObject(data.estimates); + for (const key of keys) { + const estimate = estimates ? getNumber(estimates[key]) : undefined; + if (estimate !== undefined) { + return estimate; + } + + const topLevel = getNumber(data[key]); + if (topLevel !== undefined) { + return topLevel; + } + } + + return undefined; +} + +function summarizeFeeDecision(data: JsonObject): string { + const action = + getString(data.action) ?? + getString(data.recommendation) ?? + getString(data.decision) ?? + 'inspect_fee_context'; + + const summary = + getString(data.summary) ?? + getString(data.message) ?? + getString(data.reason) ?? + 'Satoshi API returned live Bitcoin fee context for the browser agent.'; + + const nextBlockFee = getEstimate(data, [ + '1', + 'high', + 'fastestFee', + 'fastest_fee', + 'nextBlockFee', + 'next_block_fee_sat_vb', + ]); + + const feeLine = + nextBlockFee === undefined + ? '' + : ` Next-block fee baseline: ${nextBlockFee} sat/vB.`; + + return `${action}: ${summary}${feeLine}`; +} + +async function fetchSatoshiFees(): Promise { + const baseUrl = process.env.SATOSHI_API_URL ?? 'https://bitcoinsapi.com'; + const headers: Record = { Accept: 'application/json' }; + + if (process.env.SATOSHI_API_KEY) { + headers['X-API-Key'] = process.env.SATOSHI_API_KEY; + } + + const response = await fetch( + `${baseUrl.replace(/\/$/, '')}/api/v1/fees/recommended`, + { headers } + ); + + if (!response.ok) { + throw new Error( + `Satoshi API returned HTTP ${response.status}: ${await response.text()}` + ); + } + + const payload = (await response.json()) as SatoshiEnvelope; + if (!payload.data || typeof payload.data !== 'object') { + throw new Error('Satoshi API response did not include a data object.'); + } + + return payload; +} + +async function main() { + const feePayload = await fetchSatoshiFees(); + const feeDecision = summarizeFeeDecision(feePayload.data ?? {}); + + const stagehand = new Stagehand({ + env: 'BROWSERBASE', + model: process.env.BROWSERBASE_MODEL ?? 'google/gemini-3-flash-preview', + }); + + await stagehand.init(); + + try { + const page = stagehand.context.pages()[0]; + if (!page) { + throw new Error('Browserbase session did not create an initial page.'); + } + + await page.goto('https://mempool.space/', { + waitUntil: 'domcontentloaded', + }); + + console.log('Satoshi API fee decision'); + console.log(`- ${feeDecision}`); + console.log(''); + console.log('Browserbase session'); + console.log(`- session_id: ${stagehand.browserbaseSessionID ?? 'unknown'}`); + console.log(`- page_title: ${await page.title()}`); + } finally { + await stagehand.close(); + } +} + +main().catch(error => { + console.error(error); + process.exitCode = 1; +}); diff --git a/examples/community/satoshi-api/tsconfig.json b/examples/community/satoshi-api/tsconfig.json new file mode 100644 index 0000000..284ad42 --- /dev/null +++ b/examples/community/satoshi-api/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3a0aaf5..d1bcfa8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - - 'examples/*' \ No newline at end of file + - 'examples/*' + - 'examples/community/*'