hd-wallet-ui 1.2.0 → 1.2.2

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.
@@ -10,6 +10,8 @@
10
10
  * Revocation: [0x52][0x01][timestamp:4][txhash:32] = 38 bytes
11
11
  */
12
12
 
13
+ import { apiUrl } from './address-derivation.js';
14
+
13
15
  // =============================================================================
14
16
  // Trust Levels (PGP-style)
15
17
  // =============================================================================
@@ -41,6 +43,16 @@ const VERSION = 0x01;
41
43
  // Legacy ASCII prefixes
42
44
  const LEGACY_TRUST_PREFIX = 'TRUST';
43
45
  const LEGACY_REVOKE_PREFIX = 'REVOKE';
46
+ const SOLANA_TRUST_RPC_ENDPOINTS = [
47
+ 'https://solana-rpc.publicnode.com',
48
+ 'https://mainnet.helius-rpc.com/?api-key=1d8740dc-e5f4-421c-b823-e1bad1889eda',
49
+ 'https://api.mainnet-beta.solana.com',
50
+ ];
51
+ const SOLANA_TRUST_MAX_SIGNATURES = 40;
52
+ const SOLANA_TRUST_REQUEST_DELAY_MS = 350;
53
+ const SOLANA_TRUST_UNAVAILABLE_COOLDOWN_MS = 5 * 60 * 1000;
54
+ let _solanaTrustLastRequestAt = 0;
55
+ let _solanaTrustUnavailableUntil = 0;
44
56
 
45
57
  // =============================================================================
46
58
  // Binary Encoding Helpers
@@ -420,39 +432,80 @@ export async function scanBitcoinTrustTransactions(address) {
420
432
  * Uses RPC to get transactions with memo instructions.
421
433
  */
422
434
  export async function scanSolanaTrustTransactions(address) {
435
+ const isRateLimited = (msg) => {
436
+ const t = (msg || '').toLowerCase();
437
+ return t.includes('429') || t.includes('rate') || t.includes('limit') || t.includes('too many');
438
+ };
439
+ const isEndpointUnavailable = (msg) => {
440
+ const t = (msg || '').toLowerCase();
441
+ return t.includes('403') || t.includes('404') || t.includes('forbidden') || t.includes('not found');
442
+ };
443
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
444
+ const waitForThrottle = async () => {
445
+ const elapsed = Date.now() - _solanaTrustLastRequestAt;
446
+ if (elapsed < SOLANA_TRUST_REQUEST_DELAY_MS) {
447
+ await sleep(SOLANA_TRUST_REQUEST_DELAY_MS - elapsed);
448
+ }
449
+ _solanaTrustLastRequestAt = Date.now();
450
+ };
451
+ const solanaRpcCall = async (method, params) => {
452
+ let lastError = 'Unknown Solana RPC error';
453
+
454
+ for (const endpoint of SOLANA_TRUST_RPC_ENDPOINTS) {
455
+ try {
456
+ await waitForThrottle();
457
+ const response = await fetch(apiUrl(endpoint), {
458
+ method: 'POST',
459
+ headers: { 'Content-Type': 'application/json' },
460
+ body: JSON.stringify({
461
+ jsonrpc: '2.0',
462
+ id: 1,
463
+ method,
464
+ params,
465
+ }),
466
+ });
467
+
468
+ if (!response.ok) {
469
+ lastError = `HTTP ${response.status}`;
470
+ continue;
471
+ }
472
+
473
+ const data = await response.json();
474
+ if (data.error) {
475
+ lastError = data.error.message || 'Solana RPC returned error';
476
+ continue;
477
+ }
478
+
479
+ return { ok: true, result: data.result };
480
+ } catch (e) {
481
+ lastError = e?.message || 'Solana RPC fetch failed';
482
+ }
483
+ }
484
+
485
+ return { ok: false, error: lastError };
486
+ };
487
+
423
488
  try {
424
- const response = await fetch('https://api.mainnet-beta.solana.com', {
425
- method: 'POST',
426
- headers: { 'Content-Type': 'application/json' },
427
- body: JSON.stringify({
428
- jsonrpc: '2.0',
429
- id: 1,
430
- method: 'getSignaturesForAddress',
431
- params: [address, { limit: 100 }],
432
- }),
433
- });
489
+ if (Date.now() < _solanaTrustUnavailableUntil) {
490
+ return [];
491
+ }
434
492
 
435
- if (!response.ok) throw new Error('Failed to fetch Solana signatures');
436
- const data = await response.json();
437
- const signatures = data.result || [];
493
+ const sigResp = await solanaRpcCall('getSignaturesForAddress', [address, { limit: SOLANA_TRUST_MAX_SIGNATURES }]);
494
+ if (!sigResp.ok) {
495
+ if (isRateLimited(sigResp.error) || isEndpointUnavailable(sigResp.error)) {
496
+ _solanaTrustUnavailableUntil = Date.now() + SOLANA_TRUST_UNAVAILABLE_COOLDOWN_MS;
497
+ }
498
+ throw new Error(`Failed to fetch Solana signatures (${sigResp.error})`);
499
+ }
500
+
501
+ const signatures = Array.isArray(sigResp.result) ? sigResp.result : [];
438
502
 
439
503
  const trustTxs = [];
440
504
 
441
505
  for (const sig of signatures) {
442
- const txResponse = await fetch('https://api.mainnet-beta.solana.com', {
443
- method: 'POST',
444
- headers: { 'Content-Type': 'application/json' },
445
- body: JSON.stringify({
446
- jsonrpc: '2.0',
447
- id: 1,
448
- method: 'getTransaction',
449
- params: [sig.signature, { encoding: 'jsonParsed' }],
450
- }),
451
- });
452
-
453
- if (!txResponse.ok) continue;
454
- const txData = await txResponse.json();
455
- const tx = txData.result;
506
+ const txResp = await solanaRpcCall('getTransaction', [sig.signature, { encoding: 'jsonParsed' }]);
507
+ if (!txResp.ok) continue;
508
+ const tx = txResp.result;
456
509
 
457
510
  if (!tx || !tx.meta) continue;
458
511
 
@@ -477,7 +530,7 @@ export async function scanSolanaTrustTransactions(address) {
477
530
 
478
531
  return trustTxs;
479
532
  } catch (err) {
480
- console.error('Solana trust scan failed:', err);
533
+ console.warn('Solana trust scan skipped:', err.message || err);
481
534
  return [];
482
535
  }
483
536
  }