@agentspend/sdk 0.1.2 → 0.3.1

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