opencard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/API.md +564 -0
  2. package/DATABASE.md +143 -0
  3. package/README.md +106 -0
  4. package/admin/index.html +18 -0
  5. package/admin/package-lock.json +2663 -0
  6. package/admin/package.json +25 -0
  7. package/admin/postcss.config.js +6 -0
  8. package/admin/src/App.tsx +198 -0
  9. package/admin/src/index.css +44 -0
  10. package/admin/src/main.tsx +10 -0
  11. package/admin/src/pages/Cards.tsx +181 -0
  12. package/admin/src/pages/Dashboard.tsx +139 -0
  13. package/admin/src/pages/Transactions.tsx +223 -0
  14. package/admin/tailwind.config.js +27 -0
  15. package/admin/tsconfig.json +20 -0
  16. package/admin/tsconfig.tsbuildinfo +1 -0
  17. package/admin/vite.config.ts +17 -0
  18. package/drizzle.config.ts +11 -0
  19. package/examples/agent-client-sdk.ts +36 -0
  20. package/examples/agent-client.ts +111 -0
  21. package/examples/agent-fund-sdk.ts +35 -0
  22. package/package.json +41 -0
  23. package/sdk/README.md +139 -0
  24. package/sdk/package-lock.json +240 -0
  25. package/sdk/package.json +43 -0
  26. package/sdk/src/client.ts +194 -0
  27. package/sdk/src/errors.ts +66 -0
  28. package/sdk/src/index.ts +35 -0
  29. package/sdk/src/types.ts +138 -0
  30. package/sdk/src/x402.ts +158 -0
  31. package/sdk/tsconfig.json +20 -0
  32. package/src/config/env.ts +45 -0
  33. package/src/config/tiers.ts +51 -0
  34. package/src/db/index.ts +9 -0
  35. package/src/db/migrate.ts +16 -0
  36. package/src/db/schema.ts +82 -0
  37. package/src/index.ts +89 -0
  38. package/src/middleware/errorHandler.ts +27 -0
  39. package/src/middleware/rateLimit.ts +54 -0
  40. package/src/middleware/walletAuth.ts +89 -0
  41. package/src/middleware/x402.ts +194 -0
  42. package/src/routes/admin.ts +150 -0
  43. package/src/routes/cards.ts +120 -0
  44. package/src/routes/paid.ts +154 -0
  45. package/src/routes/public.ts +40 -0
  46. package/src/services/cardService.ts +395 -0
  47. package/src/services/kripicard.ts +128 -0
  48. package/src/services/walletService.ts +78 -0
  49. package/src/types/index.ts +128 -0
  50. package/src/utils/logger.ts +19 -0
  51. package/src/utils/pricing.ts +75 -0
  52. package/tsconfig.json +21 -0
@@ -0,0 +1,223 @@
1
+ import { useState, useEffect } from "react";
2
+
3
+ interface Transaction {
4
+ id: string;
5
+ walletAddress: string;
6
+ cardId: string | null;
7
+ type: string;
8
+ amountUsd: string;
9
+ cardAmountUsd: string;
10
+ feeUsd: string;
11
+ kripiCardFeeUsd: string;
12
+ txHash: string | null;
13
+ status: string;
14
+ errorMessage: string | null;
15
+ createdAt: string;
16
+ }
17
+
18
+ export default function Transactions({ token }: { token: string }) {
19
+ const [transactions, setTransactions] = useState<Transaction[]>([]);
20
+ const [filter, setFilter] = useState<string>("all");
21
+ const [loading, setLoading] = useState(true);
22
+
23
+ useEffect(() => {
24
+ fetchTransactions();
25
+ }, [filter]);
26
+
27
+ async function fetchTransactions() {
28
+ setLoading(true);
29
+ try {
30
+ const params = filter !== "all" ? `?status=${filter}` : "";
31
+ const res = await fetch(`/admin/api/transactions${params}`, {
32
+ headers: { Authorization: `Bearer ${token}` },
33
+ });
34
+ if (res.ok) {
35
+ const data = await res.json();
36
+ setTransactions(data.transactions);
37
+ }
38
+ } catch {
39
+ // ignore
40
+ }
41
+ setLoading(false);
42
+ }
43
+
44
+ const statusColors: Record<string, string> = {
45
+ completed: "var(--success)",
46
+ failed: "var(--danger)",
47
+ pending: "var(--warning)",
48
+ };
49
+
50
+ return (
51
+ <div className="space-y-6">
52
+ <div className="flex items-center justify-between">
53
+ <h2
54
+ className="text-2xl font-bold"
55
+ style={{ color: "var(--text-primary)" }}
56
+ >
57
+ Transactions
58
+ </h2>
59
+ <div className="flex gap-1">
60
+ {["all", "completed", "failed", "pending"].map((f) => (
61
+ <button
62
+ key={f}
63
+ onClick={() => setFilter(f)}
64
+ className="px-3 py-1.5 rounded-lg text-xs font-medium capitalize transition-all"
65
+ style={{
66
+ background: filter === f ? "var(--accent-glow)" : "transparent",
67
+ color: filter === f ? "var(--accent)" : "var(--text-muted)",
68
+ }}
69
+ >
70
+ {f}
71
+ </button>
72
+ ))}
73
+ </div>
74
+ </div>
75
+
76
+ {loading ? (
77
+ <div className="flex justify-center py-12">
78
+ <div
79
+ className="w-8 h-8 rounded-full border-2 border-t-transparent animate-spin"
80
+ style={{
81
+ borderColor: "var(--accent)",
82
+ borderTopColor: "transparent",
83
+ }}
84
+ />
85
+ </div>
86
+ ) : (
87
+ <div
88
+ className="overflow-x-auto rounded-xl"
89
+ style={{ border: "1px solid var(--border)" }}
90
+ >
91
+ <table className="w-full text-sm">
92
+ <thead>
93
+ <tr style={{ background: "var(--bg-secondary)" }}>
94
+ <th
95
+ className="text-left px-4 py-3 font-medium"
96
+ style={{ color: "var(--text-muted)" }}
97
+ >
98
+ Type
99
+ </th>
100
+ <th
101
+ className="text-left px-4 py-3 font-medium"
102
+ style={{ color: "var(--text-muted)" }}
103
+ >
104
+ Wallet
105
+ </th>
106
+ <th
107
+ className="text-right px-4 py-3 font-medium"
108
+ style={{ color: "var(--text-muted)" }}
109
+ >
110
+ Charged
111
+ </th>
112
+ <th
113
+ className="text-right px-4 py-3 font-medium"
114
+ style={{ color: "var(--text-muted)" }}
115
+ >
116
+ Card Load
117
+ </th>
118
+ <th
119
+ className="text-right px-4 py-3 font-medium"
120
+ style={{ color: "var(--text-muted)" }}
121
+ >
122
+ Our Fee
123
+ </th>
124
+ <th
125
+ className="text-center px-4 py-3 font-medium"
126
+ style={{ color: "var(--text-muted)" }}
127
+ >
128
+ Status
129
+ </th>
130
+ <th
131
+ className="text-left px-4 py-3 font-medium"
132
+ style={{ color: "var(--text-muted)" }}
133
+ >
134
+ Date
135
+ </th>
136
+ </tr>
137
+ </thead>
138
+ <tbody>
139
+ {transactions.map((tx) => (
140
+ <tr
141
+ key={tx.id}
142
+ className="transition-colors"
143
+ style={{ borderTop: "1px solid var(--border)" }}
144
+ onMouseEnter={(e) =>
145
+ (e.currentTarget.style.background = "var(--bg-card-hover)")
146
+ }
147
+ onMouseLeave={(e) =>
148
+ (e.currentTarget.style.background = "transparent")
149
+ }
150
+ >
151
+ <td className="px-4 py-3">
152
+ <span
153
+ className="text-xs px-2 py-1 rounded-md font-medium"
154
+ style={{
155
+ background: "var(--accent-glow)",
156
+ color: "var(--accent)",
157
+ }}
158
+ >
159
+ {tx.type.replace("card_", "")}
160
+ </span>
161
+ </td>
162
+ <td
163
+ className="px-4 py-3 font-mono text-xs"
164
+ style={{ color: "var(--text-secondary)" }}
165
+ >
166
+ {tx.walletAddress.slice(0, 6)}...
167
+ {tx.walletAddress.slice(-4)}
168
+ </td>
169
+ <td
170
+ className="px-4 py-3 text-right font-medium"
171
+ style={{ color: "var(--text-primary)" }}
172
+ >
173
+ ${parseFloat(tx.amountUsd).toFixed(2)}
174
+ </td>
175
+ <td
176
+ className="px-4 py-3 text-right"
177
+ style={{ color: "var(--text-secondary)" }}
178
+ >
179
+ ${parseFloat(tx.cardAmountUsd).toFixed(2)}
180
+ </td>
181
+ <td
182
+ className="px-4 py-3 text-right"
183
+ style={{ color: "var(--success)" }}
184
+ >
185
+ ${parseFloat(tx.feeUsd).toFixed(2)}
186
+ </td>
187
+ <td className="px-4 py-3 text-center">
188
+ <span
189
+ className="text-xs px-2 py-1 rounded-full font-medium"
190
+ style={{
191
+ background: `${statusColors[tx.status]}15`,
192
+ color: statusColors[tx.status] || "var(--text-muted)",
193
+ }}
194
+ >
195
+ {tx.status}
196
+ </span>
197
+ </td>
198
+ <td
199
+ className="px-4 py-3 text-xs"
200
+ style={{ color: "var(--text-muted)" }}
201
+ >
202
+ {new Date(tx.createdAt).toLocaleString()}
203
+ </td>
204
+ </tr>
205
+ ))}
206
+ {transactions.length === 0 && (
207
+ <tr>
208
+ <td
209
+ colSpan={7}
210
+ className="px-4 py-12 text-center"
211
+ style={{ color: "var(--text-muted)" }}
212
+ >
213
+ No transactions found
214
+ </td>
215
+ </tr>
216
+ )}
217
+ </tbody>
218
+ </table>
219
+ </div>
220
+ )}
221
+ </div>
222
+ );
223
+ }
@@ -0,0 +1,27 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4
+ theme: {
5
+ extend: {
6
+ fontFamily: {
7
+ sans: ["Inter", "system-ui", "sans-serif"],
8
+ },
9
+ colors: {
10
+ brand: {
11
+ 50: "#eef2ff",
12
+ 100: "#e0e7ff",
13
+ 200: "#c7d2fe",
14
+ 300: "#a5b4fc",
15
+ 400: "#818cf8",
16
+ 500: "#6366f1",
17
+ 600: "#4f46e5",
18
+ 700: "#4338ca",
19
+ 800: "#3730a3",
20
+ 900: "#312e81",
21
+ 950: "#1e1b4b",
22
+ },
23
+ },
24
+ },
25
+ },
26
+ plugins: [],
27
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": false,
16
+ "noUnusedParameters": false,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src"]
20
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/app.tsx","./src/main.tsx","./src/pages/cards.tsx","./src/pages/dashboard.tsx","./src/pages/transactions.tsx"],"version":"5.9.3"}
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ base: "/admin/app/",
7
+ build: {
8
+ outDir: "dist",
9
+ },
10
+ server: {
11
+ proxy: {
12
+ "/admin/api": "http://localhost:3000",
13
+ "/health": "http://localhost:3000",
14
+ "/pricing": "http://localhost:3000",
15
+ },
16
+ },
17
+ });
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+ import "dotenv/config";
3
+
4
+ export default defineConfig({
5
+ schema: "./src/db/schema.ts",
6
+ out: "./drizzle/migrations",
7
+ dialect: "postgresql",
8
+ dbCredentials: {
9
+ url: process.env.DATABASE_URL!,
10
+ },
11
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * AI Agent using @opencard/sdk
3
+ *
4
+ * Compare with agent-client.ts (raw x402 protocol) — same result, 5 lines.
5
+ */
6
+ import { OpenCardClient } from "@opencardsdk/sdk";
7
+ import "dotenv/config";
8
+
9
+ const client = new OpenCardClient({
10
+ privateKey: process.env.WALLET_KEY as `0x${string}`,
11
+ baseUrl: process.env.OPENCARD_API_URL || "http://localhost:3000",
12
+ });
13
+
14
+ console.log(`Wallet: ${client.address}`);
15
+
16
+ // One line — SDK handles 402 → USDC payment → retry automatically
17
+ const result = await client.createCard({
18
+ amount: 10,
19
+ nameOnCard: "AI AGENT",
20
+ email: "agent@example.com",
21
+ });
22
+
23
+ console.log("Card created:", {
24
+ cardId: result.card.cardId,
25
+ status: result.card.status,
26
+ balance: result.card.balance,
27
+ lastFour: result.card.lastFour,
28
+ });
29
+
30
+ if (result.cardDetails) {
31
+ console.log("Card details:", {
32
+ number: result.cardDetails.cardNumber,
33
+ expiry: `${result.cardDetails.expiryMonth}/${result.cardDetails.expiryYear}`,
34
+ cvv: result.cardDetails.cvv,
35
+ });
36
+ }
@@ -0,0 +1,111 @@
1
+ import { createWalletClient, http, parseEther, encodeFunctionData } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { base } from "viem/chains";
4
+ import "dotenv/config";
5
+
6
+ // Mock AI Agent Wallet
7
+ // In reality, this would be the agent's actual wallet
8
+ const account = privateKeyToAccount(
9
+ (process.env.TEST_WALLET_KEY as `0x${string}`) ||
10
+ "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
11
+ );
12
+
13
+ const client = createWalletClient({
14
+ account,
15
+ chain: base,
16
+ transport: http(),
17
+ });
18
+
19
+ const USDC_ABI = [
20
+ {
21
+ inputs: [
22
+ { name: "to", type: "address" },
23
+ { name: "amount", type: "uint256" },
24
+ ],
25
+ name: "transfer",
26
+ outputs: [{ name: "", type: "bool" }],
27
+ stateMutability: "nonpayable",
28
+ type: "function",
29
+ },
30
+ ] as const;
31
+
32
+ async function purchaseCard() {
33
+ const endpoint = "http://localhost:3000/cards/create/tier/10";
34
+ const body = JSON.stringify({
35
+ nameOnCard: "AI AGENT",
36
+ email: "agent@example.com",
37
+ });
38
+
39
+ console.log("🤖 Agent: Requesting card...");
40
+
41
+ // 1. Initial Request (Expect 402)
42
+ const res = await fetch(endpoint, {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body,
46
+ });
47
+
48
+ if (res.status === 402) {
49
+ const challenge = await res.json();
50
+ console.log("🔴 Server: 402 Payment Required");
51
+ console.log(" Details:", JSON.stringify(challenge, null, 2));
52
+
53
+ // 2. Parse Payment Requirements
54
+ const paymentReq = challenge.accepts[0];
55
+ const amount = BigInt(paymentReq.maxAmountRequired);
56
+ const to = paymentReq.payTo as `0x${string}`;
57
+ const token = paymentReq.asset as `0x${string}`;
58
+
59
+ console.log(
60
+ `🤖 Agent: Detected payment requirement: ${amount} units of ${token} on Base`,
61
+ );
62
+
63
+ // 3. Execute Transaction (Mocked for this example, or real if keys provided)
64
+ // In production, the agent signs and broadcasts the USDC transfer
65
+ // const hash = await client.writeContract({...});
66
+ const mockTxHash =
67
+ "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
68
+
69
+ console.log(`🤖 Agent: Payment sent! Tx Hash: ${mockTxHash}`);
70
+
71
+ // 4. Construct x402 Payment Proof
72
+ const paymentProof = {
73
+ scheme: "exact",
74
+ network: paymentReq.network,
75
+ payload: {
76
+ authorization: {
77
+ from: account.address,
78
+ to,
79
+ value: amount.toString(),
80
+ validAfter: "0",
81
+ validBefore: "9999999999",
82
+ nonce: "0",
83
+ },
84
+ signature: "0x...", // Signature of the authorization
85
+ txHash: mockTxHash, // The transaction hash
86
+ },
87
+ };
88
+
89
+ const encodedProof = Buffer.from(JSON.stringify(paymentProof)).toString(
90
+ "base64",
91
+ );
92
+
93
+ // 5. Retry Request with Payment Header
94
+ console.log("🤖 Agent: Retrying request with payment proof...");
95
+ const paidRes = await fetch(endpoint, {
96
+ method: "POST",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ "X-Payment": encodedProof,
100
+ },
101
+ body,
102
+ });
103
+
104
+ const result = await paidRes.json();
105
+ console.log("🟢 Server Response:", paidRes.status, result);
106
+ } else {
107
+ console.log("Response:", res.status, await res.json());
108
+ }
109
+ }
110
+
111
+ purchaseCard().catch(console.error);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Fund an existing card using @opencard/sdk
3
+ */
4
+ import { OpenCardClient, InsufficientBalanceError, ApiError } from "@opencardsdk/sdk";
5
+ import "dotenv/config";
6
+
7
+ const client = new OpenCardClient({
8
+ privateKey: process.env.WALLET_KEY as `0x${string}`,
9
+ baseUrl: process.env.OPENCARD_API_URL || "http://localhost:3000",
10
+ });
11
+
12
+ try {
13
+ // Check available tiers first
14
+ const tiers = await client.getTiers();
15
+ console.log(
16
+ "Funding tiers:",
17
+ tiers.funding.map((t) => `$${t.fundAmount} (cost: $${t.totalCost})`),
18
+ );
19
+
20
+ // Fund a card
21
+ const result = await client.fundCard({
22
+ amount: 25,
23
+ cardId: "your-card-id-here",
24
+ });
25
+
26
+ console.log("Card funded:", result);
27
+ } catch (error) {
28
+ if (error instanceof InsufficientBalanceError) {
29
+ console.error("Not enough USDC:", error.message);
30
+ } else if (error instanceof ApiError) {
31
+ console.error(`API error ${error.status}:`, error.body);
32
+ } else {
33
+ throw error;
34
+ }
35
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "opencard",
3
+ "version": "1.0.0",
4
+ "description": "x402-Powered Virtual Card Issuance API for AI Agents",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "tsx watch src/index.ts",
9
+ "build": "tsc",
10
+ "start": "node dist/index.js",
11
+ "db:generate": "drizzle-kit generate",
12
+ "db:push": "drizzle-kit push",
13
+ "db:migrate": "tsx src/db/migrate.ts",
14
+ "typecheck": "tsc --noEmit"
15
+ },
16
+ "dependencies": {
17
+ "express": "^4.21.0",
18
+ "@x402/express": "latest",
19
+ "@x402/evm": "latest",
20
+ "@x402/core": "latest",
21
+ "@coinbase/x402": "latest",
22
+ "viem": "^2.0.0",
23
+ "drizzle-orm": "^0.36.0",
24
+ "@neondatabase/serverless": "^0.10.0",
25
+ "zod": "^3.23.0",
26
+ "pino": "^9.0.0",
27
+ "pino-pretty": "^11.0.0",
28
+ "dotenv": "^16.4.0",
29
+ "express-rate-limit": "^7.0.0",
30
+ "helmet": "^8.0.0",
31
+ "cors": "^2.8.5"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5.5.0",
35
+ "tsx": "^4.0.0",
36
+ "drizzle-kit": "^0.28.0",
37
+ "@types/express": "^5.0.0",
38
+ "@types/node": "^22.0.0",
39
+ "@types/cors": "^2.8.0"
40
+ }
41
+ }
package/sdk/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # @opencardsdk/sdk
2
+
3
+ Client SDK for OpenCard — create virtual cards with USDC payments via the x402 protocol.
4
+
5
+ Wraps the raw x402 flow (402 → parse challenge → pay USDC → retry with proof) into one-liner methods.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @opencardsdk/sdk viem
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { OpenCardClient } from '@opencardsdk/sdk';
17
+
18
+ const client = new OpenCardClient({
19
+ privateKey: '0x...', // or pass a viem WalletClient
20
+ baseUrl: 'https://api.opencard.dev',
21
+ });
22
+
23
+ // One line — SDK handles payment automatically
24
+ const card = await client.createCard({
25
+ amount: 10, // $10 tier
26
+ nameOnCard: 'AI AGENT',
27
+ email: 'agent@example.com',
28
+ });
29
+
30
+ console.log(card.cardDetails); // { cardNumber, cvv, expiry, ... }
31
+ ```
32
+
33
+ ## API
34
+
35
+ ### `new OpenCardClient(config)`
36
+
37
+ | Option | Type | Required | Description |
38
+ |--------|------|----------|-------------|
39
+ | `privateKey` | `0x${string}` | One of | Hex private key — SDK creates wallet internally |
40
+ | `walletClient` | `WalletClient` | One of | Your own viem WalletClient (takes precedence) |
41
+ | `baseUrl` | `string` | No | API URL (default: `https://api.opencard.dev`) |
42
+ | `rpcUrl` | `string` | No | Base RPC URL (default: public RPC) |
43
+ | `timeout` | `number` | No | Request timeout in ms (default: 60000) |
44
+
45
+ ### `client.createCard(params): Promise<CardResult>`
46
+
47
+ Create a virtual card. Pays USDC automatically.
48
+
49
+ ```typescript
50
+ const result = await client.createCard({
51
+ amount: 50, // 10 | 25 | 50 | 100 | 200 | 500
52
+ nameOnCard: 'AI AGENT',
53
+ email: 'agent@example.com',
54
+ });
55
+ ```
56
+
57
+ ### `client.fundCard(params): Promise<FundResult>`
58
+
59
+ Fund an existing card.
60
+
61
+ ```typescript
62
+ const result = await client.fundCard({
63
+ amount: 25,
64
+ cardId: 'card-uuid',
65
+ });
66
+ ```
67
+
68
+ ### `client.getTiers(): Promise<TiersResponse>`
69
+
70
+ Get pricing tiers and fee breakdown (no payment required).
71
+
72
+ ### `client.health(): Promise<{ status: string }>`
73
+
74
+ Check if the OpenCard API is reachable.
75
+
76
+ ### `client.address: 0x${string}`
77
+
78
+ The wallet address being used for payments.
79
+
80
+ ## Error Handling
81
+
82
+ ```typescript
83
+ import {
84
+ OpenCardClient,
85
+ InsufficientBalanceError,
86
+ PaymentError,
87
+ ApiError,
88
+ TimeoutError,
89
+ } from '@opencardsdk/sdk';
90
+
91
+ try {
92
+ const card = await client.createCard({ ... });
93
+ } catch (error) {
94
+ if (error instanceof InsufficientBalanceError) {
95
+ console.log(`Need ${error.required}, have ${error.available}`);
96
+ } else if (error instanceof PaymentError) {
97
+ console.log(`Payment failed: ${error.message}, tx: ${error.txHash}`);
98
+ } else if (error instanceof ApiError) {
99
+ console.log(`Server error ${error.status}:`, error.body);
100
+ } else if (error instanceof TimeoutError) {
101
+ console.log('Request timed out');
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Advanced: Low-Level x402 Utilities
107
+
108
+ For custom integrations, the x402 protocol helpers are exported directly:
109
+
110
+ ```typescript
111
+ import {
112
+ parseChallenge,
113
+ checkBalance,
114
+ executePayment,
115
+ buildPaymentProof,
116
+ handleX402Payment,
117
+ } from '@opencardsdk/sdk';
118
+ ```
119
+
120
+ ## How It Works
121
+
122
+ ```
123
+ Your Code SDK OpenCard API Base Chain
124
+ | | | |
125
+ |-- createCard ->| | |
126
+ | |--- POST /create/tier -->| |
127
+ | |<-- 402 + challenge -----| |
128
+ | | | |
129
+ | |--- USDC.transfer() -----|-------- tx ------->|
130
+ | |<-- txHash --------------|-------- receipt ---|
131
+ | | | |
132
+ | |--- POST + X-Payment --->| |
133
+ | |<-- 201 + card details --| |
134
+ |<- CardResult --| | |
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT