uvd-x402-sdk 2.11.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/adapters/index.d.mts +1 -1
  2. package/dist/adapters/index.d.ts +1 -1
  3. package/dist/adapters/index.js.map +1 -1
  4. package/dist/adapters/index.mjs.map +1 -1
  5. package/dist/backend/index.d.mts +1 -1
  6. package/dist/backend/index.d.ts +1 -1
  7. package/dist/backend/index.js.map +1 -1
  8. package/dist/backend/index.mjs.map +1 -1
  9. package/dist/{index-C60c_e5z.d.mts → index-C6Vxnneo.d.mts} +1 -1
  10. package/dist/{index-VIOUicmO.d.ts → index-DmJGKD9r.d.ts} +1 -1
  11. package/dist/{index-D-dO_FoP.d.mts → index-fIhvHqCQ.d.mts} +18 -22
  12. package/dist/{index-D-dO_FoP.d.ts → index-fIhvHqCQ.d.ts} +18 -22
  13. package/dist/index.d.mts +56 -2
  14. package/dist/index.d.ts +56 -2
  15. package/dist/index.js +59 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +58 -1
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/providers/algorand/index.d.mts +11 -5
  20. package/dist/providers/algorand/index.d.ts +11 -5
  21. package/dist/providers/algorand/index.js +142 -27
  22. package/dist/providers/algorand/index.js.map +1 -1
  23. package/dist/providers/algorand/index.mjs +142 -27
  24. package/dist/providers/algorand/index.mjs.map +1 -1
  25. package/dist/providers/evm/index.d.mts +1 -1
  26. package/dist/providers/evm/index.d.ts +1 -1
  27. package/dist/providers/evm/index.js.map +1 -1
  28. package/dist/providers/evm/index.mjs.map +1 -1
  29. package/dist/providers/near/index.d.mts +1 -1
  30. package/dist/providers/near/index.d.ts +1 -1
  31. package/dist/providers/near/index.js.map +1 -1
  32. package/dist/providers/near/index.mjs.map +1 -1
  33. package/dist/providers/solana/index.d.mts +1 -1
  34. package/dist/providers/solana/index.d.ts +1 -1
  35. package/dist/providers/solana/index.js.map +1 -1
  36. package/dist/providers/solana/index.mjs.map +1 -1
  37. package/dist/providers/stellar/index.d.mts +1 -1
  38. package/dist/providers/stellar/index.d.ts +1 -1
  39. package/dist/providers/stellar/index.js.map +1 -1
  40. package/dist/providers/stellar/index.mjs.map +1 -1
  41. package/dist/react/index.d.mts +3 -3
  42. package/dist/react/index.d.ts +3 -3
  43. package/dist/react/index.js.map +1 -1
  44. package/dist/react/index.mjs.map +1 -1
  45. package/dist/utils/index.d.mts +1 -1
  46. package/dist/utils/index.d.ts +1 -1
  47. package/dist/utils/index.js.map +1 -1
  48. package/dist/utils/index.mjs.map +1 -1
  49. package/package.json +1 -1
  50. package/src/facilitator.ts +106 -0
  51. package/src/index.ts +4 -0
  52. package/src/providers/algorand/index.ts +122 -32
  53. package/src/types/index.ts +18 -22
@@ -40,6 +40,7 @@ import type {
40
40
  import { X402Error } from '../../types';
41
41
  import { getChainByName } from '../../chains';
42
42
  import { chainToCAIP2 } from '../../utils';
43
+ import { getFacilitatorAddress } from '../../facilitator';
43
44
 
44
45
  /**
45
46
  * Browser-compatible base64 encoding for Uint8Array
@@ -318,11 +319,13 @@ export class AlgorandProvider implements WalletAdapter {
318
319
  }
319
320
 
320
321
  /**
321
- * Create Algorand ASA transfer payment
322
+ * Create Algorand atomic group payment (GoPlausible x402-avm spec)
322
323
  *
323
- * Transaction structure:
324
- * 1. ASA Transfer from user to recipient
325
- * 2. Facilitator pays transaction fees
324
+ * Transaction structure (atomic group):
325
+ * - Transaction 0: Fee payment (UNSIGNED) - facilitator -> facilitator, covers all fees
326
+ * - Transaction 1: ASA transfer (SIGNED) - client -> merchant
327
+ *
328
+ * The facilitator signs transaction 0 and submits the complete atomic group.
326
329
  */
327
330
  async signPayment(paymentInfo: PaymentInfo, chainConfig: ChainConfig): Promise<string> {
328
331
  await loadAlgorandDeps();
@@ -341,6 +344,19 @@ export class AlgorandProvider implements WalletAdapter {
341
344
  const recipient = paymentInfo.recipients?.algorand || paymentInfo.recipient;
342
345
  const assetId = parseInt(chainConfig.usdc.address, 10);
343
346
 
347
+ // Get facilitator address (fee payer) - automatically from SDK config
348
+ // Priority: paymentInfo.facilitator > SDK default for chain
349
+ const chainName = chainConfig?.name || 'algorand';
350
+ const facilitatorAddress =
351
+ paymentInfo.facilitator || getFacilitatorAddress(chainName, 'algorand');
352
+
353
+ if (!facilitatorAddress) {
354
+ throw new X402Error(
355
+ 'Facilitator address not configured for Algorand',
356
+ 'PAYMENT_FAILED'
357
+ );
358
+ }
359
+
344
360
  // Parse amount (6 decimals for USDC)
345
361
  const amount = Math.floor(parseFloat(paymentInfo.amount) * 1_000_000);
346
362
 
@@ -348,46 +364,92 @@ export class AlgorandProvider implements WalletAdapter {
348
364
  // Get suggested transaction parameters
349
365
  const suggestedParams = await algodClient.getTransactionParams().do();
350
366
 
351
- // Create ASA transfer transaction
367
+ // Transaction 0: Fee payment (facilitator -> facilitator, 0 amount)
368
+ // This transaction pays fees for both txns in the group (fee pooling)
352
369
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
353
- const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
370
+ const feeTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
371
+ sender: facilitatorAddress,
372
+ receiver: facilitatorAddress, // self-transfer
373
+ amount: 0,
374
+ suggestedParams: {
375
+ ...suggestedParams,
376
+ fee: 2000, // Covers both transactions (1000 each)
377
+ flatFee: true,
378
+ },
379
+ } as any);
380
+
381
+ // Transaction 1: ASA transfer (client -> merchant)
382
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
383
+ const paymentTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
354
384
  sender: this.address,
355
385
  receiver: recipient,
356
386
  amount: BigInt(amount),
357
387
  assetIndex: assetId,
358
- suggestedParams: suggestedParams,
388
+ suggestedParams: {
389
+ ...suggestedParams,
390
+ fee: 0, // Fee paid by transaction 0
391
+ flatFee: true,
392
+ },
359
393
  note: new TextEncoder().encode('x402 payment via uvd-x402-sdk'),
360
394
  } as any);
361
395
 
362
- // Sign with the active wallet (Lute or Pera)
363
- let signedTxn: Uint8Array;
396
+ // Assign group ID to both transactions (creates atomic group)
397
+ const txnGroup = algosdk.assignGroupID([feeTxn, paymentTxn]);
398
+
399
+ // Encode fee transaction (UNSIGNED - facilitator will sign)
400
+ const unsignedFeeTxnBytes = algosdk.encodeUnsignedTransaction(txnGroup[0]);
401
+ const unsignedFeeTxnBase64 = uint8ArrayToBase64(unsignedFeeTxnBytes);
402
+
403
+ // Sign the payment transaction (index 1) with the active wallet
404
+ let signedPaymentTxnBytes: Uint8Array;
364
405
 
365
406
  if (this.walletType === 'lute' && this.luteWallet) {
366
407
  // Lute uses signTxns with base64 encoded transactions
367
- const txnBase64 = uint8ArrayToBase64(txn.toByte());
368
- const signedTxns = await this.luteWallet.signTxns([{ txn: txnBase64 }]);
369
- if (!signedTxns || signedTxns.length === 0 || !signedTxns[0]) {
408
+ // For atomic groups, pass both txns but only sign the one we control
409
+ const feeTxnBase64 = uint8ArrayToBase64(txnGroup[0].toByte());
410
+ const paymentTxnBase64 = uint8ArrayToBase64(txnGroup[1].toByte());
411
+
412
+ // Sign only the payment transaction (index 1), leave fee txn unsigned
413
+ const signedTxns = await this.luteWallet.signTxns([
414
+ { txn: feeTxnBase64, signers: [] }, // Don't sign - facilitator will
415
+ { txn: paymentTxnBase64 }, // Sign this one
416
+ ]);
417
+
418
+ if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
370
419
  throw new X402Error('No signed transaction returned', 'SIGNATURE_REJECTED');
371
420
  }
372
- // Lute returns base64 encoded signed transaction
373
- signedTxn = Uint8Array.from(atob(signedTxns[0]), c => c.charCodeAt(0));
421
+
422
+ // Get the signed payment transaction (index 1)
423
+ const signedResult = signedTxns[1];
424
+ signedPaymentTxnBytes = this.decodeSignedTxn(signedResult);
374
425
  } else if (this.walletType === 'pera' && this.peraWallet) {
375
426
  // Pera uses signTransaction with transaction objects
376
- const signedTxns = await this.peraWallet.signTransaction([[{ txn }]]);
377
- if (!signedTxns || signedTxns.length === 0) {
427
+ // For atomic groups, pass both txns but only sign the one we control
428
+ const signedTxns = await this.peraWallet.signTransaction([
429
+ [
430
+ { txn: txnGroup[0], signers: [] }, // Don't sign - facilitator will
431
+ { txn: txnGroup[1] }, // Sign this one
432
+ ],
433
+ ]);
434
+
435
+ if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
378
436
  throw new X402Error('No signed transaction returned', 'SIGNATURE_REJECTED');
379
437
  }
380
- signedTxn = signedTxns[0];
438
+
439
+ signedPaymentTxnBytes = signedTxns[1];
381
440
  } else {
382
441
  throw new X402Error('No wallet available for signing', 'WALLET_NOT_CONNECTED');
383
442
  }
384
443
 
444
+ const signedPaymentTxnBase64 = uint8ArrayToBase64(signedPaymentTxnBytes);
445
+
446
+ // Build payload following GoPlausible x402-avm spec
385
447
  const payload: AlgorandPaymentPayload = {
386
- from: this.address,
387
- to: recipient,
388
- amount: amount.toString(),
389
- assetId: assetId,
390
- signedTxn: uint8ArrayToBase64(signedTxn),
448
+ paymentIndex: 1, // Index of the payment transaction in the group
449
+ paymentGroup: [
450
+ unsignedFeeTxnBase64, // Transaction 0: unsigned fee tx
451
+ signedPaymentTxnBase64, // Transaction 1: signed payment tx
452
+ ],
391
453
  };
392
454
 
393
455
  return JSON.stringify(payload);
@@ -408,6 +470,32 @@ export class AlgorandProvider implements WalletAdapter {
408
470
  }
409
471
  }
410
472
 
473
+ /**
474
+ * Decode signed transaction from wallet response (handles various formats)
475
+ */
476
+ private decodeSignedTxn(signedResult: unknown): Uint8Array {
477
+ if (signedResult instanceof Uint8Array) {
478
+ return signedResult;
479
+ } else if (typeof signedResult === 'string') {
480
+ // Try to decode as base64
481
+ try {
482
+ return Uint8Array.from(atob(signedResult), c => c.charCodeAt(0));
483
+ } catch {
484
+ // If standard base64 fails, try URL-safe base64
485
+ const standardBase64 = signedResult.replace(/-/g, '+').replace(/_/g, '/');
486
+ return Uint8Array.from(atob(standardBase64), c => c.charCodeAt(0));
487
+ }
488
+ } else if (ArrayBuffer.isView(signedResult)) {
489
+ return new Uint8Array(
490
+ (signedResult as ArrayBufferView).buffer,
491
+ (signedResult as ArrayBufferView).byteOffset,
492
+ (signedResult as ArrayBufferView).byteLength
493
+ );
494
+ } else {
495
+ throw new X402Error('Unexpected signed transaction format', 'PAYMENT_FAILED');
496
+ }
497
+ }
498
+
411
499
  /**
412
500
  * Encode Algorand payment as X-PAYMENT header
413
501
  *
@@ -423,17 +511,19 @@ export class AlgorandProvider implements WalletAdapter {
423
511
  ): string {
424
512
  const payload = JSON.parse(paymentPayload) as AlgorandPaymentPayload;
425
513
 
426
- // Use chain name from config, or default to 'algorand'
427
- const networkName = chainConfig?.name || 'algorand';
514
+ // Determine network name for x402
515
+ // Use "algorand-mainnet" or "algorand-testnet" format for facilitator
516
+ let networkName: string;
517
+ if (chainConfig?.name === 'algorand-testnet') {
518
+ networkName = 'algorand-testnet';
519
+ } else {
520
+ networkName = 'algorand-mainnet'; // Default to mainnet
521
+ }
428
522
 
429
- // Build the payload data
523
+ // Build the payload data (GoPlausible x402-avm spec)
430
524
  const payloadData = {
431
- from: payload.from,
432
- to: payload.to,
433
- amount: payload.amount,
434
- assetId: payload.assetId,
435
- signedTxn: payload.signedTxn,
436
- ...(payload.note && { note: payload.note }),
525
+ paymentIndex: payload.paymentIndex,
526
+ paymentGroup: payload.paymentGroup,
437
527
  };
438
528
 
439
529
  // Format in x402 standard format (v1 or v2)
@@ -447,7 +537,7 @@ export class AlgorandProvider implements WalletAdapter {
447
537
  : {
448
538
  x402Version: 1 as const,
449
539
  scheme: 'exact' as const,
450
- network: networkName, // Plain chain name for v1
540
+ network: networkName,
451
541
  payload: payloadData,
452
542
  };
453
543
 
@@ -345,23 +345,21 @@ export interface NEARPaymentPayload {
345
345
  /**
346
346
  * Algorand payment payload (atomic transaction group)
347
347
  *
348
- * Algorand uses a unique payment model where the facilitator creates and signs
349
- * an atomic transaction group. The user signs their portion (the ASA transfer)
350
- * and the facilitator submits the complete group.
348
+ * Follows the GoPlausible x402-avm spec for atomic groups:
349
+ * - Transaction 0: Fee payment (UNSIGNED) - facilitator -> facilitator, covers all fees
350
+ * - Transaction 1: ASA transfer (SIGNED) - client -> merchant
351
+ *
352
+ * The facilitator signs transaction 0 and submits the complete atomic group.
351
353
  */
352
354
  export interface AlgorandPaymentPayload {
353
- /** Sender's Algorand address (58-character base32) */
354
- from: string;
355
- /** Recipient's Algorand address */
356
- to: string;
357
- /** Amount in base units (microAlgos for ALGO, or base units for ASA) */
358
- amount: string;
359
- /** USDC ASA ID (31566704 for mainnet, 10458941 for testnet) */
360
- assetId: number;
361
- /** Base64-encoded signed transaction bytes */
362
- signedTxn: string;
363
- /** Optional note field */
364
- note?: string;
355
+ /** Index of the payment transaction in the group (always 1) */
356
+ paymentIndex: number;
357
+ /**
358
+ * Array of base64-encoded msgpack transactions forming the atomic group:
359
+ * - [0]: Unsigned fee transaction (facilitator signs)
360
+ * - [1]: Signed ASA transfer (client signed)
361
+ */
362
+ paymentGroup: string[];
365
363
  }
366
364
 
367
365
  /**
@@ -497,15 +495,13 @@ export interface X402NEARPayload {
497
495
  }
498
496
 
499
497
  /**
500
- * Algorand-specific payload in x402 header
498
+ * Algorand-specific payload in x402 header (atomic group format)
501
499
  */
502
500
  export interface X402AlgorandPayload {
503
- from: string;
504
- to: string;
505
- amount: string;
506
- assetId: number;
507
- signedTxn: string;
508
- note?: string;
501
+ /** Index of the payment transaction in the group (always 1) */
502
+ paymentIndex: number;
503
+ /** Array of base64-encoded msgpack transactions */
504
+ paymentGroup: string[];
509
505
  }
510
506
 
511
507
  /**