uvd-x402-sdk 2.3.0 → 2.5.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 (56) hide show
  1. package/dist/adapters/index.d.mts +7 -1
  2. package/dist/adapters/index.d.ts +7 -1
  3. package/dist/adapters/index.js +87 -3
  4. package/dist/adapters/index.js.map +1 -1
  5. package/dist/adapters/index.mjs +87 -3
  6. package/dist/adapters/index.mjs.map +1 -1
  7. package/dist/index.d.mts +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +244 -146
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +243 -147
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/providers/evm/index.d.mts +7 -2
  14. package/dist/providers/evm/index.d.ts +7 -2
  15. package/dist/providers/evm/index.js +101 -13
  16. package/dist/providers/evm/index.js.map +1 -1
  17. package/dist/providers/evm/index.mjs +101 -13
  18. package/dist/providers/evm/index.mjs.map +1 -1
  19. package/dist/providers/near/index.d.mts +6 -2
  20. package/dist/providers/near/index.d.ts +6 -2
  21. package/dist/providers/near/index.js +562 -5
  22. package/dist/providers/near/index.js.map +1 -1
  23. package/dist/providers/near/index.mjs +562 -5
  24. package/dist/providers/near/index.mjs.map +1 -1
  25. package/dist/providers/solana/index.d.mts +6 -4
  26. package/dist/providers/solana/index.d.ts +6 -4
  27. package/dist/providers/solana/index.js +33 -7
  28. package/dist/providers/solana/index.js.map +1 -1
  29. package/dist/providers/solana/index.mjs +33 -7
  30. package/dist/providers/solana/index.mjs.map +1 -1
  31. package/dist/providers/stellar/index.d.mts +6 -2
  32. package/dist/providers/stellar/index.d.ts +6 -2
  33. package/dist/providers/stellar/index.js +568 -11
  34. package/dist/providers/stellar/index.js.map +1 -1
  35. package/dist/providers/stellar/index.mjs +568 -11
  36. package/dist/providers/stellar/index.mjs.map +1 -1
  37. package/dist/react/index.js +96 -12
  38. package/dist/react/index.js.map +1 -1
  39. package/dist/react/index.mjs +96 -12
  40. package/dist/react/index.mjs.map +1 -1
  41. package/dist/utils/index.d.mts +30 -1
  42. package/dist/utils/index.d.ts +30 -1
  43. package/dist/utils/index.js +101 -0
  44. package/dist/utils/index.js.map +1 -1
  45. package/dist/utils/index.mjs +100 -1
  46. package/dist/utils/index.mjs.map +1 -1
  47. package/package.json +1 -1
  48. package/src/adapters/wagmi.ts +20 -5
  49. package/src/client/X402Client.ts +32 -15
  50. package/src/index.ts +3 -0
  51. package/src/providers/evm/index.ts +40 -15
  52. package/src/providers/near/index.ts +25 -8
  53. package/src/providers/solana/index.ts +29 -10
  54. package/src/providers/stellar/index.ts +31 -14
  55. package/src/utils/index.ts +5 -0
  56. package/src/utils/validation.ts +151 -0
@@ -43,9 +43,11 @@ import type {
43
43
  PaymentInfo,
44
44
  SolanaPaymentPayload,
45
45
  WalletAdapter,
46
+ X402Version,
46
47
  } from '../../types';
47
48
  import { X402Error } from '../../types';
48
49
  import { getChainByName } from '../../chains';
50
+ import { chainToCAIP2 } from '../../utils';
49
51
 
50
52
  /**
51
53
  * Browser-compatible base64 encoding for Uint8Array
@@ -395,24 +397,41 @@ export class SVMProvider implements WalletAdapter {
395
397
  /**
396
398
  * Encode SVM payment as X-PAYMENT header
397
399
  *
398
- * @param paymentPayload - The payment payload JSON string
399
- * @param chainConfig - Optional chain config (defaults to 'solana' if not provided)
400
+ * @param paymentPayload - JSON-encoded payment payload from signPayment()
401
+ * @param chainConfig - Chain configuration (optional, defaults to 'solana')
402
+ * @param version - x402 protocol version (1 or 2, defaults to 1)
403
+ * @returns Base64-encoded X-PAYMENT header value
400
404
  */
401
- encodePaymentHeader(paymentPayload: string, chainConfig?: ChainConfig): string {
405
+ encodePaymentHeader(
406
+ paymentPayload: string,
407
+ chainConfig?: ChainConfig,
408
+ version: X402Version = 1
409
+ ): string {
402
410
  const payload = JSON.parse(paymentPayload) as SolanaPaymentPayload;
403
411
 
404
412
  // Use chain name from config, or default to 'solana' for backward compatibility
405
413
  const networkName = chainConfig?.name || 'solana';
406
414
 
407
- const x402Payload = {
408
- x402Version: 1,
409
- scheme: 'exact',
410
- network: networkName,
411
- payload: {
412
- transaction: payload.transaction,
413
- },
415
+ // Build the payload data
416
+ const payloadData = {
417
+ transaction: payload.transaction,
414
418
  };
415
419
 
420
+ // Format in x402 standard format (v1 or v2)
421
+ const x402Payload = version === 2
422
+ ? {
423
+ x402Version: 2 as const,
424
+ scheme: 'exact' as const,
425
+ network: chainToCAIP2(networkName), // CAIP-2 format for v2
426
+ payload: payloadData,
427
+ }
428
+ : {
429
+ x402Version: 1 as const,
430
+ scheme: 'exact' as const,
431
+ network: networkName, // Plain chain name for v1
432
+ payload: payloadData,
433
+ };
434
+
416
435
  return btoa(JSON.stringify(x402Payload));
417
436
  }
418
437
 
@@ -25,8 +25,10 @@ import type {
25
25
  PaymentInfo,
26
26
  StellarPaymentPayload,
27
27
  WalletAdapter,
28
+ X402Version,
28
29
  } from '../../types';
29
30
  import { X402Error } from '../../types';
31
+ import { chainToCAIP2 } from '../../utils';
30
32
 
31
33
  /**
32
34
  * Browser-compatible text to Uint8Array encoding
@@ -343,25 +345,40 @@ export class StellarProvider implements WalletAdapter {
343
345
 
344
346
  /**
345
347
  * Encode Stellar payment as X-PAYMENT header
348
+ *
349
+ * @param paymentPayload - JSON-encoded payment payload from signPayment()
350
+ * @param version - x402 protocol version (1 or 2, defaults to 1)
351
+ * @returns Base64-encoded X-PAYMENT header value
346
352
  */
347
- encodePaymentHeader(paymentPayload: string): string {
353
+ encodePaymentHeader(paymentPayload: string, version: X402Version = 1): string {
348
354
  const payload = JSON.parse(paymentPayload) as StellarPaymentPayload;
349
355
 
350
- const x402Payload = {
351
- x402Version: 1,
352
- scheme: 'exact',
353
- network: 'stellar',
354
- payload: {
355
- from: payload.from,
356
- to: payload.to,
357
- amount: payload.amount,
358
- tokenContract: payload.tokenContract,
359
- authorizationEntryXdr: payload.authorizationEntryXdr,
360
- nonce: payload.nonce,
361
- signatureExpirationLedger: payload.signatureExpirationLedger,
362
- },
356
+ // Build the payload data
357
+ const payloadData = {
358
+ from: payload.from,
359
+ to: payload.to,
360
+ amount: payload.amount,
361
+ tokenContract: payload.tokenContract,
362
+ authorizationEntryXdr: payload.authorizationEntryXdr,
363
+ nonce: payload.nonce,
364
+ signatureExpirationLedger: payload.signatureExpirationLedger,
363
365
  };
364
366
 
367
+ // Format in x402 standard format (v1 or v2)
368
+ const x402Payload = version === 2
369
+ ? {
370
+ x402Version: 2 as const,
371
+ scheme: 'exact' as const,
372
+ network: chainToCAIP2('stellar'), // CAIP-2 format for v2
373
+ payload: payloadData,
374
+ }
375
+ : {
376
+ x402Version: 1 as const,
377
+ scheme: 'exact' as const,
378
+ network: 'stellar', // Plain chain name for v1
379
+ payload: payloadData,
380
+ };
381
+
365
382
  return btoa(JSON.stringify(x402Payload));
366
383
  }
367
384
 
@@ -18,3 +18,8 @@ export {
18
18
  isCAIP2Format,
19
19
  convertX402Header,
20
20
  } from './x402';
21
+
22
+ export {
23
+ validateRecipient,
24
+ validateAmount,
25
+ } from './validation';
@@ -0,0 +1,151 @@
1
+ /**
2
+ * uvd-x402-sdk - Validation Utilities
3
+ *
4
+ * Functions for validating payment parameters to prevent
5
+ * invalid or empty values from being processed.
6
+ */
7
+
8
+ import { X402Error } from '../types';
9
+
10
+ /**
11
+ * Regular expression for validating Ethereum addresses
12
+ */
13
+ const ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
14
+
15
+ /**
16
+ * Regular expression for validating Solana addresses (base58, 32-44 chars)
17
+ */
18
+ const SOLANA_ADDRESS_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
19
+
20
+ /**
21
+ * Regular expression for validating Stellar addresses (G... format)
22
+ */
23
+ const STELLAR_ADDRESS_REGEX = /^G[A-Z2-7]{55}$/;
24
+
25
+ /**
26
+ * Regular expression for validating NEAR addresses
27
+ */
28
+ const NEAR_ADDRESS_REGEX = /^[a-z0-9._-]+$/;
29
+
30
+ /**
31
+ * Validate that a recipient address is present and valid
32
+ *
33
+ * This function ensures that:
34
+ * 1. The recipient is not null, undefined, or empty
35
+ * 2. The recipient is not just whitespace
36
+ * 3. For EVM, it's a valid checksummed or lowercase 0x address
37
+ *
38
+ * @param recipient - The recipient address to validate
39
+ * @param networkType - Optional network type for format validation
40
+ * @throws X402Error with code 'INVALID_RECIPIENT' if validation fails
41
+ */
42
+ export function validateRecipient(
43
+ recipient: string | undefined | null,
44
+ networkType?: 'evm' | 'svm' | 'solana' | 'stellar' | 'near'
45
+ ): asserts recipient is string {
46
+ // Check for null, undefined, or empty
47
+ if (!recipient) {
48
+ throw new X402Error(
49
+ 'Recipient address is required. The payTo/recipient field cannot be empty. ' +
50
+ 'Please provide a valid recipient address where payments should be sent.',
51
+ 'INVALID_RECIPIENT'
52
+ );
53
+ }
54
+
55
+ // Check for whitespace-only
56
+ const trimmed = recipient.trim();
57
+ if (trimmed === '') {
58
+ throw new X402Error(
59
+ 'Recipient address cannot be empty or whitespace. ' +
60
+ 'Please provide a valid recipient address.',
61
+ 'INVALID_RECIPIENT'
62
+ );
63
+ }
64
+
65
+ // Network-specific validation
66
+ if (networkType) {
67
+ switch (networkType) {
68
+ case 'evm':
69
+ if (!ETH_ADDRESS_REGEX.test(trimmed)) {
70
+ throw new X402Error(
71
+ `Invalid EVM recipient address: "${trimmed}". ` +
72
+ 'Expected a 40-character hexadecimal address starting with 0x.',
73
+ 'INVALID_RECIPIENT'
74
+ );
75
+ }
76
+ break;
77
+
78
+ case 'svm':
79
+ case 'solana':
80
+ if (!SOLANA_ADDRESS_REGEX.test(trimmed)) {
81
+ throw new X402Error(
82
+ `Invalid Solana recipient address: "${trimmed}". ` +
83
+ 'Expected a base58-encoded public key (32-44 characters).',
84
+ 'INVALID_RECIPIENT'
85
+ );
86
+ }
87
+ break;
88
+
89
+ case 'stellar':
90
+ if (!STELLAR_ADDRESS_REGEX.test(trimmed)) {
91
+ throw new X402Error(
92
+ `Invalid Stellar recipient address: "${trimmed}". ` +
93
+ 'Expected a G-prefixed public key (56 characters).',
94
+ 'INVALID_RECIPIENT'
95
+ );
96
+ }
97
+ break;
98
+
99
+ case 'near':
100
+ if (!NEAR_ADDRESS_REGEX.test(trimmed) || trimmed.length > 64) {
101
+ throw new X402Error(
102
+ `Invalid NEAR recipient address: "${trimmed}". ` +
103
+ 'Expected a valid NEAR account ID.',
104
+ 'INVALID_RECIPIENT'
105
+ );
106
+ }
107
+ break;
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Validate payment amount
114
+ *
115
+ * Ensures the amount is a valid positive number string
116
+ *
117
+ * @param amount - The amount string to validate
118
+ * @throws X402Error with code 'INVALID_AMOUNT' if validation fails
119
+ */
120
+ export function validateAmount(amount: string | undefined | null): asserts amount is string {
121
+ if (!amount) {
122
+ throw new X402Error(
123
+ 'Payment amount is required.',
124
+ 'INVALID_AMOUNT'
125
+ );
126
+ }
127
+
128
+ const trimmed = amount.trim();
129
+ if (trimmed === '') {
130
+ throw new X402Error(
131
+ 'Payment amount cannot be empty.',
132
+ 'INVALID_AMOUNT'
133
+ );
134
+ }
135
+
136
+ // Parse as number
137
+ const num = parseFloat(trimmed);
138
+ if (isNaN(num)) {
139
+ throw new X402Error(
140
+ `Invalid payment amount: "${trimmed}". Expected a valid number.`,
141
+ 'INVALID_AMOUNT'
142
+ );
143
+ }
144
+
145
+ if (num <= 0) {
146
+ throw new X402Error(
147
+ `Payment amount must be positive. Got: ${trimmed}`,
148
+ 'INVALID_AMOUNT'
149
+ );
150
+ }
151
+ }