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,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uvd-x402-sdk - Stellar Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides Stellar wallet connection and payment creation via Freighter.
|
|
5
|
+
* Uses Soroban authorization entries for gasless USDC transfers.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { X402Client } from 'uvd-x402-sdk';
|
|
10
|
+
* import { StellarProvider } from 'uvd-x402-sdk/stellar';
|
|
11
|
+
*
|
|
12
|
+
* const client = new X402Client();
|
|
13
|
+
* const stellar = new StellarProvider();
|
|
14
|
+
*
|
|
15
|
+
* // Connect
|
|
16
|
+
* const address = await stellar.connect();
|
|
17
|
+
*
|
|
18
|
+
* // Create payment
|
|
19
|
+
* const paymentPayload = await stellar.createPayment(paymentInfo);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
ChainConfig,
|
|
25
|
+
PaymentInfo,
|
|
26
|
+
StellarPaymentPayload,
|
|
27
|
+
WalletAdapter,
|
|
28
|
+
} from '../../types';
|
|
29
|
+
import { X402Error } from '../../types';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Browser-compatible text to Uint8Array encoding
|
|
33
|
+
* Avoids Node.js Buffer dependency for browser bundlers
|
|
34
|
+
*/
|
|
35
|
+
function stringToUint8Array(str: string): Uint8Array {
|
|
36
|
+
return new TextEncoder().encode(str);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Browser-compatible base64 decoding to Uint8Array
|
|
41
|
+
*/
|
|
42
|
+
function base64ToUint8Array(base64: string): Uint8Array {
|
|
43
|
+
const binary = atob(base64);
|
|
44
|
+
const bytes = new Uint8Array(binary.length);
|
|
45
|
+
for (let i = 0; i < binary.length; i++) {
|
|
46
|
+
bytes[i] = binary.charCodeAt(i);
|
|
47
|
+
}
|
|
48
|
+
return bytes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Stellar configuration
|
|
52
|
+
const STELLAR_CONFIG = {
|
|
53
|
+
networkPassphrase: 'Public Global Stellar Network ; September 2015',
|
|
54
|
+
horizonUrl: 'https://horizon.stellar.org',
|
|
55
|
+
sorobanRpcUrl: 'https://mainnet.sorobanrpc.com',
|
|
56
|
+
usdcIssuer: 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* StellarProvider - Wallet adapter for Stellar via Freighter
|
|
61
|
+
*/
|
|
62
|
+
export class StellarProvider implements WalletAdapter {
|
|
63
|
+
readonly id = 'freighter';
|
|
64
|
+
readonly name = 'Freighter';
|
|
65
|
+
readonly networkType = 'stellar' as const;
|
|
66
|
+
|
|
67
|
+
private publicKey: string | null = null;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if Freighter wallet is available
|
|
71
|
+
*/
|
|
72
|
+
isAvailable(): boolean {
|
|
73
|
+
if (typeof window === 'undefined') return false;
|
|
74
|
+
// Check for Freighter global
|
|
75
|
+
return typeof (window as Window & { freighter?: unknown }).freighter !== 'undefined';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Connect to Freighter wallet
|
|
80
|
+
*/
|
|
81
|
+
async connect(): Promise<string> {
|
|
82
|
+
const freighterApi = await this.getFreighterApi();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Check if Freighter is connected
|
|
86
|
+
const connectionResult = await freighterApi.isConnected();
|
|
87
|
+
if (!connectionResult.isConnected) {
|
|
88
|
+
throw new X402Error(
|
|
89
|
+
'Freighter wallet not installed. Please install from freighter.app',
|
|
90
|
+
'WALLET_NOT_FOUND'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Request access
|
|
95
|
+
const accessResult = await freighterApi.requestAccess();
|
|
96
|
+
if (accessResult.error) {
|
|
97
|
+
throw new X402Error(accessResult.error, 'WALLET_CONNECTION_REJECTED');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Get address
|
|
101
|
+
const addressResult = await freighterApi.getAddress();
|
|
102
|
+
if (addressResult.error) {
|
|
103
|
+
throw new X402Error(addressResult.error, 'WALLET_CONNECTION_REJECTED');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const address = addressResult.address;
|
|
107
|
+
if (!address) {
|
|
108
|
+
throw new X402Error('Failed to get Stellar public key', 'WALLET_CONNECTION_REJECTED');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Validate format (G... public key)
|
|
112
|
+
if (!address.startsWith('G') || address.length !== 56) {
|
|
113
|
+
throw new X402Error('Invalid Stellar public key format', 'WALLET_CONNECTION_REJECTED');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.publicKey = address;
|
|
117
|
+
return address;
|
|
118
|
+
} catch (error: unknown) {
|
|
119
|
+
if (error instanceof X402Error) throw error;
|
|
120
|
+
|
|
121
|
+
if (error instanceof Error) {
|
|
122
|
+
if (error.message.includes('User rejected') || error.message.includes('cancelled')) {
|
|
123
|
+
throw new X402Error('Connection rejected by user', 'WALLET_CONNECTION_REJECTED');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
throw new X402Error(
|
|
128
|
+
`Failed to connect Freighter: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
129
|
+
'UNKNOWN_ERROR',
|
|
130
|
+
error
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Disconnect from Freighter
|
|
137
|
+
*/
|
|
138
|
+
async disconnect(): Promise<void> {
|
|
139
|
+
this.publicKey = null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get current address
|
|
144
|
+
*/
|
|
145
|
+
getAddress(): string | null {
|
|
146
|
+
return this.publicKey;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get USDC balance
|
|
151
|
+
*/
|
|
152
|
+
async getBalance(_chainConfig: ChainConfig): Promise<string> {
|
|
153
|
+
if (!this.publicKey) {
|
|
154
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(
|
|
159
|
+
`${STELLAR_CONFIG.horizonUrl}/accounts/${this.publicKey}`
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
if (response.status === 404) {
|
|
164
|
+
// Account not activated
|
|
165
|
+
return '0.00';
|
|
166
|
+
}
|
|
167
|
+
throw new Error('Failed to fetch Stellar account');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const account = await response.json();
|
|
171
|
+
|
|
172
|
+
// Find USDC balance
|
|
173
|
+
const usdcBalance = account.balances.find(
|
|
174
|
+
(b: { asset_code?: string; asset_issuer?: string }) =>
|
|
175
|
+
b.asset_code === 'USDC' && b.asset_issuer === STELLAR_CONFIG.usdcIssuer
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
if (!usdcBalance) {
|
|
179
|
+
return '0.00';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const balance = parseFloat(usdcBalance.balance);
|
|
183
|
+
return balance.toFixed(2);
|
|
184
|
+
} catch {
|
|
185
|
+
return '0.00';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create Stellar payment (Soroban authorization entry)
|
|
191
|
+
*
|
|
192
|
+
* User signs an authorization that proves they authorized the USDC transfer.
|
|
193
|
+
* Facilitator wraps this in a fee-bump transaction and pays all XLM network fees.
|
|
194
|
+
*/
|
|
195
|
+
async signPayment(paymentInfo: PaymentInfo, chainConfig: ChainConfig): Promise<string> {
|
|
196
|
+
if (!this.publicKey) {
|
|
197
|
+
throw new X402Error('Wallet not connected', 'WALLET_NOT_CONNECTED');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const freighterApi = await this.getFreighterApi();
|
|
201
|
+
const stellarSdk = await import('@stellar/stellar-sdk');
|
|
202
|
+
const { Address, xdr, StrKey, nativeToScVal, hash, Networks } = stellarSdk;
|
|
203
|
+
|
|
204
|
+
// Get recipient
|
|
205
|
+
const recipient = paymentInfo.recipients?.stellar || paymentInfo.recipient;
|
|
206
|
+
|
|
207
|
+
// Parse amount (7 decimals for Stellar USDC)
|
|
208
|
+
const amountStroops = Math.floor(parseFloat(paymentInfo.amount) * 10_000_000);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
// Get current ledger from Soroban RPC
|
|
212
|
+
const rpcResponse = await fetch(STELLAR_CONFIG.sorobanRpcUrl, {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
jsonrpc: '2.0',
|
|
217
|
+
id: 1,
|
|
218
|
+
method: 'getLatestLedger',
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const rpcData = await rpcResponse.json();
|
|
223
|
+
if (rpcData.error) {
|
|
224
|
+
throw new Error(`Soroban RPC error: ${rpcData.error.message}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const currentLedger = rpcData.result.sequence;
|
|
228
|
+
const signatureExpirationLedger = currentLedger + 60; // ~5 minutes
|
|
229
|
+
|
|
230
|
+
// Generate random nonce
|
|
231
|
+
const nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
|
232
|
+
|
|
233
|
+
// Build authorization for transfer(from, to, amount)
|
|
234
|
+
const fromAddress = new Address(this.publicKey);
|
|
235
|
+
const toAddress = new Address(recipient);
|
|
236
|
+
const amountScVal = nativeToScVal(BigInt(amountStroops), { type: 'i128' });
|
|
237
|
+
|
|
238
|
+
const args = [fromAddress.toScVal(), toAddress.toScVal(), amountScVal];
|
|
239
|
+
|
|
240
|
+
// Create contract address
|
|
241
|
+
const contractIdBytes = StrKey.decodeContract(chainConfig.usdc.address);
|
|
242
|
+
// @ts-ignore - Stellar SDK accepts Uint8Array
|
|
243
|
+
const contractScAddress = Address.contract(contractIdBytes).toScAddress();
|
|
244
|
+
|
|
245
|
+
// Build authorized invocation
|
|
246
|
+
const contractFn = new xdr.InvokeContractArgs({
|
|
247
|
+
contractAddress: contractScAddress,
|
|
248
|
+
functionName: 'transfer',
|
|
249
|
+
args: args,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const invocation = new xdr.SorobanAuthorizedInvocation({
|
|
253
|
+
function: xdr.SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn(contractFn),
|
|
254
|
+
subInvocations: [],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Build HashIdPreimageSorobanAuthorization for signing
|
|
258
|
+
// Cast to Buffer for hash() - Uint8Array is compatible at runtime
|
|
259
|
+
const networkId = hash(stringToUint8Array(Networks.PUBLIC) as unknown as Buffer);
|
|
260
|
+
|
|
261
|
+
const preimageData = new xdr.HashIdPreimageSorobanAuthorization({
|
|
262
|
+
networkId: networkId,
|
|
263
|
+
nonce: xdr.Int64.fromString(nonce.toString()),
|
|
264
|
+
signatureExpirationLedger: signatureExpirationLedger,
|
|
265
|
+
invocation: invocation,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const hashIdPreimage = xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(preimageData);
|
|
269
|
+
const preimageXdr = hashIdPreimage.toXDR('base64');
|
|
270
|
+
|
|
271
|
+
// Sign with Freighter
|
|
272
|
+
const signResult = await freighterApi.signAuthEntry(preimageXdr, {
|
|
273
|
+
networkPassphrase: STELLAR_CONFIG.networkPassphrase,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (signResult.error) {
|
|
277
|
+
throw new X402Error(signResult.error, 'SIGNATURE_REJECTED');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!signResult.signedAuthEntry) {
|
|
281
|
+
throw new X402Error('Freighter did not return signed auth entry', 'PAYMENT_FAILED');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Build the signed SorobanAuthorizationEntry
|
|
285
|
+
const signatureBytes = base64ToUint8Array(signResult.signedAuthEntry);
|
|
286
|
+
const publicKeyBytes = StrKey.decodeEd25519PublicKey(this.publicKey);
|
|
287
|
+
|
|
288
|
+
const sigMapEntries = [
|
|
289
|
+
new xdr.ScMapEntry({
|
|
290
|
+
key: xdr.ScVal.scvSymbol('public_key'),
|
|
291
|
+
val: xdr.ScVal.scvBytes(publicKeyBytes),
|
|
292
|
+
}),
|
|
293
|
+
new xdr.ScMapEntry({
|
|
294
|
+
key: xdr.ScVal.scvSymbol('signature'),
|
|
295
|
+
val: xdr.ScVal.scvBytes(signatureBytes as unknown as Buffer),
|
|
296
|
+
}),
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
const signatureScVal = xdr.ScVal.scvVec([xdr.ScVal.scvMap(sigMapEntries)]);
|
|
300
|
+
|
|
301
|
+
const signedAddressCredentials = new xdr.SorobanAddressCredentials({
|
|
302
|
+
address: fromAddress.toScAddress(),
|
|
303
|
+
nonce: xdr.Int64.fromString(nonce.toString()),
|
|
304
|
+
signatureExpirationLedger: signatureExpirationLedger,
|
|
305
|
+
signature: signatureScVal,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const signedCredentials = xdr.SorobanCredentials.sorobanCredentialsAddress(signedAddressCredentials);
|
|
309
|
+
const signedAuthEntry = new xdr.SorobanAuthorizationEntry({
|
|
310
|
+
credentials: signedCredentials,
|
|
311
|
+
rootInvocation: invocation,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const authorizationEntryXdr = signedAuthEntry.toXDR('base64');
|
|
315
|
+
|
|
316
|
+
const payload: StellarPaymentPayload = {
|
|
317
|
+
from: this.publicKey,
|
|
318
|
+
to: recipient,
|
|
319
|
+
amount: amountStroops.toString(),
|
|
320
|
+
tokenContract: chainConfig.usdc.address,
|
|
321
|
+
authorizationEntryXdr,
|
|
322
|
+
nonce,
|
|
323
|
+
signatureExpirationLedger,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return JSON.stringify(payload);
|
|
327
|
+
} catch (error: unknown) {
|
|
328
|
+
if (error instanceof X402Error) throw error;
|
|
329
|
+
|
|
330
|
+
if (error instanceof Error) {
|
|
331
|
+
if (error.message.includes('User rejected') || error.message.includes('cancelled')) {
|
|
332
|
+
throw new X402Error('Signature rejected by user', 'SIGNATURE_REJECTED');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
throw new X402Error(
|
|
337
|
+
`Failed to create Stellar payment: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
338
|
+
'PAYMENT_FAILED',
|
|
339
|
+
error
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Encode Stellar payment as X-PAYMENT header
|
|
346
|
+
*/
|
|
347
|
+
encodePaymentHeader(paymentPayload: string): string {
|
|
348
|
+
const payload = JSON.parse(paymentPayload) as StellarPaymentPayload;
|
|
349
|
+
|
|
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
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
return btoa(JSON.stringify(x402Payload));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Private helpers
|
|
369
|
+
|
|
370
|
+
private async getFreighterApi(): Promise<typeof import('@stellar/freighter-api')> {
|
|
371
|
+
const freighterApi = await import('@stellar/freighter-api');
|
|
372
|
+
return freighterApi;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export default StellarProvider;
|