uvd-x402-sdk 2.5.0 → 2.10.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.
- package/README.md +380 -3
- package/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +82 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +82 -1
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +1036 -0
- package/dist/backend/index.d.ts +1036 -0
- package/dist/backend/index.js +1722 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/index.mjs +1704 -0
- package/dist/backend/index.mjs.map +1 -0
- package/dist/{index-BrFeSWKm.d.mts → index-C60c_e5z.d.mts} +13 -4
- package/dist/{index-DR2vXt-c.d.mts → index-D-dO_FoP.d.mts} +70 -4
- package/dist/{index-DR2vXt-c.d.ts → index-D-dO_FoP.d.ts} +70 -4
- package/dist/{index-BYX9BU79.d.ts → index-VIOUicmO.d.ts} +13 -4
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +115 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -2
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +86 -0
- package/dist/providers/algorand/index.d.ts +86 -0
- package/dist/providers/algorand/index.js +903 -0
- package/dist/providers/algorand/index.js.map +1 -0
- package/dist/providers/algorand/index.mjs +898 -0
- package/dist/providers/algorand/index.mjs.map +1 -0
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/evm/index.js +78 -1
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs +78 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/near/index.d.mts +1 -1
- package/dist/providers/near/index.d.ts +1 -1
- package/dist/providers/near/index.js +78 -1
- package/dist/providers/near/index.js.map +1 -1
- package/dist/providers/near/index.mjs +78 -1
- package/dist/providers/near/index.mjs.map +1 -1
- package/dist/providers/solana/index.d.mts +1 -1
- package/dist/providers/solana/index.d.ts +1 -1
- package/dist/providers/solana/index.js +78 -1
- package/dist/providers/solana/index.js.map +1 -1
- package/dist/providers/solana/index.mjs +78 -1
- package/dist/providers/solana/index.mjs.map +1 -1
- package/dist/providers/stellar/index.d.mts +1 -1
- package/dist/providers/stellar/index.d.ts +1 -1
- package/dist/providers/stellar/index.js +78 -1
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs +78 -1
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js +82 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +82 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +57 -5
- package/dist/utils/index.d.ts +57 -5
- package/dist/utils/index.js +96 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +93 -2
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +24 -3
- package/src/adapters/wagmi.ts +4 -0
- package/src/backend/index.ts +2131 -0
- package/src/chains/index.ts +94 -2
- package/src/client/X402Client.ts +4 -0
- package/src/index.ts +26 -1
- package/src/providers/algorand/index.ts +356 -0
- package/src/types/index.ts +78 -3
- package/src/utils/index.ts +4 -0
- package/src/utils/validation.ts +76 -3
package/src/chains/index.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* uvd-x402-sdk - Chain Registry
|
|
3
3
|
*
|
|
4
|
-
* Complete configuration for all
|
|
5
|
-
* EVM chains (
|
|
4
|
+
* Complete configuration for all 16 supported blockchain networks.
|
|
5
|
+
* EVM chains (10): Use ERC-3009 TransferWithAuthorization
|
|
6
6
|
* SVM chains (2): Solana and Fogo - Use SPL tokens with partially-signed transactions
|
|
7
7
|
* Stellar (1): Uses Soroban authorization entries
|
|
8
8
|
* NEAR (1): Uses NEP-366 meta-transactions
|
|
9
|
+
* Algorand (2): Uses ASA transfers with atomic transaction groups
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import type { ChainConfig, NetworkType, TokenType, TokenConfig } from '../types';
|
|
@@ -541,6 +542,76 @@ export const SUPPORTED_CHAINS: Record<string, ChainConfig> = {
|
|
|
541
542
|
enabled: true, // NEP-366 meta-transactions supported
|
|
542
543
|
},
|
|
543
544
|
},
|
|
545
|
+
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// ALGORAND (2 networks) - Uses ASA transfers with atomic transaction groups
|
|
548
|
+
// ============================================================================
|
|
549
|
+
|
|
550
|
+
algorand: {
|
|
551
|
+
chainId: 0, // Non-EVM (Algorand uses genesis hash for network identification)
|
|
552
|
+
chainIdHex: '0x0',
|
|
553
|
+
name: 'algorand',
|
|
554
|
+
displayName: 'Algorand',
|
|
555
|
+
networkType: 'algorand',
|
|
556
|
+
rpcUrl: 'https://mainnet-api.algonode.cloud',
|
|
557
|
+
explorerUrl: 'https://allo.info',
|
|
558
|
+
nativeCurrency: {
|
|
559
|
+
name: 'Algo',
|
|
560
|
+
symbol: 'ALGO',
|
|
561
|
+
decimals: 6, // Algorand uses 6 decimals (microAlgos)
|
|
562
|
+
},
|
|
563
|
+
usdc: {
|
|
564
|
+
address: '31566704', // USDC ASA ID on Algorand mainnet
|
|
565
|
+
decimals: 6,
|
|
566
|
+
name: 'USDC',
|
|
567
|
+
version: '1',
|
|
568
|
+
},
|
|
569
|
+
tokens: {
|
|
570
|
+
usdc: {
|
|
571
|
+
address: '31566704', // USDC ASA ID on Algorand mainnet
|
|
572
|
+
decimals: 6,
|
|
573
|
+
name: 'USDC',
|
|
574
|
+
version: '1',
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
x402: {
|
|
578
|
+
facilitatorUrl: DEFAULT_FACILITATOR_URL,
|
|
579
|
+
enabled: true,
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
'algorand-testnet': {
|
|
584
|
+
chainId: 0, // Non-EVM
|
|
585
|
+
chainIdHex: '0x0',
|
|
586
|
+
name: 'algorand-testnet',
|
|
587
|
+
displayName: 'Algorand Testnet',
|
|
588
|
+
networkType: 'algorand',
|
|
589
|
+
rpcUrl: 'https://testnet-api.algonode.cloud',
|
|
590
|
+
explorerUrl: 'https://testnet.allo.info',
|
|
591
|
+
nativeCurrency: {
|
|
592
|
+
name: 'Algo',
|
|
593
|
+
symbol: 'ALGO',
|
|
594
|
+
decimals: 6,
|
|
595
|
+
},
|
|
596
|
+
usdc: {
|
|
597
|
+
address: '10458941', // USDC ASA ID on Algorand testnet
|
|
598
|
+
decimals: 6,
|
|
599
|
+
name: 'USDC',
|
|
600
|
+
version: '1',
|
|
601
|
+
},
|
|
602
|
+
tokens: {
|
|
603
|
+
usdc: {
|
|
604
|
+
address: '10458941', // USDC ASA ID on Algorand testnet
|
|
605
|
+
decimals: 6,
|
|
606
|
+
name: 'USDC',
|
|
607
|
+
version: '1',
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
x402: {
|
|
611
|
+
facilitatorUrl: DEFAULT_FACILITATOR_URL,
|
|
612
|
+
enabled: true,
|
|
613
|
+
},
|
|
614
|
+
},
|
|
544
615
|
};
|
|
545
616
|
|
|
546
617
|
/**
|
|
@@ -637,6 +708,8 @@ export function getExplorerTxUrl(chainName: string, txHash: string): string | nu
|
|
|
637
708
|
return `${chain.explorerUrl}/tx/${txHash}`;
|
|
638
709
|
case 'near':
|
|
639
710
|
return `${chain.explorerUrl}/txns/${txHash}`;
|
|
711
|
+
case 'algorand':
|
|
712
|
+
return `${chain.explorerUrl}/tx/${txHash}`;
|
|
640
713
|
default:
|
|
641
714
|
return null;
|
|
642
715
|
}
|
|
@@ -659,11 +732,30 @@ export function getExplorerAddressUrl(chainName: string, address: string): strin
|
|
|
659
732
|
return `${chain.explorerUrl}/account/${address}`;
|
|
660
733
|
case 'near':
|
|
661
734
|
return `${chain.explorerUrl}/address/${address}`;
|
|
735
|
+
case 'algorand':
|
|
736
|
+
return `${chain.explorerUrl}/account/${address}`;
|
|
662
737
|
default:
|
|
663
738
|
return null;
|
|
664
739
|
}
|
|
665
740
|
}
|
|
666
741
|
|
|
742
|
+
/**
|
|
743
|
+
* Get list of Algorand chains
|
|
744
|
+
*/
|
|
745
|
+
export function getAlgorandChains(): ChainConfig[] {
|
|
746
|
+
return Object.values(SUPPORTED_CHAINS).filter(
|
|
747
|
+
chain => chain.networkType === 'algorand' && chain.x402.enabled
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Check if a chain is Algorand-based
|
|
753
|
+
*/
|
|
754
|
+
export function isAlgorandChain(chainName: string): boolean {
|
|
755
|
+
const chain = getChainByName(chainName);
|
|
756
|
+
return chain?.networkType === 'algorand';
|
|
757
|
+
}
|
|
758
|
+
|
|
667
759
|
// ============================================================================
|
|
668
760
|
// MULTI-TOKEN SUPPORT FUNCTIONS
|
|
669
761
|
// ============================================================================
|
package/src/client/X402Client.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* x402 Payment SDK - Gasless crypto payments using the Ultravioleta facilitator.
|
|
5
5
|
*
|
|
6
|
-
* Supports
|
|
6
|
+
* Supports 16 blockchain networks:
|
|
7
7
|
* - EVM (10): Base, Ethereum, Polygon, Arbitrum, Optimism, Avalanche, Celo, HyperEVM, Unichain, Monad
|
|
8
8
|
* - SVM (2): Solana, Fogo
|
|
9
9
|
* - Stellar (1): Stellar
|
|
10
10
|
* - NEAR (1): NEAR Protocol
|
|
11
|
+
* - Algorand (2): Algorand mainnet and testnet
|
|
11
12
|
*
|
|
12
13
|
* Supports both x402 v1 and v2 protocols.
|
|
13
14
|
*
|
|
@@ -60,6 +61,18 @@
|
|
|
60
61
|
* const header = near.encodePaymentHeader(payload);
|
|
61
62
|
* ```
|
|
62
63
|
*
|
|
64
|
+
* @example With Algorand
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { AlgorandProvider } from 'uvd-x402-sdk/algorand';
|
|
67
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
68
|
+
*
|
|
69
|
+
* const algorand = new AlgorandProvider();
|
|
70
|
+
* const address = await algorand.connect();
|
|
71
|
+
* const algorandConfig = getChainByName('algorand')!;
|
|
72
|
+
* const payload = await algorand.signPayment(paymentInfo, algorandConfig);
|
|
73
|
+
* const header = algorand.encodePaymentHeader(payload, algorandConfig);
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
63
76
|
* @example With React
|
|
64
77
|
* ```tsx
|
|
65
78
|
* import { X402Provider, useX402, usePayment } from 'uvd-x402-sdk/react';
|
|
@@ -100,6 +113,9 @@ export {
|
|
|
100
113
|
getSupportedTokens,
|
|
101
114
|
isTokenSupported,
|
|
102
115
|
getChainsByToken,
|
|
116
|
+
// Algorand helper functions
|
|
117
|
+
getAlgorandChains,
|
|
118
|
+
isAlgorandChain,
|
|
103
119
|
} from './chains';
|
|
104
120
|
|
|
105
121
|
// x402 utilities
|
|
@@ -119,6 +135,11 @@ export {
|
|
|
119
135
|
// Validation utilities
|
|
120
136
|
validateRecipient,
|
|
121
137
|
validateAmount,
|
|
138
|
+
// Payment header utilities
|
|
139
|
+
createPaymentHeaders,
|
|
140
|
+
getPaymentHeader,
|
|
141
|
+
DEFAULT_PAYMENT_HEADER,
|
|
142
|
+
PAYMENT_HEADER_NAMES,
|
|
122
143
|
} from './utils';
|
|
123
144
|
|
|
124
145
|
// Types
|
|
@@ -143,11 +164,14 @@ export type {
|
|
|
143
164
|
PaymentInfo,
|
|
144
165
|
PaymentRequest,
|
|
145
166
|
PaymentResult,
|
|
167
|
+
PaymentHeaders,
|
|
146
168
|
PaymentPayload,
|
|
147
169
|
EVMPaymentPayload,
|
|
148
170
|
SolanaPaymentPayload,
|
|
149
171
|
StellarPaymentPayload,
|
|
150
172
|
NEARPaymentPayload,
|
|
173
|
+
AlgorandPaymentPayload,
|
|
174
|
+
X402HeaderName,
|
|
151
175
|
|
|
152
176
|
// x402 header types (v1 and v2)
|
|
153
177
|
X402Version,
|
|
@@ -160,6 +184,7 @@ export type {
|
|
|
160
184
|
X402SolanaPayload,
|
|
161
185
|
X402StellarPayload,
|
|
162
186
|
X402NEARPayload,
|
|
187
|
+
X402AlgorandPayload,
|
|
163
188
|
|
|
164
189
|
// Config types
|
|
165
190
|
X402ClientConfig,
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uvd-x402-sdk - Algorand Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides wallet connection and payment creation for Algorand via Pera Wallet.
|
|
5
|
+
* Uses ASA (Algorand Standard Assets) transfers for USDC payments.
|
|
6
|
+
*
|
|
7
|
+
* USDC ASA IDs:
|
|
8
|
+
* - Mainnet: 31566704
|
|
9
|
+
* - Testnet: 10458941
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { AlgorandProvider } from 'uvd-x402-sdk/algorand';
|
|
14
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
15
|
+
*
|
|
16
|
+
* const algorand = new AlgorandProvider();
|
|
17
|
+
*
|
|
18
|
+
* // Connect to Pera Wallet
|
|
19
|
+
* const address = await algorand.connect();
|
|
20
|
+
*
|
|
21
|
+
* // Create Algorand payment
|
|
22
|
+
* const chainConfig = getChainByName('algorand')!;
|
|
23
|
+
* const paymentPayload = await algorand.signPayment(paymentInfo, chainConfig);
|
|
24
|
+
* const header = algorand.encodePaymentHeader(paymentPayload, chainConfig);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import type {
|
|
29
|
+
ChainConfig,
|
|
30
|
+
PaymentInfo,
|
|
31
|
+
AlgorandPaymentPayload,
|
|
32
|
+
WalletAdapter,
|
|
33
|
+
X402Version,
|
|
34
|
+
} from '../../types';
|
|
35
|
+
import { X402Error } from '../../types';
|
|
36
|
+
import { getChainByName } from '../../chains';
|
|
37
|
+
import { chainToCAIP2 } from '../../utils';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Browser-compatible base64 encoding for Uint8Array
|
|
41
|
+
*/
|
|
42
|
+
function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
43
|
+
let binary = '';
|
|
44
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
45
|
+
binary += String.fromCharCode(bytes[i]);
|
|
46
|
+
}
|
|
47
|
+
return btoa(binary);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Lazy import Algorand dependencies
|
|
51
|
+
let algosdk: typeof import('algosdk') | null = null;
|
|
52
|
+
let PeraWalletConnect: typeof import('@perawallet/connect').PeraWalletConnect | null = null;
|
|
53
|
+
|
|
54
|
+
async function loadAlgorandDeps() {
|
|
55
|
+
if (!algosdk) {
|
|
56
|
+
algosdk = await import('algosdk');
|
|
57
|
+
}
|
|
58
|
+
if (!PeraWalletConnect) {
|
|
59
|
+
const peraModule = await import('@perawallet/connect');
|
|
60
|
+
PeraWalletConnect = peraModule.PeraWalletConnect;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* AlgorandProvider - Wallet adapter for Algorand via Pera Wallet
|
|
66
|
+
*
|
|
67
|
+
* Supports both mainnet and testnet through chain configuration.
|
|
68
|
+
*/
|
|
69
|
+
export class AlgorandProvider implements WalletAdapter {
|
|
70
|
+
readonly id = 'pera';
|
|
71
|
+
readonly name = 'Pera Wallet';
|
|
72
|
+
readonly networkType = 'algorand' as const;
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
private peraWallet: any = null;
|
|
76
|
+
private address: string | null = null;
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
private algodClients: Map<string, any> = new Map();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if Pera Wallet is available
|
|
82
|
+
* Note: Pera works as a WalletConnect modal, so it's always "available"
|
|
83
|
+
*/
|
|
84
|
+
isAvailable(): boolean {
|
|
85
|
+
return typeof window !== 'undefined';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Connect to Pera Wallet
|
|
90
|
+
*/
|
|
91
|
+
async connect(_chainName?: string): Promise<string> {
|
|
92
|
+
await loadAlgorandDeps();
|
|
93
|
+
|
|
94
|
+
if (!PeraWalletConnect) {
|
|
95
|
+
throw new X402Error('Failed to load Pera Wallet SDK', 'WALLET_NOT_FOUND');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Create Pera Wallet instance
|
|
100
|
+
this.peraWallet = new PeraWalletConnect!();
|
|
101
|
+
|
|
102
|
+
// Try to reconnect from previous session
|
|
103
|
+
const accounts = await this.peraWallet.reconnectSession();
|
|
104
|
+
|
|
105
|
+
if (accounts.length > 0) {
|
|
106
|
+
this.address = accounts[0];
|
|
107
|
+
return accounts[0];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If no previous session, connect fresh
|
|
111
|
+
const newAccounts = await this.peraWallet.connect();
|
|
112
|
+
|
|
113
|
+
if (newAccounts.length === 0) {
|
|
114
|
+
throw new X402Error('No accounts returned from Pera Wallet', 'WALLET_CONNECTION_REJECTED');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.address = newAccounts[0];
|
|
118
|
+
|
|
119
|
+
// Set up disconnect handler
|
|
120
|
+
this.peraWallet.connector?.on('disconnect', () => {
|
|
121
|
+
this.address = null;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return newAccounts[0];
|
|
125
|
+
} catch (error: unknown) {
|
|
126
|
+
if (error instanceof Error) {
|
|
127
|
+
if (error.message.includes('rejected') || error.message.includes('cancelled')) {
|
|
128
|
+
throw new X402Error('Connection rejected by user', 'WALLET_CONNECTION_REJECTED');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw new X402Error(
|
|
132
|
+
`Failed to connect Pera Wallet: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
133
|
+
'UNKNOWN_ERROR',
|
|
134
|
+
error
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Disconnect from Pera Wallet
|
|
141
|
+
*/
|
|
142
|
+
async disconnect(): Promise<void> {
|
|
143
|
+
if (this.peraWallet) {
|
|
144
|
+
try {
|
|
145
|
+
await this.peraWallet.disconnect();
|
|
146
|
+
} catch {
|
|
147
|
+
// Ignore disconnect errors
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.peraWallet = null;
|
|
151
|
+
this.address = null;
|
|
152
|
+
this.algodClients.clear();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get current address
|
|
157
|
+
*/
|
|
158
|
+
getAddress(): string | null {
|
|
159
|
+
return this.address;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get USDC (ASA) balance
|
|
164
|
+
*/
|
|
165
|
+
async getBalance(chainConfig: ChainConfig): Promise<string> {
|
|
166
|
+
await loadAlgorandDeps();
|
|
167
|
+
|
|
168
|
+
if (!this.address) {
|
|
169
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const algodClient = await this.getAlgodClient(chainConfig);
|
|
173
|
+
const assetId = parseInt(chainConfig.usdc.address, 10);
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const accountInfo = await algodClient.accountInformation(this.address).do();
|
|
177
|
+
|
|
178
|
+
// Find the USDC asset in the account's assets
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
const assets: any[] = accountInfo.assets || accountInfo['assets'] || [];
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
const usdcAsset = assets.find((asset: any) =>
|
|
183
|
+
(asset.assetId || asset['asset-id']) === assetId
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (!usdcAsset) {
|
|
187
|
+
return '0.00'; // Account not opted into USDC
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const amount = Number(usdcAsset.amount || usdcAsset['amount']);
|
|
191
|
+
const balance = amount / Math.pow(10, chainConfig.usdc.decimals);
|
|
192
|
+
return balance.toFixed(2);
|
|
193
|
+
} catch {
|
|
194
|
+
return '0.00';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create Algorand ASA transfer payment
|
|
200
|
+
*
|
|
201
|
+
* Transaction structure:
|
|
202
|
+
* 1. ASA Transfer from user to recipient
|
|
203
|
+
* 2. Facilitator pays transaction fees
|
|
204
|
+
*/
|
|
205
|
+
async signPayment(paymentInfo: PaymentInfo, chainConfig: ChainConfig): Promise<string> {
|
|
206
|
+
await loadAlgorandDeps();
|
|
207
|
+
|
|
208
|
+
if (!this.peraWallet || !this.address) {
|
|
209
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!algosdk) {
|
|
213
|
+
throw new X402Error('Algorand SDK not loaded', 'UNKNOWN_ERROR');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const algodClient = await this.getAlgodClient(chainConfig);
|
|
217
|
+
|
|
218
|
+
// Get recipient address (use algorand-specific or fallback to default)
|
|
219
|
+
const recipient = paymentInfo.recipients?.algorand || paymentInfo.recipient;
|
|
220
|
+
const assetId = parseInt(chainConfig.usdc.address, 10);
|
|
221
|
+
|
|
222
|
+
// Parse amount (6 decimals for USDC)
|
|
223
|
+
const amount = Math.floor(parseFloat(paymentInfo.amount) * 1_000_000);
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// Get suggested transaction parameters
|
|
227
|
+
const suggestedParams = await algodClient.getTransactionParams().do();
|
|
228
|
+
|
|
229
|
+
// Create ASA transfer transaction
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
231
|
+
const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
232
|
+
sender: this.address,
|
|
233
|
+
receiver: recipient,
|
|
234
|
+
amount: BigInt(amount),
|
|
235
|
+
assetIndex: assetId,
|
|
236
|
+
suggestedParams: suggestedParams,
|
|
237
|
+
note: new TextEncoder().encode('x402 payment via uvd-x402-sdk'),
|
|
238
|
+
} as any);
|
|
239
|
+
|
|
240
|
+
// Sign with Pera Wallet
|
|
241
|
+
const signedTxns = await this.peraWallet.signTransaction([[{ txn }]]);
|
|
242
|
+
|
|
243
|
+
if (!signedTxns || signedTxns.length === 0) {
|
|
244
|
+
throw new X402Error('No signed transaction returned', 'SIGNATURE_REJECTED');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const signedTxn = signedTxns[0];
|
|
248
|
+
|
|
249
|
+
const payload: AlgorandPaymentPayload = {
|
|
250
|
+
from: this.address,
|
|
251
|
+
to: recipient,
|
|
252
|
+
amount: amount.toString(),
|
|
253
|
+
assetId: assetId,
|
|
254
|
+
signedTxn: uint8ArrayToBase64(signedTxn),
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return JSON.stringify(payload);
|
|
258
|
+
} catch (error: unknown) {
|
|
259
|
+
if (error instanceof X402Error) {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
if (error instanceof Error) {
|
|
263
|
+
if (error.message.includes('rejected') || error.message.includes('cancelled')) {
|
|
264
|
+
throw new X402Error('Signature rejected by user', 'SIGNATURE_REJECTED');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
throw new X402Error(
|
|
268
|
+
`Failed to sign transaction: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
269
|
+
'PAYMENT_FAILED',
|
|
270
|
+
error
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Encode Algorand payment as X-PAYMENT header
|
|
277
|
+
*
|
|
278
|
+
* @param paymentPayload - JSON-encoded payment payload from signPayment()
|
|
279
|
+
* @param chainConfig - Chain configuration
|
|
280
|
+
* @param version - x402 protocol version (1 or 2, defaults to 1)
|
|
281
|
+
* @returns Base64-encoded X-PAYMENT header value
|
|
282
|
+
*/
|
|
283
|
+
encodePaymentHeader(
|
|
284
|
+
paymentPayload: string,
|
|
285
|
+
chainConfig?: ChainConfig,
|
|
286
|
+
version: X402Version = 1
|
|
287
|
+
): string {
|
|
288
|
+
const payload = JSON.parse(paymentPayload) as AlgorandPaymentPayload;
|
|
289
|
+
|
|
290
|
+
// Use chain name from config, or default to 'algorand'
|
|
291
|
+
const networkName = chainConfig?.name || 'algorand';
|
|
292
|
+
|
|
293
|
+
// Build the payload data
|
|
294
|
+
const payloadData = {
|
|
295
|
+
from: payload.from,
|
|
296
|
+
to: payload.to,
|
|
297
|
+
amount: payload.amount,
|
|
298
|
+
assetId: payload.assetId,
|
|
299
|
+
signedTxn: payload.signedTxn,
|
|
300
|
+
...(payload.note && { note: payload.note }),
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Format in x402 standard format (v1 or v2)
|
|
304
|
+
const x402Payload = version === 2
|
|
305
|
+
? {
|
|
306
|
+
x402Version: 2 as const,
|
|
307
|
+
scheme: 'exact' as const,
|
|
308
|
+
network: chainToCAIP2(networkName), // CAIP-2 format for v2
|
|
309
|
+
payload: payloadData,
|
|
310
|
+
}
|
|
311
|
+
: {
|
|
312
|
+
x402Version: 1 as const,
|
|
313
|
+
scheme: 'exact' as const,
|
|
314
|
+
network: networkName, // Plain chain name for v1
|
|
315
|
+
payload: payloadData,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return btoa(JSON.stringify(x402Payload));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Private helpers
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get or create an Algod client for a specific chain
|
|
325
|
+
*/
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
+
private async getAlgodClient(chainConfig?: ChainConfig): Promise<any> {
|
|
328
|
+
await loadAlgorandDeps();
|
|
329
|
+
|
|
330
|
+
if (!algosdk) {
|
|
331
|
+
throw new X402Error('Algorand SDK not loaded', 'UNKNOWN_ERROR');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const config = chainConfig || getChainByName('algorand');
|
|
335
|
+
if (!config) {
|
|
336
|
+
throw new X402Error('Chain config not found', 'CHAIN_NOT_SUPPORTED');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Cache by RPC URL
|
|
340
|
+
const cacheKey = config.rpcUrl;
|
|
341
|
+
|
|
342
|
+
if (this.algodClients.has(cacheKey)) {
|
|
343
|
+
return this.algodClients.get(cacheKey)!;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Create new Algod client
|
|
347
|
+
// Algonode.cloud doesn't require auth token
|
|
348
|
+
const client = new algosdk.Algodv2('', config.rpcUrl, '');
|
|
349
|
+
this.algodClients.set(cacheKey, client);
|
|
350
|
+
|
|
351
|
+
return client;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Default export
|
|
356
|
+
export default AlgorandProvider;
|