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