@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/dist/index.cjs +58437 -123
- package/dist/index.es.js +46684 -2198
- package/dist/index.js +46684 -2198
- package/package.json +6 -6
- package/src/TransactionManager.ts +109 -11
- package/src/getPubkey.ts +29 -3
- package/src/index.ts +239 -5
- package/src/kkapi-batch-client.ts +97 -20
- package/src/supportedCaips.ts +6 -1
- package/src/txbuilder/createUnsignedSolanaTx.ts +132 -0
- package/src/txbuilder/createUnsignedTronTx.ts +97 -0
- package/src/utils/portfolio-helpers.ts +2 -2
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "highlander",
|
|
3
3
|
"name": "@pioneer-platform/pioneer-sdk",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.17.0",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"
|
|
7
|
-
"@pioneer-platform/pioneer-caip": "^9.
|
|
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.
|
|
10
|
-
"@pioneer-platform/pioneer-discovery": "^8.
|
|
11
|
-
"@pioneer-platform/pioneer-events": "^8.
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
77
|
-
|
|
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 '
|
|
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
|
-
//
|
|
1657
|
-
|
|
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://
|
|
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);
|