@pioneer-platform/pioneer-sdk 8.15.41 → 8.17.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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "author": "highlander",
3
3
  "name": "@pioneer-platform/pioneer-sdk",
4
- "version": "8.15.41",
4
+ "version": "8.17.0",
5
5
  "dependencies": {
6
- "@keepkey/keepkey-sdk": "^0.2.62",
7
- "@pioneer-platform/pioneer-caip": "^9.10.18",
6
+ "keepkey-vault-sdk": "^1.0.2",
7
+ "@pioneer-platform/pioneer-caip": "^9.12.0",
8
8
  "@pioneer-platform/pioneer-client": "^9.10.24",
9
- "@pioneer-platform/pioneer-coins": "^9.11.18",
10
- "@pioneer-platform/pioneer-discovery": "^8.15.41",
11
- "@pioneer-platform/pioneer-events": "^8.12.13",
9
+ "@pioneer-platform/pioneer-coins": "^9.13.0",
10
+ "@pioneer-platform/pioneer-discovery": "^8.17.0",
11
+ "@pioneer-platform/pioneer-events": "^8.13.0",
12
12
  "coinselect": "^3.1.13",
13
13
  "eventemitter3": "^5.0.1",
14
14
  "neotraverse": "^0.6.8",
@@ -6,7 +6,9 @@ import type EventEmitter from 'events';
6
6
  import { CAIP_TO_COIN_MAP, SUPPORTED_CAIPS } from './supportedCaips';
7
7
  import { createUnsignedEvmTx } from './txbuilder/createUnsignedEvmTx';
8
8
  import { createUnsignedRippleTx } from './txbuilder/createUnsignedRippleTx';
9
+ import { createUnsignedSolanaTx } from './txbuilder/createUnsignedSolanaTx';
9
10
  import { createUnsignedTendermintTx } from './txbuilder/createUnsignedTendermintTx';
11
+ import { createUnsignedTronTx } from './txbuilder/createUnsignedTronTx';
10
12
  import { createUnsignedUxtoTx } from './txbuilder/createUnsignedUxtoTx';
11
13
 
12
14
  const TAG = ' | Transaction | ';
@@ -138,16 +140,43 @@ export class TransactionManager {
138
140
  break;
139
141
  }
140
142
  case 'OTHER': {
141
- unsignedTx = await createUnsignedRippleTx(
142
- caip,
143
- to,
144
- amount,
145
- memo,
146
- this.pubkeys,
147
- this.pioneer,
148
- this.pubkeyContext,
149
- isMax,
150
- );
143
+ // Distinguish between different chains in the OTHER category
144
+ if (caip.startsWith('solana:')) {
145
+ unsignedTx = await createUnsignedSolanaTx(
146
+ caip,
147
+ to,
148
+ amount,
149
+ memo,
150
+ this.pubkeys,
151
+ this.pioneer,
152
+ this.pubkeyContext,
153
+ isMax,
154
+ );
155
+ } else if (caip === 'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144') {
156
+ unsignedTx = await createUnsignedRippleTx(
157
+ caip,
158
+ to,
159
+ amount,
160
+ memo,
161
+ this.pubkeys,
162
+ this.pioneer,
163
+ this.pubkeyContext,
164
+ isMax,
165
+ );
166
+ } else if (caip.startsWith('tron:')) {
167
+ unsignedTx = await createUnsignedTronTx(
168
+ caip,
169
+ to,
170
+ amount,
171
+ memo,
172
+ this.pubkeys,
173
+ this.pioneer,
174
+ this.pubkeyContext,
175
+ isMax,
176
+ );
177
+ } else {
178
+ throw new Error(`Unsupported OTHER CAIP for transaction building: ${caip}`);
179
+ }
151
180
  break;
152
181
  }
153
182
  default: {
@@ -331,10 +360,79 @@ export class TransactionManager {
331
360
  break;
332
361
  }
333
362
  case 'OTHER': {
334
- if (caip === 'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144') {
363
+ if (caip.startsWith('solana:')) {
364
+ // Solana signing via keepkey-server REST API
365
+ const solanaSignRequest = {
366
+ addressNList: unsignedTx.addressNList,
367
+ serialized: unsignedTx.serialized,
368
+ fromAddress: unsignedTx.fromAddress,
369
+ toAddress: unsignedTx.toAddress,
370
+ lamports: unsignedTx.lamports,
371
+ blockhash: unsignedTx.blockhash
372
+ };
373
+
374
+ try {
375
+ const response = await fetch('http://localhost:1646/solana/sign-transaction', {
376
+ method: 'POST',
377
+ headers: {
378
+ 'Content-Type': 'application/json',
379
+ },
380
+ body: JSON.stringify(solanaSignRequest)
381
+ });
382
+
383
+ if (!response.ok) {
384
+ const errorData = await response.json();
385
+ throw new Error(`Solana signing failed: ${errorData.error || response.statusText}`);
386
+ }
387
+
388
+ const responseSign = await response.json();
389
+ if (responseSign?.serialized) {
390
+ signedTx = responseSign.serialized;
391
+ } else {
392
+ throw new Error('Solana signing failed - no serialized transaction in response');
393
+ }
394
+ } catch (e: any) {
395
+ console.error(tag, 'Solana signing error:', e);
396
+ throw new Error(`Solana signing failed: ${e.message}`);
397
+ }
398
+ } else if (caip === 'ripple:4109c6f2045fc7eff4cde8f9905d19c2/slip44:144') {
335
399
  let responseSign = await this.keepKeySdk.xrp.xrpSignTransaction(unsignedTx);
336
400
  if (typeof responseSign === 'string') responseSign = JSON.parse(responseSign);
337
401
  signedTx = responseSign.value.signatures[0].serializedTx;
402
+ } else if (caip.startsWith('tron:')) {
403
+ // TRON signing via keepkey-server REST API
404
+ const tronSignRequest = {
405
+ addressNList: unsignedTx.addressNList,
406
+ from: unsignedTx.from,
407
+ to: unsignedTx.to,
408
+ amount: unsignedTx.amountInSun,
409
+ memo: unsignedTx.memo || '',
410
+ };
411
+
412
+ try {
413
+ const response = await fetch('http://localhost:1646/tron/sign-transaction', {
414
+ method: 'POST',
415
+ headers: {
416
+ 'Content-Type': 'application/json',
417
+ },
418
+ body: JSON.stringify(tronSignRequest)
419
+ });
420
+
421
+ if (!response.ok) {
422
+ const errorData = await response.json();
423
+ throw new Error(`TRON signing failed: ${errorData.error || response.statusText}`);
424
+ }
425
+
426
+ const responseSign = await response.json();
427
+ if (responseSign?.serialized) {
428
+ signedTx = responseSign.serialized;
429
+ } else {
430
+ throw new Error('TRON signing failed - no serialized transaction in response');
431
+ }
432
+ } catch (e: any) {
433
+ console.error(tag, 'TRON signing error:', e);
434
+ throw new Error(`TRON signing failed: ${e.message}`);
435
+ }
338
436
  } else {
339
437
  throw new Error(`Unsupported OTHER CAIP: ${caip}`);
340
438
  }
package/src/getPubkey.ts CHANGED
@@ -38,7 +38,9 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
38
38
  'eip155:137': 'EVM',
39
39
  'eip155:*': 'EVM',
40
40
  'ripple:4109c6f2045fc7eff4cde8f9905d19c2': 'XRP',
41
- 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'SOLANA', // Solana (not yet supported by KeepKey)
41
+ 'solana:5eykt4usfv8p8njdtrepy1vzqkqzkvdp': 'SOLANA', // Solana
42
+ 'tron:0x2b6653dc': 'TRON', // TRON (Shasta testnet)
43
+ 'ton:-239': 'TON', // TON (testnet)
42
44
  'zcash:main': 'UTXO',
43
45
  };
44
46
 
@@ -73,8 +75,32 @@ export const getPubkey = async (networkId: string, path: any, sdk: any, context:
73
75
  ({ address } = await sdk.address.xrpGetAddress(addressInfo));
74
76
  break;
75
77
  case 'SOLANA':
76
- console.warn(`⚠️ Solana address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
77
- return null; // Skip Solana for now until KeepKey SDK supports it
78
+ // Check if SDK has solanaGetAddress method
79
+ if (sdk.address.solanaGetAddress) {
80
+ ({ address } = await sdk.address.solanaGetAddress(addressInfo));
81
+ } else {
82
+ console.warn(`⚠️ Solana address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
83
+ return null;
84
+ }
85
+ break;
86
+ case 'TRON':
87
+ // Check if SDK has tronGetAddress method
88
+ if (sdk.address.tronGetAddress) {
89
+ ({ address } = await sdk.address.tronGetAddress(addressInfo));
90
+ } else {
91
+ console.warn(`⚠️ TRON address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
92
+ return null;
93
+ }
94
+ break;
95
+ case 'TON':
96
+ // Check if SDK has tonGetAddress method
97
+ if (sdk.address.tonGetAddress) {
98
+ ({ address } = await sdk.address.tonGetAddress(addressInfo));
99
+ } else {
100
+ console.warn(`⚠️ TON address derivation not yet supported by KeepKey SDK. Skipping ${networkId}`);
101
+ return null;
102
+ }
103
+ break;
78
104
  default:
79
105
  throw new Error(`Unsupported network type for networkId: ${networkId}`);
80
106
  }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { KeepKeySdk } from '@keepkey/keepkey-sdk';
1
+ import { KeepKeySdk } from 'keepkey-vault-sdk';
2
2
  import { caipToNetworkId, networkIdToCaip } from '@pioneer-platform/pioneer-caip';
3
3
  import { Pioneer } from '@pioneer-platform/pioneer-client';
4
4
  import { addressNListToBIP32, getPaths } from '@pioneer-platform/pioneer-coins';
@@ -10,7 +10,7 @@ import { getCharts } from './charts/index.js';
10
10
  //internal
11
11
  import { logger } from './utils/logger.js';
12
12
  import { getPubkey } from './getPubkey.js';
13
- import { optimizedGetPubkeys } from './kkapi-batch-client.js';
13
+ import { optimizedGetPubkeys, checkKkapiHealth } from './kkapi-batch-client.js';
14
14
  import { OfflineClient } from './offline-client.js';
15
15
  import { TransactionManager } from './TransactionManager.js';
16
16
  import { createUnsignedTendermintTx } from './txbuilder/createUnsignedTendermintTx.js';
@@ -128,6 +128,7 @@ export class SDK {
128
128
  public assetsMap: any;
129
129
  public dashboard: any;
130
130
  public nfts: any[];
131
+ public pendingTransactions: any[]; // Track recent/pending transactions
131
132
  public events: any;
132
133
  public pairWallet: (options: any) => Promise<any>;
133
134
  public setContext: (context: string) => Promise<{ success: boolean }>;
@@ -236,6 +237,7 @@ export class SDK {
236
237
  this.nodes = config.nodes || [];
237
238
  this.charts = ['covalent', 'zapper'];
238
239
  this.nfts = [];
240
+ this.pendingTransactions = []; // Initialize empty pending transactions array
239
241
  this.isPioneer = null;
240
242
  this.pioneer = null;
241
243
  this.context = '';
@@ -299,6 +301,11 @@ export class SDK {
299
301
  const key = getPubkeyKey(pubkey);
300
302
  if (this.pubkeySet.has(key)) return false;
301
303
 
304
+ // PHASE 4: Normalize network IDs to lowercase for consistency
305
+ if (pubkey.networks && Array.isArray(pubkey.networks)) {
306
+ pubkey.networks = pubkey.networks.map((n: string) => n.toLowerCase());
307
+ }
308
+
302
309
  this.pubkeys.push(pubkey);
303
310
  this.pubkeySet.add(key);
304
311
  return true;
@@ -373,6 +380,12 @@ export class SDK {
373
380
 
374
381
  // Update SDK state if we have balances
375
382
  if (allBalances.length > 0) {
383
+ // PHASE 5: Normalize all CAIPs to lowercase for consistency
384
+ allBalances.forEach((balance: any) => {
385
+ if (balance.caip) balance.caip = balance.caip.toLowerCase();
386
+ if (balance.networkId) balance.networkId = balance.networkId.toLowerCase();
387
+ });
388
+
376
389
  this.balances = allBalances;
377
390
  this.events.emit('SET_BALANCES', this.balances);
378
391
  }
@@ -814,6 +827,46 @@ export class SDK {
814
827
  let txManager = new TransactionManager(transactionDependencies, this.events);
815
828
  let unsignedTx = await txManager.transfer(sendPayload);
816
829
  console.log(tag, 'unsignedTx: ', unsignedTx);
830
+
831
+ // CRITICAL: Store transaction details in assetContext for optimistic balance update
832
+ // These will be used by broadcastTx to debit the balance immediately
833
+ if (this.assetContext && sendPayload) {
834
+ this.assetContext.sendAmount = sendPayload.amount;
835
+ this.assetContext.sendTo = sendPayload.to;
836
+ this.assetContext.sendMemo = sendPayload.memo;
837
+
838
+ // Extract fee from unsignedTx if available
839
+ // Different chains have different fee structures
840
+ let estimatedFee = '0';
841
+ if (unsignedTx) {
842
+ // UTXO chains
843
+ if (unsignedTx.fee) {
844
+ estimatedFee = typeof unsignedTx.fee === 'string'
845
+ ? unsignedTx.fee
846
+ : unsignedTx.fee.toString();
847
+ }
848
+ // EVM chains
849
+ else if (unsignedTx.gasPrice && unsignedTx.gasLimit) {
850
+ const gasPrice = parseFloat(unsignedTx.gasPrice);
851
+ const gasLimit = parseFloat(unsignedTx.gasLimit);
852
+ // Convert from gwei to native token (rough estimate)
853
+ estimatedFee = ((gasPrice * gasLimit) / 1000000000).toFixed(8);
854
+ }
855
+ // Tendermint chains
856
+ else if (unsignedTx.fee && unsignedTx.fee.amount && Array.isArray(unsignedTx.fee.amount)) {
857
+ estimatedFee = unsignedTx.fee.amount[0]?.amount || '0';
858
+ }
859
+ }
860
+
861
+ this.assetContext.sendFee = estimatedFee;
862
+
863
+ console.log(tag, '💾 Stored transaction details for optimistic update:', {
864
+ amount: this.assetContext.sendAmount,
865
+ fee: this.assetContext.sendFee,
866
+ to: this.assetContext.sendTo
867
+ });
868
+ }
869
+
817
870
  return unsignedTx;
818
871
  } catch (e) {
819
872
  console.error(e);
@@ -943,12 +996,149 @@ export class SDK {
943
996
  serialized: signedTx,
944
997
  };
945
998
  let txid = await txManager.broadcast(payload);
999
+
1000
+ // OPTIMISTIC UPDATE: Debit balance immediately after successful broadcast
1001
+ if (txid && !txid.error) {
1002
+ // Extract transaction details from assetContext (set during buildTx)
1003
+ const amount = this.assetContext?.sendAmount || '0';
1004
+ const fee = this.assetContext?.sendFee || '0';
1005
+ const to = this.assetContext?.sendTo || '';
1006
+ const from = this.pubkeyContext?.address || this.pubkeyContext?.master || '';
1007
+
1008
+ // Only perform optimistic update if we have valid transaction details
1009
+ if (amount !== '0' && parseFloat(amount) > 0) {
1010
+ console.log(tag, '💰 Performing optimistic balance update:', {
1011
+ caip,
1012
+ amount,
1013
+ fee,
1014
+ from,
1015
+ to
1016
+ });
1017
+
1018
+ // Debit the balance optimistically
1019
+ this.debitBalance(caip, amount, fee);
1020
+
1021
+ // Track pending transaction
1022
+ this.addPendingTransaction({
1023
+ txid,
1024
+ caip,
1025
+ amount,
1026
+ to,
1027
+ from,
1028
+ fee,
1029
+ broadcastTime: Date.now(),
1030
+ status: 'pending'
1031
+ });
1032
+ }
1033
+ }
1034
+
946
1035
  return txid;
947
1036
  } catch (e) {
948
1037
  console.error(e);
949
1038
  throw e;
950
1039
  }
951
1040
  };
1041
+
1042
+ // Add pending transaction to tracking array
1043
+ this.addPendingTransaction = function (txData: {
1044
+ txid: string;
1045
+ caip: string;
1046
+ amount: string;
1047
+ to: string;
1048
+ from: string;
1049
+ fee: string;
1050
+ broadcastTime: number;
1051
+ status: 'pending' | 'confirmed' | 'failed';
1052
+ }) {
1053
+ const tag = TAG + ' | addPendingTransaction | ';
1054
+ console.log(tag, 'Adding pending transaction:', txData);
1055
+
1056
+ // Add to pending transactions array (most recent first)
1057
+ this.pendingTransactions.unshift({
1058
+ ...txData,
1059
+ status: txData.status || 'pending',
1060
+ addedAt: Date.now()
1061
+ });
1062
+
1063
+ // Keep only last 50 pending transactions to avoid memory bloat
1064
+ if (this.pendingTransactions.length > 50) {
1065
+ this.pendingTransactions = this.pendingTransactions.slice(0, 50);
1066
+ }
1067
+
1068
+ // Emit events for UI updates
1069
+ this.events.emit('PENDING_TX_ADDED', txData);
1070
+ this.events.emit('PENDING_TXS_UPDATED', this.pendingTransactions);
1071
+
1072
+ console.log(tag, `Pending transactions count: ${this.pendingTransactions.length}`);
1073
+
1074
+ return txData;
1075
+ };
1076
+
1077
+ // Optimistically debit balance after broadcast (before confirmation)
1078
+ this.debitBalance = function (caip: string, amount: string, fee: string = '0') {
1079
+ const tag = TAG + ' | debitBalance | ';
1080
+ console.log(tag, 'Debiting balance:', { caip, amount, fee });
1081
+
1082
+ // Find the balance entry for this asset
1083
+ const balanceIndex = this.balances.findIndex((b: any) => b.caip === caip);
1084
+
1085
+ if (balanceIndex === -1) {
1086
+ console.warn(tag, 'Balance not found for CAIP:', caip);
1087
+ return null;
1088
+ }
1089
+
1090
+ const currentBalance = this.balances[balanceIndex];
1091
+ const currentBalanceNum = parseFloat(currentBalance.balance || '0');
1092
+ const amountNum = parseFloat(amount);
1093
+ const feeNum = parseFloat(fee);
1094
+ const totalDebit = amountNum + feeNum;
1095
+
1096
+ // Calculate new balance
1097
+ const newBalanceNum = currentBalanceNum - totalDebit;
1098
+
1099
+ // Safety check: don't allow negative balance
1100
+ if (newBalanceNum < 0) {
1101
+ console.warn(tag, 'Cannot debit - would result in negative balance:', {
1102
+ current: currentBalanceNum,
1103
+ debit: totalDebit,
1104
+ result: newBalanceNum
1105
+ });
1106
+ return null;
1107
+ }
1108
+
1109
+ // Update the balance with optimistic flag
1110
+ const updatedBalance = {
1111
+ ...currentBalance,
1112
+ balance: newBalanceNum.toFixed(8),
1113
+ valueUsd: newBalanceNum * (parseFloat(currentBalance.priceUsd) || 0),
1114
+ updated: Date.now(),
1115
+ optimistic: true, // Mark as optimistic update (will be replaced by real data on next sync)
1116
+ previousBalance: currentBalanceNum // Store previous balance for rollback if needed
1117
+ };
1118
+
1119
+ this.balances[balanceIndex] = updatedBalance;
1120
+
1121
+ console.log(tag, '✅ Balance updated:', {
1122
+ previous: currentBalanceNum.toFixed(8),
1123
+ new: newBalanceNum.toFixed(8),
1124
+ deducted: totalDebit.toFixed(8)
1125
+ });
1126
+
1127
+ // Emit balance update events
1128
+ this.events.emit('BALANCES_UPDATED', this.balances);
1129
+ this.events.emit('SET_BALANCES', this.balances);
1130
+
1131
+ // Rebuild dashboard with updated balances
1132
+ try {
1133
+ this.dashboard = this.buildDashboardFromBalances();
1134
+ this.events.emit('SET_DASHBOARD', this.dashboard);
1135
+ console.log(tag, '✅ Dashboard rebuilt with updated balance');
1136
+ } catch (dashboardError) {
1137
+ console.error(tag, 'Error rebuilding dashboard:', dashboardError);
1138
+ }
1139
+
1140
+ return updatedBalance;
1141
+ };
952
1142
  this.swap = async function (swapPayload) {
953
1143
  let tag = `${TAG} | swap | `;
954
1144
  try {
@@ -1622,6 +1812,14 @@ export class SDK {
1622
1812
  const assetQuery: { caip: string; pubkey: string }[] = [];
1623
1813
  for (const networkId of networkIds) {
1624
1814
  const pubkeys = findPubkeysForNetwork(this.pubkeys, networkId, this.paths, tag);
1815
+ console.log(`🔍 [DIAGNOSTIC] ${networkId}: Found ${pubkeys.length} pubkeys`);
1816
+ if (networkId.startsWith('bip122:000000000019')) {
1817
+ console.log('🔍 [BITCOIN DIAGNOSTIC] Bitcoin pubkeys:', pubkeys.map((p: any) => ({
1818
+ pubkey: p.pubkey?.substring(0, 20) + '...',
1819
+ networks: p.networks,
1820
+ symbol: p.symbol
1821
+ })));
1822
+ }
1625
1823
  const caip = await networkIdToCaip(networkId);
1626
1824
  assetQuery.push(...buildAssetQuery(pubkeys, caip));
1627
1825
  }
@@ -1653,8 +1851,36 @@ export class SDK {
1653
1851
 
1654
1852
  console.log(`⏱️ [PERF] Enrichment completed in ${(performance.now() - enrichStart).toFixed(0)}ms`);
1655
1853
 
1656
- // Update state and emit events
1657
- this.balances = balances;
1854
+ // PHASE 5: Normalize all CAIPs to lowercase for consistency
1855
+ balances.forEach((balance: any) => {
1856
+ if (balance.caip) balance.caip = balance.caip.toLowerCase();
1857
+ if (balance.networkId) balance.networkId = balance.networkId.toLowerCase();
1858
+ });
1859
+
1860
+ // CRITICAL: Deduplicate balances BEFORE setting
1861
+ // Merge new balances with existing balances, deduplicating by identifier (caip:pubkey)
1862
+ console.log(tag, `⚙️ Merging balances: ${balances.length} new + ${this.balances.length} existing`);
1863
+
1864
+ // Create a Map with identifier as key to deduplicate
1865
+ // Put NEW balances LAST so they override stale cached data
1866
+ const uniqueBalances = new Map(
1867
+ [...this.balances, ...balances].map((balance: any) => [
1868
+ balance.identifier || `${balance.caip}:${balance.pubkey}`,
1869
+ balance
1870
+ ])
1871
+ );
1872
+
1873
+ const beforeCount = [...this.balances, ...balances].length;
1874
+ this.balances = Array.from(uniqueBalances.values());
1875
+ const duplicatesRemoved = beforeCount - this.balances.length;
1876
+
1877
+ if (duplicatesRemoved > 0) {
1878
+ console.log(tag, `🔧 Removed ${duplicatesRemoved} duplicate balances`);
1879
+ }
1880
+
1881
+ console.log(tag, `✅ Final balance count: ${this.balances.length} unique balances`);
1882
+
1883
+ // Emit events
1658
1884
  this.events.emit('SET_BALANCES', this.balances);
1659
1885
 
1660
1886
  // Build and emit dashboard
@@ -1852,6 +2078,10 @@ export class SDK {
1852
2078
  }
1853
2079
 
1854
2080
  if (!asset.caip) throw Error('Invalid Asset! missing caip!');
2081
+
2082
+ // PHASE 6: Normalize CAIP to lowercase for consistency
2083
+ asset.caip = asset.caip.toLowerCase();
2084
+
1855
2085
  if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
1856
2086
 
1857
2087
  // Validate pubkeys for network (throws descriptive errors)
@@ -1902,7 +2132,7 @@ export class SDK {
1902
2132
  'bip122:12a765e31ffd4059bada1e25190f6e98': { address: 'https://blockchair.com/litecoin/address/', tx: 'https://blockchair.com/litecoin/transaction/' },
1903
2133
  'bip122:00000000001a91e3dace36e2be3bf030': { address: 'https://dogechain.info/address/', tx: 'https://dogechain.info/tx/' },
1904
2134
  'bip122:000000000000000000651ef99cb9fcbe': { address: 'https://blockchair.com/bitcoin-cash/address/', tx: 'https://blockchair.com/bitcoin-cash/transaction/' },
1905
- 'bip122:000007d91d1254d60e2dd1ae58038307': { address: 'https://chainz.cryptoid.info/dash/address.dws?', tx: 'https://chainz.cryptoid.info/dash/tx.dws?' },
2135
+ 'bip122:000007d91d1254d60e2dd1ae58038307': { address: 'https://explorer.dash.org/insight/address/', tx: 'https://explorer.dash.org/insight/tx/' },
1906
2136
  'bip122:4da631f2ac1bed857bd968c67c913978': { address: 'https://digiexplorer.info/address/', tx: 'https://digiexplorer.info/tx/' },
1907
2137
  'bip122:00040fe8ec8471911baa1db1266ea15d': { address: 'https://explorer.zcha.in/accounts/', tx: 'https://explorer.zcha.in/transactions/' },
1908
2138
  'cosmos:cosmoshub-4': { address: 'https://www.mintscan.io/cosmos/address/', tx: 'https://www.mintscan.io/cosmos/tx/' },
@@ -2109,6 +2339,10 @@ export class SDK {
2109
2339
  }
2110
2340
 
2111
2341
  if (!asset.caip) throw Error('Invalid Asset! missing caip!');
2342
+
2343
+ // PHASE 6: Normalize CAIP to lowercase for consistency
2344
+ asset.caip = asset.caip.toLowerCase();
2345
+
2112
2346
  if (!asset.networkId) asset.networkId = caipToNetworkId(asset.caip);
2113
2347
 
2114
2348
  console.log(tag, 'networkId:', asset.networkId);