Skip to content

Commit 28449a9

Browse files
committed
Add Satoshi API community example
1 parent 029c84f commit 28449a9

7 files changed

Lines changed: 228 additions & 3 deletions

File tree

examples/community/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
# WIP
1+
# Community Integrations
22

3-
For community integrations
3+
Community-contributed examples for pairing Browserbase with external tools,
4+
APIs, and services.
5+
6+
| Example | Description |
7+
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
8+
| [satoshi-api](satoshi-api/) | Give a Browserbase Stagehand agent live Bitcoin fee intelligence before it acts on wallet, payment, exchange, or checkout pages. |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BROWSERBASE_API_KEY=your_browserbase_api_key
2+
BROWSERBASE_PROJECT_ID=your_project_id
3+
BROWSERBASE_MODEL=google/gemini-3-flash-preview
4+
5+
SATOSHI_API_URL=https://bitcoinsapi.com
6+
SATOSHI_API_KEY=
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Satoshi API + Browserbase
2+
3+
Give a Browserbase Stagehand agent live Bitcoin fee intelligence before it acts
4+
on a payment, wallet, exchange, or checkout page.
5+
6+
Satoshi API is a self-hostable Bitcoin REST API and hosted service at
7+
`https://bitcoinsapi.com`. This example fetches a free fee recommendation from
8+
Satoshi API, starts a Browserbase browser session, opens a live Bitcoin fee
9+
page, and prints the fee-aware decision next to the Browserbase session ID.
10+
11+
## Use Cases
12+
13+
- Check whether a Bitcoin payment flow should send now or wait.
14+
- Add fee context to wallet and checkout QA runs.
15+
- Pair browser automation with x402-paid Bitcoin data for accountless agents.
16+
- Store the Browserbase session replay next to the fee decision for audit logs.
17+
18+
## Prerequisites
19+
20+
- Node.js 18 or newer.
21+
- A Browserbase API key.
22+
- Optional: a free Satoshi API key for higher limits.
23+
24+
## Setup
25+
26+
```bash
27+
cp .env.example .env
28+
npm install
29+
npm run start
30+
```
31+
32+
The no-token quickstart uses:
33+
34+
```text
35+
GET https://bitcoinsapi.com/api/v1/fees/recommended
36+
```
37+
38+
For x402 pay-per-call analysis, start with the paid route:
39+
40+
```text
41+
GET https://bitcoinsapi.com/api/v1/fees/now
42+
```
43+
44+
## Environment Variables
45+
46+
| Variable | Required | Description |
47+
| ------------------------ | --------- | ---------------------------------------------------------------------- |
48+
| `BROWSERBASE_API_KEY` | Yes | Browserbase API key used by Stagehand. |
49+
| `BROWSERBASE_PROJECT_ID` | Sometimes | Browserbase project ID if your account or SDK setup requires it. |
50+
| `BROWSERBASE_MODEL` | No | Model Gateway model name. Defaults to `google/gemini-3-flash-preview`. |
51+
| `SATOSHI_API_URL` | No | Defaults to `https://bitcoinsapi.com`. |
52+
| `SATOSHI_API_KEY` | No | Optional Satoshi API key for higher public endpoint limits. |
53+
54+
## What The Example Does
55+
56+
1. Fetches `GET /api/v1/fees/recommended` from Satoshi API.
57+
2. Starts a Browserbase Stagehand session.
58+
3. Opens `https://mempool.space/` for live browser context.
59+
4. Prints the fee decision, page title, and Browserbase session ID.
60+
61+
## Resources
62+
63+
- Satoshi API: `https://bitcoinsapi.com`
64+
- Satoshi API source: `https://github.com/Bortlesboat/bitcoin-api`
65+
- Browserbase docs: `https://docs.browserbase.com`
66+
- Browserbase x402 docs:
67+
`https://docs.browserbase.com/integrations/x402/introduction`
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "browserbase-satoshi-api-example",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"start": "tsx src/index.ts",
8+
"typecheck": "tsc --noEmit"
9+
},
10+
"dependencies": {
11+
"@browserbasehq/stagehand": "^3.0.0",
12+
"dotenv": "^17.2.3"
13+
},
14+
"devDependencies": {
15+
"@types/node": "^25.0.9",
16+
"tsx": "^4.21.0",
17+
"typescript": "^6.0.2"
18+
}
19+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Stagehand } from '@browserbasehq/stagehand';
2+
import 'dotenv/config';
3+
4+
type JsonObject = Record<string, unknown>;
5+
6+
interface SatoshiEnvelope {
7+
data?: JsonObject;
8+
meta?: JsonObject;
9+
error?: unknown;
10+
}
11+
12+
function getString(value: unknown): string | undefined {
13+
return typeof value === 'string' && value.trim().length > 0
14+
? value
15+
: undefined;
16+
}
17+
18+
function getNumber(value: unknown): number | undefined {
19+
return typeof value === 'number' && Number.isFinite(value)
20+
? value
21+
: undefined;
22+
}
23+
24+
function summarizeFeeDecision(data: JsonObject): string {
25+
const action =
26+
getString(data.action) ??
27+
getString(data.recommendation) ??
28+
getString(data.decision) ??
29+
'inspect_fee_context';
30+
31+
const summary =
32+
getString(data.summary) ??
33+
getString(data.message) ??
34+
getString(data.reason) ??
35+
'Satoshi API returned live Bitcoin fee context for the browser agent.';
36+
37+
const nextBlockFee =
38+
getNumber(data.next_block_fee_sat_vb) ??
39+
getNumber(data.fastestFee) ??
40+
getNumber(data.fastest_fee) ??
41+
getNumber(data.nextBlockFee);
42+
43+
const feeLine =
44+
nextBlockFee === undefined
45+
? ''
46+
: ` Next-block fee baseline: ${nextBlockFee} sat/vB.`;
47+
48+
return `${action}: ${summary}${feeLine}`;
49+
}
50+
51+
async function fetchSatoshiFees(): Promise<SatoshiEnvelope> {
52+
const baseUrl = process.env.SATOSHI_API_URL ?? 'https://bitcoinsapi.com';
53+
const headers: Record<string, string> = { Accept: 'application/json' };
54+
55+
if (process.env.SATOSHI_API_KEY) {
56+
headers['X-API-Key'] = process.env.SATOSHI_API_KEY;
57+
}
58+
59+
const response = await fetch(
60+
`${baseUrl.replace(/\/$/, '')}/api/v1/fees/recommended`,
61+
{ headers }
62+
);
63+
64+
if (!response.ok) {
65+
throw new Error(
66+
`Satoshi API returned HTTP ${response.status}: ${await response.text()}`
67+
);
68+
}
69+
70+
const payload = (await response.json()) as SatoshiEnvelope;
71+
if (!payload.data || typeof payload.data !== 'object') {
72+
throw new Error('Satoshi API response did not include a data object.');
73+
}
74+
75+
return payload;
76+
}
77+
78+
async function main() {
79+
const feePayload = await fetchSatoshiFees();
80+
const feeDecision = summarizeFeeDecision(feePayload.data ?? {});
81+
82+
const stagehand = new Stagehand({
83+
env: 'BROWSERBASE',
84+
model: process.env.BROWSERBASE_MODEL ?? 'google/gemini-3-flash-preview',
85+
});
86+
87+
await stagehand.init();
88+
89+
try {
90+
const page = stagehand.context.pages()[0];
91+
if (!page) {
92+
throw new Error('Browserbase session did not create an initial page.');
93+
}
94+
95+
await page.goto('https://mempool.space/', {
96+
waitUntil: 'domcontentloaded',
97+
});
98+
99+
console.log('Satoshi API fee decision');
100+
console.log(`- ${feeDecision}`);
101+
console.log('');
102+
console.log('Browserbase session');
103+
console.log(`- session_id: ${stagehand.browserbaseSessionID ?? 'unknown'}`);
104+
console.log(`- page_title: ${await page.title()}`);
105+
} finally {
106+
await stagehand.close();
107+
}
108+
}
109+
110+
main().catch(error => {
111+
console.error(error);
112+
process.exitCode = 1;
113+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "ESNext",
5+
"moduleResolution": "bundler",
6+
"strict": true,
7+
"esModuleInterop": true,
8+
"forceConsistentCasingInFileNames": true,
9+
"skipLibCheck": true,
10+
"noEmit": true,
11+
"types": ["node"]
12+
},
13+
"include": ["src/**/*.ts"]
14+
}

pnpm-workspace.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
packages:
2-
- 'examples/*'
2+
- 'examples/*'
3+
- 'examples/community/*'

0 commit comments

Comments
 (0)