@agentspend/sdk 0.1.1 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,21 +1,5 @@
1
- export interface ChargeRequest {
2
- wallet_id: string;
3
- amount_cents: number;
4
- currency?: string;
5
- description?: string;
6
- metadata?: Record<string, string>;
7
- idempotency_key?: string;
8
- }
9
- export interface ChargeResponse {
10
- charged: true;
11
- wallet_id: string;
12
- amount_cents: number;
13
- currency: string;
14
- remaining_limit_cents: number;
15
- stripe_payment_intent_id: string;
16
- stripe_charge_id: string;
17
- charge_attempt_id: string;
18
- }
1
+ import type { ChargeResponse, PaywallPaymentContext } from "@agentspend/types";
2
+ export type { ChargeRequest, ChargeResponse, ErrorResponse, PaymentMethod, PaywallPaymentContext } from "@agentspend/types";
19
3
  export interface AgentSpendOptions {
20
4
  /**
21
5
  * Base URL for the AgentSpend Platform API.
@@ -24,8 +8,18 @@ export interface AgentSpendOptions {
24
8
  * otherwise it falls back to the hosted default.
25
9
  */
26
10
  platformApiBaseUrl?: string;
27
- serviceApiKey: string;
11
+ /** Service API key. Optional — crypto-only services don't need one. */
12
+ serviceApiKey?: string;
28
13
  fetchImpl?: typeof fetch;
14
+ /** Crypto / x402 configuration. */
15
+ crypto?: {
16
+ /** Static payTo address for crypto-only services. */
17
+ receiverAddress?: string;
18
+ /** Chain identifier. Default: "eip155:8453" (Base). */
19
+ network?: string;
20
+ /** x402 facilitator URL. Default: "https://x402.org/facilitator". */
21
+ facilitatorUrl?: string;
22
+ };
29
23
  }
30
24
  export interface ChargeOptions {
31
25
  amount_cents: number;
@@ -43,17 +37,24 @@ export interface HonoContextLike {
43
37
  req: {
44
38
  header(name: string): string | undefined;
45
39
  json(): Promise<unknown>;
40
+ url: string;
41
+ method: string;
46
42
  };
47
43
  json(body: unknown, status?: number): unknown;
44
+ header(name: string, value: string): void;
45
+ set(key: string, value: unknown): void;
46
+ get(key: string): unknown;
48
47
  }
49
48
  export interface PaywallOptions {
50
49
  currency?: string;
51
50
  description?: string;
52
51
  metadata?: (body: unknown) => Record<string, unknown>;
52
+ /** Dynamic pricing: derive amount from the parsed request body. */
53
+ amountFromRequest?: (body: unknown) => number;
53
54
  }
55
+ export declare function getPaymentContext(c: HonoContextLike): PaywallPaymentContext | null;
54
56
  export interface AgentSpend {
55
57
  charge(walletId: string, opts: ChargeOptions): Promise<ChargeResponse>;
56
58
  paywall(amountCents: number, opts?: PaywallOptions): (c: HonoContextLike, next: () => Promise<void>) => Promise<unknown>;
57
59
  }
58
60
  export declare function createAgentSpend(options: AgentSpendOptions): AgentSpend;
59
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,13 @@
1
- export class AgentSpendChargeError extends Error {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentSpendChargeError = void 0;
4
+ exports.getPaymentContext = getPaymentContext;
5
+ exports.createAgentSpend = createAgentSpend;
6
+ // ---------------------------------------------------------------------------
7
+ // x402 imports – server-side only (HTTP calls to facilitator, no crypto deps)
8
+ // ---------------------------------------------------------------------------
9
+ const server_1 = require("@x402/core/server");
10
+ class AgentSpendChargeError extends Error {
2
11
  statusCode;
3
12
  details;
4
13
  constructor(message, statusCode, details) {
@@ -7,13 +16,48 @@ export class AgentSpendChargeError extends Error {
7
16
  this.details = details;
8
17
  }
9
18
  }
10
- export function createAgentSpend(options) {
19
+ exports.AgentSpendChargeError = AgentSpendChargeError;
20
+ // ---------------------------------------------------------------------------
21
+ // Payment context helper
22
+ // ---------------------------------------------------------------------------
23
+ const PAYMENT_CONTEXT_KEY = "payment";
24
+ function getPaymentContext(c) {
25
+ const ctx = c.get(PAYMENT_CONTEXT_KEY);
26
+ return ctx ?? null;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Factory
30
+ // ---------------------------------------------------------------------------
31
+ function createAgentSpend(options) {
32
+ // Validate: at least one of serviceApiKey or crypto must be provided
33
+ if (!options.serviceApiKey && !options.crypto) {
34
+ throw new AgentSpendChargeError("At least one of serviceApiKey or crypto config must be provided", 500);
35
+ }
11
36
  const fetchImpl = options.fetchImpl ?? globalThis.fetch;
12
37
  if (!fetchImpl) {
13
38
  throw new AgentSpendChargeError("No fetch implementation available", 500);
14
39
  }
15
40
  const platformApiBaseUrl = resolvePlatformApiBaseUrl(options.platformApiBaseUrl);
41
+ // -------------------------------------------------------------------
42
+ // x402 singleton setup (Decision 9)
43
+ // Server-side: facilitator handles verify + settle over HTTP.
44
+ // No client-side EVM scheme needed — we delegate to the facilitator.
45
+ // -------------------------------------------------------------------
46
+ let facilitator = null;
47
+ let resourceServer = null;
48
+ const cryptoNetwork = (options.crypto?.network ?? "eip155:8453");
49
+ if (options.crypto || options.serviceApiKey) {
50
+ const facilitatorUrl = options.crypto?.facilitatorUrl ?? "https://x402.org/facilitator";
51
+ facilitator = new server_1.HTTPFacilitatorClient({ url: facilitatorUrl });
52
+ resourceServer = new server_1.x402ResourceServer(facilitator);
53
+ }
54
+ // -------------------------------------------------------------------
55
+ // charge() — card-only, unchanged
56
+ // -------------------------------------------------------------------
16
57
  async function charge(walletIdInput, opts) {
58
+ if (!options.serviceApiKey) {
59
+ throw new AgentSpendChargeError("charge() requires serviceApiKey", 500);
60
+ }
17
61
  const walletId = toWalletId(walletIdInput);
18
62
  if (!walletId) {
19
63
  throw new AgentSpendChargeError("wallet_id must start with wal_", 400);
@@ -43,50 +87,216 @@ export function createAgentSpend(options) {
43
87
  }
44
88
  return responseBody;
45
89
  }
90
+ // -------------------------------------------------------------------
91
+ // paywall() — unified card + crypto middleware
92
+ // -------------------------------------------------------------------
46
93
  function paywall(amountCents, opts) {
47
- if (!Number.isInteger(amountCents) || amountCents <= 0) {
48
- throw new AgentSpendChargeError("amountCents must be a positive integer", 500);
94
+ // Allow amountCents === 0 when amountFromRequest is provided (dynamic pricing)
95
+ if (!opts?.amountFromRequest) {
96
+ if (!Number.isInteger(amountCents) || amountCents <= 0) {
97
+ throw new AgentSpendChargeError("amountCents must be a positive integer", 500);
98
+ }
49
99
  }
50
100
  return async function paywallMiddleware(c, next) {
51
- const walletIdFromHeader = c.req.header("x-wallet-id");
52
- let body = null;
53
- let walletId = walletIdFromHeader ? toWalletId(walletIdFromHeader) : null;
54
- if (!walletId || opts?.metadata) {
55
- body = await c.req.json().catch(() => ({}));
56
- if (!walletId) {
57
- const bodyWalletId = typeof body?.wallet_id === "string" ? body.wallet_id : null;
58
- walletId = toWalletId(bodyWalletId);
101
+ // Step 1: Parse body once (Decision 11)
102
+ const body = await c.req.json().catch(() => ({}));
103
+ // Step 2: Determine effective amount
104
+ let effectiveAmount = amountCents;
105
+ if (opts?.amountFromRequest) {
106
+ effectiveAmount = opts.amountFromRequest(body);
107
+ if (!Number.isInteger(effectiveAmount) || effectiveAmount <= 0) {
108
+ return c.json({ error: "Could not determine payment amount from request" }, 400);
59
109
  }
60
110
  }
111
+ const currency = opts?.currency ?? "usd";
112
+ // Step 3: Check for x-payment header → crypto payment
113
+ const paymentHeader = c.req.header("x-payment");
114
+ if (paymentHeader) {
115
+ return handleCryptoPayment(c, next, paymentHeader, effectiveAmount, currency, body, opts);
116
+ }
117
+ // Step 4: Check for x-wallet-id header or body.wallet_id → card payment
118
+ const walletIdFromHeader = c.req.header("x-wallet-id");
119
+ let walletId = walletIdFromHeader ? toWalletId(walletIdFromHeader) : null;
61
120
  if (!walletId) {
62
- return c.json({ error: "wallet_id is required (x-wallet-id header or JSON body wallet_id)" }, 400);
121
+ const bodyWalletId = typeof body?.wallet_id === "string"
122
+ ? body.wallet_id
123
+ : null;
124
+ walletId = toWalletId(bodyWalletId);
63
125
  }
64
- try {
65
- await charge(walletId, {
66
- amount_cents: amountCents,
67
- currency: opts?.currency ?? "usd",
68
- description: opts?.description,
69
- metadata: opts?.metadata ? toStringMetadata(opts.metadata(body)) : undefined,
70
- idempotency_key: c.req.header("x-request-id") ?? c.req.header("idempotency-key") ?? undefined
71
- });
126
+ if (walletId) {
127
+ return handleCardPayment(c, next, walletId, effectiveAmount, currency, body, opts);
72
128
  }
73
- catch (error) {
74
- if (error instanceof AgentSpendChargeError) {
75
- if (error.statusCode === 402) {
76
- return c.json({ error: "Payment required", details: error.details }, 402);
77
- }
78
- return c.json({ error: error.message, details: error.details }, error.statusCode);
129
+ // Step 5: Neither → return 402 with Payment-Required header (Decision 8)
130
+ return return402Response(c, effectiveAmount, currency);
131
+ };
132
+ }
133
+ // -------------------------------------------------------------------
134
+ // handleCardPayment existing charge() flow
135
+ // -------------------------------------------------------------------
136
+ async function handleCardPayment(c, next, walletId, amountCents, currency, body, opts) {
137
+ if (!options.serviceApiKey) {
138
+ return c.json({ error: "Card payments require serviceApiKey" }, 500);
139
+ }
140
+ try {
141
+ const chargeResult = await charge(walletId, {
142
+ amount_cents: amountCents,
143
+ currency,
144
+ description: opts?.description,
145
+ metadata: opts?.metadata ? toStringMetadata(opts.metadata(body)) : undefined,
146
+ idempotency_key: c.req.header("x-request-id") ?? c.req.header("idempotency-key") ?? undefined
147
+ });
148
+ const paymentContext = {
149
+ method: "card",
150
+ amount_cents: amountCents,
151
+ currency,
152
+ wallet_id: walletId,
153
+ remaining_limit_cents: chargeResult.remaining_limit_cents
154
+ };
155
+ c.set(PAYMENT_CONTEXT_KEY, paymentContext);
156
+ }
157
+ catch (error) {
158
+ if (error instanceof AgentSpendChargeError) {
159
+ if (error.statusCode === 402) {
160
+ return c.json({ error: "Payment required", details: error.details }, 402);
79
161
  }
80
- return c.json({ error: "Unexpected paywall failure" }, 500);
162
+ return c.json({ error: error.message, details: error.details }, error.statusCode);
81
163
  }
164
+ return c.json({ error: "Unexpected paywall failure" }, 500);
165
+ }
166
+ await next();
167
+ }
168
+ // -------------------------------------------------------------------
169
+ // handleCryptoPayment — x402 verify + settle via facilitator
170
+ // -------------------------------------------------------------------
171
+ async function handleCryptoPayment(c, next, paymentHeader, amountCents, currency, _body, _opts) {
172
+ if (!facilitator) {
173
+ return c.json({ error: "Crypto payments not configured" }, 500);
174
+ }
175
+ try {
176
+ // Decode the x-payment header (base64 JSON payment payload)
177
+ let paymentPayload;
178
+ try {
179
+ paymentPayload = JSON.parse(Buffer.from(paymentHeader, "base64").toString("utf-8"));
180
+ }
181
+ catch {
182
+ return c.json({ error: "Invalid payment payload encoding" }, 400);
183
+ }
184
+ // Resolve the payTo address for verification context
185
+ const payTo = await resolvePayToAddress();
186
+ // Build the payment requirements that the payment should satisfy
187
+ const paymentRequirements = {
188
+ scheme: "exact",
189
+ network: cryptoNetwork,
190
+ amount: String(amountCents),
191
+ asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
192
+ payTo,
193
+ maxTimeoutSeconds: 300,
194
+ extra: {}
195
+ };
196
+ // Verify payment via facilitator
197
+ const verifyResult = await facilitator.verify(paymentPayload, paymentRequirements);
198
+ if (!verifyResult.isValid) {
199
+ return c.json({ error: "Payment verification failed", details: verifyResult.invalidReason }, 402);
200
+ }
201
+ // Settle payment via facilitator
202
+ const settleResult = await facilitator.settle(paymentPayload, paymentRequirements);
203
+ if (!settleResult.success) {
204
+ return c.json({ error: "Payment settlement failed", details: settleResult.errorReason }, 402);
205
+ }
206
+ const paymentContext = {
207
+ method: "crypto",
208
+ amount_cents: amountCents,
209
+ currency,
210
+ transaction_hash: settleResult.transaction,
211
+ payer_address: verifyResult.payer ?? undefined,
212
+ network: cryptoNetwork
213
+ };
214
+ c.set(PAYMENT_CONTEXT_KEY, paymentContext);
82
215
  await next();
83
- };
216
+ }
217
+ catch (error) {
218
+ if (error instanceof AgentSpendChargeError) {
219
+ return c.json({ error: error.message, details: error.details }, error.statusCode);
220
+ }
221
+ return c.json({ error: "Crypto payment processing failed", details: error.message }, 500);
222
+ }
223
+ }
224
+ // -------------------------------------------------------------------
225
+ // return402Response — x402 Payment-Required format (Decision 8)
226
+ // -------------------------------------------------------------------
227
+ async function return402Response(c, amountCents, currency) {
228
+ try {
229
+ const payTo = await resolvePayToAddress();
230
+ const paymentRequirements = {
231
+ scheme: "exact",
232
+ network: cryptoNetwork,
233
+ amount: String(amountCents),
234
+ asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
235
+ payTo,
236
+ maxTimeoutSeconds: 300,
237
+ extra: {}
238
+ };
239
+ // Build x402 v2 PaymentRequired response
240
+ const paymentRequired = {
241
+ x402Version: 2,
242
+ error: "Payment required",
243
+ resource: {
244
+ url: c.req.url,
245
+ description: `Payment of ${amountCents} cents`,
246
+ mimeType: "application/json"
247
+ },
248
+ accepts: [paymentRequirements]
249
+ };
250
+ // Set Payment-Required header (base64 encoded)
251
+ const headerValue = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
252
+ c.header("Payment-Required", headerValue);
253
+ return c.json({ error: "Payment required", amount_cents: amountCents, currency }, 402);
254
+ }
255
+ catch {
256
+ // If we can't resolve a payTo address, return a plain 402
257
+ return c.json({ error: "Payment required", amount_cents: amountCents, currency }, 402);
258
+ }
259
+ }
260
+ // -------------------------------------------------------------------
261
+ // resolvePayToAddress — static wallet or Stripe Machine Payments
262
+ // -------------------------------------------------------------------
263
+ async function resolvePayToAddress() {
264
+ // Static address for crypto-only services
265
+ if (options.crypto?.receiverAddress) {
266
+ return options.crypto.receiverAddress;
267
+ }
268
+ // Stripe Connect service → get deposit address from platform
269
+ if (options.serviceApiKey) {
270
+ const response = await fetchImpl(joinUrl(platformApiBaseUrl, "/v1/crypto/deposit-address"), {
271
+ method: "POST",
272
+ headers: {
273
+ authorization: `Bearer ${options.serviceApiKey}`,
274
+ "content-type": "application/json"
275
+ },
276
+ body: JSON.stringify({ amount_cents: 0, currency: "usd" })
277
+ });
278
+ if (!response.ok) {
279
+ throw new AgentSpendChargeError("Failed to resolve crypto deposit address", 502);
280
+ }
281
+ const data = (await response.json());
282
+ if (!data.deposit_address) {
283
+ throw new AgentSpendChargeError("No deposit address returned", 502);
284
+ }
285
+ return data.deposit_address;
286
+ }
287
+ throw new AgentSpendChargeError("No crypto payTo address available", 500);
84
288
  }
289
+ // -------------------------------------------------------------------
290
+ // Return public interface
291
+ // -------------------------------------------------------------------
85
292
  return {
86
293
  charge,
87
294
  paywall
88
295
  };
89
296
  }
297
+ // ---------------------------------------------------------------------------
298
+ // Helpers (unchanged from original)
299
+ // ---------------------------------------------------------------------------
90
300
  function toWalletId(input) {
91
301
  if (typeof input !== "string") {
92
302
  return null;
@@ -132,11 +342,9 @@ function resolvePlatformApiBaseUrl(explicitBaseUrl) {
132
342
  if (explicitBaseUrl && explicitBaseUrl.trim().length > 0) {
133
343
  return explicitBaseUrl.trim();
134
344
  }
135
- // Prefer environment config when running on Node/Bun. Guarded for non-Node runtimes.
136
345
  const envValue = typeof process !== "undefined" && process.env ? process.env.AGENTSPEND_API_URL : undefined;
137
346
  if (typeof envValue === "string" && envValue.trim().length > 0) {
138
347
  return envValue.trim();
139
348
  }
140
349
  return DEFAULT_PLATFORM_API_BASE_URL;
141
350
  }
142
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,24 +1,17 @@
1
1
  {
2
2
  "name": "@agentspend/sdk",
3
- "version": "0.1.1",
4
- "type": "module",
3
+ "version": "0.2.0",
5
4
  "main": "dist/index.js",
6
5
  "types": "dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "types": "./dist/index.d.ts",
10
- "default": "./dist/index.js"
11
- }
12
- },
13
- "files": [
14
- "dist"
15
- ],
16
6
  "publishConfig": {
17
7
  "access": "public"
18
8
  },
9
+ "dependencies": {
10
+ "@agentspend/types": "^0.2.0",
11
+ "@x402/core": "^2.3.1"
12
+ },
19
13
  "scripts": {
20
- "build": "tsc -p tsconfig.json",
21
- "prepublishOnly": "bun run build",
22
- "typecheck": "tsc --noEmit -p tsconfig.json"
14
+ "build": "tsc",
15
+ "typecheck": "tsc --noEmit"
23
16
  }
24
17
  }
package/src/index.ts ADDED
@@ -0,0 +1,565 @@
1
+ import type {
2
+ ChargeRequest,
3
+ ChargeResponse,
4
+ ErrorResponse,
5
+ PaymentMethod,
6
+ PaywallPaymentContext
7
+ } from "@agentspend/types";
8
+
9
+ export type {
10
+ ChargeRequest,
11
+ ChargeResponse,
12
+ ErrorResponse,
13
+ PaymentMethod,
14
+ PaywallPaymentContext
15
+ } from "@agentspend/types";
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // x402 imports – server-side only (HTTP calls to facilitator, no crypto deps)
19
+ // ---------------------------------------------------------------------------
20
+ import { HTTPFacilitatorClient, x402ResourceServer } from "@x402/core/server";
21
+ import type {
22
+ PaymentRequirements,
23
+ PaymentPayload,
24
+ VerifyResponse,
25
+ SettleResponse,
26
+ Network
27
+ } from "@x402/core/types";
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Options
31
+ // ---------------------------------------------------------------------------
32
+
33
+ export interface AgentSpendOptions {
34
+ /**
35
+ * Base URL for the AgentSpend Platform API.
36
+ *
37
+ * If omitted, the SDK will use `process.env.AGENTSPEND_API_URL` when available,
38
+ * otherwise it falls back to the hosted default.
39
+ */
40
+ platformApiBaseUrl?: string;
41
+ /** Service API key. Optional — crypto-only services don't need one. */
42
+ serviceApiKey?: string;
43
+ fetchImpl?: typeof fetch;
44
+ /** Crypto / x402 configuration. */
45
+ crypto?: {
46
+ /** Static payTo address for crypto-only services. */
47
+ receiverAddress?: string;
48
+ /** Chain identifier. Default: "eip155:8453" (Base). */
49
+ network?: string;
50
+ /** x402 facilitator URL. Default: "https://x402.org/facilitator". */
51
+ facilitatorUrl?: string;
52
+ };
53
+ }
54
+
55
+ export interface ChargeOptions {
56
+ amount_cents: number;
57
+ currency?: string;
58
+ description?: string;
59
+ metadata?: Record<string, string>;
60
+ idempotency_key?: string;
61
+ }
62
+
63
+ export class AgentSpendChargeError extends Error {
64
+ statusCode: number;
65
+ details: unknown;
66
+
67
+ constructor(message: string, statusCode: number, details?: unknown) {
68
+ super(message);
69
+ this.statusCode = statusCode;
70
+ this.details = details;
71
+ }
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Context abstraction (Hono-compatible)
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export interface HonoContextLike {
79
+ req: {
80
+ header(name: string): string | undefined;
81
+ json(): Promise<unknown>;
82
+ url: string;
83
+ method: string;
84
+ };
85
+ json(body: unknown, status?: number): unknown;
86
+ header(name: string, value: string): void;
87
+ set(key: string, value: unknown): void;
88
+ get(key: string): unknown;
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Paywall options
93
+ // ---------------------------------------------------------------------------
94
+
95
+ export interface PaywallOptions {
96
+ currency?: string;
97
+ description?: string;
98
+ metadata?: (body: unknown) => Record<string, unknown>;
99
+ /** Dynamic pricing: derive amount from the parsed request body. */
100
+ amountFromRequest?: (body: unknown) => number;
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Payment context helper
105
+ // ---------------------------------------------------------------------------
106
+
107
+ const PAYMENT_CONTEXT_KEY = "payment";
108
+
109
+ export function getPaymentContext(c: HonoContextLike): PaywallPaymentContext | null {
110
+ const ctx = c.get(PAYMENT_CONTEXT_KEY);
111
+ return (ctx as PaywallPaymentContext) ?? null;
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Public interface
116
+ // ---------------------------------------------------------------------------
117
+
118
+ export interface AgentSpend {
119
+ charge(walletId: string, opts: ChargeOptions): Promise<ChargeResponse>;
120
+ paywall(
121
+ amountCents: number,
122
+ opts?: PaywallOptions
123
+ ): (c: HonoContextLike, next: () => Promise<void>) => Promise<unknown>;
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Factory
128
+ // ---------------------------------------------------------------------------
129
+
130
+ export function createAgentSpend(options: AgentSpendOptions): AgentSpend {
131
+ // Validate: at least one of serviceApiKey or crypto must be provided
132
+ if (!options.serviceApiKey && !options.crypto) {
133
+ throw new AgentSpendChargeError(
134
+ "At least one of serviceApiKey or crypto config must be provided",
135
+ 500
136
+ );
137
+ }
138
+
139
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
140
+ if (!fetchImpl) {
141
+ throw new AgentSpendChargeError("No fetch implementation available", 500);
142
+ }
143
+
144
+ const platformApiBaseUrl = resolvePlatformApiBaseUrl(options.platformApiBaseUrl);
145
+
146
+ // -------------------------------------------------------------------
147
+ // x402 singleton setup (Decision 9)
148
+ // Server-side: facilitator handles verify + settle over HTTP.
149
+ // No client-side EVM scheme needed — we delegate to the facilitator.
150
+ // -------------------------------------------------------------------
151
+ let facilitator: HTTPFacilitatorClient | null = null;
152
+ let resourceServer: x402ResourceServer | null = null;
153
+ const cryptoNetwork: Network = (options.crypto?.network ?? "eip155:8453") as Network;
154
+
155
+ if (options.crypto || options.serviceApiKey) {
156
+ const facilitatorUrl =
157
+ options.crypto?.facilitatorUrl ?? "https://x402.org/facilitator";
158
+ facilitator = new HTTPFacilitatorClient({ url: facilitatorUrl });
159
+ resourceServer = new x402ResourceServer(facilitator);
160
+ }
161
+
162
+ // -------------------------------------------------------------------
163
+ // charge() — card-only, unchanged
164
+ // -------------------------------------------------------------------
165
+
166
+ async function charge(walletIdInput: string, opts: ChargeOptions): Promise<ChargeResponse> {
167
+ if (!options.serviceApiKey) {
168
+ throw new AgentSpendChargeError("charge() requires serviceApiKey", 500);
169
+ }
170
+
171
+ const walletId = toWalletId(walletIdInput);
172
+ if (!walletId) {
173
+ throw new AgentSpendChargeError("wallet_id must start with wal_", 400);
174
+ }
175
+ if (!Number.isInteger(opts.amount_cents) || opts.amount_cents <= 0) {
176
+ throw new AgentSpendChargeError("amount_cents must be a positive integer", 400);
177
+ }
178
+
179
+ const payload: ChargeRequest = {
180
+ wallet_id: walletId,
181
+ amount_cents: opts.amount_cents,
182
+ currency: opts.currency ?? "usd",
183
+ ...(opts.description ? { description: opts.description } : {}),
184
+ ...(opts.metadata ? { metadata: opts.metadata } : {}),
185
+ idempotency_key: opts.idempotency_key ?? bestEffortIdempotencyKey()
186
+ };
187
+
188
+ const response = await fetchImpl(joinUrl(platformApiBaseUrl, "/v1/charge"), {
189
+ method: "POST",
190
+ headers: {
191
+ authorization: `Bearer ${options.serviceApiKey}`,
192
+ "content-type": "application/json"
193
+ },
194
+ body: JSON.stringify(payload)
195
+ });
196
+
197
+ const responseBody = (await response.json().catch(() => ({}))) as Partial<ChargeResponse> &
198
+ Partial<ErrorResponse> &
199
+ Record<string, unknown>;
200
+ if (!response.ok) {
201
+ throw new AgentSpendChargeError(
202
+ typeof responseBody.error === "string" ? responseBody.error : "AgentSpend charge failed",
203
+ response.status,
204
+ responseBody
205
+ );
206
+ }
207
+
208
+ return responseBody as ChargeResponse;
209
+ }
210
+
211
+ // -------------------------------------------------------------------
212
+ // paywall() — unified card + crypto middleware
213
+ // -------------------------------------------------------------------
214
+
215
+ function paywall(amountCents: number, opts?: PaywallOptions) {
216
+ // Allow amountCents === 0 when amountFromRequest is provided (dynamic pricing)
217
+ if (!opts?.amountFromRequest) {
218
+ if (!Number.isInteger(amountCents) || amountCents <= 0) {
219
+ throw new AgentSpendChargeError("amountCents must be a positive integer", 500);
220
+ }
221
+ }
222
+
223
+ return async function paywallMiddleware(
224
+ c: HonoContextLike,
225
+ next: () => Promise<void>
226
+ ): Promise<unknown> {
227
+ // Step 1: Parse body once (Decision 11)
228
+ const body: unknown = await c.req.json().catch(() => ({}));
229
+
230
+ // Step 2: Determine effective amount
231
+ let effectiveAmount = amountCents;
232
+ if (opts?.amountFromRequest) {
233
+ effectiveAmount = opts.amountFromRequest(body);
234
+ if (!Number.isInteger(effectiveAmount) || effectiveAmount <= 0) {
235
+ return c.json({ error: "Could not determine payment amount from request" }, 400);
236
+ }
237
+ }
238
+
239
+ const currency = opts?.currency ?? "usd";
240
+
241
+ // Step 3: Check for x-payment header → crypto payment
242
+ const paymentHeader = c.req.header("x-payment");
243
+ if (paymentHeader) {
244
+ return handleCryptoPayment(c, next, paymentHeader, effectiveAmount, currency, body, opts);
245
+ }
246
+
247
+ // Step 4: Check for x-wallet-id header or body.wallet_id → card payment
248
+ const walletIdFromHeader = c.req.header("x-wallet-id");
249
+ let walletId = walletIdFromHeader ? toWalletId(walletIdFromHeader) : null;
250
+ if (!walletId) {
251
+ const bodyWalletId =
252
+ typeof (body as { wallet_id?: unknown })?.wallet_id === "string"
253
+ ? (body as { wallet_id: string }).wallet_id
254
+ : null;
255
+ walletId = toWalletId(bodyWalletId);
256
+ }
257
+
258
+ if (walletId) {
259
+ return handleCardPayment(c, next, walletId, effectiveAmount, currency, body, opts);
260
+ }
261
+
262
+ // Step 5: Neither → return 402 with Payment-Required header (Decision 8)
263
+ return return402Response(c, effectiveAmount, currency);
264
+ };
265
+ }
266
+
267
+ // -------------------------------------------------------------------
268
+ // handleCardPayment — existing charge() flow
269
+ // -------------------------------------------------------------------
270
+
271
+ async function handleCardPayment(
272
+ c: HonoContextLike,
273
+ next: () => Promise<void>,
274
+ walletId: string,
275
+ amountCents: number,
276
+ currency: string,
277
+ body: unknown,
278
+ opts?: PaywallOptions
279
+ ): Promise<unknown> {
280
+ if (!options.serviceApiKey) {
281
+ return c.json({ error: "Card payments require serviceApiKey" }, 500);
282
+ }
283
+
284
+ try {
285
+ const chargeResult = await charge(walletId, {
286
+ amount_cents: amountCents,
287
+ currency,
288
+ description: opts?.description,
289
+ metadata: opts?.metadata ? toStringMetadata(opts.metadata(body)) : undefined,
290
+ idempotency_key:
291
+ c.req.header("x-request-id") ?? c.req.header("idempotency-key") ?? undefined
292
+ });
293
+
294
+ const paymentContext: PaywallPaymentContext = {
295
+ method: "card",
296
+ amount_cents: amountCents,
297
+ currency,
298
+ wallet_id: walletId,
299
+ remaining_limit_cents: chargeResult.remaining_limit_cents
300
+ };
301
+ c.set(PAYMENT_CONTEXT_KEY, paymentContext);
302
+ } catch (error) {
303
+ if (error instanceof AgentSpendChargeError) {
304
+ if (error.statusCode === 402) {
305
+ return c.json({ error: "Payment required", details: error.details }, 402);
306
+ }
307
+ return c.json({ error: error.message, details: error.details }, error.statusCode);
308
+ }
309
+ return c.json({ error: "Unexpected paywall failure" }, 500);
310
+ }
311
+
312
+ await next();
313
+ }
314
+
315
+ // -------------------------------------------------------------------
316
+ // handleCryptoPayment — x402 verify + settle via facilitator
317
+ // -------------------------------------------------------------------
318
+
319
+ async function handleCryptoPayment(
320
+ c: HonoContextLike,
321
+ next: () => Promise<void>,
322
+ paymentHeader: string,
323
+ amountCents: number,
324
+ currency: string,
325
+ _body: unknown,
326
+ _opts?: PaywallOptions
327
+ ): Promise<unknown> {
328
+ if (!facilitator) {
329
+ return c.json({ error: "Crypto payments not configured" }, 500);
330
+ }
331
+
332
+ try {
333
+ // Decode the x-payment header (base64 JSON payment payload)
334
+ let paymentPayload: PaymentPayload;
335
+ try {
336
+ paymentPayload = JSON.parse(
337
+ Buffer.from(paymentHeader, "base64").toString("utf-8")
338
+ ) as PaymentPayload;
339
+ } catch {
340
+ return c.json({ error: "Invalid payment payload encoding" }, 400);
341
+ }
342
+
343
+ // Resolve the payTo address for verification context
344
+ const payTo = await resolvePayToAddress();
345
+
346
+ // Build the payment requirements that the payment should satisfy
347
+ const paymentRequirements: PaymentRequirements = {
348
+ scheme: "exact",
349
+ network: cryptoNetwork,
350
+ amount: String(amountCents),
351
+ asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
352
+ payTo,
353
+ maxTimeoutSeconds: 300,
354
+ extra: {}
355
+ };
356
+
357
+ // Verify payment via facilitator
358
+ const verifyResult: VerifyResponse = await facilitator.verify(
359
+ paymentPayload,
360
+ paymentRequirements
361
+ );
362
+
363
+ if (!verifyResult.isValid) {
364
+ return c.json(
365
+ { error: "Payment verification failed", details: verifyResult.invalidReason },
366
+ 402
367
+ );
368
+ }
369
+
370
+ // Settle payment via facilitator
371
+ const settleResult: SettleResponse = await facilitator.settle(
372
+ paymentPayload,
373
+ paymentRequirements
374
+ );
375
+
376
+ if (!settleResult.success) {
377
+ return c.json(
378
+ { error: "Payment settlement failed", details: settleResult.errorReason },
379
+ 402
380
+ );
381
+ }
382
+
383
+ const paymentContext: PaywallPaymentContext = {
384
+ method: "crypto",
385
+ amount_cents: amountCents,
386
+ currency,
387
+ transaction_hash: settleResult.transaction,
388
+ payer_address: verifyResult.payer ?? undefined,
389
+ network: cryptoNetwork
390
+ };
391
+ c.set(PAYMENT_CONTEXT_KEY, paymentContext);
392
+
393
+ await next();
394
+ } catch (error) {
395
+ if (error instanceof AgentSpendChargeError) {
396
+ return c.json({ error: error.message, details: error.details }, error.statusCode);
397
+ }
398
+ return c.json(
399
+ { error: "Crypto payment processing failed", details: (error as Error).message },
400
+ 500
401
+ );
402
+ }
403
+ }
404
+
405
+ // -------------------------------------------------------------------
406
+ // return402Response — x402 Payment-Required format (Decision 8)
407
+ // -------------------------------------------------------------------
408
+
409
+ async function return402Response(
410
+ c: HonoContextLike,
411
+ amountCents: number,
412
+ currency: string
413
+ ): Promise<unknown> {
414
+ try {
415
+ const payTo = await resolvePayToAddress();
416
+
417
+ const paymentRequirements: PaymentRequirements = {
418
+ scheme: "exact",
419
+ network: cryptoNetwork,
420
+ amount: String(amountCents),
421
+ asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
422
+ payTo,
423
+ maxTimeoutSeconds: 300,
424
+ extra: {}
425
+ };
426
+
427
+ // Build x402 v2 PaymentRequired response
428
+ const paymentRequired = {
429
+ x402Version: 2,
430
+ error: "Payment required",
431
+ resource: {
432
+ url: c.req.url,
433
+ description: `Payment of ${amountCents} cents`,
434
+ mimeType: "application/json"
435
+ },
436
+ accepts: [paymentRequirements]
437
+ };
438
+
439
+ // Set Payment-Required header (base64 encoded)
440
+ const headerValue = Buffer.from(
441
+ JSON.stringify(paymentRequired)
442
+ ).toString("base64");
443
+ c.header("Payment-Required", headerValue);
444
+
445
+ return c.json({ error: "Payment required", amount_cents: amountCents, currency }, 402);
446
+ } catch {
447
+ // If we can't resolve a payTo address, return a plain 402
448
+ return c.json(
449
+ { error: "Payment required", amount_cents: amountCents, currency },
450
+ 402
451
+ );
452
+ }
453
+ }
454
+
455
+ // -------------------------------------------------------------------
456
+ // resolvePayToAddress — static wallet or Stripe Machine Payments
457
+ // -------------------------------------------------------------------
458
+
459
+ async function resolvePayToAddress(): Promise<string> {
460
+ // Static address for crypto-only services
461
+ if (options.crypto?.receiverAddress) {
462
+ return options.crypto.receiverAddress;
463
+ }
464
+
465
+ // Stripe Connect service → get deposit address from platform
466
+ if (options.serviceApiKey) {
467
+ const response = await fetchImpl(
468
+ joinUrl(platformApiBaseUrl, "/v1/crypto/deposit-address"),
469
+ {
470
+ method: "POST",
471
+ headers: {
472
+ authorization: `Bearer ${options.serviceApiKey}`,
473
+ "content-type": "application/json"
474
+ },
475
+ body: JSON.stringify({ amount_cents: 0, currency: "usd" })
476
+ }
477
+ );
478
+
479
+ if (!response.ok) {
480
+ throw new AgentSpendChargeError("Failed to resolve crypto deposit address", 502);
481
+ }
482
+
483
+ const data = (await response.json()) as { deposit_address?: string };
484
+ if (!data.deposit_address) {
485
+ throw new AgentSpendChargeError("No deposit address returned", 502);
486
+ }
487
+ return data.deposit_address;
488
+ }
489
+
490
+ throw new AgentSpendChargeError("No crypto payTo address available", 500);
491
+ }
492
+
493
+ // -------------------------------------------------------------------
494
+ // Return public interface
495
+ // -------------------------------------------------------------------
496
+
497
+ return {
498
+ charge,
499
+ paywall
500
+ };
501
+ }
502
+
503
+ // ---------------------------------------------------------------------------
504
+ // Helpers (unchanged from original)
505
+ // ---------------------------------------------------------------------------
506
+
507
+ function toWalletId(input: unknown): string | null {
508
+ if (typeof input !== "string") {
509
+ return null;
510
+ }
511
+ const trimmed = input.trim();
512
+ if (!trimmed.startsWith("wal_")) {
513
+ return null;
514
+ }
515
+ return trimmed;
516
+ }
517
+
518
+ function joinUrl(base: string, path: string): string {
519
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
520
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
521
+ return `${normalizedBase}${normalizedPath}`;
522
+ }
523
+
524
+ function bestEffortIdempotencyKey(): string {
525
+ const uuid = globalThis.crypto?.randomUUID?.();
526
+ if (uuid) {
527
+ return uuid;
528
+ }
529
+ return `auto_${Date.now()}_${Math.random().toString(16).slice(2)}`;
530
+ }
531
+
532
+ function toStringMetadata(input: unknown): Record<string, string> {
533
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
534
+ return {};
535
+ }
536
+
537
+ const result: Record<string, string> = {};
538
+ for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
539
+ if (typeof value === "string") {
540
+ result[key] = value;
541
+ } else if (typeof value === "number" && Number.isFinite(value)) {
542
+ result[key] = String(value);
543
+ } else if (typeof value === "boolean") {
544
+ result[key] = value ? "true" : "false";
545
+ }
546
+ }
547
+ return result;
548
+ }
549
+
550
+ const DEFAULT_PLATFORM_API_BASE_URL = "https://api.agentspend.co";
551
+
552
+ function resolvePlatformApiBaseUrl(explicitBaseUrl: string | undefined): string {
553
+ if (explicitBaseUrl && explicitBaseUrl.trim().length > 0) {
554
+ return explicitBaseUrl.trim();
555
+ }
556
+
557
+ const envValue =
558
+ typeof process !== "undefined" && process.env ? process.env.AGENTSPEND_API_URL : undefined;
559
+
560
+ if (typeof envValue === "string" && envValue.trim().length > 0) {
561
+ return envValue.trim();
562
+ }
563
+
564
+ return DEFAULT_PLATFORM_API_BASE_URL;
565
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,wBAAwB,EAAE,MAAM,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAMD,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;gBAEL,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAKnE;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE;QACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACzC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;KAC1B,CAAC;IACF,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACvE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1H;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAgGvE"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}