Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Ready-to-use automation templates for Stagehand and Browserbase. Each template h
| proxies | [TS](typescript/proxies) | [PY](python/proxies) | - | Demonstrate different proxy configurations with Browserbase sessions |
| proxies-weather | [TS](typescript/proxies-weather) | [PY](python/proxies-weather) | - | Geolocation proxies fetching location-specific weather data from multiple cities |
| puppeteer | [TS](typescript/puppeteer) | - | - | Raw Puppeteer usage with Browserbase |
| satoshi-api-fee-agent | [TS](typescript/satoshi-api-fee-agent) | - | - | Give browser agents live Bitcoin fee intelligence before payment or wallet flows |
| sec-filing-research | [TS](typescript/sec-filing-research) | [PY](python/sec-filing-research) | - | Search SEC EDGAR for a company and extract recent filing metadata |
| selenium | [TS](typescript/selenium) | [PY](python/selenium) | - | Raw Selenium usage with Browserbase |
| smart-fetch-scraper | [TS](typescript/smart-fetch-scraper) | [PY](python/smart-fetch-scraper) | - | Scrape a webpage using the fastest method available -- Fetch API first, full browser session as fallback |
Expand Down
3 changes: 3 additions & 0 deletions typescript/satoshi-api-fee-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BROWSERBASE_API_KEY=your_browserbase_api_key
SATOSHI_API_URL=https://bitcoinsapi.com
SATOSHI_API_KEY=
81 changes: 81 additions & 0 deletions typescript/satoshi-api-fee-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Satoshi API Fee Agent

## AT A GLANCE

- Goal: give a Browserbase Stagehand agent live Bitcoin fee intelligence before
it acts on a payment, wallet, exchange, or checkout page.
- Uses Satoshi API's free `GET /api/v1/fees/recommended` endpoint for the
first no-token fee decision.
- Opens a Browserbase cloud browser, visits a live Bitcoin fee page, and
extracts visible page context to compare with the API response.
- Documents the optional x402 paid path, starting with `GET /api/v1/fees/now`,
for accountless premium Bitcoin data.

## GLOSSARY

- Satoshi API: a self-hostable Bitcoin REST API and hosted service for
structured Bitcoin data.
Docs -> https://bitcoinsapi.com
- fee recommendation: current sat/vB guidance for next-block and lower-urgency
confirmation targets.
Docs -> https://bitcoinsapi.com/docs
- x402: HTTP `402 Payment Required` flow for accountless pay-per-call API
access.
Docs -> https://bitcoinsapi.com/x402/start

## QUICKSTART

1. cd typescript/satoshi-api-fee-agent
2. pnpm install
3. cp .env.example .env
4. Add your Browserbase API key to .env
5. pnpm start

## EXPECTED OUTPUT

- Fetches live Bitcoin fee guidance from Satoshi API
- Starts a Browserbase Stagehand session
- Opens `https://mempool.space/` for live browser context
- Extracts visible fee page context
- Prints the Satoshi API recommendation, Browserbase session link, page title,
and extracted page summary
- Closes the browser session cleanly

## COMMON PITFALLS

- Missing Browserbase credentials: verify `.env` contains `BROWSERBASE_API_KEY`
- Satoshi API rate limits: add `SATOSHI_API_KEY` for higher limits, or self-host
Satoshi API
- Paid endpoint confusion: use `/api/v1/fees/recommended` for the free
quickstart; `/api/v1/fees/now` is the x402 paid first-call route
- Page extraction drift: keep the Satoshi API decision separate from selectors;
Browserbase handles browser context while Satoshi API handles the Bitcoin fee
decision

## USE CASES

- Fee-aware payment agents: decide whether to send now or wait before clicking
through a payment flow
- Wallet and checkout QA: compare UI fee suggestions against live mempool
guidance
- Exchange monitoring: attach fee context to Browserbase session replays
- x402-native workflows: combine Browserbase browser sessions with paid Bitcoin
data calls on demand

## NEXT STEPS

- Swap `https://mempool.space/` for your wallet, checkout, or exchange URL
- Persist the Satoshi API response next to the Browserbase session replay URL
- Call `GET /api/v1/tx/{txid}/status` after a Browserbase agent finds or submits
a transaction ID
- Try the x402 paid route at `GET /api/v1/fees/now` when you need accountless
premium analysis

## HELPFUL RESOURCES

Satoshi API: https://bitcoinsapi.com
Satoshi API source: https://github.com/Bortlesboat/bitcoin-api
Browserbase Docs: https://docs.browserbase.com
Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction
Browserbase x402 Docs: https://docs.browserbase.com/integrations/x402/introduction
Templates: https://www.browserbase.com/templates
178 changes: 178 additions & 0 deletions typescript/satoshi-api-fee-agent/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Satoshi API Fee Agent - See README.md for full documentation

import "dotenv/config";
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";

const FeePageSchema = z.object({
visibleFastFee: z
.string()
.describe("Fast or next-block fee visible on the current page, if present."),
visibleEconomyFee: z
.string()
.describe("Lower-priority or economy fee visible on the current page, if present."),
congestionSummary: z
.string()
.describe("Short summary of visible mempool or fee congestion context."),
});

type JsonObject = Record<string, unknown>;

interface SatoshiFeePayload {
data: JsonObject;
meta?: JsonObject;
}

function getObject(value: unknown): JsonObject | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? (value as JsonObject)
: undefined;
}

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 getEstimate(data: JsonObject, 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 summarizeSatoshiRecommendation(data: JsonObject): string {
const recommendation =
getString(data.recommendation) ??
getString(data.summary) ??
getString(data.message) ??
"Satoshi API returned live Bitcoin fee context for this browser agent.";

const nextBlockFee = getEstimate(data, [
"1",
"high",
"fastestFee",
"fastest_fee",
"nextBlockFee",
"next_block_fee_sat_vb",
]);
const sixBlockFee = getEstimate(data, ["6", "medium", "halfHourFee"]);
const dayFee = getEstimate(data, ["144", "low", "economyFee"]);

const feeParts = [
nextBlockFee === undefined ? null : `next block ${nextBlockFee} sat/vB`,
sixBlockFee === undefined ? null : `~6 blocks ${sixBlockFee} sat/vB`,
dayFee === undefined ? null : `~1 day ${dayFee} sat/vB`,
].filter((part): part is string => part !== null);

return feeParts.length > 0 ? `${recommendation} (${feeParts.join(", ")})` : recommendation;
}

async function fetchSatoshiFeeRecommendation(): Promise<SatoshiFeePayload> {
const baseUrl = process.env.SATOSHI_API_URL ?? "https://bitcoinsapi.com";
const headers: Record<string, string> = {
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 unknown;
const envelope = getObject(payload);
const data = envelope ? getObject(envelope.data) : undefined;

if (!data) {
throw new Error("Satoshi API response did not include a data object.");
}

return {
data,
meta: envelope ? getObject(envelope.meta) : undefined,
};
}

async function main() {
const satoshiFeePayload = await fetchSatoshiFeeRecommendation();
const satoshiRecommendation = summarizeSatoshiRecommendation(satoshiFeePayload.data);

const stagehand = new Stagehand({
env: "BROWSERBASE",
model: "google/gemini-3-flash-preview",
verbose: 1,
browserbaseSessionCreateParams: {
browserSettings: {
viewport: {
width: 1280,
height: 720,
},
},
},
});

try {
await stagehand.init();

const page = stagehand.context.pages()[0];
if (!page) {
throw new Error("Browserbase session did not create an initial page.");
}

console.log(
`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`,
);
console.log("Satoshi API fee recommendation:");
console.log(satoshiRecommendation);

await page.goto("https://mempool.space/", {
waitUntil: "domcontentloaded",
timeoutMs: 60000,
});

const pageContext = await stagehand.extract(
`Extract the visible Bitcoin fee context from this page.
Return the fast or next-block fee, a lower-priority or economy fee, and a short congestion summary.
If a value is not visible, say "not visible".`,
FeePageSchema,
);

console.log("\nBrowserbase session:");
console.log(`Page title: ${await page.title()}`);
console.log("Visible page context:");
console.log(JSON.stringify(pageContext, null, 2));
} finally {
await stagehand.close();
console.log("Session closed successfully");
}
}

main().catch((error) => {
console.error("Error running Satoshi API fee agent:", error);
console.error("\nCommon issues:");
console.error(" - Check .env file has BROWSERBASE_API_KEY");
console.error(" - Add SATOSHI_API_KEY for higher Satoshi API limits");
console.error(" - Use /api/v1/fees/recommended for the free quickstart");
console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction");
process.exit(1);
});
18 changes: 18 additions & 0 deletions typescript/satoshi-api-fee-agent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "satoshi-api-fee-agent-template",
"type": "module",
"scripts": {
"build": "tsc",
"start": "tsx index.ts"
},
"dependencies": {
"@browserbasehq/stagehand": "latest",
"dotenv": "^16.4.7",
"zod": "latest"
},
"devDependencies": {
"@types/node": "^25.0.0",
"tsx": "^4.19.2",
"typescript": "^5.0.0"
}
}
14 changes: 14 additions & 0 deletions typescript/satoshi-api-fee-agent/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["node"]
},
"include": ["index.ts"]
}