uvd-x402-sdk 2.10.1 → 2.12.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/dist/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +1 -1
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs.map +1 -1
- package/dist/{index-C60c_e5z.d.mts → index-C6Vxnneo.d.mts} +1 -1
- package/dist/{index-VIOUicmO.d.ts → index-DmJGKD9r.d.ts} +1 -1
- package/dist/{index-D-dO_FoP.d.mts → index-fIhvHqCQ.d.mts} +18 -22
- package/dist/{index-D-dO_FoP.d.ts → index-fIhvHqCQ.d.ts} +18 -22
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +42 -15
- package/dist/providers/algorand/index.d.ts +42 -15
- package/dist/providers/algorand/index.js +198 -32
- package/dist/providers/algorand/index.js.map +1 -1
- package/dist/providers/algorand/index.mjs +198 -32
- package/dist/providers/algorand/index.mjs.map +1 -1
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/evm/index.js.map +1 -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.map +1 -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.map +1 -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.map +1 -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.map +1 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +6 -1
- package/src/providers/algorand/index.ts +261 -40
- package/src/types/index.ts +18 -22
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* uvd-x402-sdk - Algorand Provider
|
|
3
3
|
*
|
|
4
|
-
* Provides wallet connection and payment creation for Algorand
|
|
4
|
+
* Provides wallet connection and payment creation for Algorand.
|
|
5
|
+
* Supports both Lute Wallet (desktop browser extension) and Pera Wallet (mobile).
|
|
5
6
|
* Uses ASA (Algorand Standard Assets) transfers for USDC payments.
|
|
6
7
|
*
|
|
8
|
+
* Wallet Priority:
|
|
9
|
+
* 1. Lute Wallet - Desktop browser extension (preferred for desktop)
|
|
10
|
+
* 2. Pera Wallet - Mobile via WalletConnect (fallback/mobile)
|
|
11
|
+
*
|
|
7
12
|
* USDC ASA IDs:
|
|
8
13
|
* - Mainnet: 31566704
|
|
9
14
|
* - Testnet: 10458941
|
|
@@ -15,7 +20,7 @@
|
|
|
15
20
|
*
|
|
16
21
|
* const algorand = new AlgorandProvider();
|
|
17
22
|
*
|
|
18
|
-
* // Connect to Pera
|
|
23
|
+
* // Connect to Lute (desktop) or Pera (mobile) automatically
|
|
19
24
|
* const address = await algorand.connect();
|
|
20
25
|
*
|
|
21
26
|
* // Create Algorand payment
|
|
@@ -50,27 +55,60 @@ function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
|
50
55
|
// Lazy import Algorand dependencies
|
|
51
56
|
let algosdk: typeof import('algosdk') | null = null;
|
|
52
57
|
let PeraWalletConnect: typeof import('@perawallet/connect').PeraWalletConnect | null = null;
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
+
let LuteConnect: any = null;
|
|
53
60
|
|
|
54
61
|
async function loadAlgorandDeps() {
|
|
55
62
|
if (!algosdk) {
|
|
56
63
|
algosdk = await import('algosdk');
|
|
57
64
|
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function loadPeraWallet() {
|
|
58
68
|
if (!PeraWalletConnect) {
|
|
59
69
|
const peraModule = await import('@perawallet/connect');
|
|
60
70
|
PeraWalletConnect = peraModule.PeraWalletConnect;
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
async function loadLuteWallet() {
|
|
75
|
+
if (!LuteConnect) {
|
|
76
|
+
try {
|
|
77
|
+
const luteModule = await import('lute-connect');
|
|
78
|
+
LuteConnect = luteModule.default;
|
|
79
|
+
} catch {
|
|
80
|
+
// Lute not installed, will fall back to Pera
|
|
81
|
+
LuteConnect = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
/**
|
|
65
|
-
*
|
|
87
|
+
* Check if Lute wallet extension is installed
|
|
88
|
+
*/
|
|
89
|
+
function isLuteAvailable(): boolean {
|
|
90
|
+
if (typeof window === 'undefined') return false;
|
|
91
|
+
// Lute injects itself into window.algorand or window.lute
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
const win = window as any;
|
|
94
|
+
return !!(win.algorand || win.lute);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* AlgorandProvider - Wallet adapter for Algorand
|
|
66
99
|
*
|
|
67
|
-
* Supports
|
|
100
|
+
* Supports Lute Wallet (desktop) and Pera Wallet (mobile).
|
|
101
|
+
* Automatically detects and uses the best available wallet.
|
|
68
102
|
*/
|
|
69
103
|
export class AlgorandProvider implements WalletAdapter {
|
|
70
|
-
readonly id = '
|
|
71
|
-
readonly name = '
|
|
104
|
+
readonly id = 'algorand';
|
|
105
|
+
readonly name = 'Algorand Wallet';
|
|
72
106
|
readonly networkType = 'algorand' as const;
|
|
73
107
|
|
|
108
|
+
// Active wallet type
|
|
109
|
+
private walletType: 'lute' | 'pera' | null = null;
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
private luteWallet: any = null;
|
|
74
112
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
113
|
private peraWallet: any = null;
|
|
76
114
|
private address: string | null = null;
|
|
@@ -78,19 +116,91 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
78
116
|
private algodClients: Map<string, any> = new Map();
|
|
79
117
|
|
|
80
118
|
/**
|
|
81
|
-
* Check if
|
|
82
|
-
*
|
|
119
|
+
* Check if any Algorand wallet is available
|
|
120
|
+
* Returns true if Lute extension is installed OR we can use Pera (always available via WalletConnect)
|
|
83
121
|
*/
|
|
84
122
|
isAvailable(): boolean {
|
|
85
123
|
return typeof window !== 'undefined';
|
|
86
124
|
}
|
|
87
125
|
|
|
88
126
|
/**
|
|
89
|
-
*
|
|
127
|
+
* Get the name of the currently connected wallet
|
|
128
|
+
*/
|
|
129
|
+
getWalletName(): string {
|
|
130
|
+
if (this.walletType === 'lute') return 'Lute Wallet';
|
|
131
|
+
if (this.walletType === 'pera') return 'Pera Wallet';
|
|
132
|
+
return 'Algorand Wallet';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Connect to Algorand wallet
|
|
137
|
+
* Priority: Lute (desktop extension) > Pera (mobile via WalletConnect)
|
|
90
138
|
*/
|
|
91
139
|
async connect(_chainName?: string): Promise<string> {
|
|
92
140
|
await loadAlgorandDeps();
|
|
93
141
|
|
|
142
|
+
// Try Lute first (better desktop UX)
|
|
143
|
+
if (isLuteAvailable()) {
|
|
144
|
+
try {
|
|
145
|
+
return await this.connectLute();
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Lute failed, try Pera
|
|
148
|
+
console.warn('Lute connection failed, falling back to Pera:', error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Fall back to Pera (mobile/WalletConnect)
|
|
153
|
+
return await this.connectPera();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Connect to Lute Wallet (desktop browser extension)
|
|
158
|
+
*/
|
|
159
|
+
private async connectLute(): Promise<string> {
|
|
160
|
+
await loadLuteWallet();
|
|
161
|
+
|
|
162
|
+
if (!LuteConnect) {
|
|
163
|
+
throw new X402Error('Lute Wallet SDK not available', 'WALLET_NOT_FOUND');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
this.luteWallet = new LuteConnect('402milly');
|
|
168
|
+
|
|
169
|
+
// Get Algorand genesis ID for mainnet
|
|
170
|
+
const genesisId = 'mainnet-v1.0';
|
|
171
|
+
|
|
172
|
+
// Connect and get accounts
|
|
173
|
+
const accounts = await this.luteWallet.connect(genesisId);
|
|
174
|
+
|
|
175
|
+
if (!accounts || accounts.length === 0) {
|
|
176
|
+
throw new X402Error('No accounts returned from Lute Wallet', 'WALLET_CONNECTION_REJECTED');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.address = accounts[0];
|
|
180
|
+
this.walletType = 'lute';
|
|
181
|
+
|
|
182
|
+
return accounts[0];
|
|
183
|
+
} catch (error: unknown) {
|
|
184
|
+
if (error instanceof X402Error) throw error;
|
|
185
|
+
if (error instanceof Error) {
|
|
186
|
+
if (error.message.includes('rejected') || error.message.includes('cancelled')) {
|
|
187
|
+
throw new X402Error('Connection rejected by user', 'WALLET_CONNECTION_REJECTED');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
throw new X402Error(
|
|
191
|
+
`Failed to connect Lute Wallet: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
192
|
+
'UNKNOWN_ERROR',
|
|
193
|
+
error
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Connect to Pera Wallet (mobile via WalletConnect)
|
|
200
|
+
*/
|
|
201
|
+
private async connectPera(): Promise<string> {
|
|
202
|
+
await loadPeraWallet();
|
|
203
|
+
|
|
94
204
|
if (!PeraWalletConnect) {
|
|
95
205
|
throw new X402Error('Failed to load Pera Wallet SDK', 'WALLET_NOT_FOUND');
|
|
96
206
|
}
|
|
@@ -104,6 +214,7 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
104
214
|
|
|
105
215
|
if (accounts.length > 0) {
|
|
106
216
|
this.address = accounts[0];
|
|
217
|
+
this.walletType = 'pera';
|
|
107
218
|
return accounts[0];
|
|
108
219
|
}
|
|
109
220
|
|
|
@@ -115,10 +226,12 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
115
226
|
}
|
|
116
227
|
|
|
117
228
|
this.address = newAccounts[0];
|
|
229
|
+
this.walletType = 'pera';
|
|
118
230
|
|
|
119
231
|
// Set up disconnect handler
|
|
120
232
|
this.peraWallet.connector?.on('disconnect', () => {
|
|
121
233
|
this.address = null;
|
|
234
|
+
this.walletType = null;
|
|
122
235
|
});
|
|
123
236
|
|
|
124
237
|
return newAccounts[0];
|
|
@@ -137,18 +250,27 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
137
250
|
}
|
|
138
251
|
|
|
139
252
|
/**
|
|
140
|
-
* Disconnect from
|
|
253
|
+
* Disconnect from wallet
|
|
141
254
|
*/
|
|
142
255
|
async disconnect(): Promise<void> {
|
|
143
|
-
if (this.
|
|
256
|
+
if (this.walletType === 'lute' && this.luteWallet) {
|
|
257
|
+
try {
|
|
258
|
+
// Lute doesn't have a disconnect method, just clear state
|
|
259
|
+
} catch {
|
|
260
|
+
// Ignore disconnect errors
|
|
261
|
+
}
|
|
262
|
+
this.luteWallet = null;
|
|
263
|
+
}
|
|
264
|
+
if (this.walletType === 'pera' && this.peraWallet) {
|
|
144
265
|
try {
|
|
145
266
|
await this.peraWallet.disconnect();
|
|
146
267
|
} catch {
|
|
147
268
|
// Ignore disconnect errors
|
|
148
269
|
}
|
|
270
|
+
this.peraWallet = null;
|
|
149
271
|
}
|
|
150
|
-
this.peraWallet = null;
|
|
151
272
|
this.address = null;
|
|
273
|
+
this.walletType = null;
|
|
152
274
|
this.algodClients.clear();
|
|
153
275
|
}
|
|
154
276
|
|
|
@@ -196,16 +318,18 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
196
318
|
}
|
|
197
319
|
|
|
198
320
|
/**
|
|
199
|
-
* Create Algorand
|
|
321
|
+
* Create Algorand atomic group payment (GoPlausible x402-avm spec)
|
|
200
322
|
*
|
|
201
|
-
* Transaction structure:
|
|
202
|
-
*
|
|
203
|
-
*
|
|
323
|
+
* Transaction structure (atomic group):
|
|
324
|
+
* - Transaction 0: Fee payment (UNSIGNED) - facilitator -> facilitator, covers all fees
|
|
325
|
+
* - Transaction 1: ASA transfer (SIGNED) - client -> merchant
|
|
326
|
+
*
|
|
327
|
+
* The facilitator signs transaction 0 and submits the complete atomic group.
|
|
204
328
|
*/
|
|
205
329
|
async signPayment(paymentInfo: PaymentInfo, chainConfig: ChainConfig): Promise<string> {
|
|
206
330
|
await loadAlgorandDeps();
|
|
207
331
|
|
|
208
|
-
if (!this.
|
|
332
|
+
if (!this.address || !this.walletType) {
|
|
209
333
|
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
210
334
|
}
|
|
211
335
|
|
|
@@ -219,6 +343,15 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
219
343
|
const recipient = paymentInfo.recipients?.algorand || paymentInfo.recipient;
|
|
220
344
|
const assetId = parseInt(chainConfig.usdc.address, 10);
|
|
221
345
|
|
|
346
|
+
// Get facilitator address (fee payer)
|
|
347
|
+
const facilitatorAddress = paymentInfo.facilitator;
|
|
348
|
+
if (!facilitatorAddress) {
|
|
349
|
+
throw new X402Error(
|
|
350
|
+
'Facilitator address required for Algorand payments. Set paymentInfo.facilitator',
|
|
351
|
+
'PAYMENT_FAILED'
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
222
355
|
// Parse amount (6 decimals for USDC)
|
|
223
356
|
const amount = Math.floor(parseFloat(paymentInfo.amount) * 1_000_000);
|
|
224
357
|
|
|
@@ -226,32 +359,92 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
226
359
|
// Get suggested transaction parameters
|
|
227
360
|
const suggestedParams = await algodClient.getTransactionParams().do();
|
|
228
361
|
|
|
229
|
-
//
|
|
362
|
+
// Transaction 0: Fee payment (facilitator -> facilitator, 0 amount)
|
|
363
|
+
// This transaction pays fees for both txns in the group (fee pooling)
|
|
230
364
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
231
|
-
const
|
|
365
|
+
const feeTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
|
|
366
|
+
sender: facilitatorAddress,
|
|
367
|
+
receiver: facilitatorAddress, // self-transfer
|
|
368
|
+
amount: 0,
|
|
369
|
+
suggestedParams: {
|
|
370
|
+
...suggestedParams,
|
|
371
|
+
fee: 2000, // Covers both transactions (1000 each)
|
|
372
|
+
flatFee: true,
|
|
373
|
+
},
|
|
374
|
+
} as any);
|
|
375
|
+
|
|
376
|
+
// Transaction 1: ASA transfer (client -> merchant)
|
|
377
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
378
|
+
const paymentTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
232
379
|
sender: this.address,
|
|
233
380
|
receiver: recipient,
|
|
234
381
|
amount: BigInt(amount),
|
|
235
382
|
assetIndex: assetId,
|
|
236
|
-
suggestedParams:
|
|
383
|
+
suggestedParams: {
|
|
384
|
+
...suggestedParams,
|
|
385
|
+
fee: 0, // Fee paid by transaction 0
|
|
386
|
+
flatFee: true,
|
|
387
|
+
},
|
|
237
388
|
note: new TextEncoder().encode('x402 payment via uvd-x402-sdk'),
|
|
238
389
|
} as any);
|
|
239
390
|
|
|
240
|
-
//
|
|
241
|
-
const
|
|
391
|
+
// Assign group ID to both transactions (creates atomic group)
|
|
392
|
+
const txnGroup = algosdk.assignGroupID([feeTxn, paymentTxn]);
|
|
393
|
+
|
|
394
|
+
// Encode fee transaction (UNSIGNED - facilitator will sign)
|
|
395
|
+
const unsignedFeeTxnBytes = algosdk.encodeUnsignedTransaction(txnGroup[0]);
|
|
396
|
+
const unsignedFeeTxnBase64 = uint8ArrayToBase64(unsignedFeeTxnBytes);
|
|
397
|
+
|
|
398
|
+
// Sign the payment transaction (index 1) with the active wallet
|
|
399
|
+
let signedPaymentTxnBytes: Uint8Array;
|
|
400
|
+
|
|
401
|
+
if (this.walletType === 'lute' && this.luteWallet) {
|
|
402
|
+
// Lute uses signTxns with base64 encoded transactions
|
|
403
|
+
// For atomic groups, pass both txns but only sign the one we control
|
|
404
|
+
const feeTxnBase64 = uint8ArrayToBase64(txnGroup[0].toByte());
|
|
405
|
+
const paymentTxnBase64 = uint8ArrayToBase64(txnGroup[1].toByte());
|
|
406
|
+
|
|
407
|
+
// Sign only the payment transaction (index 1), leave fee txn unsigned
|
|
408
|
+
const signedTxns = await this.luteWallet.signTxns([
|
|
409
|
+
{ txn: feeTxnBase64, signers: [] }, // Don't sign - facilitator will
|
|
410
|
+
{ txn: paymentTxnBase64 }, // Sign this one
|
|
411
|
+
]);
|
|
412
|
+
|
|
413
|
+
if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
|
|
414
|
+
throw new X402Error('No signed transaction returned', 'SIGNATURE_REJECTED');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Get the signed payment transaction (index 1)
|
|
418
|
+
const signedResult = signedTxns[1];
|
|
419
|
+
signedPaymentTxnBytes = this.decodeSignedTxn(signedResult);
|
|
420
|
+
} else if (this.walletType === 'pera' && this.peraWallet) {
|
|
421
|
+
// Pera uses signTransaction with transaction objects
|
|
422
|
+
// For atomic groups, pass both txns but only sign the one we control
|
|
423
|
+
const signedTxns = await this.peraWallet.signTransaction([
|
|
424
|
+
[
|
|
425
|
+
{ txn: txnGroup[0], signers: [] }, // Don't sign - facilitator will
|
|
426
|
+
{ txn: txnGroup[1] }, // Sign this one
|
|
427
|
+
],
|
|
428
|
+
]);
|
|
429
|
+
|
|
430
|
+
if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
|
|
431
|
+
throw new X402Error('No signed transaction returned', 'SIGNATURE_REJECTED');
|
|
432
|
+
}
|
|
242
433
|
|
|
243
|
-
|
|
244
|
-
|
|
434
|
+
signedPaymentTxnBytes = signedTxns[1];
|
|
435
|
+
} else {
|
|
436
|
+
throw new X402Error('No wallet available for signing', 'WALLET_NOT_CONNECTED');
|
|
245
437
|
}
|
|
246
438
|
|
|
247
|
-
const
|
|
439
|
+
const signedPaymentTxnBase64 = uint8ArrayToBase64(signedPaymentTxnBytes);
|
|
248
440
|
|
|
441
|
+
// Build payload following GoPlausible x402-avm spec
|
|
249
442
|
const payload: AlgorandPaymentPayload = {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
443
|
+
paymentIndex: 1, // Index of the payment transaction in the group
|
|
444
|
+
paymentGroup: [
|
|
445
|
+
unsignedFeeTxnBase64, // Transaction 0: unsigned fee tx
|
|
446
|
+
signedPaymentTxnBase64, // Transaction 1: signed payment tx
|
|
447
|
+
],
|
|
255
448
|
};
|
|
256
449
|
|
|
257
450
|
return JSON.stringify(payload);
|
|
@@ -272,6 +465,32 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
272
465
|
}
|
|
273
466
|
}
|
|
274
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Decode signed transaction from wallet response (handles various formats)
|
|
470
|
+
*/
|
|
471
|
+
private decodeSignedTxn(signedResult: unknown): Uint8Array {
|
|
472
|
+
if (signedResult instanceof Uint8Array) {
|
|
473
|
+
return signedResult;
|
|
474
|
+
} else if (typeof signedResult === 'string') {
|
|
475
|
+
// Try to decode as base64
|
|
476
|
+
try {
|
|
477
|
+
return Uint8Array.from(atob(signedResult), c => c.charCodeAt(0));
|
|
478
|
+
} catch {
|
|
479
|
+
// If standard base64 fails, try URL-safe base64
|
|
480
|
+
const standardBase64 = signedResult.replace(/-/g, '+').replace(/_/g, '/');
|
|
481
|
+
return Uint8Array.from(atob(standardBase64), c => c.charCodeAt(0));
|
|
482
|
+
}
|
|
483
|
+
} else if (ArrayBuffer.isView(signedResult)) {
|
|
484
|
+
return new Uint8Array(
|
|
485
|
+
(signedResult as ArrayBufferView).buffer,
|
|
486
|
+
(signedResult as ArrayBufferView).byteOffset,
|
|
487
|
+
(signedResult as ArrayBufferView).byteLength
|
|
488
|
+
);
|
|
489
|
+
} else {
|
|
490
|
+
throw new X402Error('Unexpected signed transaction format', 'PAYMENT_FAILED');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
275
494
|
/**
|
|
276
495
|
* Encode Algorand payment as X-PAYMENT header
|
|
277
496
|
*
|
|
@@ -287,17 +506,19 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
287
506
|
): string {
|
|
288
507
|
const payload = JSON.parse(paymentPayload) as AlgorandPaymentPayload;
|
|
289
508
|
|
|
290
|
-
//
|
|
291
|
-
|
|
509
|
+
// Determine network name for x402
|
|
510
|
+
// Use "algorand-mainnet" or "algorand-testnet" format for facilitator
|
|
511
|
+
let networkName: string;
|
|
512
|
+
if (chainConfig?.name === 'algorand-testnet') {
|
|
513
|
+
networkName = 'algorand-testnet';
|
|
514
|
+
} else {
|
|
515
|
+
networkName = 'algorand-mainnet'; // Default to mainnet
|
|
516
|
+
}
|
|
292
517
|
|
|
293
|
-
// Build the payload data
|
|
518
|
+
// Build the payload data (GoPlausible x402-avm spec)
|
|
294
519
|
const payloadData = {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
amount: payload.amount,
|
|
298
|
-
assetId: payload.assetId,
|
|
299
|
-
signedTxn: payload.signedTxn,
|
|
300
|
-
...(payload.note && { note: payload.note }),
|
|
520
|
+
paymentIndex: payload.paymentIndex,
|
|
521
|
+
paymentGroup: payload.paymentGroup,
|
|
301
522
|
};
|
|
302
523
|
|
|
303
524
|
// Format in x402 standard format (v1 or v2)
|
|
@@ -311,7 +532,7 @@ export class AlgorandProvider implements WalletAdapter {
|
|
|
311
532
|
: {
|
|
312
533
|
x402Version: 1 as const,
|
|
313
534
|
scheme: 'exact' as const,
|
|
314
|
-
network: networkName,
|
|
535
|
+
network: networkName,
|
|
315
536
|
payload: payloadData,
|
|
316
537
|
};
|
|
317
538
|
|
package/src/types/index.ts
CHANGED
|
@@ -345,23 +345,21 @@ export interface NEARPaymentPayload {
|
|
|
345
345
|
/**
|
|
346
346
|
* Algorand payment payload (atomic transaction group)
|
|
347
347
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
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
|
-
/**
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
/**
|