@asgcard/pay 0.1.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/LICENSE +21 -0
  3. package/README.md +393 -0
  4. package/dist/cjs/adapters/base.js +174 -0
  5. package/dist/cjs/adapters/base.js.map +1 -0
  6. package/dist/cjs/adapters/evm.js +263 -0
  7. package/dist/cjs/adapters/evm.js.map +1 -0
  8. package/dist/cjs/adapters/index.js +13 -0
  9. package/dist/cjs/adapters/index.js.map +1 -0
  10. package/dist/cjs/adapters/stellar.js +173 -0
  11. package/dist/cjs/adapters/stellar.js.map +1 -0
  12. package/dist/cjs/adapters/stripe.js +338 -0
  13. package/dist/cjs/adapters/stripe.js.map +1 -0
  14. package/dist/cjs/adapters/types.js +3 -0
  15. package/dist/cjs/adapters/types.js.map +1 -0
  16. package/dist/cjs/client.js +309 -0
  17. package/dist/cjs/client.js.map +1 -0
  18. package/dist/cjs/index.js +36 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/logger.js +7 -0
  21. package/dist/cjs/logger.js.map +1 -0
  22. package/dist/cjs/mpp.js +187 -0
  23. package/dist/cjs/mpp.js.map +1 -0
  24. package/dist/cjs/policy.js +71 -0
  25. package/dist/cjs/policy.js.map +1 -0
  26. package/dist/cjs/stellar.js +87 -0
  27. package/dist/cjs/stellar.js.map +1 -0
  28. package/dist/esm/adapters/base.js +170 -0
  29. package/dist/esm/adapters/base.js.map +1 -0
  30. package/dist/esm/adapters/evm.js +258 -0
  31. package/dist/esm/adapters/evm.js.map +1 -0
  32. package/dist/esm/adapters/index.js +5 -0
  33. package/dist/esm/adapters/index.js.map +1 -0
  34. package/dist/esm/adapters/stellar.js +136 -0
  35. package/dist/esm/adapters/stellar.js.map +1 -0
  36. package/dist/esm/adapters/stripe.js +334 -0
  37. package/dist/esm/adapters/stripe.js.map +1 -0
  38. package/dist/esm/adapters/types.js +2 -0
  39. package/dist/esm/adapters/types.js.map +1 -0
  40. package/dist/esm/client.js +302 -0
  41. package/dist/esm/client.js.map +1 -0
  42. package/dist/esm/index.js +16 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/logger.js +3 -0
  45. package/dist/esm/logger.js.map +1 -0
  46. package/dist/esm/mpp.js +175 -0
  47. package/dist/esm/mpp.js.map +1 -0
  48. package/dist/esm/policy.js +67 -0
  49. package/dist/esm/policy.js.map +1 -0
  50. package/dist/esm/stellar.js +50 -0
  51. package/dist/esm/stellar.js.map +1 -0
  52. package/dist/types/adapters/base.d.ts +53 -0
  53. package/dist/types/adapters/base.d.ts.map +1 -0
  54. package/dist/types/adapters/evm.d.ts +81 -0
  55. package/dist/types/adapters/evm.d.ts.map +1 -0
  56. package/dist/types/adapters/index.d.ts +6 -0
  57. package/dist/types/adapters/index.d.ts.map +1 -0
  58. package/dist/types/adapters/stellar.d.ts +67 -0
  59. package/dist/types/adapters/stellar.d.ts.map +1 -0
  60. package/dist/types/adapters/stripe.d.ts +206 -0
  61. package/dist/types/adapters/stripe.d.ts.map +1 -0
  62. package/dist/types/adapters/types.d.ts +35 -0
  63. package/dist/types/adapters/types.d.ts.map +1 -0
  64. package/dist/types/client.d.ts +89 -0
  65. package/dist/types/client.d.ts.map +1 -0
  66. package/dist/types/index.d.ts +15 -0
  67. package/dist/types/index.d.ts.map +1 -0
  68. package/dist/types/logger.d.ts +10 -0
  69. package/dist/types/logger.d.ts.map +1 -0
  70. package/dist/types/mpp.d.ts +153 -0
  71. package/dist/types/mpp.d.ts.map +1 -0
  72. package/dist/types/policy.d.ts +40 -0
  73. package/dist/types/policy.d.ts.map +1 -0
  74. package/dist/types/stellar.d.ts +27 -0
  75. package/dist/types/stellar.d.ts.map +1 -0
  76. package/package.json +86 -0
@@ -0,0 +1,334 @@
1
+ import { noopLogger } from '../logger';
2
+ import { buildMppCredential, base64urlEncode, } from '../mpp';
3
+ /**
4
+ * Required Stripe API version for MPP features.
5
+ * @see https://docs.stripe.com/payments/machine/mpp
6
+ */
7
+ const STRIPE_API_VERSION = '2026-03-04.preview';
8
+ // ─── Stripe MPP Adapter ─────────────────────────────────────────────
9
+ /**
10
+ * StripePaymentAdapter — Machine Payments Protocol (MPP) settlement.
11
+ *
12
+ * Implements the full MPP flow for Stripe:
13
+ * 1. Receives 402 + `WWW-Authenticate: Payment` challenge
14
+ * 2. Creates a Shared Payment Token (SPT) via Stripe API
15
+ * 3. Builds MPP Credential with SPT
16
+ * 4. Returns credential for `Authorization: Payment` header
17
+ *
18
+ * Works in two modes:
19
+ * - **Autonomous** (with paymentMethodId): Creates SPTs automatically
20
+ * - **Delegated** (without): Requires external SPT provisioning via `setSptToken()`
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Autonomous mode — agent has a payment method on file
25
+ * const adapter = new StripePaymentAdapter({
26
+ * stripeSecretKey: 'sk_test_...',
27
+ * networkId: 'my-network',
28
+ * paymentMethodId: 'pm_card_visa', // test card
29
+ * });
30
+ *
31
+ * // Delegated mode — external system provides SPTs
32
+ * const adapter = new StripePaymentAdapter({
33
+ * stripeSecretKey: 'sk_test_...',
34
+ * });
35
+ * adapter.setSptToken('spt_...');
36
+ * ```
37
+ *
38
+ * @see https://mpp.dev/payment-methods/stripe — MPP Stripe method spec
39
+ * @see https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens
40
+ */
41
+ export class StripePaymentAdapter {
42
+ chainName = 'Stripe MPP';
43
+ caip2Id;
44
+ secretKey;
45
+ networkId;
46
+ paymentMethodTypes;
47
+ paymentMethodId;
48
+ currency;
49
+ sptExpirySeconds;
50
+ stripeApiBase;
51
+ log;
52
+ /** Externally-provided SPT for delegated mode */
53
+ externalSpt = null;
54
+ /** Last MPP challenge processed (for credential building) */
55
+ lastChallenge = null;
56
+ constructor(options) {
57
+ if (!options.stripeSecretKey) {
58
+ throw new Error('StripePaymentAdapter requires a stripeSecretKey. ' +
59
+ 'Get one from https://dashboard.stripe.com/apikeys');
60
+ }
61
+ this.secretKey = options.stripeSecretKey;
62
+ this.networkId = options.networkId ?? 'internal';
63
+ this.paymentMethodTypes = options.paymentMethodTypes ?? ['card'];
64
+ this.paymentMethodId = options.paymentMethodId ?? null;
65
+ this.currency = options.currency ?? 'usd';
66
+ this.sptExpirySeconds = options.sptExpirySeconds ?? 300;
67
+ this.stripeApiBase = options.stripeApiBase ?? 'https://api.stripe.com';
68
+ this.log = options.logger ?? noopLogger;
69
+ // CAIP-2-style identifier for Stripe
70
+ this.caip2Id = this.secretKey.startsWith('sk_live')
71
+ ? 'stripe:live'
72
+ : 'stripe:test';
73
+ }
74
+ /**
75
+ * Set or update an externally-provided SPT token.
76
+ * Used in delegated mode when an external system provides the SPT.
77
+ */
78
+ setSptToken(token) {
79
+ this.externalSpt = token;
80
+ this.log(`[Stripe/MPP] 🔑 External SPT set: ${token.slice(0, 16)}…`);
81
+ }
82
+ /**
83
+ * Store a parsed MPP challenge for credential building.
84
+ * Called by OwsClient when a 402 with `WWW-Authenticate: Payment method="stripe"` is received.
85
+ */
86
+ setMppChallenge(challenge) {
87
+ this.lastChallenge = challenge;
88
+ this.log(`[Stripe/MPP] 📋 Challenge received: id=${challenge.id}, realm=${challenge.realm}`);
89
+ }
90
+ /**
91
+ * Get the last built MPP credential (base64url-encoded).
92
+ * Used by OwsClient to populate the `Authorization: Payment` header.
93
+ */
94
+ getLastCredential() {
95
+ return this._lastCredential;
96
+ }
97
+ _lastCredential = null;
98
+ getAddress() {
99
+ if (this.externalSpt)
100
+ return `spt:${this.externalSpt.slice(0, 16)}…`;
101
+ if (this.paymentMethodId)
102
+ return `pm:${this.paymentMethodId.slice(0, 16)}…`;
103
+ return `stripe:${this.secretKey.slice(0, 12)}…`;
104
+ }
105
+ /**
106
+ * Execute a Stripe MPP payment.
107
+ *
108
+ * This method handles the full SPT lifecycle:
109
+ * 1. If an external SPT is set → use it directly
110
+ * 2. If paymentMethodId is set → create SPT autonomously via Stripe API
111
+ * 3. Build MPP Credential with the SPT
112
+ * 4. Return credential string (used as "tx hash" equivalent)
113
+ *
114
+ * @param destination - Recipient (Stripe account or payment endpoint)
115
+ * @param amount - Amount in smallest currency unit (e.g. cents)
116
+ * @param network - Network identifier (ignored for Stripe, kept for interface compat)
117
+ * @returns Base64url-encoded MPP credential string, or null on failure
118
+ */
119
+ async pay(destination, amount, network) {
120
+ const tag = '[Stripe/MPP]';
121
+ try {
122
+ const amountInt = parseInt(amount, 10);
123
+ if (isNaN(amountInt) || amountInt <= 0) {
124
+ this.log(`${tag} ❌ Invalid amount: ${amount}`);
125
+ return null;
126
+ }
127
+ const formatted = `$${(amountInt / 100).toFixed(2)}`;
128
+ this.log(`${tag} 🚀 ${formatted} (${this.currency.toUpperCase()}) → ${destination.slice(0, 20)}…`);
129
+ // ── Step 1: Get or create SPT ──────────────────────────────
130
+ let sptId;
131
+ if (this.externalSpt) {
132
+ // Delegated mode: use externally-provided SPT
133
+ sptId = this.externalSpt;
134
+ this.log(`${tag} 🔑 Using external SPT: ${sptId.slice(0, 16)}…`);
135
+ }
136
+ else if (this.paymentMethodId) {
137
+ // Autonomous mode: create SPT via Stripe API
138
+ this.log(`${tag} 🔧 Creating SPT for pm: ${this.paymentMethodId.slice(0, 16)}…`);
139
+ const spt = await this.createSpt(amountInt, this.currency);
140
+ if (!spt) {
141
+ this.log(`${tag} ❌ SPT creation failed`);
142
+ return null;
143
+ }
144
+ sptId = spt.id;
145
+ this.log(`${tag} ✅ SPT created: ${sptId.slice(0, 20)}…`);
146
+ }
147
+ else {
148
+ this.log(`${tag} ❌ No SPT and no paymentMethodId — cannot pay autonomously`);
149
+ return null;
150
+ }
151
+ // ── Step 2: Build MPP Credential ───────────────────────────
152
+ if (this.lastChallenge) {
153
+ // Full MPP flow: build proper credential with challenge echo
154
+ const credential = buildMppCredential(this.lastChallenge, this.getAddress(), { spt: sptId });
155
+ this._lastCredential = credential;
156
+ this.log(`${tag} 📝 Credential built (challenge: ${this.lastChallenge.id})`);
157
+ return credential;
158
+ }
159
+ else {
160
+ // Fallback: SPT-only mode (direct Stripe API, no MPP challenge)
161
+ // Create PaymentIntent directly
162
+ this.log(`${tag} 💳 No MPP challenge — creating PaymentIntent directly`);
163
+ const pi = await this.createPaymentIntent(sptId, amountInt, this.currency);
164
+ if (!pi) {
165
+ this.log(`${tag} ❌ PaymentIntent creation failed`);
166
+ return null;
167
+ }
168
+ this.log(`${tag} ✅ PaymentIntent: ${pi.id} (${pi.status})`);
169
+ return pi.id;
170
+ }
171
+ }
172
+ catch (error) {
173
+ this.log(`${tag} ❌ ${error.message}`);
174
+ return null;
175
+ }
176
+ }
177
+ // ─── Stripe API Methods ─────────────────────────────────────────
178
+ /**
179
+ * Create a Shared Payment Token (SPT) via Stripe API.
180
+ *
181
+ * Test mode: POST /v1/test_helpers/shared_payment/granted_tokens
182
+ * Live mode: POST /v1/shared_payment/issued_tokens (TBD by Stripe)
183
+ */
184
+ async createSpt(amount, currency) {
185
+ const isTest = this.secretKey.startsWith('sk_test');
186
+ const endpoint = isTest
187
+ ? '/v1/test_helpers/shared_payment/granted_tokens'
188
+ : '/v1/shared_payment/issued_tokens';
189
+ const expiresAt = Math.floor((Date.now() + this.sptExpirySeconds * 1000) / 1000);
190
+ const body = new URLSearchParams();
191
+ body.append('payment_method', this.paymentMethodId);
192
+ body.append('usage_limits[currency]', currency);
193
+ body.append('usage_limits[max_amount]', amount.toString());
194
+ body.append('usage_limits[expires_at]', expiresAt.toString());
195
+ try {
196
+ const response = await fetch(`${this.stripeApiBase}${endpoint}`, {
197
+ method: 'POST',
198
+ headers: {
199
+ 'Authorization': `Basic ${Buffer.from(`${this.secretKey}:`).toString('base64')}`,
200
+ 'Content-Type': 'application/x-www-form-urlencoded',
201
+ 'Stripe-Version': STRIPE_API_VERSION,
202
+ },
203
+ body,
204
+ });
205
+ if (!response.ok) {
206
+ const error = await response.json();
207
+ this.log(`[Stripe/MPP] ❌ SPT API error: ${error?.error?.message || response.statusText}`);
208
+ return null;
209
+ }
210
+ return await response.json();
211
+ }
212
+ catch (error) {
213
+ this.log(`[Stripe/MPP] ❌ SPT request failed: ${error.message}`);
214
+ return null;
215
+ }
216
+ }
217
+ /**
218
+ * Create and confirm a PaymentIntent using an SPT.
219
+ * Used in fallback mode (no MPP challenge, direct Stripe API).
220
+ */
221
+ async createPaymentIntent(sptId, amount, currency) {
222
+ const body = new URLSearchParams();
223
+ body.append('amount', amount.toString());
224
+ body.append('currency', currency);
225
+ body.append('confirm', 'true');
226
+ body.append('automatic_payment_methods[enabled]', 'true');
227
+ body.append('payment_method_data[shared_payment_granted_token]', sptId);
228
+ body.append('metadata[protocol]', 'mpp');
229
+ body.append('metadata[agent]', 'asgcard-pay');
230
+ try {
231
+ const response = await fetch(`${this.stripeApiBase}/v1/payment_intents`, {
232
+ method: 'POST',
233
+ headers: {
234
+ 'Authorization': `Basic ${Buffer.from(`${this.secretKey}:`).toString('base64')}`,
235
+ 'Content-Type': 'application/x-www-form-urlencoded',
236
+ 'Stripe-Version': STRIPE_API_VERSION,
237
+ },
238
+ body,
239
+ });
240
+ if (!response.ok) {
241
+ const error = await response.json();
242
+ this.log(`[Stripe/MPP] ❌ PI error: ${error?.error?.message || response.statusText}`);
243
+ return null;
244
+ }
245
+ return await response.json();
246
+ }
247
+ catch (error) {
248
+ this.log(`[Stripe/MPP] ❌ PI request failed: ${error.message}`);
249
+ return null;
250
+ }
251
+ }
252
+ /**
253
+ * Create a crypto deposit-mode PaymentIntent.
254
+ * This is the verified-working flow for MPP on Stripe.
255
+ * Uses Tempo network + USDC stablecoins.
256
+ *
257
+ * @param amount - Amount in cents (e.g. 100 = $1.00)
258
+ * @param currency - Currency code (default: 'usd')
259
+ * @returns PaymentIntentResult with deposit address, or null on failure
260
+ *
261
+ * @example
262
+ * ```ts
263
+ * const pi = await adapter.createCryptoPaymentIntent(100, 'usd');
264
+ * // pi.next_action.crypto_display_details.deposit_addresses.tempo.address
265
+ * ```
266
+ */
267
+ async createCryptoPaymentIntent(amount, currency = 'usd') {
268
+ const body = new URLSearchParams();
269
+ body.append('amount', amount.toString());
270
+ body.append('currency', currency);
271
+ body.append('payment_method_types[]', 'crypto');
272
+ body.append('payment_method_data[type]', 'crypto');
273
+ body.append('payment_method_options[crypto][mode]', 'deposit');
274
+ body.append('payment_method_options[crypto][deposit_options][networks][]', 'tempo');
275
+ body.append('confirm', 'true');
276
+ body.append('metadata[protocol]', 'mpp');
277
+ body.append('metadata[agent]', 'asgcard-pay');
278
+ try {
279
+ const response = await fetch(`${this.stripeApiBase}/v1/payment_intents`, {
280
+ method: 'POST',
281
+ headers: {
282
+ 'Authorization': `Basic ${Buffer.from(`${this.secretKey}:`).toString('base64')}`,
283
+ 'Content-Type': 'application/x-www-form-urlencoded',
284
+ 'Stripe-Version': STRIPE_API_VERSION,
285
+ },
286
+ body,
287
+ });
288
+ if (!response.ok) {
289
+ const error = await response.json();
290
+ this.log(`[Stripe/MPP] ❌ Crypto PI error: ${error?.error?.message || response.statusText}`);
291
+ return null;
292
+ }
293
+ const pi = await response.json();
294
+ this.log(`[Stripe/MPP] ✅ Crypto PI: ${pi.id} (${pi.status})`);
295
+ // Extract deposit address if available
296
+ const depositAddr = pi.next_action?.crypto_display_details?.deposit_addresses?.tempo?.address;
297
+ if (depositAddr) {
298
+ this.log(`[Stripe/MPP] 📬 Deposit → ${depositAddr}`);
299
+ }
300
+ return pi;
301
+ }
302
+ catch (error) {
303
+ this.log(`[Stripe/MPP] ❌ Crypto PI request failed: ${error.message}`);
304
+ return null;
305
+ }
306
+ }
307
+ /**
308
+ * Build and return a fully-formed MPP Challenge for server-side use.
309
+ * Useful if you want to GATE your own API with MPP 402.
310
+ *
311
+ * @param amount - Amount to charge (in smallest unit)
312
+ * @param options - Challenge options
313
+ * @returns WWW-Authenticate header value
314
+ */
315
+ buildServerChallenge(amount, options = {}) {
316
+ const currency = options.currency ?? this.currency;
317
+ const expires = new Date(Date.now() + (options.expiresInSeconds ?? this.sptExpirySeconds) * 1000).toISOString();
318
+ // Build the request object
319
+ const requestObj = {
320
+ amount,
321
+ currency,
322
+ description: options.description,
323
+ methodDetails: {
324
+ networkId: this.networkId,
325
+ paymentMethodTypes: this.paymentMethodTypes,
326
+ },
327
+ };
328
+ const requestB64 = base64urlEncode(JSON.stringify(requestObj));
329
+ // Generate challenge ID (HMAC-bound in production, random for now)
330
+ const challengeId = `ch_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
331
+ return `Payment id="${challengeId}", realm="pay.asgcard.dev", method="stripe", intent="charge", expires="${expires}", request="${requestB64}"`;
332
+ }
333
+ }
334
+ //# sourceMappingURL=stripe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stripe.js","sourceRoot":"","sources":["../../../src/adapters/stripe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/adapters/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,302 @@
1
+ import axios from 'axios';
2
+ import { PolicyEngine } from './policy';
3
+ import { noopLogger } from './logger';
4
+ import { detectProtocol, extractMppChallenges, decodeChallengeRequest, } from './mpp';
5
+ /**
6
+ * OwsClient — Dual-protocol autonomous HTTP client for AI agents.
7
+ *
8
+ * Supports BOTH payment protocols:
9
+ * - **x402** (Coinbase/Cloudflare): JSON body challenges + X-PAYMENT header
10
+ * - **MPP** (Stripe/Tempo): WWW-Authenticate challenges + Authorization: Payment header
11
+ *
12
+ * Wraps Axios with an interceptor that automatically detects the protocol,
13
+ * validates spend against PolicyEngine, settles via pluggable adapter,
14
+ * and retries — all without human interaction.
15
+ *
16
+ * @see https://openwallet.sh — Open Wallet Standard specification
17
+ * @see https://x402.org — x402 payment protocol
18
+ * @see https://mpp.dev — Machine Payments Protocol
19
+ * @see https://pay.asgcard.dev — Production ASG Pay infrastructure
20
+ */
21
+ export class OwsClient {
22
+ api;
23
+ policyEngine;
24
+ adapter;
25
+ log;
26
+ constructor(options) {
27
+ this.log = options.logger ?? noopLogger;
28
+ this.policyEngine = new PolicyEngine(options.policy, this.log);
29
+ this.adapter = options.adapter;
30
+ this.api = axios.create({
31
+ baseURL: options.baseURL,
32
+ });
33
+ // ── Axios Interceptor: Dual-Protocol 402 Handler ────────────────
34
+ this.api.interceptors.response.use((response) => response, async (error) => {
35
+ if (error.response && error.response.status === 402) {
36
+ return this.handle402(error);
37
+ }
38
+ return Promise.reject(error);
39
+ });
40
+ }
41
+ /**
42
+ * Handle a 402 Payment Required challenge.
43
+ * Auto-detects protocol (MPP vs x402) and routes accordingly.
44
+ */
45
+ async handle402(error) {
46
+ this.log('[OWS] ⚡ Received 402 Payment Required');
47
+ // Detect protocol from response
48
+ const headers = error.response.headers;
49
+ const body = error.response.data;
50
+ const protocol = detectProtocol(headers, body);
51
+ this.log(`[OWS] 🔍 Protocol detected: ${protocol}`);
52
+ switch (protocol) {
53
+ case 'mpp':
54
+ return this.handleMpp402(error, headers);
55
+ case 'x402':
56
+ return this.handleX402(error);
57
+ default:
58
+ throw new Error('Unrecognisable 402 challenge — neither x402 nor MPP.');
59
+ }
60
+ }
61
+ // ─── MPP Protocol Handler ─────────────────────────────────────────
62
+ /**
63
+ * Handle an MPP 402: parse WWW-Authenticate, create credential, retry.
64
+ */
65
+ async handleMpp402(error, headers) {
66
+ this.log('[OWS/MPP] 🏛️ Processing MPP challenge');
67
+ // ── Step 1: Parse challenges from WWW-Authenticate headers ────
68
+ const challenges = extractMppChallenges(headers);
69
+ if (challenges.length === 0) {
70
+ throw new Error('MPP 402 but no valid Payment challenges in WWW-Authenticate header.');
71
+ }
72
+ // Select the best challenge (prefer stripe if adapter is Stripe, else first)
73
+ const challenge = this.selectChallenge(challenges);
74
+ this.log(`[OWS/MPP] 📋 Challenge: method=${challenge.method}, id=${challenge.id}, realm=${challenge.realm}`);
75
+ // ── Step 2: Check expiration ──────────────────────────────────
76
+ if (challenge.expires) {
77
+ const expiresAt = new Date(challenge.expires).getTime();
78
+ if (Date.now() > expiresAt) {
79
+ this.log('[OWS/MPP] 🛑 Challenge expired');
80
+ return Promise.reject(error);
81
+ }
82
+ }
83
+ // ── Step 3: Decode request and extract amount ─────────────────
84
+ const request = decodeChallengeRequest(challenge);
85
+ const requestedUsdAmount = this.extractMppUsdAmount(request);
86
+ this.log(`[OWS/MPP] 💰 Amount: $${requestedUsdAmount} ${request.currency?.toUpperCase() || 'USD'}`);
87
+ // ── Step 4: Policy gate ───────────────────────────────────────
88
+ if (!this.policyEngine.checkPolicy(requestedUsdAmount, request.recipient)) {
89
+ this.log('[OWS/MPP] 🛑 REJECTED by PolicyEngine');
90
+ return Promise.reject(error);
91
+ }
92
+ this.log('[OWS/MPP] ✅ Policy passed — settling via adapter…');
93
+ // ── Step 5: Pass challenge to adapter (if Stripe) ─────────────
94
+ if (this.isStripeAdapter(this.adapter)) {
95
+ this.adapter.setMppChallenge(challenge);
96
+ }
97
+ // ── Step 6: Settle via adapter ────────────────────────────────
98
+ const result = await this.adapter.pay(request.recipient || challenge.realm, request.amount, challenge.method);
99
+ if (!result) {
100
+ this.log('[OWS/MPP] ❌ Settlement failed');
101
+ return Promise.reject(error);
102
+ }
103
+ // ── Step 7: Record spend ──────────────────────────────────────
104
+ this.policyEngine.recordSpend(requestedUsdAmount);
105
+ // ── Step 8: Build Authorization header ────────────────────────
106
+ // For Stripe adapter: result IS the base64url credential
107
+ // For others: wrap result as a generic credential
108
+ let authHeader;
109
+ if (this.isStripeAdapter(this.adapter) && this.adapter.getLastCredential()) {
110
+ authHeader = `Payment ${this.adapter.getLastCredential()}`;
111
+ }
112
+ else {
113
+ // Generic credential for non-Stripe MPP methods
114
+ const credentialPayload = {
115
+ challenge: {
116
+ id: challenge.id,
117
+ realm: challenge.realm,
118
+ method: challenge.method,
119
+ intent: challenge.intent,
120
+ request: challenge.request,
121
+ ...(challenge.expires ? { expires: challenge.expires } : {}),
122
+ },
123
+ source: this.adapter.getAddress(),
124
+ payload: { transaction: result },
125
+ };
126
+ const encoded = Buffer.from(JSON.stringify(credentialPayload))
127
+ .toString('base64')
128
+ .replace(/\+/g, '-')
129
+ .replace(/\//g, '_')
130
+ .replace(/=+$/, '');
131
+ authHeader = `Payment ${encoded}`;
132
+ }
133
+ this.log(`[OWS/MPP] 🔁 Retrying with Authorization: Payment …`);
134
+ // ── Step 9: Retry original request ────────────────────────────
135
+ const originalRequest = error.config;
136
+ if (!originalRequest) {
137
+ throw new Error('Cannot retry — original request config is missing.');
138
+ }
139
+ if (!originalRequest.headers) {
140
+ originalRequest.headers = {};
141
+ }
142
+ originalRequest.headers['Authorization'] = authHeader;
143
+ return this.api.request(originalRequest);
144
+ }
145
+ // ─── x402 Protocol Handler ────────────────────────────────────────
146
+ /**
147
+ * Handle an x402 402: parse JSON body, settle on-chain, retry with X-PAYMENT.
148
+ */
149
+ async handleX402(error) {
150
+ this.log('[OWS/x402] ⛓️ Processing x402 challenge');
151
+ const challenge = error.response.data;
152
+ // Validate x402 envelope
153
+ if (!challenge.x402Version && !challenge.accepts) {
154
+ throw new Error('Unrecognisable 402 challenge — not x402-compliant.');
155
+ }
156
+ if (!challenge.accepts || challenge.accepts.length === 0) {
157
+ throw new Error('402 challenge has no accepts[] entries.');
158
+ }
159
+ const acceptRules = challenge.accepts[0];
160
+ const { payTo, amount: atomicAmount, network } = acceptRules;
161
+ // ── Extract USD amount ────────────────────────────────────────
162
+ const requestedUsdAmount = this.extractUsdAmount(challenge, acceptRules);
163
+ this.log(`[OWS/x402] 💰 Amount: $${requestedUsdAmount} → ${payTo.slice(0, 10)}…`);
164
+ this.log(`[OWS/x402] ⛓️ Chain: ${this.adapter.chainName} (${this.adapter.caip2Id})`);
165
+ // ── Policy gate ───────────────────────────────────────────────
166
+ if (!this.policyEngine.checkPolicy(requestedUsdAmount, payTo)) {
167
+ this.log('[OWS/x402] 🛑 REJECTED by PolicyEngine');
168
+ return Promise.reject(error);
169
+ }
170
+ this.log('[OWS/x402] ✅ Policy passed — settling on-chain…');
171
+ // ── On-chain settlement ───────────────────────────────────────
172
+ const txHash = await this.adapter.pay(payTo, atomicAmount, network);
173
+ if (!txHash) {
174
+ this.log('[OWS/x402] ❌ Settlement failed');
175
+ return Promise.reject(error);
176
+ }
177
+ // ── Record spend ──────────────────────────────────────────────
178
+ this.policyEngine.recordSpend(requestedUsdAmount);
179
+ // ── Build X-PAYMENT proof ─────────────────────────────────────
180
+ const paymentPayload = {
181
+ x402Version: challenge.x402Version || 2,
182
+ accepted: {
183
+ scheme: acceptRules.scheme,
184
+ network: acceptRules.network,
185
+ amount: acceptRules.amount,
186
+ payTo: acceptRules.payTo,
187
+ asset: acceptRules.asset,
188
+ },
189
+ payload: {
190
+ transaction: txHash,
191
+ chain: this.adapter.caip2Id,
192
+ },
193
+ };
194
+ const tokenBase64 = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
195
+ this.log(`[OWS/x402] 🔁 Retrying with X-PAYMENT (tx: ${txHash.slice(0, 16)}…)`);
196
+ // ── Retry original request ────────────────────────────────────
197
+ const originalRequest = error.config;
198
+ if (!originalRequest) {
199
+ throw new Error('Cannot retry — original request config is missing.');
200
+ }
201
+ if (!originalRequest.headers) {
202
+ originalRequest.headers = {};
203
+ }
204
+ originalRequest.headers['X-PAYMENT'] = tokenBase64;
205
+ return this.api.request(originalRequest);
206
+ }
207
+ // ─── MPP Helpers ──────────────────────────────────────────────────
208
+ /**
209
+ * Select the best challenge when multiple are offered.
210
+ * Prefers the method matching the current adapter.
211
+ */
212
+ selectChallenge(challenges) {
213
+ if (challenges.length === 1)
214
+ return challenges[0];
215
+ // Match adapter type to challenge method
216
+ if (this.isStripeAdapter(this.adapter)) {
217
+ const stripeChallenge = challenges.find((c) => c.method === 'stripe');
218
+ if (stripeChallenge)
219
+ return stripeChallenge;
220
+ }
221
+ // Fallback: first challenge
222
+ return challenges[0];
223
+ }
224
+ /**
225
+ * Extract USD amount from MPP request object.
226
+ */
227
+ extractMppUsdAmount(request) {
228
+ const amount = parseFloat(request.amount);
229
+ if (isNaN(amount) || amount <= 0)
230
+ return 0.01;
231
+ // If currency is USD and has decimals info, convert
232
+ const decimals = request.decimals ?? 2;
233
+ return amount / Math.pow(10, decimals);
234
+ }
235
+ // ─── x402 Helpers ─────────────────────────────────────────────────
236
+ /**
237
+ * Extract USD amount from x402 challenge.
238
+ * Priority: resource.usdAmount → accepts[].maxAmountRequired → heuristic from description.
239
+ */
240
+ extractUsdAmount(challenge, acceptRules) {
241
+ // 1. Explicit USD amount field (best)
242
+ if (challenge.resource?.usdAmount && challenge.resource.usdAmount > 0) {
243
+ return challenge.resource.usdAmount;
244
+ }
245
+ // 2. maxAmountRequired in accepts (x402 v2)
246
+ if (acceptRules.maxAmountRequired) {
247
+ const parsed = parseFloat(acceptRules.maxAmountRequired);
248
+ if (!isNaN(parsed) && parsed > 0)
249
+ return parsed;
250
+ }
251
+ // 3. Fallback: parse from description text (last resort)
252
+ const desc = challenge.resource?.description || '';
253
+ const match = desc.match(/\$(\d+(?:\.\d+)?)/);
254
+ if (match) {
255
+ return parseFloat(match[1]);
256
+ }
257
+ // 4. Last resort: use atomic amount as-is
258
+ this.log('[OWS] ⚠️ Could not determine USD amount — using atomic fallback');
259
+ return parseFloat(atomicToUsd(acceptRules.amount, acceptRules.asset));
260
+ }
261
+ // ─── Type Guards ──────────────────────────────────────────────────
262
+ isStripeAdapter(adapter) {
263
+ return adapter.chainName === 'Stripe MPP';
264
+ }
265
+ // ─── Public API ───────────────────────────────────────────────────
266
+ /**
267
+ * High-level helper for AI agents.
268
+ * The agent simply calls performTask() — the interceptor handles
269
+ * everything else (payment, policy, retry) transparently.
270
+ */
271
+ async performTask(endpoint, data) {
272
+ this.log(`[Agent] 🧠 Sending task to ${endpoint}`);
273
+ const res = await this.api.post(endpoint, data);
274
+ this.log(`[Agent] ✅ Task completed`);
275
+ return res.data;
276
+ }
277
+ /**
278
+ * GET request with autonomous 402 handling.
279
+ */
280
+ async get(endpoint) {
281
+ const res = await this.api.get(endpoint);
282
+ return res.data;
283
+ }
284
+ }
285
+ /**
286
+ * Convert atomic units to approximate USD (best-effort).
287
+ * Used only as a last-resort fallback.
288
+ */
289
+ function atomicToUsd(amount, asset) {
290
+ const n = BigInt(amount);
291
+ switch (asset.toUpperCase()) {
292
+ case 'USDC':
293
+ return (Number(n) / 1e6).toFixed(2);
294
+ case 'ETH':
295
+ return (Number(n) / 1e18 * 3000).toFixed(2);
296
+ case 'XLM':
297
+ return (Number(n) / 1e7 * 0.1).toFixed(2);
298
+ default:
299
+ return '0.01';
300
+ }
301
+ }
302
+ //# sourceMappingURL=client.js.map