diff --git a/typescript/examples/x402-http-client/README.md b/typescript/examples/x402-http-client/README.md new file mode 100644 index 0000000..502a57a --- /dev/null +++ b/typescript/examples/x402-http-client/README.md @@ -0,0 +1,82 @@ +# x402 HTTP Client Example + +Demonstrates using Ampersend with the standard x402 fetch wrapper for HTTP API payments. + +## Overview + +This example shows how to add Ampersend payment authorization to x402-protected HTTP APIs. It uses the same `@x402/fetch` wrapper as the official x402 SDK, with minimal additions for Ampersend integration. + +**Compare with the [official x402-v2 fetch example](https://github.com/coinbase/x402/tree/main/examples/typescript/clients/fetch) to see the minimal setup difference.** + +## Installation + +```bash +# From the ampersend-examples root +pnpm install + +# Or install this package specifically +cd typescript/examples/x402-http-client +pnpm install +``` + +## Usage + +Set your private key and run: + +```bash +PRIVATE_KEY=0x... pnpm dev +``` + +## How It Works + +The example demonstrates the core Ampersend pattern for HTTP payments: + +```typescript +import { x402Client, wrapFetchWithPayment } from "@x402/fetch" +import { AccountWallet, NaiveTreasurer } from "@ampersend_ai/ampersend-sdk" +import { wrapWithAmpersend } from "@ampersend_ai/ampersend-sdk/x402" + +// --- Standard x402 setup (same as official example) --- +const client = new x402Client() + +// --- Ampersend additions (replaces registerExactEvmScheme) --- +const wallet = AccountWallet.fromPrivateKey(privateKey) +const treasurer = new NaiveTreasurer(wallet) // Decides whether to pay +wrapWithAmpersend(client, treasurer, ["base", "base-sepolia"]) + +// --- Use it (identical to official example) --- +const fetchWithPayment = wrapFetchWithPayment(fetch, client) +const response = await fetchWithPayment("https://paid-api.example.com/resource") +``` + +## Comparison with x402-v2 + +| x402-v2 Official | Ampersend | Purpose | +|------------------|-----------|---------| +| `privateKeyToAccount(key)` | `AccountWallet.fromPrivateKey(key)` | Wallet creation | +| `registerExactEvmScheme(client, { signer })` | `wrapWithAmpersend(client, treasurer, networks)` | Payment registration | +| - | `new NaiveTreasurer(wallet)` | Payment authorization | + +The key difference: Ampersend uses a **Treasurer** pattern that allows sophisticated payment policies (budgets, approvals, limits) instead of auto-signing everything. + +## Development + +```bash +pnpm build # Build TypeScript +pnpm dev # Run with tsx +pnpm lint # Run ESLint +pnpm format # Check formatting +``` + +## Project Structure + +``` +src/ +└── index.ts # Main example demonstrating HTTP payment flow +``` + +## Learn More + +- [HTTP x402 Adapter Documentation](https://github.com/edgeandnode/ampersend-sdk/tree/main/typescript/packages/ampersend-sdk/src/x402/http) +- [Ampersend SDK Documentation](https://github.com/edgeandnode/ampersend-sdk) +- [x402 Protocol](https://github.com/coinbase/x402) diff --git a/typescript/examples/x402-http-client/eslint.config.mjs b/typescript/examples/x402-http-client/eslint.config.mjs new file mode 100644 index 0000000..fccab12 --- /dev/null +++ b/typescript/examples/x402-http-client/eslint.config.mjs @@ -0,0 +1,3 @@ +import config from "../../eslint.config.mjs" + +export default config diff --git a/typescript/examples/x402-http-client/package.json b/typescript/examples/x402-http-client/package.json new file mode 100644 index 0000000..88511fd --- /dev/null +++ b/typescript/examples/x402-http-client/package.json @@ -0,0 +1,27 @@ +{ + "name": "@edgeandnode/x402-http-client-example", + "description": "Example x402 HTTP client with Ampersend payment handling", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx src/index.ts", + "build": "tsc -b tsconfig.json", + "clean": "rm -rf dist .tsbuildinfo", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier . --check --ignore-path ../../.prettierignore", + "format:fix": "prettier . --write --list-different --ignore-path ../../.prettierignore" + }, + "dependencies": { + "@ampersend_ai/ampersend-sdk": "^0.0.3", + "@modelcontextprotocol/sdk": "github:edgeandnode/mcp-typescript-sdk#2de06543904483073d8cc13db1d0e08e16601081", + "@x402/core": "^2.1.0", + "@x402/fetch": "^2.1.0", + "fastmcp": "github:edgeandnode/fastmcp#6446ea7b56eb291a6d4d7321e4a41ff0aacba1ef", + "x402": "^0.6.6" + }, + "devDependencies": { + "tsx": "^4.21.0" + } +} diff --git a/typescript/examples/x402-http-client/src/index.ts b/typescript/examples/x402-http-client/src/index.ts new file mode 100644 index 0000000..20386db --- /dev/null +++ b/typescript/examples/x402-http-client/src/index.ts @@ -0,0 +1,26 @@ +import { x402Client, wrapFetchWithPayment } from "@x402/fetch" +import { AccountWallet, NaiveTreasurer } from "@ampersend_ai/ampersend-sdk" +import { wrapWithAmpersend } from "@ampersend_ai/ampersend-sdk/x402" + +async function main() { + const privateKey = process.env.PRIVATE_KEY as `0x${string}` + if (!privateKey) { + console.error("PRIVATE_KEY environment variable is required") + process.exit(1) + } + + // --- Standard x402 setup (same as official example) --- + const client = new x402Client() + + // --- Ampersend additions (replaces registerExactEvmScheme) --- + const wallet = AccountWallet.fromPrivateKey(privateKey) + const treasurer = new NaiveTreasurer(wallet) // Decides whether to pay + wrapWithAmpersend(client, treasurer, ["base", "base-sepolia"]) + + // --- Use it (identical to official example) --- + const fetchWithPayment = wrapFetchWithPayment(fetch, client) + const response = await fetchWithPayment("https://paid-api.example.com/resource") + console.log("Response status:", response.status) +} + +main().catch(console.error) diff --git a/typescript/examples/x402-http-client/tsconfig.json b/typescript/examples/x402-http-client/tsconfig.json new file mode 100644 index 0000000..9a184a4 --- /dev/null +++ b/typescript/examples/x402-http-client/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.jsonc", + "include": ["src/**/*"], + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + } +}