@a3stack/payments 0.1.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.
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@a3stack/payments",
3
+ "version": "0.1.0",
4
+ "description": "x402 payment flows for AI agents — client (paying) and server (receiving)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format esm,cjs --dts --outDir dist",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "@x402/fetch": "^2.4.0",
22
+ "@x402/evm": "^2.4.0",
23
+ "viem": "^2.39.3",
24
+ "zod": "^3.24.2"
25
+ },
26
+ "devDependencies": {
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5.4.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ }
33
+ }
package/src/client.ts ADDED
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Payment Client — wraps x402/fetch for easy agent-to-agent payments
3
+ */
4
+
5
+ import { createPublicClient, createWalletClient, http, formatUnits } from "viem";
6
+ import type { Account } from "viem";
7
+ import {
8
+ DEFAULT_MAX_AMOUNT,
9
+ NETWORK_USDC,
10
+ DEFAULT_NETWORK,
11
+ ERC20_ABI,
12
+ NETWORK_RPC,
13
+ BASE_RPC,
14
+ } from "./constants.js";
15
+ import type {
16
+ PaymentClientConfig,
17
+ PaymentBalance,
18
+ PaymentDetails,
19
+ } from "./types.js";
20
+
21
+ export class PaymentClient {
22
+ private config: PaymentClientConfig;
23
+ private _paidFetch?: typeof fetch;
24
+
25
+ constructor(config: PaymentClientConfig) {
26
+ this.config = {
27
+ ...config,
28
+ chains: config.chains ?? ["eip155:*"],
29
+ maxAmountPerRequest: config.maxAmountPerRequest ?? DEFAULT_MAX_AMOUNT,
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Initialize the payment-wrapped fetch (lazy — only creates when first used)
35
+ */
36
+ private async getPaidFetch(): Promise<typeof fetch> {
37
+ if (this._paidFetch) return this._paidFetch;
38
+
39
+ // Dynamic import to avoid bundling issues
40
+ const [{ wrapFetchWithPaymentFromConfig }, { ExactEvmScheme, toClientEvmSigner }] = await Promise.all([
41
+ import("@x402/fetch"),
42
+ import("@x402/evm"),
43
+ ]);
44
+
45
+ const account = this.config.account as Account;
46
+ const chains = this.config.chains!;
47
+
48
+ // ExactEvmScheme requires a ClientEvmSigner with address + signTypedData + readContract
49
+ // We build a minimal signer from the viem account + a public client for readContract
50
+ const rpcUrl = BASE_RPC;
51
+ const publicClient = createPublicClient({ transport: http(rpcUrl) });
52
+
53
+ // Build a ClientEvmSigner manually
54
+ const signer = toClientEvmSigner({
55
+ address: account.address,
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ signTypedData: (params: any) => account.signTypedData?.(params) ?? Promise.reject(new Error("signTypedData not available on this account")),
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ readContract: (params: any) => publicClient.readContract(params),
60
+ } as Parameters<typeof toClientEvmSigner>[0]);
61
+
62
+ // Build scheme registrations
63
+ const schemes = chains.map((network) => ({
64
+ network: network as `${string}:${string}`,
65
+ client: new ExactEvmScheme(signer),
66
+ }));
67
+
68
+ this._paidFetch = wrapFetchWithPaymentFromConfig(fetch, { schemes });
69
+ return this._paidFetch;
70
+ }
71
+
72
+ /**
73
+ * A fetch function that automatically handles x402 payment challenges
74
+ */
75
+ get fetch(): (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response> {
76
+ return async (input, init) => {
77
+ const paidFetch = await this.getPaidFetch();
78
+ return paidFetch(input, init);
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Create a payment-capable fetch pre-configured for a specific agent.
84
+ * The wallet is resolved automatically from the 402 payment requirements.
85
+ */
86
+ fetchForWallet(
87
+ _paymentWallet: `0x${string}`
88
+ ): (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response> {
89
+ // The x402 protocol handles payment wallet resolution automatically via 402 response
90
+ // This just returns the standard paid fetch — the wallet in payment requirements
91
+ // is provided by the server's 402 response
92
+ return this.fetch;
93
+ }
94
+
95
+ /**
96
+ * Check your USDC balance on a network
97
+ */
98
+ async getBalance(
99
+ network = DEFAULT_NETWORK,
100
+ rpc?: string
101
+ ): Promise<PaymentBalance> {
102
+ const usdcAddress = NETWORK_USDC[network];
103
+ if (!usdcAddress) {
104
+ throw new Error(
105
+ `No USDC address configured for network "${network}". ` +
106
+ `Supported networks: ${Object.keys(NETWORK_USDC).join(", ")}`
107
+ );
108
+ }
109
+
110
+ const rpcUrl = rpc ?? NETWORK_RPC[network] ?? BASE_RPC;
111
+ const client = createPublicClient({ transport: http(rpcUrl) });
112
+
113
+ const [rawBalance, decimals, symbol] = await Promise.all([
114
+ client.readContract({
115
+ address: usdcAddress,
116
+ abi: ERC20_ABI,
117
+ functionName: "balanceOf",
118
+ args: [this.config.account.address],
119
+ }) as Promise<bigint>,
120
+ client.readContract({
121
+ address: usdcAddress,
122
+ abi: ERC20_ABI,
123
+ functionName: "decimals",
124
+ }) as Promise<number>,
125
+ client.readContract({
126
+ address: usdcAddress,
127
+ abi: ERC20_ABI,
128
+ functionName: "symbol",
129
+ }) as Promise<string>,
130
+ ]);
131
+
132
+ return {
133
+ amount: rawBalance,
134
+ formatted: formatUnits(rawBalance, decimals),
135
+ symbol,
136
+ decimals,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Decode payment receipt from a successful x402 response
142
+ * Returns null if no payment was made (wasn't a 402 endpoint)
143
+ */
144
+ decodeReceipt(response: Response): PaymentDetails | null {
145
+ const paymentResponseHeader = response.headers.get("x-payment-response") ??
146
+ response.headers.get("PAYMENT-RESPONSE");
147
+ if (!paymentResponseHeader) return null;
148
+
149
+ try {
150
+ // The x402 response header contains base64-encoded payment details
151
+ const decoded = JSON.parse(
152
+ Buffer.from(paymentResponseHeader, "base64").toString("utf8")
153
+ );
154
+ return {
155
+ from: decoded.sender ?? decoded.from,
156
+ to: decoded.recipient ?? decoded.to ?? decoded.payTo,
157
+ amount: decoded.amount ?? decoded.value,
158
+ asset: decoded.asset ?? decoded.token,
159
+ network: decoded.network ?? decoded.chain ?? DEFAULT_NETWORK,
160
+ txHash: decoded.txHash ?? decoded.hash,
161
+ timestamp: decoded.timestamp ?? Date.now(),
162
+ };
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Check if an endpoint requires payment (without making the actual request)
170
+ */
171
+ async checkPaymentRequirements(url: string): Promise<{
172
+ requiresPayment: boolean;
173
+ amount?: string;
174
+ asset?: string;
175
+ network?: string;
176
+ payTo?: string;
177
+ }> {
178
+ const response = await fetch(url, { method: "HEAD" });
179
+
180
+ if (response.status !== 402) {
181
+ return { requiresPayment: false };
182
+ }
183
+
184
+ const reqHeader = response.headers.get("x-payment-required") ??
185
+ response.headers.get("X-PAYMENT-REQUIRED");
186
+ if (!reqHeader) return { requiresPayment: true };
187
+
188
+ try {
189
+ const reqs = JSON.parse(Buffer.from(reqHeader, "base64").toString("utf8"));
190
+ const first = Array.isArray(reqs.accepts) ? reqs.accepts[0] : reqs;
191
+ return {
192
+ requiresPayment: true,
193
+ amount: first.maxAmountRequired,
194
+ asset: first.asset,
195
+ network: first.network,
196
+ payTo: first.payTo,
197
+ };
198
+ } catch {
199
+ return { requiresPayment: true };
200
+ }
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Create a payment client
206
+ */
207
+ export function createPaymentClient(config: PaymentClientConfig): PaymentClient {
208
+ return new PaymentClient(config);
209
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Payment Constants
3
+ */
4
+
5
+ /** USDC on Base mainnet */
6
+ export const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const;
7
+
8
+ /** USDC on Ethereum mainnet */
9
+ export const USDC_ETH = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" as const;
10
+
11
+ /** USDC on Polygon */
12
+ export const USDC_POLYGON = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359" as const;
13
+
14
+ /** USDC on Arbitrum */
15
+ export const USDC_ARBITRUM = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" as const;
16
+
17
+ /** USDC on Optimism */
18
+ export const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" as const;
19
+
20
+ /** Default USDC token per network */
21
+ export const NETWORK_USDC: Record<string, `0x${string}`> = {
22
+ "eip155:1": USDC_ETH,
23
+ "eip155:8453": USDC_BASE,
24
+ "eip155:137": USDC_POLYGON,
25
+ "eip155:42161": USDC_ARBITRUM,
26
+ "eip155:10": USDC_OPTIMISM,
27
+ };
28
+
29
+ /** Default network (Base mainnet) */
30
+ export const DEFAULT_NETWORK = "eip155:8453";
31
+
32
+ /** Default max auto-pay amount: 10 USDC (10,000,000 base units) */
33
+ export const DEFAULT_MAX_AMOUNT = "10000000";
34
+
35
+ /** Default payment timeout: 5 minutes */
36
+ export const DEFAULT_TIMEOUT_SECONDS = 300;
37
+
38
+ /** ERC-20 ABI for balance checks */
39
+ export const ERC20_ABI = [
40
+ {
41
+ name: "balanceOf",
42
+ type: "function",
43
+ stateMutability: "view",
44
+ inputs: [{ name: "account", type: "address" }],
45
+ outputs: [{ name: "", type: "uint256" }],
46
+ },
47
+ {
48
+ name: "decimals",
49
+ type: "function",
50
+ stateMutability: "view",
51
+ inputs: [],
52
+ outputs: [{ name: "", type: "uint8" }],
53
+ },
54
+ {
55
+ name: "symbol",
56
+ type: "function",
57
+ stateMutability: "view",
58
+ inputs: [],
59
+ outputs: [{ name: "", type: "string" }],
60
+ },
61
+ ] as const;
62
+
63
+ /** Base mainnet RPC */
64
+ export const BASE_RPC = "https://mainnet.base.org";
65
+
66
+ /** Default RPC per CAIP-2 network */
67
+ export const NETWORK_RPC: Record<string, string> = {
68
+ "eip155:1": "https://eth.llamarpc.com",
69
+ "eip155:8453": BASE_RPC,
70
+ "eip155:137": "https://polygon-rpc.com",
71
+ "eip155:42161": "https://arb1.arbitrum.io/rpc",
72
+ "eip155:10": "https://mainnet.optimism.io",
73
+ };
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @a3stack/payments
3
+ * x402 payment flows for AI agents — client (paying) and server (receiving)
4
+ */
5
+
6
+ export { PaymentClient, createPaymentClient } from "./client.js";
7
+ export { PaymentServer, createPaymentServer } from "./server.js";
8
+ export {
9
+ USDC_BASE,
10
+ USDC_ETH,
11
+ USDC_POLYGON,
12
+ USDC_ARBITRUM,
13
+ USDC_OPTIMISM,
14
+ NETWORK_USDC,
15
+ DEFAULT_NETWORK,
16
+ DEFAULT_MAX_AMOUNT,
17
+ } from "./constants.js";
18
+ export type {
19
+ PaymentClientConfig,
20
+ PaymentServerConfig,
21
+ PaymentDetails,
22
+ PaymentBalance,
23
+ PaymentVerifyResult,
24
+ PaymentRequirements,
25
+ PaymentContext,
26
+ } from "./types.js";
package/src/server.ts ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Payment Server — receive x402 payments in your HTTP server
3
+ */
4
+
5
+ import type { IncomingMessage, ServerResponse } from "node:http";
6
+ import {
7
+ DEFAULT_NETWORK,
8
+ DEFAULT_TIMEOUT_SECONDS,
9
+ NETWORK_USDC,
10
+ } from "./constants.js";
11
+ import type {
12
+ PaymentServerConfig,
13
+ PaymentRequirements,
14
+ PaymentVerifyResult,
15
+ PaymentDetails,
16
+ } from "./types.js";
17
+
18
+ /**
19
+ * Payment server for accepting x402 payments
20
+ */
21
+ export class PaymentServer {
22
+ private config: Required<PaymentServerConfig>;
23
+
24
+ constructor(config: PaymentServerConfig) {
25
+ const network = config.network ?? DEFAULT_NETWORK;
26
+ this.config = {
27
+ payTo: config.payTo,
28
+ amount: config.amount,
29
+ asset: config.asset ?? NETWORK_USDC[network] ?? NETWORK_USDC["eip155:8453"],
30
+ network,
31
+ description: config.description ?? "AI agent service payment",
32
+ maxTimeoutSeconds: config.maxTimeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Build x402 PaymentRequirements for a resource URL
38
+ */
39
+ buildRequirements(
40
+ resource: string,
41
+ overrides?: Partial<PaymentServerConfig>
42
+ ): PaymentRequirements {
43
+ return {
44
+ scheme: "exact",
45
+ network: overrides?.network ?? this.config.network,
46
+ maxAmountRequired: overrides?.amount ?? this.config.amount,
47
+ resource,
48
+ description: overrides?.description ?? this.config.description,
49
+ payTo: overrides?.payTo ?? this.config.payTo,
50
+ maxTimeoutSeconds: this.config.maxTimeoutSeconds,
51
+ asset: overrides?.asset ?? this.config.asset,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Build the X-PAYMENT-REQUIRED header value (base64-encoded JSON)
57
+ */
58
+ buildRequirementsHeader(resource: string, overrides?: Partial<PaymentServerConfig>): string {
59
+ const requirements = this.buildRequirements(resource, overrides);
60
+ // x402 v2 format: { version: 2, accepts: [...] }
61
+ const payload = {
62
+ version: 2,
63
+ accepts: [requirements],
64
+ };
65
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
66
+ }
67
+
68
+ /**
69
+ * Parse the X-PAYMENT header from an incoming request
70
+ */
71
+ parsePaymentHeader(header: string | null): {
72
+ payload: unknown;
73
+ network: string;
74
+ } | null {
75
+ if (!header) return null;
76
+ try {
77
+ const decoded = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
78
+ return {
79
+ payload: decoded.payload ?? decoded,
80
+ network: decoded.network ?? decoded.x402Version?.network ?? this.config.network,
81
+ };
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Verify a payment — this is a lightweight signature check.
89
+ * For production, use a proper facilitator (e.g. x402.org/faciliate).
90
+ *
91
+ * Note: Full on-chain verification requires a funded facilitator wallet
92
+ * to settle the payment. This SDK provides the verification primitive;
93
+ * settlement is handled by the facilitator service.
94
+ */
95
+ async verify(
96
+ request: Request | IncomingMessage
97
+ ): Promise<PaymentVerifyResult> {
98
+ // Get the X-PAYMENT header
99
+ let paymentHeader: string | null = null;
100
+
101
+ if (request instanceof Request) {
102
+ paymentHeader =
103
+ request.headers.get("x-payment") ??
104
+ request.headers.get("X-PAYMENT");
105
+ } else {
106
+ const raw = (request as IncomingMessage).headers["x-payment"];
107
+ paymentHeader = Array.isArray(raw) ? raw[0] : raw ?? null;
108
+ }
109
+
110
+ if (!paymentHeader) {
111
+ return {
112
+ valid: false,
113
+ error: "Missing X-PAYMENT header. This endpoint requires x402 payment.",
114
+ };
115
+ }
116
+
117
+ const parsed = this.parsePaymentHeader(paymentHeader);
118
+ if (!parsed) {
119
+ return { valid: false, error: "Invalid X-PAYMENT header format" };
120
+ }
121
+
122
+ // For full on-chain verification, we'd use ExactEvmFacilitator from @x402/evm
123
+ // Here we do a structural validation (signature format check)
124
+ try {
125
+ const payload = parsed.payload as Record<string, unknown>;
126
+
127
+ // Check if it's an EIP-3009 payload
128
+ if (payload.authorization && typeof payload.authorization === "object") {
129
+ const auth = payload.authorization as Record<string, unknown>;
130
+ if (!auth.from || !auth.to || !auth.value || !auth.nonce) {
131
+ return { valid: false, error: "Invalid EIP-3009 authorization structure" };
132
+ }
133
+
134
+ const payment: PaymentDetails = {
135
+ from: auth.from as `0x${string}`,
136
+ to: auth.to as `0x${string}`,
137
+ amount: auth.value as string,
138
+ asset: this.config.asset,
139
+ network: parsed.network,
140
+ timestamp: Date.now(),
141
+ };
142
+
143
+ return { valid: true, payment };
144
+ }
145
+
146
+ // Check if it's a Permit2 payload
147
+ if (payload.permit2Authorization) {
148
+ const p2 = payload.permit2Authorization as Record<string, unknown>;
149
+ const payment: PaymentDetails = {
150
+ from: p2.from as `0x${string}`,
151
+ to: (p2 as Record<string, Record<string, unknown>>).witness?.to as `0x${string}`,
152
+ amount: (p2.permitted as Record<string, string>)?.amount,
153
+ asset: (p2.permitted as Record<string, string>)?.token as `0x${string}`,
154
+ network: parsed.network,
155
+ timestamp: Date.now(),
156
+ };
157
+ return { valid: true, payment };
158
+ }
159
+
160
+ return { valid: false, error: "Unrecognized payment payload format" };
161
+ } catch (e) {
162
+ return { valid: false, error: `Payment verification error: ${(e as Error).message}` };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Express-compatible middleware for payment verification.
168
+ * Automatically sends 402 response if payment is missing/invalid.
169
+ *
170
+ * Usage:
171
+ * app.use('/paid-tool', paymentServer.middleware(), handler)
172
+ */
173
+ middleware(overrides?: Partial<PaymentServerConfig>) {
174
+ return async (
175
+ req: IncomingMessage & { url?: string; payment?: PaymentDetails },
176
+ res: ServerResponse,
177
+ next: () => void
178
+ ) => {
179
+ const paymentHeader =
180
+ (req.headers["x-payment"] as string) ??
181
+ (req.headers["X-PAYMENT"] as string);
182
+
183
+ if (!paymentHeader) {
184
+ // Return 402 with payment requirements
185
+ const resource = `${req.headers["host"] ?? ""}${req.url ?? "/"}`;
186
+ const requirementsHeader = this.buildRequirementsHeader(
187
+ `https://${resource}`,
188
+ overrides
189
+ );
190
+
191
+ res.writeHead(402, {
192
+ "Content-Type": "application/json",
193
+ "X-PAYMENT-REQUIRED": requirementsHeader,
194
+ });
195
+ res.end(
196
+ JSON.stringify({
197
+ error: "Payment Required",
198
+ message: this.config.description,
199
+ amount: `${this.config.amount} USDC base units`,
200
+ network: this.config.network,
201
+ })
202
+ );
203
+ return;
204
+ }
205
+
206
+ const result = await this.verify(req);
207
+ if (!result.valid) {
208
+ res.writeHead(402, { "Content-Type": "application/json" });
209
+ res.end(JSON.stringify({ error: "Payment Invalid", message: result.error }));
210
+ return;
211
+ }
212
+
213
+ // Attach payment details to request for downstream handlers
214
+ (req as typeof req & { payment: PaymentDetails }).payment = result.payment!;
215
+ next();
216
+ };
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Create a payment server for receiving x402 payments
222
+ */
223
+ export function createPaymentServer(config: PaymentServerConfig): PaymentServer {
224
+ return new PaymentServer(config);
225
+ }
package/src/types.ts ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @a3stack/payments — Type Definitions
3
+ */
4
+
5
+ export interface PaymentClientConfig {
6
+ /**
7
+ * viem Account (e.g. from privateKeyToAccount())
8
+ * Must have signTypedData for payment signing.
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ account: any;
12
+ /**
13
+ * Supported chains in CAIP-2 format.
14
+ * Defaults to ["eip155:*"] (all EVM chains)
15
+ */
16
+ chains?: string[];
17
+ /**
18
+ * Maximum USDC amount (in base units, 6 decimals) to auto-pay per request.
19
+ * e.g. "1000000" = 1.00 USDC
20
+ * Defaults to "10000000" (10 USDC)
21
+ */
22
+ maxAmountPerRequest?: string;
23
+ }
24
+
25
+ export interface PaymentServerConfig {
26
+ /** Address to receive payments */
27
+ payTo: `0x${string}`;
28
+ /**
29
+ * Required payment amount in USDC base units (6 decimals).
30
+ * e.g. "100000" = 0.10 USDC
31
+ */
32
+ amount: string;
33
+ /**
34
+ * Token asset address. Defaults to USDC on Base mainnet.
35
+ */
36
+ asset?: `0x${string}`;
37
+ /**
38
+ * Network in CAIP-2 format. Defaults to "eip155:8453" (Base mainnet)
39
+ */
40
+ network?: string;
41
+ /**
42
+ * Description shown in payment requirements
43
+ */
44
+ description?: string;
45
+ /**
46
+ * Max timeout for payment verification in seconds. Defaults to 300.
47
+ */
48
+ maxTimeoutSeconds?: number;
49
+ }
50
+
51
+ export interface PaymentDetails {
52
+ from: `0x${string}`;
53
+ to: `0x${string}`;
54
+ amount: string;
55
+ asset: `0x${string}`;
56
+ network: string;
57
+ txHash?: `0x${string}`;
58
+ timestamp: number;
59
+ }
60
+
61
+ export interface PaymentVerifyResult {
62
+ valid: boolean;
63
+ payment?: PaymentDetails;
64
+ error?: string;
65
+ }
66
+
67
+ export interface PaymentBalance {
68
+ amount: bigint;
69
+ formatted: string;
70
+ symbol: string;
71
+ decimals: number;
72
+ }
73
+
74
+ export interface PaymentRequirements {
75
+ scheme: string;
76
+ network: string;
77
+ maxAmountRequired: string;
78
+ resource: string;
79
+ description?: string;
80
+ mimeType?: string;
81
+ payTo: string;
82
+ maxTimeoutSeconds: number;
83
+ asset: string;
84
+ outputSchema?: unknown;
85
+ extra?: Record<string, unknown>;
86
+ }
87
+
88
+ /**
89
+ * Middleware context — added to express req/res
90
+ */
91
+ export interface PaymentContext {
92
+ payment: PaymentDetails;
93
+ requirements: PaymentRequirements;
94
+ }