uvd-x402-sdk 2.0.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/LICENSE +21 -0
- package/README.md +782 -0
- package/dist/index-BrBqP1I8.d.ts +199 -0
- package/dist/index-D6Sr4ARD.d.mts +429 -0
- package/dist/index-D6Sr4ARD.d.ts +429 -0
- package/dist/index-DJ4Cvrev.d.mts +199 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1178 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1146 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/evm/index.d.mts +84 -0
- package/dist/providers/evm/index.d.ts +84 -0
- package/dist/providers/evm/index.js +740 -0
- package/dist/providers/evm/index.js.map +1 -0
- package/dist/providers/evm/index.mjs +735 -0
- package/dist/providers/evm/index.mjs.map +1 -0
- package/dist/providers/near/index.d.mts +99 -0
- package/dist/providers/near/index.d.ts +99 -0
- package/dist/providers/near/index.js +483 -0
- package/dist/providers/near/index.js.map +1 -0
- package/dist/providers/near/index.mjs +478 -0
- package/dist/providers/near/index.mjs.map +1 -0
- package/dist/providers/solana/index.d.mts +115 -0
- package/dist/providers/solana/index.d.ts +115 -0
- package/dist/providers/solana/index.js +771 -0
- package/dist/providers/solana/index.js.map +1 -0
- package/dist/providers/solana/index.mjs +765 -0
- package/dist/providers/solana/index.mjs.map +1 -0
- package/dist/providers/stellar/index.d.mts +67 -0
- package/dist/providers/stellar/index.d.ts +67 -0
- package/dist/providers/stellar/index.js +306 -0
- package/dist/providers/stellar/index.js.map +1 -0
- package/dist/providers/stellar/index.mjs +301 -0
- package/dist/providers/stellar/index.mjs.map +1 -0
- package/dist/react/index.d.mts +73 -0
- package/dist/react/index.d.ts +73 -0
- package/dist/react/index.js +1218 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +1211 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/utils/index.d.mts +103 -0
- package/dist/utils/index.d.ts +103 -0
- package/dist/utils/index.js +575 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +562 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +149 -0
- package/src/chains/index.ts +539 -0
- package/src/client/X402Client.ts +663 -0
- package/src/client/index.ts +1 -0
- package/src/index.ts +166 -0
- package/src/providers/evm/index.ts +394 -0
- package/src/providers/near/index.ts +664 -0
- package/src/providers/solana/index.ts +489 -0
- package/src/providers/stellar/index.ts +376 -0
- package/src/react/index.tsx +417 -0
- package/src/types/index.ts +561 -0
- package/src/utils/index.ts +20 -0
- package/src/utils/x402.ts +295 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uvd-x402-sdk - SVM Provider (Solana Virtual Machine)
|
|
3
|
+
*
|
|
4
|
+
* Provides wallet connection and payment creation for SVM-based chains via Phantom.
|
|
5
|
+
* Supports: Solana, Fogo
|
|
6
|
+
* Uses partially-signed transactions where the facilitator is the fee payer.
|
|
7
|
+
*
|
|
8
|
+
* @example Solana
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { SVMProvider } from 'uvd-x402-sdk/solana';
|
|
11
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
12
|
+
*
|
|
13
|
+
* const svm = new SVMProvider();
|
|
14
|
+
*
|
|
15
|
+
* // Connect
|
|
16
|
+
* const address = await svm.connect();
|
|
17
|
+
*
|
|
18
|
+
* // Create Solana payment
|
|
19
|
+
* const chainConfig = getChainByName('solana')!;
|
|
20
|
+
* const paymentPayload = await svm.signPayment(paymentInfo, chainConfig);
|
|
21
|
+
* const header = svm.encodePaymentHeader(paymentPayload, chainConfig);
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Fogo
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { SVMProvider } from 'uvd-x402-sdk/solana';
|
|
27
|
+
* import { getChainByName } from 'uvd-x402-sdk';
|
|
28
|
+
*
|
|
29
|
+
* const svm = new SVMProvider();
|
|
30
|
+
*
|
|
31
|
+
* // Connect (same wallet works for all SVM chains)
|
|
32
|
+
* const address = await svm.connect();
|
|
33
|
+
*
|
|
34
|
+
* // Create Fogo payment
|
|
35
|
+
* const chainConfig = getChainByName('fogo')!;
|
|
36
|
+
* const paymentPayload = await svm.signPayment(paymentInfo, chainConfig);
|
|
37
|
+
* const header = svm.encodePaymentHeader(paymentPayload, chainConfig);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type {
|
|
42
|
+
ChainConfig,
|
|
43
|
+
PaymentInfo,
|
|
44
|
+
SolanaPaymentPayload,
|
|
45
|
+
WalletAdapter,
|
|
46
|
+
} from '../../types';
|
|
47
|
+
import { X402Error } from '../../types';
|
|
48
|
+
import { getChainByName } from '../../chains';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Browser-compatible base64 encoding for Uint8Array
|
|
52
|
+
* Avoids Node.js Buffer dependency for browser bundlers
|
|
53
|
+
*/
|
|
54
|
+
function uint8ArrayToBase64(bytes: Uint8Array): string {
|
|
55
|
+
let binary = '';
|
|
56
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
57
|
+
binary += String.fromCharCode(bytes[i]);
|
|
58
|
+
}
|
|
59
|
+
return btoa(binary);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Lazy import Solana dependencies to avoid bundling when not used
|
|
63
|
+
let Connection: typeof import('@solana/web3.js').Connection;
|
|
64
|
+
let PublicKey: typeof import('@solana/web3.js').PublicKey;
|
|
65
|
+
let TransactionMessage: typeof import('@solana/web3.js').TransactionMessage;
|
|
66
|
+
let VersionedTransaction: typeof import('@solana/web3.js').VersionedTransaction;
|
|
67
|
+
let ComputeBudgetProgram: typeof import('@solana/web3.js').ComputeBudgetProgram;
|
|
68
|
+
let getAssociatedTokenAddress: typeof import('@solana/spl-token').getAssociatedTokenAddress;
|
|
69
|
+
let createTransferCheckedInstruction: typeof import('@solana/spl-token').createTransferCheckedInstruction;
|
|
70
|
+
let createAssociatedTokenAccountIdempotentInstruction: typeof import('@solana/spl-token').createAssociatedTokenAccountIdempotentInstruction;
|
|
71
|
+
let TOKEN_PROGRAM_ID: typeof import('@solana/spl-token').TOKEN_PROGRAM_ID;
|
|
72
|
+
|
|
73
|
+
async function loadSolanaDeps() {
|
|
74
|
+
if (!Connection) {
|
|
75
|
+
const web3 = await import('@solana/web3.js');
|
|
76
|
+
const splToken = await import('@solana/spl-token');
|
|
77
|
+
Connection = web3.Connection;
|
|
78
|
+
PublicKey = web3.PublicKey;
|
|
79
|
+
TransactionMessage = web3.TransactionMessage;
|
|
80
|
+
VersionedTransaction = web3.VersionedTransaction;
|
|
81
|
+
ComputeBudgetProgram = web3.ComputeBudgetProgram;
|
|
82
|
+
getAssociatedTokenAddress = splToken.getAssociatedTokenAddress;
|
|
83
|
+
createTransferCheckedInstruction = splToken.createTransferCheckedInstruction;
|
|
84
|
+
createAssociatedTokenAccountIdempotentInstruction = splToken.createAssociatedTokenAccountIdempotentInstruction;
|
|
85
|
+
TOKEN_PROGRAM_ID = splToken.TOKEN_PROGRAM_ID;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Phantom wallet provider interface
|
|
91
|
+
*/
|
|
92
|
+
interface PhantomProvider {
|
|
93
|
+
isPhantom?: boolean;
|
|
94
|
+
isConnected?: boolean;
|
|
95
|
+
publicKey?: { toBase58(): string };
|
|
96
|
+
connect(): Promise<{ publicKey: { toBase58(): string } }>;
|
|
97
|
+
disconnect(): Promise<void>;
|
|
98
|
+
signTransaction<T>(transaction: T): Promise<T>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* SVMProvider - Wallet adapter for SVM chains (Solana, Fogo) via Phantom
|
|
103
|
+
*
|
|
104
|
+
* @alias SolanaProvider for backward compatibility
|
|
105
|
+
*/
|
|
106
|
+
export class SVMProvider implements WalletAdapter {
|
|
107
|
+
readonly id = 'phantom';
|
|
108
|
+
readonly name = 'Phantom';
|
|
109
|
+
readonly networkType = 'svm' as const;
|
|
110
|
+
|
|
111
|
+
private provider: PhantomProvider | null = null;
|
|
112
|
+
private publicKey: InstanceType<typeof PublicKey> | null = null;
|
|
113
|
+
private connections: Map<string, InstanceType<typeof Connection>> = new Map();
|
|
114
|
+
private address: string | null = null;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if Phantom wallet is available
|
|
118
|
+
*/
|
|
119
|
+
isAvailable(): boolean {
|
|
120
|
+
if (typeof window === 'undefined') return false;
|
|
121
|
+
return !!(
|
|
122
|
+
(window as Window & { phantom?: { solana?: PhantomProvider } }).phantom?.solana?.isPhantom ||
|
|
123
|
+
(window as Window & { solana?: PhantomProvider }).solana?.isPhantom
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Connect to Phantom wallet
|
|
129
|
+
*/
|
|
130
|
+
async connect(): Promise<string> {
|
|
131
|
+
await loadSolanaDeps();
|
|
132
|
+
|
|
133
|
+
// Get Phantom provider
|
|
134
|
+
this.provider = await this.getPhantomProvider();
|
|
135
|
+
if (!this.provider) {
|
|
136
|
+
throw new X402Error(
|
|
137
|
+
'Phantom wallet not installed. Please install from phantom.app',
|
|
138
|
+
'WALLET_NOT_FOUND'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Check if already connected
|
|
144
|
+
if (this.provider.publicKey && this.provider.isConnected) {
|
|
145
|
+
const publicKeyString = this.provider.publicKey.toBase58();
|
|
146
|
+
this.publicKey = new PublicKey(publicKeyString);
|
|
147
|
+
this.address = publicKeyString;
|
|
148
|
+
await this.initConnection();
|
|
149
|
+
return publicKeyString;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Connect
|
|
153
|
+
const resp = await this.provider.connect();
|
|
154
|
+
const publicKeyString = resp.publicKey.toBase58();
|
|
155
|
+
|
|
156
|
+
this.publicKey = new PublicKey(publicKeyString);
|
|
157
|
+
this.address = publicKeyString;
|
|
158
|
+
|
|
159
|
+
await this.initConnection();
|
|
160
|
+
|
|
161
|
+
return publicKeyString;
|
|
162
|
+
} catch (error: unknown) {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
if (error.message.includes('User rejected') || (error as { code?: number }).code === 4001) {
|
|
165
|
+
throw new X402Error('Connection rejected by user', 'WALLET_CONNECTION_REJECTED');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw new X402Error(
|
|
169
|
+
`Failed to connect Phantom: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
170
|
+
'UNKNOWN_ERROR',
|
|
171
|
+
error
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Disconnect from Phantom
|
|
178
|
+
*/
|
|
179
|
+
async disconnect(): Promise<void> {
|
|
180
|
+
if (this.provider) {
|
|
181
|
+
try {
|
|
182
|
+
await this.provider.disconnect();
|
|
183
|
+
} catch {
|
|
184
|
+
// Ignore disconnect errors
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
this.provider = null;
|
|
188
|
+
this.publicKey = null;
|
|
189
|
+
this.connections.clear();
|
|
190
|
+
this.address = null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get current address
|
|
195
|
+
*/
|
|
196
|
+
getAddress(): string | null {
|
|
197
|
+
return this.address;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get USDC balance
|
|
202
|
+
*/
|
|
203
|
+
async getBalance(chainConfig: ChainConfig): Promise<string> {
|
|
204
|
+
await loadSolanaDeps();
|
|
205
|
+
|
|
206
|
+
if (!this.address) {
|
|
207
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await this.initConnection(chainConfig);
|
|
211
|
+
const connection = this.connections.get(chainConfig.name);
|
|
212
|
+
if (!connection) {
|
|
213
|
+
throw new X402Error('Failed to connect to Solana RPC', 'NETWORK_ERROR');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const response = await fetch(chainConfig.rpcUrl, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify({
|
|
221
|
+
jsonrpc: '2.0',
|
|
222
|
+
id: 1,
|
|
223
|
+
method: 'getTokenAccountsByOwner',
|
|
224
|
+
params: [
|
|
225
|
+
this.address,
|
|
226
|
+
{ mint: chainConfig.usdc.address },
|
|
227
|
+
{ encoding: 'jsonParsed' },
|
|
228
|
+
],
|
|
229
|
+
}),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const data = await response.json();
|
|
233
|
+
|
|
234
|
+
if (!data.result?.value?.length) {
|
|
235
|
+
return '0.00';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const tokenAccountInfo = data.result.value[0].account.data.parsed.info;
|
|
239
|
+
const balance = Number(tokenAccountInfo.tokenAmount.amount) / Math.pow(10, tokenAccountInfo.tokenAmount.decimals);
|
|
240
|
+
|
|
241
|
+
return balance.toFixed(2);
|
|
242
|
+
} catch {
|
|
243
|
+
return '0.00';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Create SVM payment (partially-signed transaction)
|
|
249
|
+
*
|
|
250
|
+
* Works for both Solana and Fogo chains.
|
|
251
|
+
*
|
|
252
|
+
* Transaction structure required by facilitator:
|
|
253
|
+
* 1. SetComputeUnitLimit
|
|
254
|
+
* 2. SetComputeUnitPrice
|
|
255
|
+
* 3. (Optional) CreateAssociatedTokenAccount if recipient ATA doesn't exist
|
|
256
|
+
* 4. TransferChecked (USDC transfer)
|
|
257
|
+
*
|
|
258
|
+
* Fee payer: Facilitator (not user)
|
|
259
|
+
* User pays: ZERO SOL/FOGO
|
|
260
|
+
*/
|
|
261
|
+
async signPayment(paymentInfo: PaymentInfo, chainConfig: ChainConfig): Promise<string> {
|
|
262
|
+
await loadSolanaDeps();
|
|
263
|
+
|
|
264
|
+
if (!this.provider || !this.publicKey || !this.address) {
|
|
265
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const connection = await this.getConnection(chainConfig);
|
|
269
|
+
if (!connection) {
|
|
270
|
+
throw new X402Error(`Failed to connect to ${chainConfig.displayName} RPC`, 'NETWORK_ERROR');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Get recipient and facilitator addresses
|
|
274
|
+
const recipient = paymentInfo.recipients?.solana || paymentInfo.recipient;
|
|
275
|
+
const facilitatorAddress = paymentInfo.facilitator;
|
|
276
|
+
|
|
277
|
+
if (!facilitatorAddress) {
|
|
278
|
+
throw new X402Error('Facilitator address not provided', 'INVALID_CONFIG');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const recipientPubkey = new PublicKey(recipient);
|
|
282
|
+
const facilitatorPubkey = new PublicKey(facilitatorAddress);
|
|
283
|
+
const usdcMint = new PublicKey(chainConfig.usdc.address);
|
|
284
|
+
|
|
285
|
+
// Parse amount (6 decimals for USDC)
|
|
286
|
+
const amount = Math.floor(parseFloat(paymentInfo.amount) * 1_000_000);
|
|
287
|
+
|
|
288
|
+
// Get token accounts
|
|
289
|
+
const fromTokenAccount = await getAssociatedTokenAddress(
|
|
290
|
+
usdcMint,
|
|
291
|
+
this.publicKey,
|
|
292
|
+
true,
|
|
293
|
+
TOKEN_PROGRAM_ID
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const toTokenAccount = await getAssociatedTokenAddress(
|
|
297
|
+
usdcMint,
|
|
298
|
+
recipientPubkey,
|
|
299
|
+
true,
|
|
300
|
+
TOKEN_PROGRAM_ID
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// Check if recipient ATA exists
|
|
304
|
+
const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);
|
|
305
|
+
const needsATACreation = toTokenAccountInfo === null;
|
|
306
|
+
|
|
307
|
+
// Get recent blockhash
|
|
308
|
+
const { blockhash } = await connection.getLatestBlockhash('finalized');
|
|
309
|
+
|
|
310
|
+
// Build instructions in exact order required by facilitator
|
|
311
|
+
const instructions = [];
|
|
312
|
+
|
|
313
|
+
// Instruction 0: SetComputeUnitLimit
|
|
314
|
+
instructions.push(
|
|
315
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
316
|
+
units: needsATACreation ? 50_000 : 20_000,
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Instruction 1: SetComputeUnitPrice
|
|
321
|
+
instructions.push(
|
|
322
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
323
|
+
microLamports: 1,
|
|
324
|
+
})
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Instruction 2 (optional): CreateAssociatedTokenAccountIdempotent
|
|
328
|
+
// User pays for ATA creation (not facilitator - security check in x402-rs)
|
|
329
|
+
if (needsATACreation) {
|
|
330
|
+
instructions.push(
|
|
331
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
332
|
+
this.publicKey, // User pays for ATA creation
|
|
333
|
+
toTokenAccount,
|
|
334
|
+
recipientPubkey,
|
|
335
|
+
usdcMint,
|
|
336
|
+
TOKEN_PROGRAM_ID
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Instruction 2/3: TransferChecked
|
|
342
|
+
instructions.push(
|
|
343
|
+
createTransferCheckedInstruction(
|
|
344
|
+
fromTokenAccount,
|
|
345
|
+
usdcMint,
|
|
346
|
+
toTokenAccount,
|
|
347
|
+
this.publicKey,
|
|
348
|
+
amount,
|
|
349
|
+
6,
|
|
350
|
+
[],
|
|
351
|
+
TOKEN_PROGRAM_ID
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// Build VersionedTransaction with facilitator as fee payer
|
|
356
|
+
const messageV0 = new TransactionMessage({
|
|
357
|
+
payerKey: facilitatorPubkey,
|
|
358
|
+
recentBlockhash: blockhash,
|
|
359
|
+
instructions,
|
|
360
|
+
}).compileToV0Message();
|
|
361
|
+
|
|
362
|
+
const transaction = new VersionedTransaction(messageV0);
|
|
363
|
+
|
|
364
|
+
// User signs (partial signature - facilitator will co-sign)
|
|
365
|
+
let signedTransaction: InstanceType<typeof VersionedTransaction>;
|
|
366
|
+
try {
|
|
367
|
+
signedTransaction = await this.provider.signTransaction(transaction);
|
|
368
|
+
} catch (error: unknown) {
|
|
369
|
+
if (error instanceof Error && error.message.includes('User rejected')) {
|
|
370
|
+
throw new X402Error('Signature rejected by user', 'SIGNATURE_REJECTED');
|
|
371
|
+
}
|
|
372
|
+
throw new X402Error(
|
|
373
|
+
`Failed to sign transaction: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
374
|
+
'PAYMENT_FAILED',
|
|
375
|
+
error
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Serialize partially-signed transaction (VersionedTransaction.serialize takes no args)
|
|
380
|
+
const serialized = signedTransaction.serialize();
|
|
381
|
+
|
|
382
|
+
const payload: SolanaPaymentPayload = {
|
|
383
|
+
transaction: uint8ArrayToBase64(serialized),
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return JSON.stringify(payload);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Encode SVM payment as X-PAYMENT header
|
|
391
|
+
*
|
|
392
|
+
* @param paymentPayload - The payment payload JSON string
|
|
393
|
+
* @param chainConfig - Optional chain config (defaults to 'solana' if not provided)
|
|
394
|
+
*/
|
|
395
|
+
encodePaymentHeader(paymentPayload: string, chainConfig?: ChainConfig): string {
|
|
396
|
+
const payload = JSON.parse(paymentPayload) as SolanaPaymentPayload;
|
|
397
|
+
|
|
398
|
+
// Use chain name from config, or default to 'solana' for backward compatibility
|
|
399
|
+
const networkName = chainConfig?.name || 'solana';
|
|
400
|
+
|
|
401
|
+
const x402Payload = {
|
|
402
|
+
x402Version: 1,
|
|
403
|
+
scheme: 'exact',
|
|
404
|
+
network: networkName,
|
|
405
|
+
payload: {
|
|
406
|
+
transaction: payload.transaction,
|
|
407
|
+
},
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
return btoa(JSON.stringify(x402Payload));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Private helpers
|
|
414
|
+
|
|
415
|
+
private async getPhantomProvider(): Promise<PhantomProvider | null> {
|
|
416
|
+
if (typeof window === 'undefined') return null;
|
|
417
|
+
|
|
418
|
+
// Try window.phantom.solana first
|
|
419
|
+
const win = window as Window & {
|
|
420
|
+
phantom?: { solana?: PhantomProvider };
|
|
421
|
+
solana?: PhantomProvider;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
if (win.phantom?.solana?.isPhantom) {
|
|
425
|
+
return win.phantom.solana;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Fallback to window.solana
|
|
429
|
+
if (win.solana?.isPhantom) {
|
|
430
|
+
return win.solana;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Wait a bit for Phantom to inject itself
|
|
434
|
+
for (let i = 0; i < 5; i++) {
|
|
435
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
436
|
+
if (win.phantom?.solana?.isPhantom) {
|
|
437
|
+
return win.phantom.solana;
|
|
438
|
+
}
|
|
439
|
+
if (win.solana?.isPhantom) {
|
|
440
|
+
return win.solana;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get or create a connection for a specific chain
|
|
449
|
+
*/
|
|
450
|
+
private async getConnection(chainConfig?: ChainConfig): Promise<InstanceType<typeof Connection>> {
|
|
451
|
+
await loadSolanaDeps();
|
|
452
|
+
|
|
453
|
+
const config = chainConfig || getChainByName('solana');
|
|
454
|
+
if (!config) {
|
|
455
|
+
throw new X402Error('Chain config not found', 'CHAIN_NOT_SUPPORTED');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check if we already have a connection for this chain
|
|
459
|
+
if (this.connections.has(config.name)) {
|
|
460
|
+
return this.connections.get(config.name)!;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Create new connection for this chain
|
|
464
|
+
const connection = new Connection(config.rpcUrl, 'confirmed');
|
|
465
|
+
this.connections.set(config.name, connection);
|
|
466
|
+
|
|
467
|
+
return connection;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @deprecated Use getConnection instead
|
|
472
|
+
*/
|
|
473
|
+
private async initConnection(chainConfig?: ChainConfig): Promise<void> {
|
|
474
|
+
await this.getConnection(chainConfig);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* @deprecated Use SVMProvider instead
|
|
480
|
+
*/
|
|
481
|
+
export class SolanaProvider extends SVMProvider {
|
|
482
|
+
constructor() {
|
|
483
|
+
super();
|
|
484
|
+
console.warn('SolanaProvider is deprecated. Use SVMProvider instead.');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Default export
|
|
489
|
+
export default SVMProvider;
|