@atxp/client 0.7.4 → 0.8.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/atxpClient.d.ts +1 -1
- package/dist/atxpClient.d.ts.map +1 -1
- package/dist/atxpClient.js +17 -2
- package/dist/atxpClient.js.map +1 -1
- package/dist/atxpFetcher.d.ts +5 -15
- package/dist/atxpFetcher.d.ts.map +1 -1
- package/dist/atxpFetcher.js +95 -229
- package/dist/atxpFetcher.js.map +1 -1
- package/dist/baseAccount.d.ts +10 -7
- package/dist/baseAccount.d.ts.map +1 -1
- package/dist/baseAccount.js +18 -8
- package/dist/baseAccount.js.map +1 -1
- package/dist/basePaymentMaker.d.ts +4 -3
- package/dist/basePaymentMaker.d.ts.map +1 -1
- package/dist/basePaymentMaker.js +20 -3
- package/dist/basePaymentMaker.js.map +1 -1
- package/dist/clientTestHelpers.d.ts.map +1 -1
- package/dist/destinationMakers/atxpDestinationMaker.d.ts +15 -0
- package/dist/destinationMakers/atxpDestinationMaker.d.ts.map +1 -0
- package/dist/destinationMakers/atxpDestinationMaker.js +128 -0
- package/dist/destinationMakers/atxpDestinationMaker.js.map +1 -0
- package/dist/destinationMakers/index.d.ts +9 -0
- package/dist/destinationMakers/index.d.ts.map +1 -0
- package/dist/destinationMakers/index.js +45 -0
- package/dist/destinationMakers/index.js.map +1 -0
- package/dist/destinationMakers/passthroughDestinationMaker.d.ts +8 -0
- package/dist/destinationMakers/passthroughDestinationMaker.d.ts.map +1 -0
- package/dist/destinationMakers/passthroughDestinationMaker.js +27 -0
- package/dist/destinationMakers/passthroughDestinationMaker.js.map +1 -0
- package/dist/index.cjs +566 -466
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +102 -55
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +555 -467
- package/dist/index.js.map +1 -1
- package/dist/polygonConstants.d.ts +53 -0
- package/dist/polygonConstants.d.ts.map +1 -0
- package/dist/polygonConstants.js +84 -0
- package/dist/polygonConstants.js.map +1 -0
- package/dist/solanaAccount.d.ts +8 -4
- package/dist/solanaAccount.d.ts.map +1 -1
- package/dist/solanaAccount.js +16 -4
- package/dist/solanaAccount.js.map +1 -1
- package/dist/solanaPaymentMaker.d.ts +4 -3
- package/dist/solanaPaymentMaker.d.ts.map +1 -1
- package/dist/solanaPaymentMaker.js +24 -5
- package/dist/solanaPaymentMaker.js.map +1 -1
- package/dist/types.d.ts +5 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/dist/atxpAccount.d.ts +0 -18
- package/dist/atxpAccount.d.ts.map +0 -1
- package/dist/atxpAccount.js +0 -123
- package/dist/atxpAccount.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var common = require('@atxp/common');
|
|
4
|
-
var BigNumber = require('bignumber.js');
|
|
5
4
|
var oauth = require('oauth4webapi');
|
|
5
|
+
var BigNumber = require('bignumber.js');
|
|
6
6
|
var web3_js = require('@solana/web3.js');
|
|
7
7
|
var pay = require('@solana/pay');
|
|
8
8
|
var splToken = require('@solana/spl-token');
|
|
@@ -333,9 +333,9 @@ class PaymentNetworkError extends Error {
|
|
|
333
333
|
*/
|
|
334
334
|
function atxpFetch(config) {
|
|
335
335
|
const fetcher = new ATXPFetcher({
|
|
336
|
-
|
|
336
|
+
account: config.account,
|
|
337
337
|
db: config.oAuthDb,
|
|
338
|
-
|
|
338
|
+
destinationMakers: config.destinationMakers,
|
|
339
339
|
fetchFn: config.fetchFn,
|
|
340
340
|
sideChannelFetch: config.oAuthChannelFetch,
|
|
341
341
|
allowInsecureRequests: config.allowHttp,
|
|
@@ -353,7 +353,8 @@ class ATXPFetcher {
|
|
|
353
353
|
constructor(config) {
|
|
354
354
|
this.defaultPaymentFailureHandler = async ({ payment, error }) => {
|
|
355
355
|
if (error instanceof InsufficientFundsError) {
|
|
356
|
-
|
|
356
|
+
const networkText = error.network ? ` on ${error.network}` : '';
|
|
357
|
+
this.logger.info(`PAYMENT FAILED: Insufficient ${error.currency} funds${networkText}`);
|
|
357
358
|
this.logger.info(`Required: ${error.required} ${error.currency}`);
|
|
358
359
|
if (error.available) {
|
|
359
360
|
this.logger.info(`Available: ${error.available} ${error.currency}`);
|
|
@@ -361,7 +362,7 @@ class ATXPFetcher {
|
|
|
361
362
|
this.logger.info(`Account: ${payment.accountId}`);
|
|
362
363
|
}
|
|
363
364
|
else if (error instanceof PaymentNetworkError) {
|
|
364
|
-
this.logger.info(`PAYMENT FAILED: Network error
|
|
365
|
+
this.logger.info(`PAYMENT FAILED: Network error: ${error.message}`);
|
|
365
366
|
}
|
|
366
367
|
else {
|
|
367
368
|
this.logger.info(`PAYMENT FAILED: ${error.message}`);
|
|
@@ -371,43 +372,61 @@ class ATXPFetcher {
|
|
|
371
372
|
if (!paymentRequestData.destinations || paymentRequestData.destinations.length === 0) {
|
|
372
373
|
return false;
|
|
373
374
|
}
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
destinationAddress = resolved.destinationAddress;
|
|
384
|
-
destinationNetwork = resolved.network;
|
|
385
|
-
}
|
|
386
|
-
const paymentMaker = this.paymentMakers.get(destinationNetwork);
|
|
387
|
-
if (!paymentMaker) {
|
|
388
|
-
this.logger.debug(`ATXP: payment network '${destinationNetwork}' not available, trying next destination`);
|
|
375
|
+
// Get sources from the account
|
|
376
|
+
const sources = await this.account.getSources();
|
|
377
|
+
// Apply destination mappers to transform destinations
|
|
378
|
+
// Convert PaymentRequestDestination[] to Destination[] for mapper compatibility
|
|
379
|
+
const mappedDestinations = [];
|
|
380
|
+
for (const option of paymentRequestData.destinations) {
|
|
381
|
+
const destinationMaker = this.destinationMakers.get(option.network);
|
|
382
|
+
if (!destinationMaker) {
|
|
383
|
+
this.logger.debug(`ATXP: destination maker for network '${option.network}' not available, trying next destination`);
|
|
389
384
|
continue;
|
|
390
385
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.logger.info(`ATXP: payment request denied by callback function for destination on ${destinationNetwork}`);
|
|
402
|
-
continue;
|
|
386
|
+
mappedDestinations.push(...(await destinationMaker.makeDestinations(option, this.logger, paymentRequestId, sources)));
|
|
387
|
+
}
|
|
388
|
+
if (mappedDestinations.length === 0) {
|
|
389
|
+
this.logger.info(`ATXP: no destinations found after mapping`);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
// Validate amounts are not negative
|
|
393
|
+
for (const dest of mappedDestinations) {
|
|
394
|
+
if (dest.amount.isLessThan(0)) {
|
|
395
|
+
throw new Error(`ATXP: payment amount cannot be negative: ${dest.amount.toString()} ${dest.currency}`);
|
|
403
396
|
}
|
|
404
|
-
|
|
397
|
+
}
|
|
398
|
+
// Create prospective payment for approval (using first destination for display)
|
|
399
|
+
const firstDest = mappedDestinations[0];
|
|
400
|
+
const prospectivePayment = {
|
|
401
|
+
accountId: this.account.accountId,
|
|
402
|
+
resourceUrl: paymentRequestData.resource?.toString() ?? '',
|
|
403
|
+
resourceName: paymentRequestData.resourceName ?? '',
|
|
404
|
+
currency: firstDest.currency,
|
|
405
|
+
amount: firstDest.amount,
|
|
406
|
+
iss: paymentRequestData.iss ?? '',
|
|
407
|
+
};
|
|
408
|
+
// Ask for approval once for all payment attempts
|
|
409
|
+
if (!await this.approvePayment(prospectivePayment)) {
|
|
410
|
+
this.logger.info(`ATXP: payment request denied by callback function`);
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
// Try each payment maker in order
|
|
414
|
+
let lastPaymentError = null;
|
|
415
|
+
let paymentAttempted = false;
|
|
416
|
+
for (const paymentMaker of this.account.paymentMakers) {
|
|
405
417
|
try {
|
|
406
|
-
|
|
407
|
-
|
|
418
|
+
// Pass all destinations to payment maker - it will filter and pick the one it can handle
|
|
419
|
+
const result = await paymentMaker.makePayment(mappedDestinations, paymentRequestData.iss, paymentRequestId);
|
|
420
|
+
if (result === null) {
|
|
421
|
+
this.logger.debug(`ATXP: payment maker cannot handle these destinations, trying next`);
|
|
422
|
+
continue; // Try next payment maker
|
|
423
|
+
}
|
|
424
|
+
paymentAttempted = true;
|
|
425
|
+
// Payment was successful
|
|
426
|
+
this.logger.info(`ATXP: made payment of ${firstDest.amount.toString()} ${firstDest.currency} on ${result.chain}: ${result.transactionId}`);
|
|
408
427
|
await this.onPayment({ payment: prospectivePayment });
|
|
409
428
|
// Submit payment to the server
|
|
410
|
-
const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
|
|
429
|
+
const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '', accountId: this.account.accountId });
|
|
411
430
|
const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
|
|
412
431
|
method: 'PUT',
|
|
413
432
|
headers: {
|
|
@@ -415,9 +434,10 @@ class ATXPFetcher {
|
|
|
415
434
|
'Content-Type': 'application/json'
|
|
416
435
|
},
|
|
417
436
|
body: JSON.stringify({
|
|
418
|
-
transactionId:
|
|
419
|
-
|
|
420
|
-
|
|
437
|
+
transactionId: result.transactionId,
|
|
438
|
+
...(result.transactionSubId ? { transactionSubId: result.transactionSubId } : {}),
|
|
439
|
+
chain: result.chain,
|
|
440
|
+
currency: result.currency
|
|
421
441
|
})
|
|
422
442
|
});
|
|
423
443
|
this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
|
|
@@ -430,13 +450,18 @@ class ATXPFetcher {
|
|
|
430
450
|
}
|
|
431
451
|
catch (error) {
|
|
432
452
|
const typedError = error;
|
|
433
|
-
|
|
453
|
+
paymentAttempted = true;
|
|
454
|
+
lastPaymentError = typedError;
|
|
455
|
+
this.logger.warn(`ATXP: payment maker failed: ${typedError.message}`);
|
|
434
456
|
await this.onPaymentFailure({ payment: prospectivePayment, error: typedError });
|
|
435
|
-
//
|
|
436
|
-
continue;
|
|
457
|
+
// Continue to next payment maker
|
|
437
458
|
}
|
|
438
459
|
}
|
|
439
|
-
|
|
460
|
+
// If payment was attempted but all failed, rethrow the last error
|
|
461
|
+
if (paymentAttempted && lastPaymentError) {
|
|
462
|
+
throw lastPaymentError;
|
|
463
|
+
}
|
|
464
|
+
this.logger.info(`ATXP: no payment maker could handle these destinations`);
|
|
440
465
|
return false;
|
|
441
466
|
};
|
|
442
467
|
this.handlePaymentRequestError = async (paymentRequestError) => {
|
|
@@ -463,102 +488,8 @@ class ATXPFetcher {
|
|
|
463
488
|
if (paymentRequestData.destinations && paymentRequestData.destinations.length > 0) {
|
|
464
489
|
return this.handleMultiDestinationPayment(paymentRequestData, paymentRequestUrl, paymentRequestId);
|
|
465
490
|
}
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
if (!requestedNetwork) {
|
|
469
|
-
throw new Error(`Payment network not provided`);
|
|
470
|
-
}
|
|
471
|
-
const destination = paymentRequestData.destination;
|
|
472
|
-
if (!destination) {
|
|
473
|
-
throw new Error(`destination not provided`);
|
|
474
|
-
}
|
|
475
|
-
let amount = new BigNumber.BigNumber(0);
|
|
476
|
-
if (!paymentRequestData.amount) {
|
|
477
|
-
throw new Error(`amount not provided`);
|
|
478
|
-
}
|
|
479
|
-
try {
|
|
480
|
-
amount = new BigNumber.BigNumber(paymentRequestData.amount);
|
|
481
|
-
}
|
|
482
|
-
catch {
|
|
483
|
-
throw new Error(`Invalid amount ${paymentRequestData.amount}`);
|
|
484
|
-
}
|
|
485
|
-
if (amount.lte(0)) {
|
|
486
|
-
throw new Error(`Invalid amount ${paymentRequestData.amount}`);
|
|
487
|
-
}
|
|
488
|
-
const currency = paymentRequestData.currency;
|
|
489
|
-
if (!currency) {
|
|
490
|
-
throw new Error(`Currency not provided`);
|
|
491
|
-
}
|
|
492
|
-
// Resolve atxp_base destinations to real base network destinations
|
|
493
|
-
let destinationAddress = destination;
|
|
494
|
-
let destinationNetwork = requestedNetwork;
|
|
495
|
-
const resolved = await this.resolveAtxpBaseDestination(requestedNetwork, destination, paymentRequestId, amount, currency, destination, paymentRequestData.iss);
|
|
496
|
-
if (resolved) {
|
|
497
|
-
destinationAddress = resolved.destinationAddress;
|
|
498
|
-
destinationNetwork = resolved.network;
|
|
499
|
-
}
|
|
500
|
-
const paymentMaker = this.paymentMakers.get(destinationNetwork);
|
|
501
|
-
if (!paymentMaker) {
|
|
502
|
-
this.logger.info(`ATXP: payment network '${destinationNetwork}' not set up for this client (available networks: ${Array.from(this.paymentMakers.keys()).join(', ')})`);
|
|
503
|
-
return false;
|
|
504
|
-
}
|
|
505
|
-
const prospectivePayment = {
|
|
506
|
-
accountId: this.accountId,
|
|
507
|
-
resourceUrl: paymentRequestData.resource?.toString() ?? '',
|
|
508
|
-
resourceName: paymentRequestData.resourceName ?? '',
|
|
509
|
-
network: destinationNetwork,
|
|
510
|
-
currency,
|
|
511
|
-
amount,
|
|
512
|
-
iss: paymentRequestData.iss ?? '',
|
|
513
|
-
};
|
|
514
|
-
if (!await this.approvePayment(prospectivePayment)) {
|
|
515
|
-
this.logger.info(`ATXP: payment request denied by callback function`);
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
let paymentId;
|
|
519
|
-
try {
|
|
520
|
-
paymentId = await paymentMaker.makePayment(amount, currency, destinationAddress, paymentRequestData.iss);
|
|
521
|
-
this.logger.info(`ATXP: made payment of ${amount} ${currency} on ${destinationNetwork}: ${paymentId}`);
|
|
522
|
-
// Call onPayment callback after successful payment
|
|
523
|
-
await this.onPayment({ payment: prospectivePayment });
|
|
524
|
-
}
|
|
525
|
-
catch (paymentError) {
|
|
526
|
-
// Call onPaymentFailure callback if payment fails
|
|
527
|
-
await this.onPaymentFailure({
|
|
528
|
-
payment: prospectivePayment,
|
|
529
|
-
error: paymentError
|
|
530
|
-
});
|
|
531
|
-
throw paymentError;
|
|
532
|
-
}
|
|
533
|
-
const jwt = await paymentMaker.generateJWT({ paymentRequestId, codeChallenge: '' });
|
|
534
|
-
// Make a fetch call to the authorization URL with the payment ID
|
|
535
|
-
// redirect=false is a hack
|
|
536
|
-
// The OAuth spec calls for the authorization url to return with a redirect, but fetch
|
|
537
|
-
// on mobile will automatically follow the redirect (it doesn't support the redirect=manual option)
|
|
538
|
-
// We want the redirect URL so we can extract the code from it, not the contents of the
|
|
539
|
-
// redirect URL (which might not even exist for agentic ATXP clients)
|
|
540
|
-
// So ATXP servers are set up to instead return a 200 with the redirect URL in the body
|
|
541
|
-
// if we pass redirect=false.
|
|
542
|
-
// TODO: Remove the redirect=false hack once we have a way to handle the redirect on mobile
|
|
543
|
-
const response = await this.sideChannelFetch(paymentRequestUrl.toString(), {
|
|
544
|
-
method: 'PUT',
|
|
545
|
-
headers: {
|
|
546
|
-
'Authorization': `Bearer ${jwt}`,
|
|
547
|
-
'Content-Type': 'application/json'
|
|
548
|
-
},
|
|
549
|
-
body: JSON.stringify({
|
|
550
|
-
transactionId: paymentId,
|
|
551
|
-
network: destinationNetwork,
|
|
552
|
-
currency: currency
|
|
553
|
-
})
|
|
554
|
-
});
|
|
555
|
-
this.logger.debug(`ATXP: payment was ${response.ok ? 'successfully' : 'not successfully'} PUT to ${paymentRequestUrl} : status ${response.status} ${response.statusText}`);
|
|
556
|
-
if (!response.ok) {
|
|
557
|
-
const msg = `ATXP: payment to ${paymentRequestUrl} failed: HTTP ${response.status} ${await response.text()}`;
|
|
558
|
-
this.logger.info(msg);
|
|
559
|
-
throw new Error(msg);
|
|
560
|
-
}
|
|
561
|
-
return true;
|
|
491
|
+
// Payment request doesn't have destinations - this shouldn't happen with new SDK
|
|
492
|
+
throw new Error(`ATXP: payment request does not contain destinations array`);
|
|
562
493
|
};
|
|
563
494
|
this.getPaymentRequestData = async (paymentRequestUrl) => {
|
|
564
495
|
const prRequest = await this.sideChannelFetch(paymentRequestUrl);
|
|
@@ -566,6 +497,14 @@ class ATXPFetcher {
|
|
|
566
497
|
throw new Error(`ATXP: GET ${paymentRequestUrl} failed: ${prRequest.status} ${prRequest.statusText}`);
|
|
567
498
|
}
|
|
568
499
|
const paymentRequest = await prRequest.json();
|
|
500
|
+
// Parse amount strings to BigNumber objects
|
|
501
|
+
if (paymentRequest.destinations) {
|
|
502
|
+
for (const dest of paymentRequest.destinations) {
|
|
503
|
+
if (typeof dest.amount === 'string' || typeof dest.amount === 'number') {
|
|
504
|
+
dest.amount = new BigNumber.BigNumber(dest.amount);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
569
508
|
return paymentRequest;
|
|
570
509
|
};
|
|
571
510
|
this.isAllowedAuthServer = (url) => {
|
|
@@ -579,15 +518,15 @@ class ATXPFetcher {
|
|
|
579
518
|
throw new Error(`Code challenge not provided`);
|
|
580
519
|
}
|
|
581
520
|
if (!paymentMaker) {
|
|
582
|
-
const
|
|
583
|
-
throw new Error(`Payment maker is null/undefined. Available payment
|
|
521
|
+
const paymentMakerCount = this.account.paymentMakers.length;
|
|
522
|
+
throw new Error(`Payment maker is null/undefined. Available payment maker count: ${paymentMakerCount}. This usually indicates a payment maker object was not properly instantiated.`);
|
|
584
523
|
}
|
|
585
524
|
// TypeScript should prevent this, but add runtime check for edge cases (untyped JS, version mismatches, etc.)
|
|
586
525
|
if (!paymentMaker.generateJWT) {
|
|
587
|
-
const
|
|
588
|
-
throw new Error(`Payment maker is missing generateJWT method. Available payment
|
|
526
|
+
const paymentMakerCount = this.account.paymentMakers.length;
|
|
527
|
+
throw new Error(`Payment maker is missing generateJWT method. Available payment maker count: ${paymentMakerCount}. This indicates the payment maker object does not implement the PaymentMaker interface. If using TypeScript, ensure your payment maker properly implements the PaymentMaker interface.`);
|
|
589
528
|
}
|
|
590
|
-
const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge });
|
|
529
|
+
const authToken = await paymentMaker.generateJWT({ paymentRequestId: '', codeChallenge: codeChallenge, accountId: this.account.accountId });
|
|
591
530
|
// Make a fetch call to the authorization URL with the payment ID
|
|
592
531
|
// redirect=false is a hack
|
|
593
532
|
// The OAuth spec calls for the authorization url to return with a redirect, but fetch
|
|
@@ -633,11 +572,11 @@ class ATXPFetcher {
|
|
|
633
572
|
throw new Error(`Expected redirect response from authorization URL, got ${response.status}`);
|
|
634
573
|
};
|
|
635
574
|
this.authToService = async (error) => {
|
|
636
|
-
// TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
|
|
637
|
-
if (this.paymentMakers.
|
|
575
|
+
// TODO: We need to generalize this - we can't assume that there's a single paymentMaker for the auth flow.
|
|
576
|
+
if (this.account.paymentMakers.length > 1) {
|
|
638
577
|
throw new Error(`ATXP: multiple payment makers found - cannot determine which one to use for auth`);
|
|
639
578
|
}
|
|
640
|
-
const paymentMaker =
|
|
579
|
+
const paymentMaker = this.account.paymentMakers[0];
|
|
641
580
|
if (paymentMaker) {
|
|
642
581
|
// We can do the full OAuth flow - we'll generate a signed JWT and call /authorize on the
|
|
643
582
|
// AS to get a code, then exchange the code for an access token
|
|
@@ -652,14 +591,14 @@ class ATXPFetcher {
|
|
|
652
591
|
// Call onAuthorize callback after successful authorization
|
|
653
592
|
await this.onAuthorize({
|
|
654
593
|
authorizationServer: authorizationUrl.origin,
|
|
655
|
-
userId: this.accountId
|
|
594
|
+
userId: this.account.accountId
|
|
656
595
|
});
|
|
657
596
|
}
|
|
658
597
|
catch (authError) {
|
|
659
598
|
// Call onAuthorizeFailure callback if authorization fails
|
|
660
599
|
await this.onAuthorizeFailure({
|
|
661
600
|
authorizationServer: authorizationUrl.origin,
|
|
662
|
-
userId: this.accountId,
|
|
601
|
+
userId: this.account.accountId,
|
|
663
602
|
error: authError
|
|
664
603
|
});
|
|
665
604
|
throw authError;
|
|
@@ -670,13 +609,13 @@ class ATXPFetcher {
|
|
|
670
609
|
// If we do, we'll use it to auth to the downstream resource
|
|
671
610
|
// (In pass-through scenarios, the atxpServer() middleware stores the incoming
|
|
672
611
|
// token in the DB under the '' resource URL).
|
|
673
|
-
const existingToken = await this.db.getAccessToken(this.accountId, '');
|
|
612
|
+
const existingToken = await this.db.getAccessToken(this.account.accountId, '');
|
|
674
613
|
if (!existingToken) {
|
|
675
614
|
this.logger.info(`ATXP: no token found for the current server - we can't exchange a token if we don't have one`);
|
|
676
615
|
throw error;
|
|
677
616
|
}
|
|
678
617
|
const newToken = await this.exchangeToken(existingToken, error.resourceServerUrl);
|
|
679
|
-
this.db.saveAccessToken(this.accountId, error.resourceServerUrl, newToken);
|
|
618
|
+
this.db.saveAccessToken(this.account.accountId, error.resourceServerUrl, newToken);
|
|
680
619
|
}
|
|
681
620
|
};
|
|
682
621
|
this.exchangeToken = async (myToken, newResourceUrl) => {
|
|
@@ -767,15 +706,15 @@ class ATXPFetcher {
|
|
|
767
706
|
throw error;
|
|
768
707
|
}
|
|
769
708
|
};
|
|
770
|
-
const {
|
|
709
|
+
const { account, db, destinationMakers, fetchFn = fetch, sideChannelFetch = fetchFn, strict = true, allowInsecureRequests = process.env.NODE_ENV === 'development', allowedAuthorizationServers = [common.DEFAULT_AUTHORIZATION_SERVER], approvePayment = async () => true, logger = new common.ConsoleLogger(), onAuthorize = async () => { }, onAuthorizeFailure = async () => { }, onPayment = async () => { }, onPaymentFailure = async () => { } } = config;
|
|
771
710
|
// Use React Native safe fetch if in React Native environment
|
|
772
711
|
const safeFetchFn = common.getIsReactNative() ? common.createReactNativeSafeFetch(fetchFn) : fetchFn;
|
|
773
712
|
const safeSideChannelFetch = common.getIsReactNative() ? common.createReactNativeSafeFetch(sideChannelFetch) : sideChannelFetch;
|
|
774
|
-
// ATXPClient should never actually use the callback url - instead of redirecting the user to
|
|
713
|
+
// ATXPClient should never actually use the callback url - instead of redirecting the user to
|
|
775
714
|
// an authorization url which redirects back to the callback url, ATXPClient posts the payment
|
|
776
715
|
// directly to the authorization server, then does the token exchange itself
|
|
777
716
|
this.oauthClient = new OAuthClient({
|
|
778
|
-
userId: accountId,
|
|
717
|
+
userId: account.accountId,
|
|
779
718
|
db,
|
|
780
719
|
callbackUrl: 'http://localhost:3000/unused-dummy-atxp-callback',
|
|
781
720
|
isPublic: false,
|
|
@@ -785,10 +724,10 @@ class ATXPFetcher {
|
|
|
785
724
|
allowInsecureRequests,
|
|
786
725
|
logger: logger
|
|
787
726
|
});
|
|
788
|
-
this.
|
|
727
|
+
this.account = account;
|
|
728
|
+
this.destinationMakers = destinationMakers;
|
|
789
729
|
this.sideChannelFetch = safeSideChannelFetch;
|
|
790
730
|
this.db = db;
|
|
791
|
-
this.accountId = accountId;
|
|
792
731
|
this.allowedAuthorizationServers = allowedAuthorizationServers;
|
|
793
732
|
this.approvePayment = approvePayment;
|
|
794
733
|
this.logger = logger;
|
|
@@ -797,79 +736,6 @@ class ATXPFetcher {
|
|
|
797
736
|
this.onPayment = onPayment;
|
|
798
737
|
this.onPaymentFailure = onPaymentFailure || this.defaultPaymentFailureHandler;
|
|
799
738
|
}
|
|
800
|
-
/**
|
|
801
|
-
* Resolves atxp_base or atxp_base_sepolia destinations to real base network destinations
|
|
802
|
-
* by calling the payment_info endpoint to get the destination address and network
|
|
803
|
-
*/
|
|
804
|
-
async resolveAtxpBaseDestination(network, paymentInfoUrl, paymentRequestId, amount, currency, receiver, memo) {
|
|
805
|
-
// Check if this is an atxp_base network that needs resolution
|
|
806
|
-
if (network !== 'atxp_base' && network !== 'atxp_base_sepolia') {
|
|
807
|
-
return null;
|
|
808
|
-
}
|
|
809
|
-
// Map atxp_base networks to their real counterparts
|
|
810
|
-
const realNetwork = network === 'atxp_base' ? 'base' : 'base_sepolia';
|
|
811
|
-
// Get the payment maker for the real network
|
|
812
|
-
const paymentMaker = this.paymentMakers.get(realNetwork);
|
|
813
|
-
if (!paymentMaker) {
|
|
814
|
-
this.logger.debug(`ATXP: payment network '${realNetwork}' not available for atxp_base resolution`);
|
|
815
|
-
return null;
|
|
816
|
-
}
|
|
817
|
-
// Get the buyer address (source address) from the payment maker
|
|
818
|
-
let buyerAddress;
|
|
819
|
-
try {
|
|
820
|
-
buyerAddress = await paymentMaker.getSourceAddress({
|
|
821
|
-
amount,
|
|
822
|
-
currency,
|
|
823
|
-
receiver,
|
|
824
|
-
memo
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
catch (error) {
|
|
828
|
-
this.logger.warn(`ATXP: failed to get source address from payment maker for ${realNetwork}: ${error.message}`);
|
|
829
|
-
return null;
|
|
830
|
-
}
|
|
831
|
-
// Call the payment_info endpoint
|
|
832
|
-
this.logger.debug(`ATXP: resolving ${network} destination via ${paymentInfoUrl}`);
|
|
833
|
-
try {
|
|
834
|
-
const response = await this.sideChannelFetch(paymentInfoUrl, {
|
|
835
|
-
method: 'POST',
|
|
836
|
-
headers: {
|
|
837
|
-
'Content-Type': 'application/json',
|
|
838
|
-
},
|
|
839
|
-
body: JSON.stringify({
|
|
840
|
-
paymentRequestId,
|
|
841
|
-
buyerAddress,
|
|
842
|
-
}),
|
|
843
|
-
});
|
|
844
|
-
if (!response.ok) {
|
|
845
|
-
const text = await response.text();
|
|
846
|
-
this.logger.warn(`ATXP: payment_info endpoint failed: ${response.status} ${response.statusText} ${text}`);
|
|
847
|
-
return null;
|
|
848
|
-
}
|
|
849
|
-
const data = await response.json();
|
|
850
|
-
if (data.status !== 'success') {
|
|
851
|
-
this.logger.warn(`ATXP: payment_info endpoint returned non-success status: ${JSON.stringify(data)}`);
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
if (!data.destinationAddress) {
|
|
855
|
-
this.logger.warn(`ATXP: payment_info endpoint did not return destinationAddress`);
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
if (!data.network) {
|
|
859
|
-
this.logger.warn(`ATXP: payment_info endpoint did not return network`);
|
|
860
|
-
return null;
|
|
861
|
-
}
|
|
862
|
-
this.logger.info(`ATXP: resolved ${network} destination to ${data.destinationAddress} on ${data.network}`);
|
|
863
|
-
return {
|
|
864
|
-
destinationAddress: data.destinationAddress,
|
|
865
|
-
network: data.network,
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
catch (error) {
|
|
869
|
-
this.logger.warn(`ATXP: failed to resolve ${network} destination: ${error.message}`);
|
|
870
|
-
return null;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
739
|
}
|
|
874
740
|
|
|
875
741
|
var util$1;
|
|
@@ -15954,6 +15820,191 @@ class StreamableHTTPClientTransport {
|
|
|
15954
15820
|
}
|
|
15955
15821
|
}
|
|
15956
15822
|
|
|
15823
|
+
function isDestinationResponse(obj) {
|
|
15824
|
+
return (typeof obj === 'object' &&
|
|
15825
|
+
obj !== null &&
|
|
15826
|
+
'chain' in obj &&
|
|
15827
|
+
'address' in obj &&
|
|
15828
|
+
'currency' in obj &&
|
|
15829
|
+
'amount' in obj &&
|
|
15830
|
+
typeof obj.chain === 'string' &&
|
|
15831
|
+
typeof obj.address === 'string' &&
|
|
15832
|
+
typeof obj.currency === 'string' &&
|
|
15833
|
+
typeof obj.amount === 'string');
|
|
15834
|
+
}
|
|
15835
|
+
function isDestinationsApiResponse(obj) {
|
|
15836
|
+
return (typeof obj === 'object' &&
|
|
15837
|
+
obj !== null &&
|
|
15838
|
+
'destinations' in obj &&
|
|
15839
|
+
'paymentRequestId' in obj &&
|
|
15840
|
+
Array.isArray(obj.destinations) &&
|
|
15841
|
+
typeof obj.paymentRequestId === 'string');
|
|
15842
|
+
}
|
|
15843
|
+
function parseDestinationsResponse(data) {
|
|
15844
|
+
// Validate response structure
|
|
15845
|
+
if (!isDestinationsApiResponse(data)) {
|
|
15846
|
+
throw new Error('Invalid response: expected object with destinations array and paymentRequestId');
|
|
15847
|
+
}
|
|
15848
|
+
// Validate and convert each destination
|
|
15849
|
+
return data.destinations.map((dest, index) => {
|
|
15850
|
+
if (!isDestinationResponse(dest)) {
|
|
15851
|
+
throw new Error(`Invalid destination at index ${index}: missing required fields (chain, address, currency, amount)`);
|
|
15852
|
+
}
|
|
15853
|
+
// Validate chain is a valid Chain enum value
|
|
15854
|
+
if (!common.isEnumValue(common.ChainEnum, dest.chain)) {
|
|
15855
|
+
const validChains = Object.values(common.ChainEnum).join(', ');
|
|
15856
|
+
throw new Error(`Invalid destination at index ${index}: chain "${dest.chain}" is not a valid chain. Valid chains are: ${validChains}`);
|
|
15857
|
+
}
|
|
15858
|
+
// Validate currency is a valid Currency enum value
|
|
15859
|
+
if (!common.isEnumValue(common.CurrencyEnum, dest.currency)) {
|
|
15860
|
+
const validCurrencies = Object.values(common.CurrencyEnum).join(', ');
|
|
15861
|
+
throw new Error(`Invalid destination at index ${index}: currency "${dest.currency}" is not a valid currency. Valid currencies are: ${validCurrencies}`);
|
|
15862
|
+
}
|
|
15863
|
+
// Validate amount is a valid number
|
|
15864
|
+
const amount = new BigNumber.BigNumber(dest.amount);
|
|
15865
|
+
if (amount.isNaN()) {
|
|
15866
|
+
throw new Error(`Invalid destination at index ${index}: amount "${dest.amount}" is not a valid number`);
|
|
15867
|
+
}
|
|
15868
|
+
return {
|
|
15869
|
+
chain: dest.chain,
|
|
15870
|
+
currency: dest.currency,
|
|
15871
|
+
address: dest.address,
|
|
15872
|
+
amount: amount
|
|
15873
|
+
};
|
|
15874
|
+
});
|
|
15875
|
+
}
|
|
15876
|
+
/**
|
|
15877
|
+
* Destination mapper for ATXP network destinations.
|
|
15878
|
+
* Converts destinations with network='atxp' to actual blockchain network destinations
|
|
15879
|
+
* by resolving the account ID to its associated blockchain addresses.
|
|
15880
|
+
*/
|
|
15881
|
+
class ATXPDestinationMaker {
|
|
15882
|
+
constructor(accountsServiceUrl, fetchFn = fetch) {
|
|
15883
|
+
this.accountsServiceUrl = accountsServiceUrl;
|
|
15884
|
+
this.fetchFn = fetchFn;
|
|
15885
|
+
}
|
|
15886
|
+
async makeDestinations(option, logger, paymentRequestId, sources) {
|
|
15887
|
+
if (option.network !== 'atxp') {
|
|
15888
|
+
return [];
|
|
15889
|
+
}
|
|
15890
|
+
try {
|
|
15891
|
+
// The address field contains the account ID (e.g., atxp_acct_xxx) for atxp options
|
|
15892
|
+
const accountId = option.address;
|
|
15893
|
+
// Always use the destinations endpoint
|
|
15894
|
+
const destinations = await this.getDestinations(accountId, paymentRequestId, option, sources, logger);
|
|
15895
|
+
if (destinations.length === 0) {
|
|
15896
|
+
logger.warn(`ATXPDestinationMaker: No destinations found for account ${accountId}`);
|
|
15897
|
+
}
|
|
15898
|
+
else {
|
|
15899
|
+
logger.debug(`ATXPDestinationMaker: Got ${destinations.length} destinations for account ${accountId}`);
|
|
15900
|
+
}
|
|
15901
|
+
return destinations;
|
|
15902
|
+
}
|
|
15903
|
+
catch (error) {
|
|
15904
|
+
logger.error(`ATXPDestinationMaker: Failed to make ATXP destinations: ${error}`);
|
|
15905
|
+
throw error;
|
|
15906
|
+
}
|
|
15907
|
+
}
|
|
15908
|
+
async getDestinations(accountId, paymentRequestId, option, sources, logger) {
|
|
15909
|
+
// Strip any network prefix if present
|
|
15910
|
+
const unqualifiedId = accountId.includes(':') ? accountId.split(':')[1] : accountId;
|
|
15911
|
+
const url = `${this.accountsServiceUrl}/account/${unqualifiedId}/destinations`;
|
|
15912
|
+
logger?.debug(`ATXPDestinationMaker: Fetching destinations from ${url}`);
|
|
15913
|
+
try {
|
|
15914
|
+
const requestBody = {
|
|
15915
|
+
paymentRequestId,
|
|
15916
|
+
options: [{
|
|
15917
|
+
network: option.network,
|
|
15918
|
+
currency: option.currency.toString(),
|
|
15919
|
+
address: option.address,
|
|
15920
|
+
amount: option.amount.toString()
|
|
15921
|
+
}],
|
|
15922
|
+
sources: sources
|
|
15923
|
+
};
|
|
15924
|
+
const response = await this.fetchFn(url, {
|
|
15925
|
+
method: 'POST',
|
|
15926
|
+
headers: {
|
|
15927
|
+
'Accept': 'application/json',
|
|
15928
|
+
'Content-Type': 'application/json',
|
|
15929
|
+
},
|
|
15930
|
+
body: JSON.stringify(requestBody),
|
|
15931
|
+
});
|
|
15932
|
+
if (!response.ok) {
|
|
15933
|
+
const text = await response.text();
|
|
15934
|
+
throw new Error(`Failed to fetch destinations: ${response.status} ${response.statusText} - ${text}`);
|
|
15935
|
+
}
|
|
15936
|
+
const data = await response.json();
|
|
15937
|
+
return parseDestinationsResponse(data);
|
|
15938
|
+
}
|
|
15939
|
+
catch (error) {
|
|
15940
|
+
logger?.error(`ATXPDestinationMaker: Error fetching destinations: ${error}`);
|
|
15941
|
+
throw error;
|
|
15942
|
+
}
|
|
15943
|
+
}
|
|
15944
|
+
}
|
|
15945
|
+
|
|
15946
|
+
class PassthroughDestinationMaker {
|
|
15947
|
+
constructor(network) {
|
|
15948
|
+
this.network = network;
|
|
15949
|
+
}
|
|
15950
|
+
async makeDestinations(option, _logger, _paymentRequestId, _sources) {
|
|
15951
|
+
if (option.network !== this.network) {
|
|
15952
|
+
return [];
|
|
15953
|
+
}
|
|
15954
|
+
// Check if option.network is also a Chain by inspecting the ChainEnum values
|
|
15955
|
+
if (Object.values(common.ChainEnum).includes(option.network)) {
|
|
15956
|
+
// It's a chain, so return a single passthrough destination
|
|
15957
|
+
const destination = {
|
|
15958
|
+
chain: option.network,
|
|
15959
|
+
currency: option.currency,
|
|
15960
|
+
address: option.address,
|
|
15961
|
+
amount: option.amount
|
|
15962
|
+
};
|
|
15963
|
+
return [destination];
|
|
15964
|
+
}
|
|
15965
|
+
return [];
|
|
15966
|
+
}
|
|
15967
|
+
}
|
|
15968
|
+
|
|
15969
|
+
function createDestinationMakers(config) {
|
|
15970
|
+
const { atxpAccountsServer, fetchFn = fetch } = config;
|
|
15971
|
+
// Build the map by exhaustively checking all Network values
|
|
15972
|
+
const makers = new Map();
|
|
15973
|
+
for (const network of Object.values(common.NetworkEnum)) {
|
|
15974
|
+
// Exhaustiveness check using switch with assertNever
|
|
15975
|
+
switch (network) {
|
|
15976
|
+
case common.NetworkEnum.Solana:
|
|
15977
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15978
|
+
break;
|
|
15979
|
+
case common.NetworkEnum.Base:
|
|
15980
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15981
|
+
break;
|
|
15982
|
+
case common.NetworkEnum.World:
|
|
15983
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15984
|
+
break;
|
|
15985
|
+
case common.NetworkEnum.Polygon:
|
|
15986
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15987
|
+
break;
|
|
15988
|
+
case common.NetworkEnum.BaseSepolia:
|
|
15989
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15990
|
+
break;
|
|
15991
|
+
case common.NetworkEnum.WorldSepolia:
|
|
15992
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15993
|
+
break;
|
|
15994
|
+
case common.NetworkEnum.PolygonAmoy:
|
|
15995
|
+
makers.set(network, new PassthroughDestinationMaker(network));
|
|
15996
|
+
break;
|
|
15997
|
+
case common.NetworkEnum.ATXP:
|
|
15998
|
+
makers.set(network, new ATXPDestinationMaker(atxpAccountsServer, fetchFn));
|
|
15999
|
+
break;
|
|
16000
|
+
default:
|
|
16001
|
+
// This will cause a compilation error if a new Network is added but not handled above
|
|
16002
|
+
common.assertNever(network);
|
|
16003
|
+
}
|
|
16004
|
+
}
|
|
16005
|
+
return makers;
|
|
16006
|
+
}
|
|
16007
|
+
|
|
15957
16008
|
// Detect if we're in a browser environment and bind fetch appropriately
|
|
15958
16009
|
const getFetch = () => {
|
|
15959
16010
|
if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
|
|
@@ -15965,6 +16016,7 @@ const getFetch = () => {
|
|
|
15965
16016
|
};
|
|
15966
16017
|
const DEFAULT_CLIENT_CONFIG = {
|
|
15967
16018
|
allowedAuthorizationServers: [common.DEFAULT_AUTHORIZATION_SERVER],
|
|
16019
|
+
atxpAccountsServer: common.DEFAULT_ATXP_ACCOUNTS_SERVER,
|
|
15968
16020
|
approvePayment: async (_p) => true,
|
|
15969
16021
|
fetchFn: getFetch(),
|
|
15970
16022
|
oAuthChannelFetch: getFetch(),
|
|
@@ -15994,7 +16046,20 @@ function buildClientConfig(args) {
|
|
|
15994
16046
|
const withDefaults = { ...envDefaults, ...args };
|
|
15995
16047
|
const logger = withDefaults.logger ?? new common.ConsoleLogger();
|
|
15996
16048
|
const oAuthDb = withDefaults.oAuthDb ?? new common.MemoryOAuthDb({ logger });
|
|
15997
|
-
const
|
|
16049
|
+
const fetchFn = withDefaults.fetchFn;
|
|
16050
|
+
// Build destination makers if not provided
|
|
16051
|
+
let accountsServer = withDefaults.atxpAccountsServer;
|
|
16052
|
+
// QoL hack for unspecified accounts server - if the caller is passing an atxpAccount, then assume the origin for that
|
|
16053
|
+
// is what we should use for the accounts server. In practice, the only option is accounts.atxp.ai,
|
|
16054
|
+
// but this supports staging environment
|
|
16055
|
+
if (args.atxpAccountsServer === undefined && withDefaults.account && withDefaults.account instanceof common.ATXPAccount) {
|
|
16056
|
+
accountsServer = withDefaults.account.origin;
|
|
16057
|
+
}
|
|
16058
|
+
const destinationMakers = withDefaults.destinationMakers ?? createDestinationMakers({
|
|
16059
|
+
atxpAccountsServer: accountsServer,
|
|
16060
|
+
fetchFn
|
|
16061
|
+
});
|
|
16062
|
+
const built = { oAuthDb, logger, destinationMakers };
|
|
15998
16063
|
return Object.freeze({ ...withDefaults, ...built });
|
|
15999
16064
|
}
|
|
16000
16065
|
function buildStreamableTransport(args) {
|
|
@@ -16017,7 +16082,7 @@ const USDC_MINT = new web3_js.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwy
|
|
|
16017
16082
|
const ValidateTransferError = pay.ValidateTransferError;
|
|
16018
16083
|
class SolanaPaymentMaker {
|
|
16019
16084
|
constructor(solanaEndpoint, sourceSecretKey, logger) {
|
|
16020
|
-
this.generateJWT = async ({ paymentRequestId, codeChallenge }) => {
|
|
16085
|
+
this.generateJWT = async ({ paymentRequestId, codeChallenge, accountId }) => {
|
|
16021
16086
|
// Solana/Web3.js secretKey is 64 bytes:
|
|
16022
16087
|
// first 32 bytes are the private scalar, last 32 are the public key.
|
|
16023
16088
|
// JWK expects only the 32-byte private scalar for 'd'
|
|
@@ -16031,9 +16096,20 @@ class SolanaPaymentMaker {
|
|
|
16031
16096
|
if (!(privateKey instanceof CryptoKey)) {
|
|
16032
16097
|
throw new Error('Expected CryptoKey from importJWK');
|
|
16033
16098
|
}
|
|
16034
|
-
return common.generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '');
|
|
16099
|
+
return common.generateJWT(this.source.publicKey.toBase58(), privateKey, paymentRequestId || '', codeChallenge || '', accountId);
|
|
16035
16100
|
};
|
|
16036
|
-
this.makePayment = async (
|
|
16101
|
+
this.makePayment = async (destinations, memo, _paymentRequestId) => {
|
|
16102
|
+
// Filter to solana chain destinations
|
|
16103
|
+
const solanaDestinations = destinations.filter(d => d.chain === 'solana');
|
|
16104
|
+
if (solanaDestinations.length === 0) {
|
|
16105
|
+
this.logger.debug('SolanaPaymentMaker: No solana destinations found, cannot handle payment');
|
|
16106
|
+
return null; // Cannot handle these destinations
|
|
16107
|
+
}
|
|
16108
|
+
// Pick first solana destination
|
|
16109
|
+
const dest = solanaDestinations[0];
|
|
16110
|
+
const amount = dest.amount;
|
|
16111
|
+
const currency = dest.currency;
|
|
16112
|
+
const receiver = dest.address;
|
|
16037
16113
|
if (currency.toUpperCase() !== 'USDC') {
|
|
16038
16114
|
throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
|
|
16039
16115
|
}
|
|
@@ -16048,6 +16124,8 @@ class SolanaPaymentMaker {
|
|
|
16048
16124
|
this.logger.warn(`Insufficient ${currency} balance for payment. Required: ${amount}, Available: ${balance}`);
|
|
16049
16125
|
throw new InsufficientFundsError(currency, amount, balance, 'solana');
|
|
16050
16126
|
}
|
|
16127
|
+
// Get the destination token account address (this will be the transactionId)
|
|
16128
|
+
const destinationTokenAccount = await splToken.getAssociatedTokenAddress(USDC_MINT, receiverKey);
|
|
16051
16129
|
// Increase compute units to handle both memo and token transfer
|
|
16052
16130
|
// Memo uses ~6000 CUs, token transfer needs ~6500 CUs
|
|
16053
16131
|
const modifyComputeUnits = web3_js.ComputeBudgetProgram.setComputeUnitLimit({
|
|
@@ -16064,8 +16142,14 @@ class SolanaPaymentMaker {
|
|
|
16064
16142
|
});
|
|
16065
16143
|
transaction.add(modifyComputeUnits);
|
|
16066
16144
|
transaction.add(addPriorityFee);
|
|
16067
|
-
const
|
|
16068
|
-
|
|
16145
|
+
const transactionSignature = await web3_js.sendAndConfirmTransaction(this.connection, transaction, [this.source]);
|
|
16146
|
+
// Return transaction signature as transactionId and token account address as transactionSubId
|
|
16147
|
+
return {
|
|
16148
|
+
transactionId: transactionSignature,
|
|
16149
|
+
transactionSubId: destinationTokenAccount.toBase58(),
|
|
16150
|
+
chain: 'solana',
|
|
16151
|
+
currency: 'USDC'
|
|
16152
|
+
};
|
|
16069
16153
|
}
|
|
16070
16154
|
catch (error) {
|
|
16071
16155
|
if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
|
|
@@ -16167,7 +16251,7 @@ class BasePaymentMaker {
|
|
|
16167
16251
|
getSourceAddress(_params) {
|
|
16168
16252
|
return this.signingClient.account.address;
|
|
16169
16253
|
}
|
|
16170
|
-
async generateJWT({ paymentRequestId, codeChallenge }) {
|
|
16254
|
+
async generateJWT({ paymentRequestId, codeChallenge, accountId }) {
|
|
16171
16255
|
const headerObj = { alg: 'ES256K' };
|
|
16172
16256
|
const payloadObj = {
|
|
16173
16257
|
sub: this.signingClient.account.address,
|
|
@@ -16177,6 +16261,7 @@ class BasePaymentMaker {
|
|
|
16177
16261
|
exp: Math.floor(Date.now() / 1000) + 60 * 60,
|
|
16178
16262
|
...(codeChallenge ? { code_challenge: codeChallenge } : {}),
|
|
16179
16263
|
...(paymentRequestId ? { payment_request_id: paymentRequestId } : {}),
|
|
16264
|
+
...(accountId ? { account_id: accountId } : {}),
|
|
16180
16265
|
};
|
|
16181
16266
|
const header = toBase64Url(JSON.stringify(headerObj));
|
|
16182
16267
|
const payload = toBase64Url(JSON.stringify(payloadObj));
|
|
@@ -16197,7 +16282,18 @@ class BasePaymentMaker {
|
|
|
16197
16282
|
this.logger.info(`Generated ES256K JWT: ${jwt}`);
|
|
16198
16283
|
return jwt;
|
|
16199
16284
|
}
|
|
16200
|
-
async makePayment(
|
|
16285
|
+
async makePayment(destinations, _memo, _paymentRequestId) {
|
|
16286
|
+
// Filter to base chain destinations
|
|
16287
|
+
const baseDestinations = destinations.filter(d => d.chain === 'base');
|
|
16288
|
+
if (baseDestinations.length === 0) {
|
|
16289
|
+
this.logger.debug('BasePaymentMaker: No base destinations found, cannot handle payment');
|
|
16290
|
+
return null; // Cannot handle these destinations
|
|
16291
|
+
}
|
|
16292
|
+
// Pick first base destination
|
|
16293
|
+
const dest = baseDestinations[0];
|
|
16294
|
+
const amount = dest.amount;
|
|
16295
|
+
const currency = dest.currency;
|
|
16296
|
+
const receiver = dest.address;
|
|
16201
16297
|
if (currency.toUpperCase() !== 'USDC') {
|
|
16202
16298
|
throw new PaymentNetworkError('Only USDC currency is supported; received ' + currency);
|
|
16203
16299
|
}
|
|
@@ -16240,7 +16336,12 @@ class BasePaymentMaker {
|
|
|
16240
16336
|
throw new PaymentNetworkError(`Transaction reverted: ${hash}`, new Error('Transaction reverted on chain'));
|
|
16241
16337
|
}
|
|
16242
16338
|
this.logger.info(`Transaction confirmed: ${hash} in block ${receipt.blockNumber}`);
|
|
16243
|
-
|
|
16339
|
+
// Return payment result with chain and currency
|
|
16340
|
+
return {
|
|
16341
|
+
transactionId: hash,
|
|
16342
|
+
chain: 'base',
|
|
16343
|
+
currency: 'USDC'
|
|
16344
|
+
};
|
|
16244
16345
|
}
|
|
16245
16346
|
catch (error) {
|
|
16246
16347
|
if (error instanceof InsufficientFundsError || error instanceof PaymentNetworkError) {
|
|
@@ -16261,228 +16362,22 @@ class SolanaAccount {
|
|
|
16261
16362
|
throw new Error('Source secret key is required');
|
|
16262
16363
|
}
|
|
16263
16364
|
const source = web3_js.Keypair.fromSecretKey(bs58.decode(sourceSecretKey));
|
|
16264
|
-
this.
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
function toBasicAuth$1(token) {
|
|
16272
|
-
// Basic auth is base64("username:password"), password is blank
|
|
16273
|
-
const b64 = Buffer.from(`${token}:`).toString('base64');
|
|
16274
|
-
return `Basic ${b64}`;
|
|
16275
|
-
}
|
|
16276
|
-
/**
|
|
16277
|
-
* ATXP implementation of viem's LocalAccount interface.
|
|
16278
|
-
* Delegates signing operations to the accounts-x402 API.
|
|
16279
|
-
* Includes properties needed by x402 library for wallet client compatibility.
|
|
16280
|
-
*/
|
|
16281
|
-
class ATXPLocalAccount {
|
|
16282
|
-
constructor(address, origin, token, fetchFn = fetch) {
|
|
16283
|
-
this.address = address;
|
|
16284
|
-
this.origin = origin;
|
|
16285
|
-
this.token = token;
|
|
16286
|
-
this.fetchFn = fetchFn;
|
|
16287
|
-
this.type = 'local';
|
|
16288
|
-
/**
|
|
16289
|
-
* Get public key - required by LocalAccount interface
|
|
16290
|
-
*/
|
|
16291
|
-
this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
16292
|
-
/**
|
|
16293
|
-
* Source - required by LocalAccount interface (set to 'custom')
|
|
16294
|
-
*/
|
|
16295
|
-
this.source = 'custom';
|
|
16296
|
-
// x402 library expects these properties for wallet client compatibility
|
|
16297
|
-
this.account = this; // Self-reference for x402's isSignerWallet check
|
|
16298
|
-
this.chain = { id: 8453 }; // Base mainnet - could make this configurable
|
|
16299
|
-
this.transport = {}; // Empty transport object for x402 compatibility
|
|
16300
|
-
}
|
|
16301
|
-
/**
|
|
16302
|
-
* Fetch the wallet address from the /address endpoint
|
|
16303
|
-
*/
|
|
16304
|
-
static async create(origin, token, fetchFn = fetch) {
|
|
16305
|
-
// The /address endpoint uses Basic auth like other authenticated endpoints
|
|
16306
|
-
// For X402, we need the Ethereum/Base address with USDC currency
|
|
16307
|
-
const url = new URL(`${origin}/address`);
|
|
16308
|
-
url.searchParams.set('network', 'base'); // X402 operates on Base
|
|
16309
|
-
url.searchParams.set('currency', 'USDC'); // Always USDC for X402
|
|
16310
|
-
const response = await fetchFn(url.toString(), {
|
|
16311
|
-
method: 'GET',
|
|
16312
|
-
headers: {
|
|
16313
|
-
'Authorization': toBasicAuth$1(token)
|
|
16314
|
-
}
|
|
16315
|
-
});
|
|
16316
|
-
if (!response.ok) {
|
|
16317
|
-
const errorText = await response.text();
|
|
16318
|
-
throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
|
|
16319
|
-
}
|
|
16320
|
-
const data = await response.json();
|
|
16321
|
-
const address = data.address;
|
|
16322
|
-
if (!address) {
|
|
16323
|
-
throw new Error('Address endpoint did not return an address');
|
|
16324
|
-
}
|
|
16325
|
-
// Check that the account is an Ethereum/Base account (required for X402/EVM operations)
|
|
16326
|
-
const network = data.network;
|
|
16327
|
-
if (!network) {
|
|
16328
|
-
throw new Error('Address endpoint did not return a network');
|
|
16329
|
-
}
|
|
16330
|
-
if (network !== 'ethereum' && network !== 'base') {
|
|
16331
|
-
throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
|
|
16332
|
-
}
|
|
16333
|
-
return new ATXPLocalAccount(address, origin, token, fetchFn);
|
|
16334
|
-
}
|
|
16335
|
-
/**
|
|
16336
|
-
* Sign a typed data structure using EIP-712
|
|
16337
|
-
* This is what x402 library will call for EIP-3009 authorization
|
|
16338
|
-
*/
|
|
16339
|
-
async signTypedData(typedData) {
|
|
16340
|
-
const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
|
|
16341
|
-
method: 'POST',
|
|
16342
|
-
headers: {
|
|
16343
|
-
'Authorization': toBasicAuth$1(this.token),
|
|
16344
|
-
'Content-Type': 'application/json',
|
|
16345
|
-
},
|
|
16346
|
-
body: JSON.stringify({
|
|
16347
|
-
typedData
|
|
16348
|
-
})
|
|
16349
|
-
});
|
|
16350
|
-
if (!response.ok) {
|
|
16351
|
-
const errorText = await response.text();
|
|
16352
|
-
throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
|
|
16353
|
-
}
|
|
16354
|
-
const result = await response.json();
|
|
16355
|
-
return result.signature;
|
|
16365
|
+
this.sourcePublicKey = source.publicKey.toBase58();
|
|
16366
|
+
// Format accountId as network:address
|
|
16367
|
+
this.accountId = `solana:${this.sourcePublicKey}`;
|
|
16368
|
+
this.paymentMakers = [
|
|
16369
|
+
new SolanaPaymentMaker(solanaEndpoint, sourceSecretKey)
|
|
16370
|
+
];
|
|
16356
16371
|
}
|
|
16357
16372
|
/**
|
|
16358
|
-
*
|
|
16373
|
+
* Get sources for this account
|
|
16359
16374
|
*/
|
|
16360
|
-
async
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
async signTransaction(_transaction, _args) {
|
|
16367
|
-
throw new Error('Transaction signing not implemented for ATXP local account');
|
|
16368
|
-
}
|
|
16369
|
-
}
|
|
16370
|
-
|
|
16371
|
-
function toBasicAuth(token) {
|
|
16372
|
-
// Basic auth is base64("username:password"), password is blank
|
|
16373
|
-
const b64 = Buffer.from(`${token}:`).toString('base64');
|
|
16374
|
-
return `Basic ${b64}`;
|
|
16375
|
-
}
|
|
16376
|
-
function parseConnectionString(connectionString) {
|
|
16377
|
-
const url = new URL(connectionString);
|
|
16378
|
-
const origin = url.origin;
|
|
16379
|
-
const token = url.searchParams.get('connection_token') || '';
|
|
16380
|
-
const accountId = url.searchParams.get('account_id');
|
|
16381
|
-
if (!token) {
|
|
16382
|
-
throw new Error('ATXPAccount: connection string missing connection token');
|
|
16383
|
-
}
|
|
16384
|
-
return { origin, token, accountId };
|
|
16385
|
-
}
|
|
16386
|
-
class ATXPHttpPaymentMaker {
|
|
16387
|
-
constructor(origin, token, fetchFn = fetch) {
|
|
16388
|
-
this.origin = origin;
|
|
16389
|
-
this.token = token;
|
|
16390
|
-
this.fetchFn = fetchFn;
|
|
16391
|
-
}
|
|
16392
|
-
async getSourceAddress(params) {
|
|
16393
|
-
// Call the /address_for_payment endpoint to get the source address for this account
|
|
16394
|
-
const response = await this.fetchFn(`${this.origin}/address_for_payment`, {
|
|
16395
|
-
method: 'POST',
|
|
16396
|
-
headers: {
|
|
16397
|
-
'Authorization': toBasicAuth(this.token),
|
|
16398
|
-
'Content-Type': 'application/json',
|
|
16399
|
-
},
|
|
16400
|
-
body: JSON.stringify({
|
|
16401
|
-
amount: params.amount.toString(),
|
|
16402
|
-
currency: params.currency,
|
|
16403
|
-
receiver: params.receiver,
|
|
16404
|
-
memo: params.memo,
|
|
16405
|
-
}),
|
|
16406
|
-
});
|
|
16407
|
-
if (!response.ok) {
|
|
16408
|
-
const text = await response.text();
|
|
16409
|
-
throw new Error(`ATXPAccount: /address_for_payment failed: ${response.status} ${response.statusText} ${text}`);
|
|
16410
|
-
}
|
|
16411
|
-
const json = await response.json();
|
|
16412
|
-
if (!json?.sourceAddress) {
|
|
16413
|
-
throw new Error('ATXPAccount: /address_for_payment did not return sourceAddress');
|
|
16414
|
-
}
|
|
16415
|
-
return json.sourceAddress;
|
|
16416
|
-
}
|
|
16417
|
-
async makePayment(amount, currency, receiver, memo) {
|
|
16418
|
-
// Make a regular payment via the /pay endpoint
|
|
16419
|
-
const response = await this.fetchFn(`${this.origin}/pay`, {
|
|
16420
|
-
method: 'POST',
|
|
16421
|
-
headers: {
|
|
16422
|
-
'Authorization': toBasicAuth(this.token),
|
|
16423
|
-
'Content-Type': 'application/json',
|
|
16424
|
-
},
|
|
16425
|
-
body: JSON.stringify({
|
|
16426
|
-
amount: amount.toString(),
|
|
16427
|
-
currency,
|
|
16428
|
-
receiver,
|
|
16429
|
-
memo,
|
|
16430
|
-
}),
|
|
16431
|
-
});
|
|
16432
|
-
if (!response.ok) {
|
|
16433
|
-
const text = await response.text();
|
|
16434
|
-
throw new Error(`ATXPAccount: /pay failed: ${response.status} ${response.statusText} ${text}`);
|
|
16435
|
-
}
|
|
16436
|
-
const json = await response.json();
|
|
16437
|
-
if (!json?.txHash) {
|
|
16438
|
-
throw new Error('ATXPAccount: /pay did not return txHash');
|
|
16439
|
-
}
|
|
16440
|
-
return json.txHash;
|
|
16441
|
-
}
|
|
16442
|
-
async generateJWT(params) {
|
|
16443
|
-
const response = await this.fetchFn(`${this.origin}/sign`, {
|
|
16444
|
-
method: 'POST',
|
|
16445
|
-
headers: {
|
|
16446
|
-
'Authorization': toBasicAuth(this.token),
|
|
16447
|
-
'Content-Type': 'application/json',
|
|
16448
|
-
},
|
|
16449
|
-
body: JSON.stringify({
|
|
16450
|
-
paymentRequestId: params.paymentRequestId,
|
|
16451
|
-
codeChallenge: params.codeChallenge,
|
|
16452
|
-
}),
|
|
16453
|
-
});
|
|
16454
|
-
if (!response.ok) {
|
|
16455
|
-
const text = await response.text();
|
|
16456
|
-
throw new Error(`ATXPAccount: /sign failed: ${response.status} ${response.statusText} ${text}`);
|
|
16457
|
-
}
|
|
16458
|
-
const json = await response.json();
|
|
16459
|
-
if (!json?.jwt) {
|
|
16460
|
-
throw new Error('ATXPAccount: /sign did not return jwt');
|
|
16461
|
-
}
|
|
16462
|
-
return json.jwt;
|
|
16463
|
-
}
|
|
16464
|
-
}
|
|
16465
|
-
class ATXPAccount {
|
|
16466
|
-
constructor(connectionString, opts) {
|
|
16467
|
-
const { origin, token, accountId } = parseConnectionString(connectionString);
|
|
16468
|
-
const fetchFn = opts?.fetchFn ?? fetch;
|
|
16469
|
-
const network = opts?.network ?? 'base';
|
|
16470
|
-
// Store for use in X402 payment creation
|
|
16471
|
-
this.origin = origin;
|
|
16472
|
-
this.token = token;
|
|
16473
|
-
this.fetchFn = fetchFn;
|
|
16474
|
-
if (accountId) {
|
|
16475
|
-
this.accountId = `atxp:${accountId}`;
|
|
16476
|
-
}
|
|
16477
|
-
else {
|
|
16478
|
-
this.accountId = `atxp:${common.crypto.randomUUID()}`;
|
|
16479
|
-
}
|
|
16480
|
-
this.paymentMakers = {
|
|
16481
|
-
[network]: new ATXPHttpPaymentMaker(origin, token, fetchFn),
|
|
16482
|
-
};
|
|
16483
|
-
}
|
|
16484
|
-
async getSigner() {
|
|
16485
|
-
return ATXPLocalAccount.create(this.origin, this.token, this.fetchFn);
|
|
16375
|
+
async getSources() {
|
|
16376
|
+
return [{
|
|
16377
|
+
address: this.sourcePublicKey,
|
|
16378
|
+
chain: 'solana',
|
|
16379
|
+
walletType: 'eoa'
|
|
16380
|
+
}];
|
|
16486
16381
|
}
|
|
16487
16382
|
}
|
|
16488
16383
|
|
|
@@ -16566,6 +16461,88 @@ const getWorldChainUSDCAddress = (chainId) => {
|
|
|
16566
16461
|
}
|
|
16567
16462
|
};
|
|
16568
16463
|
|
|
16464
|
+
const USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"; // Native USDC on Polygon mainnet
|
|
16465
|
+
const USDC_CONTRACT_ADDRESS_POLYGON_AMOY = "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"; // USDC on Polygon Amoy testnet
|
|
16466
|
+
// Polygon Mainnet (Chain ID: 137)
|
|
16467
|
+
// Note: Native currency upgraded from MATIC to POL on September 4, 2024
|
|
16468
|
+
const POLYGON_MAINNET = {
|
|
16469
|
+
id: 137,
|
|
16470
|
+
name: 'Polygon',
|
|
16471
|
+
nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
|
|
16472
|
+
rpcUrls: {
|
|
16473
|
+
default: { http: ['https://polygon-rpc.com'] }
|
|
16474
|
+
},
|
|
16475
|
+
blockExplorers: {
|
|
16476
|
+
default: { name: 'PolygonScan', url: 'https://polygonscan.com' }
|
|
16477
|
+
}
|
|
16478
|
+
};
|
|
16479
|
+
// Polygon Amoy Testnet (Chain ID: 80002)
|
|
16480
|
+
// Note: Amoy testnet also uses POL as native currency (following mainnet upgrade)
|
|
16481
|
+
const POLYGON_AMOY = {
|
|
16482
|
+
id: 80002,
|
|
16483
|
+
name: 'Polygon Amoy',
|
|
16484
|
+
nativeCurrency: { name: 'POL', symbol: 'POL', decimals: 18 },
|
|
16485
|
+
rpcUrls: {
|
|
16486
|
+
default: { http: ['https://rpc-amoy.polygon.technology'] }
|
|
16487
|
+
},
|
|
16488
|
+
blockExplorers: {
|
|
16489
|
+
default: { name: 'PolygonScan Amoy', url: 'https://amoy.polygonscan.com' }
|
|
16490
|
+
},
|
|
16491
|
+
testnet: true
|
|
16492
|
+
};
|
|
16493
|
+
/**
|
|
16494
|
+
* Get Polygon Mainnet configuration with custom RPC URL (e.g., with API key)
|
|
16495
|
+
* @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-mainnet.g.alchemy.com/v2/YOUR_API_KEY'
|
|
16496
|
+
*/
|
|
16497
|
+
const getPolygonMainnetWithRPC = (rpcUrl) => ({
|
|
16498
|
+
...POLYGON_MAINNET,
|
|
16499
|
+
rpcUrls: {
|
|
16500
|
+
default: { http: [rpcUrl] }
|
|
16501
|
+
}
|
|
16502
|
+
});
|
|
16503
|
+
/**
|
|
16504
|
+
* Get Polygon Amoy Testnet configuration with custom RPC URL (e.g., with API key)
|
|
16505
|
+
* @param rpcUrl - Custom RPC URL, e.g., 'https://polygon-amoy.g.alchemy.com/v2/YOUR_API_KEY'
|
|
16506
|
+
*/
|
|
16507
|
+
const getPolygonAmoyWithRPC = (rpcUrl) => ({
|
|
16508
|
+
...POLYGON_AMOY,
|
|
16509
|
+
rpcUrls: {
|
|
16510
|
+
default: { http: [rpcUrl] }
|
|
16511
|
+
}
|
|
16512
|
+
});
|
|
16513
|
+
/**
|
|
16514
|
+
* Get Polygon Chain configuration by chain ID
|
|
16515
|
+
* @param chainId - Chain ID (137 for mainnet, 80002 for Amoy testnet)
|
|
16516
|
+
* @returns Polygon Chain configuration
|
|
16517
|
+
* @throws Error if chain ID is not supported
|
|
16518
|
+
*/
|
|
16519
|
+
const getPolygonByChainId = (chainId) => {
|
|
16520
|
+
switch (chainId) {
|
|
16521
|
+
case 137:
|
|
16522
|
+
return POLYGON_MAINNET;
|
|
16523
|
+
case 80002:
|
|
16524
|
+
return POLYGON_AMOY;
|
|
16525
|
+
default:
|
|
16526
|
+
throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet), 80002 (Amoy testnet)`);
|
|
16527
|
+
}
|
|
16528
|
+
};
|
|
16529
|
+
/**
|
|
16530
|
+
* Get USDC contract address for Polygon by chain ID
|
|
16531
|
+
* @param chainId - Chain ID (137 for mainnet, 80002 for Amoy testnet)
|
|
16532
|
+
* @returns USDC contract address
|
|
16533
|
+
* @throws Error if chain ID is not supported
|
|
16534
|
+
*/
|
|
16535
|
+
const getPolygonUSDCAddress = (chainId) => {
|
|
16536
|
+
switch (chainId) {
|
|
16537
|
+
case 137:
|
|
16538
|
+
return USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
|
|
16539
|
+
case 80002:
|
|
16540
|
+
return USDC_CONTRACT_ADDRESS_POLYGON_AMOY;
|
|
16541
|
+
default:
|
|
16542
|
+
throw new Error(`Unsupported Polygon Chain ID: ${chainId}. Supported chains: 137 (mainnet), 80002 (Amoy testnet)`);
|
|
16543
|
+
}
|
|
16544
|
+
};
|
|
16545
|
+
|
|
16569
16546
|
class BaseAccount {
|
|
16570
16547
|
constructor(baseRPCUrl, sourceSecretKey) {
|
|
16571
16548
|
if (!baseRPCUrl) {
|
|
@@ -16575,27 +16552,141 @@ class BaseAccount {
|
|
|
16575
16552
|
throw new Error('Source secret key is required');
|
|
16576
16553
|
}
|
|
16577
16554
|
this.account = accounts.privateKeyToAccount(sourceSecretKey);
|
|
16578
|
-
|
|
16555
|
+
// Format accountId as network:address
|
|
16556
|
+
this.accountId = `base:${this.account.address}`;
|
|
16579
16557
|
this.walletClient = viem.createWalletClient({
|
|
16580
16558
|
account: this.account,
|
|
16581
16559
|
chain: chains.base,
|
|
16582
16560
|
transport: viem.http(baseRPCUrl),
|
|
16583
16561
|
});
|
|
16584
|
-
this.paymentMakers =
|
|
16585
|
-
|
|
16586
|
-
|
|
16562
|
+
this.paymentMakers = [
|
|
16563
|
+
new BasePaymentMaker(baseRPCUrl, this.walletClient)
|
|
16564
|
+
];
|
|
16587
16565
|
}
|
|
16588
16566
|
/**
|
|
16589
|
-
* Get
|
|
16590
|
-
* This
|
|
16567
|
+
* Get the LocalAccount (signer) for this account.
|
|
16568
|
+
* This can be used with the x402 library or other signing operations.
|
|
16591
16569
|
*/
|
|
16592
|
-
|
|
16593
|
-
// Return the viem account directly - it implements LocalAccount interface
|
|
16570
|
+
getLocalAccount() {
|
|
16594
16571
|
return this.account;
|
|
16595
16572
|
}
|
|
16573
|
+
/**
|
|
16574
|
+
* Get sources for this account
|
|
16575
|
+
*/
|
|
16576
|
+
async getSources() {
|
|
16577
|
+
return [{
|
|
16578
|
+
address: this.account.address,
|
|
16579
|
+
chain: 'base',
|
|
16580
|
+
walletType: 'eoa'
|
|
16581
|
+
}];
|
|
16582
|
+
}
|
|
16596
16583
|
}
|
|
16597
16584
|
|
|
16598
|
-
|
|
16585
|
+
function toBasicAuth(token) {
|
|
16586
|
+
// Basic auth is base64("username:password"), password is blank
|
|
16587
|
+
const b64 = Buffer.from(`${token}:`).toString('base64');
|
|
16588
|
+
return `Basic ${b64}`;
|
|
16589
|
+
}
|
|
16590
|
+
/**
|
|
16591
|
+
* ATXP implementation of viem's LocalAccount interface.
|
|
16592
|
+
* Delegates signing operations to the accounts-x402 API.
|
|
16593
|
+
* Includes properties needed by x402 library for wallet client compatibility.
|
|
16594
|
+
*/
|
|
16595
|
+
class ATXPLocalAccount {
|
|
16596
|
+
constructor(address, origin, token, fetchFn = fetch) {
|
|
16597
|
+
this.address = address;
|
|
16598
|
+
this.origin = origin;
|
|
16599
|
+
this.token = token;
|
|
16600
|
+
this.fetchFn = fetchFn;
|
|
16601
|
+
this.type = 'local';
|
|
16602
|
+
/**
|
|
16603
|
+
* Get public key - required by LocalAccount interface
|
|
16604
|
+
*/
|
|
16605
|
+
this.publicKey = '0x0000000000000000000000000000000000000000000000000000000000000000';
|
|
16606
|
+
/**
|
|
16607
|
+
* Source - required by LocalAccount interface (set to 'custom')
|
|
16608
|
+
*/
|
|
16609
|
+
this.source = 'custom';
|
|
16610
|
+
// x402 library expects these properties for wallet client compatibility
|
|
16611
|
+
this.account = this; // Self-reference for x402's isSignerWallet check
|
|
16612
|
+
this.chain = { id: 8453 }; // Base mainnet - could make this configurable
|
|
16613
|
+
this.transport = {}; // Empty transport object for x402 compatibility
|
|
16614
|
+
}
|
|
16615
|
+
/**
|
|
16616
|
+
* Fetch the wallet address from the /address endpoint
|
|
16617
|
+
*/
|
|
16618
|
+
static async create(origin, token, fetchFn = fetch) {
|
|
16619
|
+
// The /address endpoint uses Basic auth like other authenticated endpoints
|
|
16620
|
+
// For X402, we need the Ethereum/Base address with USDC currency
|
|
16621
|
+
const url = new URL(`${origin}/address`);
|
|
16622
|
+
url.searchParams.set('network', 'base'); // X402 operates on Base
|
|
16623
|
+
url.searchParams.set('currency', 'USDC'); // Always USDC for X402
|
|
16624
|
+
const response = await fetchFn(url.toString(), {
|
|
16625
|
+
method: 'GET',
|
|
16626
|
+
headers: {
|
|
16627
|
+
'Authorization': toBasicAuth(token)
|
|
16628
|
+
}
|
|
16629
|
+
});
|
|
16630
|
+
if (!response.ok) {
|
|
16631
|
+
const errorText = await response.text();
|
|
16632
|
+
throw new Error(`Failed to fetch destination address: ${response.status} ${response.statusText} ${errorText}`);
|
|
16633
|
+
}
|
|
16634
|
+
const data = await response.json();
|
|
16635
|
+
const address = data.address;
|
|
16636
|
+
if (!address) {
|
|
16637
|
+
throw new Error('Address endpoint did not return an address');
|
|
16638
|
+
}
|
|
16639
|
+
// Check that the account is an Ethereum/Base account (required for X402/EVM operations)
|
|
16640
|
+
const network = data.network;
|
|
16641
|
+
if (!network) {
|
|
16642
|
+
throw new Error('Address endpoint did not return a network');
|
|
16643
|
+
}
|
|
16644
|
+
if (network !== 'ethereum' && network !== 'base') {
|
|
16645
|
+
throw new Error(`ATXPLocalAccount requires an Ethereum/Base account, but got ${network} account`);
|
|
16646
|
+
}
|
|
16647
|
+
return new ATXPLocalAccount(address, origin, token, fetchFn);
|
|
16648
|
+
}
|
|
16649
|
+
/**
|
|
16650
|
+
* Sign a typed data structure using EIP-712
|
|
16651
|
+
* This is what x402 library will call for EIP-3009 authorization
|
|
16652
|
+
*/
|
|
16653
|
+
async signTypedData(typedData) {
|
|
16654
|
+
const response = await this.fetchFn(`${this.origin}/sign-typed-data`, {
|
|
16655
|
+
method: 'POST',
|
|
16656
|
+
headers: {
|
|
16657
|
+
'Authorization': toBasicAuth(this.token),
|
|
16658
|
+
'Content-Type': 'application/json',
|
|
16659
|
+
},
|
|
16660
|
+
body: JSON.stringify({
|
|
16661
|
+
typedData
|
|
16662
|
+
})
|
|
16663
|
+
});
|
|
16664
|
+
if (!response.ok) {
|
|
16665
|
+
const errorText = await response.text();
|
|
16666
|
+
throw new Error(`Failed to sign typed data: ${response.status} ${response.statusText} ${errorText}`);
|
|
16667
|
+
}
|
|
16668
|
+
const result = await response.json();
|
|
16669
|
+
return result.signature;
|
|
16670
|
+
}
|
|
16671
|
+
/**
|
|
16672
|
+
* Sign a message - required by LocalAccount interface but not used for X402
|
|
16673
|
+
*/
|
|
16674
|
+
async signMessage(_) {
|
|
16675
|
+
throw new Error('Message signing not implemented for ATXP local account');
|
|
16676
|
+
}
|
|
16677
|
+
/**
|
|
16678
|
+
* Sign a transaction - required by LocalAccount interface but not used for X402
|
|
16679
|
+
*/
|
|
16680
|
+
async signTransaction(_transaction, _args) {
|
|
16681
|
+
throw new Error('Transaction signing not implemented for ATXP local account');
|
|
16682
|
+
}
|
|
16683
|
+
}
|
|
16684
|
+
|
|
16685
|
+
Object.defineProperty(exports, "ATXPAccount", {
|
|
16686
|
+
enumerable: true,
|
|
16687
|
+
get: function () { return common.ATXPAccount; }
|
|
16688
|
+
});
|
|
16689
|
+
exports.ATXPDestinationMaker = ATXPDestinationMaker;
|
|
16599
16690
|
exports.ATXPLocalAccount = ATXPLocalAccount;
|
|
16600
16691
|
exports.BaseAccount = BaseAccount;
|
|
16601
16692
|
exports.BasePaymentMaker = BasePaymentMaker;
|
|
@@ -16603,11 +16694,16 @@ exports.DEFAULT_CLIENT_CONFIG = DEFAULT_CLIENT_CONFIG;
|
|
|
16603
16694
|
exports.InsufficientFundsError = InsufficientFundsError;
|
|
16604
16695
|
exports.OAuthAuthenticationRequiredError = OAuthAuthenticationRequiredError;
|
|
16605
16696
|
exports.OAuthClient = OAuthClient;
|
|
16697
|
+
exports.POLYGON_AMOY = POLYGON_AMOY;
|
|
16698
|
+
exports.POLYGON_MAINNET = POLYGON_MAINNET;
|
|
16699
|
+
exports.PassthroughDestinationMaker = PassthroughDestinationMaker;
|
|
16606
16700
|
exports.PaymentNetworkError = PaymentNetworkError;
|
|
16607
16701
|
exports.SolanaAccount = SolanaAccount;
|
|
16608
16702
|
exports.SolanaPaymentMaker = SolanaPaymentMaker;
|
|
16609
16703
|
exports.USDC_CONTRACT_ADDRESS_BASE = USDC_CONTRACT_ADDRESS_BASE;
|
|
16610
16704
|
exports.USDC_CONTRACT_ADDRESS_BASE_SEPOLIA = USDC_CONTRACT_ADDRESS_BASE_SEPOLIA;
|
|
16705
|
+
exports.USDC_CONTRACT_ADDRESS_POLYGON_AMOY = USDC_CONTRACT_ADDRESS_POLYGON_AMOY;
|
|
16706
|
+
exports.USDC_CONTRACT_ADDRESS_POLYGON_MAINNET = USDC_CONTRACT_ADDRESS_POLYGON_MAINNET;
|
|
16611
16707
|
exports.USDC_CONTRACT_ADDRESS_WORLD_MAINNET = USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
|
|
16612
16708
|
exports.USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA = USDC_CONTRACT_ADDRESS_WORLD_SEPOLIA;
|
|
16613
16709
|
exports.ValidateTransferError = ValidateTransferError;
|
|
@@ -16618,6 +16714,10 @@ exports.atxpFetch = atxpFetch;
|
|
|
16618
16714
|
exports.buildClientConfig = buildClientConfig;
|
|
16619
16715
|
exports.buildStreamableTransport = buildStreamableTransport;
|
|
16620
16716
|
exports.getBaseUSDCAddress = getBaseUSDCAddress;
|
|
16717
|
+
exports.getPolygonAmoyWithRPC = getPolygonAmoyWithRPC;
|
|
16718
|
+
exports.getPolygonByChainId = getPolygonByChainId;
|
|
16719
|
+
exports.getPolygonMainnetWithRPC = getPolygonMainnetWithRPC;
|
|
16720
|
+
exports.getPolygonUSDCAddress = getPolygonUSDCAddress;
|
|
16621
16721
|
exports.getWorldChainByChainId = getWorldChainByChainId;
|
|
16622
16722
|
exports.getWorldChainMainnetWithRPC = getWorldChainMainnetWithRPC;
|
|
16623
16723
|
exports.getWorldChainSepoliaWithRPC = getWorldChainSepoliaWithRPC;
|