@pioneer-platform/pioneer-sdk 8.12.7 → 8.13.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 +90 -28
- package/dist/index.es.js +90 -28
- package/dist/index.js +90 -28
- package/package.json +1 -1
- package/src/index.ts +90 -33
- package/src/txbuilder/createUnsignedEvmTx.ts +51 -3
package/dist/index.cjs
CHANGED
|
@@ -1098,7 +1098,15 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1098
1098
|
amountWei = balance - gasFee - buffer;
|
|
1099
1099
|
console.log(tag, "isMax calculation - balance:", balance.toString(), "gasFee:", gasFee.toString(), "buffer:", buffer.toString(), "amountWei:", amountWei.toString());
|
|
1100
1100
|
} else {
|
|
1101
|
-
|
|
1101
|
+
const amountNum = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1102
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
1103
|
+
throw new Error(`Invalid amount for native gas transfer: "${amount}" (type: ${typeof amount}). ` + `Amount must be a valid positive number.`);
|
|
1104
|
+
}
|
|
1105
|
+
const amountCalculation = amountNum * 1000000000000000000;
|
|
1106
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
1107
|
+
throw new Error(`Invalid amount calculation for native gas transfer. ` + `Input: ${amountNum}`);
|
|
1108
|
+
}
|
|
1109
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
1102
1110
|
const totalNeeded = amountWei + gasFee;
|
|
1103
1111
|
if (totalNeeded > balance) {
|
|
1104
1112
|
const availableForSwap = balance > gasFee ? balance - gasFee : 0n;
|
|
@@ -1215,6 +1223,7 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1215
1223
|
}
|
|
1216
1224
|
unsignedTx = {
|
|
1217
1225
|
chainId,
|
|
1226
|
+
from: address,
|
|
1218
1227
|
nonce: toHex(nonce),
|
|
1219
1228
|
gas: toHex(gasLimit),
|
|
1220
1229
|
gasPrice: toHex(gasPrice),
|
|
@@ -1256,11 +1265,22 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1256
1265
|
const tokenBalance = BigInt(Math.round(tokenBalanceData.data * Number(tokenMultiplier)));
|
|
1257
1266
|
amountWei = tokenBalance;
|
|
1258
1267
|
} else {
|
|
1259
|
-
|
|
1268
|
+
const amountNum = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1269
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
1270
|
+
throw new Error(`Invalid amount for ERC20 transfer: "${amount}" (type: ${typeof amount}). ` + `Amount must be a valid positive number.`);
|
|
1271
|
+
}
|
|
1272
|
+
const amountCalculation = amountNum * Number(tokenMultiplier);
|
|
1273
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
1274
|
+
throw new Error(`Invalid amount calculation for ERC20 transfer. ` + `Input: ${amountNum}, Decimals: ${tokenDecimals}, Multiplier: ${tokenMultiplier}`);
|
|
1275
|
+
}
|
|
1276
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
1260
1277
|
console.log(tag, "Token amount calculation:", {
|
|
1261
1278
|
inputAmount: amount,
|
|
1279
|
+
inputType: typeof amount,
|
|
1280
|
+
parsedAmount: amountNum,
|
|
1262
1281
|
decimals: tokenDecimals,
|
|
1263
|
-
multiplier: tokenMultiplier,
|
|
1282
|
+
multiplier: tokenMultiplier.toString(),
|
|
1283
|
+
calculation: amountCalculation,
|
|
1264
1284
|
resultWei: amountWei.toString()
|
|
1265
1285
|
});
|
|
1266
1286
|
}
|
|
@@ -3422,6 +3442,9 @@ class SDK {
|
|
|
3422
3442
|
isPioneer;
|
|
3423
3443
|
keepkeyEndpoint;
|
|
3424
3444
|
forceLocalhost;
|
|
3445
|
+
viewOnlyMode;
|
|
3446
|
+
skipDevicePairing;
|
|
3447
|
+
skipKeeperEndpoint;
|
|
3425
3448
|
getPubkeys;
|
|
3426
3449
|
getBalances;
|
|
3427
3450
|
blockchains;
|
|
@@ -3478,6 +3501,9 @@ class SDK {
|
|
|
3478
3501
|
this.keepkeyEndpoint = null;
|
|
3479
3502
|
this.forceLocalhost = config.forceLocalhost || false;
|
|
3480
3503
|
this.paths = config.paths || [];
|
|
3504
|
+
this.viewOnlyMode = config.viewOnlyMode || false;
|
|
3505
|
+
this.skipDevicePairing = config.skipDevicePairing || config.viewOnlyMode || false;
|
|
3506
|
+
this.skipKeeperEndpoint = config.skipKeeperEndpoint || config.viewOnlyMode || false;
|
|
3481
3507
|
this.blockchains = config.blockchains ? [...new Set(config.blockchains)] : [];
|
|
3482
3508
|
if (config.blockchains && config.blockchains.length !== this.blockchains.length) {
|
|
3483
3509
|
}
|
|
@@ -3518,7 +3544,20 @@ class SDK {
|
|
|
3518
3544
|
fallbackToRemote: true
|
|
3519
3545
|
}) : null;
|
|
3520
3546
|
this.pairWallet = async (options) => {
|
|
3521
|
-
|
|
3547
|
+
const tag = TAG9 + " | pairWallet | ";
|
|
3548
|
+
if (this.viewOnlyMode || this.skipDevicePairing) {
|
|
3549
|
+
console.log(tag, "\uD83D\uDC41️ [VIEW-ONLY] Skipping device pairing");
|
|
3550
|
+
return {
|
|
3551
|
+
success: false,
|
|
3552
|
+
reason: "view-only-mode",
|
|
3553
|
+
message: "Device pairing skipped in view-only mode"
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
return Promise.resolve({
|
|
3557
|
+
success: false,
|
|
3558
|
+
reason: "not-implemented",
|
|
3559
|
+
message: "Device pairing not yet implemented"
|
|
3560
|
+
});
|
|
3522
3561
|
};
|
|
3523
3562
|
this.getPubkeyKey = (pubkey) => {
|
|
3524
3563
|
return `${pubkey.pubkey}_${pubkey.pathMaster}`;
|
|
@@ -3564,6 +3603,15 @@ class SDK {
|
|
|
3564
3603
|
this.pubkeys = [];
|
|
3565
3604
|
this.pubkeySet.clear();
|
|
3566
3605
|
}
|
|
3606
|
+
this.isViewOnlyMode = () => {
|
|
3607
|
+
return this.viewOnlyMode;
|
|
3608
|
+
};
|
|
3609
|
+
this.canSignTransactions = () => {
|
|
3610
|
+
return !this.viewOnlyMode && !!this.keepKeySdk;
|
|
3611
|
+
};
|
|
3612
|
+
this.isVaultAvailable = () => {
|
|
3613
|
+
return !!this.keepkeyEndpoint && this.keepkeyEndpoint.isAvailable;
|
|
3614
|
+
};
|
|
3567
3615
|
this.getUnifiedPortfolio = async function() {
|
|
3568
3616
|
const tag = `${TAG9} | getUnifiedPortfolio | `;
|
|
3569
3617
|
try {
|
|
@@ -3714,26 +3762,35 @@ class SDK {
|
|
|
3714
3762
|
throw Error("Failed to init pioneer server!");
|
|
3715
3763
|
this.paths.concat(import_pioneer_coins4.getPaths(this.blockchains));
|
|
3716
3764
|
await this.getGasAssets();
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3765
|
+
if (!this.skipKeeperEndpoint) {
|
|
3766
|
+
this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
|
|
3767
|
+
const keepkeyEndpoint = this.keepkeyEndpoint;
|
|
3768
|
+
if (!this.skipDevicePairing) {
|
|
3769
|
+
try {
|
|
3770
|
+
const configKeepKey = {
|
|
3771
|
+
apiKey: this.keepkeyApiKey || "keepkey-api-key",
|
|
3772
|
+
pairingInfo: {
|
|
3773
|
+
name: "KeepKey SDK Demo App",
|
|
3774
|
+
imageUrl: "https://pioneers.dev/coins/keepkey.png",
|
|
3775
|
+
basePath: keepkeyEndpoint.basePath,
|
|
3776
|
+
url: keepkeyEndpoint.baseUrl
|
|
3777
|
+
}
|
|
3778
|
+
};
|
|
3779
|
+
console.log("\uD83D\uDD11 [INIT] Initializing KeepKey SDK...");
|
|
3780
|
+
const keepKeySdk = await import_keepkey_sdk.KeepKeySdk.create(configKeepKey);
|
|
3781
|
+
const features = await keepKeySdk.system.info.getFeatures();
|
|
3782
|
+
this.keepkeyApiKey = configKeepKey.apiKey;
|
|
3783
|
+
this.keepKeySdk = keepKeySdk;
|
|
3784
|
+
this.context = "keepkey:" + features.label + ".json";
|
|
3785
|
+
} catch (e) {
|
|
3786
|
+
console.error("⚠️ [INIT] KeepKey SDK initialization failed:", e);
|
|
3727
3787
|
}
|
|
3728
|
-
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
this.
|
|
3734
|
-
this.context = "keepkey:" + features.label + ".json";
|
|
3735
|
-
} catch (e) {
|
|
3736
|
-
console.error("⚠️ [INIT] KeepKey SDK initialization failed:", e);
|
|
3788
|
+
} else {
|
|
3789
|
+
console.log("\uD83D\uDC41️ [VIEW-ONLY] Skipping KeepKey SDK initialization");
|
|
3790
|
+
}
|
|
3791
|
+
} else {
|
|
3792
|
+
console.log("\uD83D\uDC41️ [VIEW-ONLY] Skipping vault endpoint detection");
|
|
3793
|
+
this.keepkeyEndpoint = null;
|
|
3737
3794
|
}
|
|
3738
3795
|
let configWss = {
|
|
3739
3796
|
username: this.username,
|
|
@@ -4763,11 +4820,16 @@ class SDK {
|
|
|
4763
4820
|
}
|
|
4764
4821
|
}
|
|
4765
4822
|
console.log(tag, `Calling batch GetCharts with ${pubkeysForBatch.length} pubkeys`);
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4823
|
+
let newBalances = [];
|
|
4824
|
+
try {
|
|
4825
|
+
const chartsResponse = await this.pioneer.GetCharts({
|
|
4826
|
+
pubkeys: pubkeysForBatch
|
|
4827
|
+
});
|
|
4828
|
+
newBalances = chartsResponse?.data?.balances || [];
|
|
4829
|
+
console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
|
|
4830
|
+
} catch (chartsError) {
|
|
4831
|
+
console.warn(tag, `GetCharts API not available (${chartsError.message}), continuing without charts data`);
|
|
4832
|
+
}
|
|
4771
4833
|
const uniqueBalances = new Map([...this.balances, ...newBalances].map((balance) => [
|
|
4772
4834
|
balance.identifier || `${balance.caip}:${balance.pubkey}`,
|
|
4773
4835
|
{
|
package/dist/index.es.js
CHANGED
|
@@ -1274,7 +1274,15 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1274
1274
|
amountWei = balance - gasFee - buffer;
|
|
1275
1275
|
console.log(tag, "isMax calculation - balance:", balance.toString(), "gasFee:", gasFee.toString(), "buffer:", buffer.toString(), "amountWei:", amountWei.toString());
|
|
1276
1276
|
} else {
|
|
1277
|
-
|
|
1277
|
+
const amountNum = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1278
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
1279
|
+
throw new Error(`Invalid amount for native gas transfer: "${amount}" (type: ${typeof amount}). ` + `Amount must be a valid positive number.`);
|
|
1280
|
+
}
|
|
1281
|
+
const amountCalculation = amountNum * 1000000000000000000;
|
|
1282
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
1283
|
+
throw new Error(`Invalid amount calculation for native gas transfer. ` + `Input: ${amountNum}`);
|
|
1284
|
+
}
|
|
1285
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
1278
1286
|
const totalNeeded = amountWei + gasFee;
|
|
1279
1287
|
if (totalNeeded > balance) {
|
|
1280
1288
|
const availableForSwap = balance > gasFee ? balance - gasFee : 0n;
|
|
@@ -1391,6 +1399,7 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1391
1399
|
}
|
|
1392
1400
|
unsignedTx = {
|
|
1393
1401
|
chainId,
|
|
1402
|
+
from: address,
|
|
1394
1403
|
nonce: toHex(nonce),
|
|
1395
1404
|
gas: toHex(gasLimit),
|
|
1396
1405
|
gasPrice: toHex(gasPrice),
|
|
@@ -1432,11 +1441,22 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1432
1441
|
const tokenBalance = BigInt(Math.round(tokenBalanceData.data * Number(tokenMultiplier)));
|
|
1433
1442
|
amountWei = tokenBalance;
|
|
1434
1443
|
} else {
|
|
1435
|
-
|
|
1444
|
+
const amountNum = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1445
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
1446
|
+
throw new Error(`Invalid amount for ERC20 transfer: "${amount}" (type: ${typeof amount}). ` + `Amount must be a valid positive number.`);
|
|
1447
|
+
}
|
|
1448
|
+
const amountCalculation = amountNum * Number(tokenMultiplier);
|
|
1449
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
1450
|
+
throw new Error(`Invalid amount calculation for ERC20 transfer. ` + `Input: ${amountNum}, Decimals: ${tokenDecimals}, Multiplier: ${tokenMultiplier}`);
|
|
1451
|
+
}
|
|
1452
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
1436
1453
|
console.log(tag, "Token amount calculation:", {
|
|
1437
1454
|
inputAmount: amount,
|
|
1455
|
+
inputType: typeof amount,
|
|
1456
|
+
parsedAmount: amountNum,
|
|
1438
1457
|
decimals: tokenDecimals,
|
|
1439
|
-
multiplier: tokenMultiplier,
|
|
1458
|
+
multiplier: tokenMultiplier.toString(),
|
|
1459
|
+
calculation: amountCalculation,
|
|
1440
1460
|
resultWei: amountWei.toString()
|
|
1441
1461
|
});
|
|
1442
1462
|
}
|
|
@@ -3598,6 +3618,9 @@ class SDK {
|
|
|
3598
3618
|
isPioneer;
|
|
3599
3619
|
keepkeyEndpoint;
|
|
3600
3620
|
forceLocalhost;
|
|
3621
|
+
viewOnlyMode;
|
|
3622
|
+
skipDevicePairing;
|
|
3623
|
+
skipKeeperEndpoint;
|
|
3601
3624
|
getPubkeys;
|
|
3602
3625
|
getBalances;
|
|
3603
3626
|
blockchains;
|
|
@@ -3654,6 +3677,9 @@ class SDK {
|
|
|
3654
3677
|
this.keepkeyEndpoint = null;
|
|
3655
3678
|
this.forceLocalhost = config.forceLocalhost || false;
|
|
3656
3679
|
this.paths = config.paths || [];
|
|
3680
|
+
this.viewOnlyMode = config.viewOnlyMode || false;
|
|
3681
|
+
this.skipDevicePairing = config.skipDevicePairing || config.viewOnlyMode || false;
|
|
3682
|
+
this.skipKeeperEndpoint = config.skipKeeperEndpoint || config.viewOnlyMode || false;
|
|
3657
3683
|
this.blockchains = config.blockchains ? [...new Set(config.blockchains)] : [];
|
|
3658
3684
|
if (config.blockchains && config.blockchains.length !== this.blockchains.length) {
|
|
3659
3685
|
}
|
|
@@ -3694,7 +3720,20 @@ class SDK {
|
|
|
3694
3720
|
fallbackToRemote: true
|
|
3695
3721
|
}) : null;
|
|
3696
3722
|
this.pairWallet = async (options) => {
|
|
3697
|
-
|
|
3723
|
+
const tag = TAG9 + " | pairWallet | ";
|
|
3724
|
+
if (this.viewOnlyMode || this.skipDevicePairing) {
|
|
3725
|
+
console.log(tag, "\uD83D\uDC41️ [VIEW-ONLY] Skipping device pairing");
|
|
3726
|
+
return {
|
|
3727
|
+
success: false,
|
|
3728
|
+
reason: "view-only-mode",
|
|
3729
|
+
message: "Device pairing skipped in view-only mode"
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
return Promise.resolve({
|
|
3733
|
+
success: false,
|
|
3734
|
+
reason: "not-implemented",
|
|
3735
|
+
message: "Device pairing not yet implemented"
|
|
3736
|
+
});
|
|
3698
3737
|
};
|
|
3699
3738
|
this.getPubkeyKey = (pubkey) => {
|
|
3700
3739
|
return `${pubkey.pubkey}_${pubkey.pathMaster}`;
|
|
@@ -3740,6 +3779,15 @@ class SDK {
|
|
|
3740
3779
|
this.pubkeys = [];
|
|
3741
3780
|
this.pubkeySet.clear();
|
|
3742
3781
|
}
|
|
3782
|
+
this.isViewOnlyMode = () => {
|
|
3783
|
+
return this.viewOnlyMode;
|
|
3784
|
+
};
|
|
3785
|
+
this.canSignTransactions = () => {
|
|
3786
|
+
return !this.viewOnlyMode && !!this.keepKeySdk;
|
|
3787
|
+
};
|
|
3788
|
+
this.isVaultAvailable = () => {
|
|
3789
|
+
return !!this.keepkeyEndpoint && this.keepkeyEndpoint.isAvailable;
|
|
3790
|
+
};
|
|
3743
3791
|
this.getUnifiedPortfolio = async function() {
|
|
3744
3792
|
const tag = `${TAG9} | getUnifiedPortfolio | `;
|
|
3745
3793
|
try {
|
|
@@ -3890,26 +3938,35 @@ class SDK {
|
|
|
3890
3938
|
throw Error("Failed to init pioneer server!");
|
|
3891
3939
|
this.paths.concat(getPaths(this.blockchains));
|
|
3892
3940
|
await this.getGasAssets();
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3941
|
+
if (!this.skipKeeperEndpoint) {
|
|
3942
|
+
this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
|
|
3943
|
+
const keepkeyEndpoint = this.keepkeyEndpoint;
|
|
3944
|
+
if (!this.skipDevicePairing) {
|
|
3945
|
+
try {
|
|
3946
|
+
const configKeepKey = {
|
|
3947
|
+
apiKey: this.keepkeyApiKey || "keepkey-api-key",
|
|
3948
|
+
pairingInfo: {
|
|
3949
|
+
name: "KeepKey SDK Demo App",
|
|
3950
|
+
imageUrl: "https://pioneers.dev/coins/keepkey.png",
|
|
3951
|
+
basePath: keepkeyEndpoint.basePath,
|
|
3952
|
+
url: keepkeyEndpoint.baseUrl
|
|
3953
|
+
}
|
|
3954
|
+
};
|
|
3955
|
+
console.log("\uD83D\uDD11 [INIT] Initializing KeepKey SDK...");
|
|
3956
|
+
const keepKeySdk = await KeepKeySdk.create(configKeepKey);
|
|
3957
|
+
const features = await keepKeySdk.system.info.getFeatures();
|
|
3958
|
+
this.keepkeyApiKey = configKeepKey.apiKey;
|
|
3959
|
+
this.keepKeySdk = keepKeySdk;
|
|
3960
|
+
this.context = "keepkey:" + features.label + ".json";
|
|
3961
|
+
} catch (e) {
|
|
3962
|
+
console.error("⚠️ [INIT] KeepKey SDK initialization failed:", e);
|
|
3903
3963
|
}
|
|
3904
|
-
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
this.
|
|
3910
|
-
this.context = "keepkey:" + features.label + ".json";
|
|
3911
|
-
} catch (e) {
|
|
3912
|
-
console.error("⚠️ [INIT] KeepKey SDK initialization failed:", e);
|
|
3964
|
+
} else {
|
|
3965
|
+
console.log("\uD83D\uDC41️ [VIEW-ONLY] Skipping KeepKey SDK initialization");
|
|
3966
|
+
}
|
|
3967
|
+
} else {
|
|
3968
|
+
console.log("\uD83D\uDC41️ [VIEW-ONLY] Skipping vault endpoint detection");
|
|
3969
|
+
this.keepkeyEndpoint = null;
|
|
3913
3970
|
}
|
|
3914
3971
|
let configWss = {
|
|
3915
3972
|
username: this.username,
|
|
@@ -4939,11 +4996,16 @@ class SDK {
|
|
|
4939
4996
|
}
|
|
4940
4997
|
}
|
|
4941
4998
|
console.log(tag, `Calling batch GetCharts with ${pubkeysForBatch.length} pubkeys`);
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4999
|
+
let newBalances = [];
|
|
5000
|
+
try {
|
|
5001
|
+
const chartsResponse = await this.pioneer.GetCharts({
|
|
5002
|
+
pubkeys: pubkeysForBatch
|
|
5003
|
+
});
|
|
5004
|
+
newBalances = chartsResponse?.data?.balances || [];
|
|
5005
|
+
console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
|
|
5006
|
+
} catch (chartsError) {
|
|
5007
|
+
console.warn(tag, `GetCharts API not available (${chartsError.message}), continuing without charts data`);
|
|
5008
|
+
}
|
|
4947
5009
|
const uniqueBalances = new Map([...this.balances, ...newBalances].map((balance) => [
|
|
4948
5010
|
balance.identifier || `${balance.caip}:${balance.pubkey}`,
|
|
4949
5011
|
{
|
package/dist/index.js
CHANGED
|
@@ -1274,7 +1274,15 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1274
1274
|
amountWei = balance - gasFee - buffer;
|
|
1275
1275
|
console.log(tag, "isMax calculation - balance:", balance.toString(), "gasFee:", gasFee.toString(), "buffer:", buffer.toString(), "amountWei:", amountWei.toString());
|
|
1276
1276
|
} else {
|
|
1277
|
-
|
|
1277
|
+
const amountNum = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1278
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
1279
|
+
throw new Error(`Invalid amount for native gas transfer: "${amount}" (type: ${typeof amount}). ` + `Amount must be a valid positive number.`);
|
|
1280
|
+
}
|
|
1281
|
+
const amountCalculation = amountNum * 1000000000000000000;
|
|
1282
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
1283
|
+
throw new Error(`Invalid amount calculation for native gas transfer. ` + `Input: ${amountNum}`);
|
|
1284
|
+
}
|
|
1285
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
1278
1286
|
const totalNeeded = amountWei + gasFee;
|
|
1279
1287
|
if (totalNeeded > balance) {
|
|
1280
1288
|
const availableForSwap = balance > gasFee ? balance - gasFee : 0n;
|
|
@@ -1391,6 +1399,7 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1391
1399
|
}
|
|
1392
1400
|
unsignedTx = {
|
|
1393
1401
|
chainId,
|
|
1402
|
+
from: address,
|
|
1394
1403
|
nonce: toHex(nonce),
|
|
1395
1404
|
gas: toHex(gasLimit),
|
|
1396
1405
|
gasPrice: toHex(gasPrice),
|
|
@@ -1432,11 +1441,22 @@ async function createUnsignedEvmTx(caip, to, amount, memo, pubkeys, pioneer, pub
|
|
|
1432
1441
|
const tokenBalance = BigInt(Math.round(tokenBalanceData.data * Number(tokenMultiplier)));
|
|
1433
1442
|
amountWei = tokenBalance;
|
|
1434
1443
|
} else {
|
|
1435
|
-
|
|
1444
|
+
const amountNum = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
1445
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
1446
|
+
throw new Error(`Invalid amount for ERC20 transfer: "${amount}" (type: ${typeof amount}). ` + `Amount must be a valid positive number.`);
|
|
1447
|
+
}
|
|
1448
|
+
const amountCalculation = amountNum * Number(tokenMultiplier);
|
|
1449
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
1450
|
+
throw new Error(`Invalid amount calculation for ERC20 transfer. ` + `Input: ${amountNum}, Decimals: ${tokenDecimals}, Multiplier: ${tokenMultiplier}`);
|
|
1451
|
+
}
|
|
1452
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
1436
1453
|
console.log(tag, "Token amount calculation:", {
|
|
1437
1454
|
inputAmount: amount,
|
|
1455
|
+
inputType: typeof amount,
|
|
1456
|
+
parsedAmount: amountNum,
|
|
1438
1457
|
decimals: tokenDecimals,
|
|
1439
|
-
multiplier: tokenMultiplier,
|
|
1458
|
+
multiplier: tokenMultiplier.toString(),
|
|
1459
|
+
calculation: amountCalculation,
|
|
1440
1460
|
resultWei: amountWei.toString()
|
|
1441
1461
|
});
|
|
1442
1462
|
}
|
|
@@ -3598,6 +3618,9 @@ class SDK {
|
|
|
3598
3618
|
isPioneer;
|
|
3599
3619
|
keepkeyEndpoint;
|
|
3600
3620
|
forceLocalhost;
|
|
3621
|
+
viewOnlyMode;
|
|
3622
|
+
skipDevicePairing;
|
|
3623
|
+
skipKeeperEndpoint;
|
|
3601
3624
|
getPubkeys;
|
|
3602
3625
|
getBalances;
|
|
3603
3626
|
blockchains;
|
|
@@ -3654,6 +3677,9 @@ class SDK {
|
|
|
3654
3677
|
this.keepkeyEndpoint = null;
|
|
3655
3678
|
this.forceLocalhost = config.forceLocalhost || false;
|
|
3656
3679
|
this.paths = config.paths || [];
|
|
3680
|
+
this.viewOnlyMode = config.viewOnlyMode || false;
|
|
3681
|
+
this.skipDevicePairing = config.skipDevicePairing || config.viewOnlyMode || false;
|
|
3682
|
+
this.skipKeeperEndpoint = config.skipKeeperEndpoint || config.viewOnlyMode || false;
|
|
3657
3683
|
this.blockchains = config.blockchains ? [...new Set(config.blockchains)] : [];
|
|
3658
3684
|
if (config.blockchains && config.blockchains.length !== this.blockchains.length) {
|
|
3659
3685
|
}
|
|
@@ -3694,7 +3720,20 @@ class SDK {
|
|
|
3694
3720
|
fallbackToRemote: true
|
|
3695
3721
|
}) : null;
|
|
3696
3722
|
this.pairWallet = async (options) => {
|
|
3697
|
-
|
|
3723
|
+
const tag = TAG9 + " | pairWallet | ";
|
|
3724
|
+
if (this.viewOnlyMode || this.skipDevicePairing) {
|
|
3725
|
+
console.log(tag, "\uD83D\uDC41️ [VIEW-ONLY] Skipping device pairing");
|
|
3726
|
+
return {
|
|
3727
|
+
success: false,
|
|
3728
|
+
reason: "view-only-mode",
|
|
3729
|
+
message: "Device pairing skipped in view-only mode"
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
return Promise.resolve({
|
|
3733
|
+
success: false,
|
|
3734
|
+
reason: "not-implemented",
|
|
3735
|
+
message: "Device pairing not yet implemented"
|
|
3736
|
+
});
|
|
3698
3737
|
};
|
|
3699
3738
|
this.getPubkeyKey = (pubkey) => {
|
|
3700
3739
|
return `${pubkey.pubkey}_${pubkey.pathMaster}`;
|
|
@@ -3740,6 +3779,15 @@ class SDK {
|
|
|
3740
3779
|
this.pubkeys = [];
|
|
3741
3780
|
this.pubkeySet.clear();
|
|
3742
3781
|
}
|
|
3782
|
+
this.isViewOnlyMode = () => {
|
|
3783
|
+
return this.viewOnlyMode;
|
|
3784
|
+
};
|
|
3785
|
+
this.canSignTransactions = () => {
|
|
3786
|
+
return !this.viewOnlyMode && !!this.keepKeySdk;
|
|
3787
|
+
};
|
|
3788
|
+
this.isVaultAvailable = () => {
|
|
3789
|
+
return !!this.keepkeyEndpoint && this.keepkeyEndpoint.isAvailable;
|
|
3790
|
+
};
|
|
3743
3791
|
this.getUnifiedPortfolio = async function() {
|
|
3744
3792
|
const tag = `${TAG9} | getUnifiedPortfolio | `;
|
|
3745
3793
|
try {
|
|
@@ -3890,26 +3938,35 @@ class SDK {
|
|
|
3890
3938
|
throw Error("Failed to init pioneer server!");
|
|
3891
3939
|
this.paths.concat(getPaths(this.blockchains));
|
|
3892
3940
|
await this.getGasAssets();
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3941
|
+
if (!this.skipKeeperEndpoint) {
|
|
3942
|
+
this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
|
|
3943
|
+
const keepkeyEndpoint = this.keepkeyEndpoint;
|
|
3944
|
+
if (!this.skipDevicePairing) {
|
|
3945
|
+
try {
|
|
3946
|
+
const configKeepKey = {
|
|
3947
|
+
apiKey: this.keepkeyApiKey || "keepkey-api-key",
|
|
3948
|
+
pairingInfo: {
|
|
3949
|
+
name: "KeepKey SDK Demo App",
|
|
3950
|
+
imageUrl: "https://pioneers.dev/coins/keepkey.png",
|
|
3951
|
+
basePath: keepkeyEndpoint.basePath,
|
|
3952
|
+
url: keepkeyEndpoint.baseUrl
|
|
3953
|
+
}
|
|
3954
|
+
};
|
|
3955
|
+
console.log("\uD83D\uDD11 [INIT] Initializing KeepKey SDK...");
|
|
3956
|
+
const keepKeySdk = await KeepKeySdk.create(configKeepKey);
|
|
3957
|
+
const features = await keepKeySdk.system.info.getFeatures();
|
|
3958
|
+
this.keepkeyApiKey = configKeepKey.apiKey;
|
|
3959
|
+
this.keepKeySdk = keepKeySdk;
|
|
3960
|
+
this.context = "keepkey:" + features.label + ".json";
|
|
3961
|
+
} catch (e) {
|
|
3962
|
+
console.error("⚠️ [INIT] KeepKey SDK initialization failed:", e);
|
|
3903
3963
|
}
|
|
3904
|
-
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
this.
|
|
3910
|
-
this.context = "keepkey:" + features.label + ".json";
|
|
3911
|
-
} catch (e) {
|
|
3912
|
-
console.error("⚠️ [INIT] KeepKey SDK initialization failed:", e);
|
|
3964
|
+
} else {
|
|
3965
|
+
console.log("\uD83D\uDC41️ [VIEW-ONLY] Skipping KeepKey SDK initialization");
|
|
3966
|
+
}
|
|
3967
|
+
} else {
|
|
3968
|
+
console.log("\uD83D\uDC41️ [VIEW-ONLY] Skipping vault endpoint detection");
|
|
3969
|
+
this.keepkeyEndpoint = null;
|
|
3913
3970
|
}
|
|
3914
3971
|
let configWss = {
|
|
3915
3972
|
username: this.username,
|
|
@@ -4939,11 +4996,16 @@ class SDK {
|
|
|
4939
4996
|
}
|
|
4940
4997
|
}
|
|
4941
4998
|
console.log(tag, `Calling batch GetCharts with ${pubkeysForBatch.length} pubkeys`);
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4999
|
+
let newBalances = [];
|
|
5000
|
+
try {
|
|
5001
|
+
const chartsResponse = await this.pioneer.GetCharts({
|
|
5002
|
+
pubkeys: pubkeysForBatch
|
|
5003
|
+
});
|
|
5004
|
+
newBalances = chartsResponse?.data?.balances || [];
|
|
5005
|
+
console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
|
|
5006
|
+
} catch (chartsError) {
|
|
5007
|
+
console.warn(tag, `GetCharts API not available (${chartsError.message}), continuing without charts data`);
|
|
5008
|
+
}
|
|
4947
5009
|
const uniqueBalances = new Map([...this.balances, ...newBalances].map((balance) => [
|
|
4948
5010
|
balance.identifier || `${balance.caip}:${balance.pubkey}`,
|
|
4949
5011
|
{
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -76,6 +76,10 @@ export interface PioneerSDKConfig {
|
|
|
76
76
|
offlineFirst?: boolean;
|
|
77
77
|
vaultUrl?: string;
|
|
78
78
|
forceLocalhost?: boolean;
|
|
79
|
+
// View-only mode configuration
|
|
80
|
+
viewOnlyMode?: boolean; // Enable view-only mode (no device/vault required)
|
|
81
|
+
skipDevicePairing?: boolean; // Skip KeepKey device pairing
|
|
82
|
+
skipKeeperEndpoint?: boolean; // Skip vault endpoint detection
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
export interface SyncState {
|
|
@@ -140,6 +144,10 @@ export class SDK {
|
|
|
140
144
|
public isPioneer: string | null;
|
|
141
145
|
public keepkeyEndpoint: { isAvailable: boolean; baseUrl: string; basePath: string } | null;
|
|
142
146
|
public forceLocalhost: boolean;
|
|
147
|
+
// View-only mode properties
|
|
148
|
+
public viewOnlyMode: boolean;
|
|
149
|
+
public skipDevicePairing: boolean;
|
|
150
|
+
public skipKeeperEndpoint: boolean;
|
|
143
151
|
// public loadPubkeyCache: (pubkeys: any) => Promise<void>;
|
|
144
152
|
public getPubkeys: (wallets?: string[]) => Promise<any[]>;
|
|
145
153
|
public getBalances: (filter?: any) => Promise<any[]>;
|
|
@@ -229,6 +237,11 @@ export class SDK {
|
|
|
229
237
|
this.forceLocalhost = config.forceLocalhost || false;
|
|
230
238
|
this.paths = config.paths || [];
|
|
231
239
|
|
|
240
|
+
// View-only mode initialization
|
|
241
|
+
this.viewOnlyMode = config.viewOnlyMode || false;
|
|
242
|
+
this.skipDevicePairing = config.skipDevicePairing || config.viewOnlyMode || false;
|
|
243
|
+
this.skipKeeperEndpoint = config.skipKeeperEndpoint || config.viewOnlyMode || false;
|
|
244
|
+
|
|
232
245
|
// Deduplicate blockchains to prevent duplicate dashboard calculations
|
|
233
246
|
this.blockchains = config.blockchains ? [...new Set(config.blockchains)] : [];
|
|
234
247
|
if (config.blockchains && config.blockchains.length !== this.blockchains.length) {
|
|
@@ -278,8 +291,25 @@ export class SDK {
|
|
|
278
291
|
: null;
|
|
279
292
|
|
|
280
293
|
this.pairWallet = async (options: any) => {
|
|
281
|
-
|
|
282
|
-
|
|
294
|
+
const tag = TAG + ' | pairWallet | ';
|
|
295
|
+
|
|
296
|
+
// Skip device pairing in view-only mode
|
|
297
|
+
if (this.viewOnlyMode || this.skipDevicePairing) {
|
|
298
|
+
console.log(tag, '👁️ [VIEW-ONLY] Skipping device pairing');
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
reason: 'view-only-mode',
|
|
302
|
+
message: 'Device pairing skipped in view-only mode'
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Normal device pairing logic would go here
|
|
307
|
+
// For now, return placeholder
|
|
308
|
+
return Promise.resolve({
|
|
309
|
+
success: false,
|
|
310
|
+
reason: 'not-implemented',
|
|
311
|
+
message: 'Device pairing not yet implemented'
|
|
312
|
+
});
|
|
283
313
|
};
|
|
284
314
|
|
|
285
315
|
// Helper method to generate unique key for a pubkey
|
|
@@ -347,6 +377,19 @@ export class SDK {
|
|
|
347
377
|
this.pubkeySet.clear();
|
|
348
378
|
}
|
|
349
379
|
|
|
380
|
+
// View-only mode helper methods
|
|
381
|
+
this.isViewOnlyMode = (): boolean => {
|
|
382
|
+
return this.viewOnlyMode;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
this.canSignTransactions = (): boolean => {
|
|
386
|
+
return !this.viewOnlyMode && !!this.keepKeySdk;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
this.isVaultAvailable = (): boolean => {
|
|
390
|
+
return !!this.keepkeyEndpoint && this.keepkeyEndpoint.isAvailable;
|
|
391
|
+
};
|
|
392
|
+
|
|
350
393
|
// Fast portfolio loading from kkapi:// cache
|
|
351
394
|
this.getUnifiedPortfolio = async function () {
|
|
352
395
|
const tag = `${TAG} | getUnifiedPortfolio | `;
|
|
@@ -558,31 +601,40 @@ export class SDK {
|
|
|
558
601
|
// Get gas assets (needed for asset map)
|
|
559
602
|
await this.getGasAssets();
|
|
560
603
|
|
|
561
|
-
// Detect KeepKey endpoint
|
|
562
|
-
|
|
563
|
-
|
|
604
|
+
// Detect KeepKey endpoint (skip if view-only mode)
|
|
605
|
+
if (!this.skipKeeperEndpoint) {
|
|
606
|
+
this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
|
|
607
|
+
const keepkeyEndpoint = this.keepkeyEndpoint;
|
|
564
608
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
609
|
+
// Initialize KeepKey SDK if available and not skipping device pairing
|
|
610
|
+
if (!this.skipDevicePairing) {
|
|
611
|
+
try {
|
|
612
|
+
const configKeepKey = {
|
|
613
|
+
apiKey: this.keepkeyApiKey || 'keepkey-api-key',
|
|
614
|
+
pairingInfo: {
|
|
615
|
+
name: 'KeepKey SDK Demo App',
|
|
616
|
+
imageUrl: 'https://pioneers.dev/coins/keepkey.png',
|
|
617
|
+
basePath: keepkeyEndpoint.basePath,
|
|
618
|
+
url: keepkeyEndpoint.baseUrl,
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
console.log('🔑 [INIT] Initializing KeepKey SDK...');
|
|
623
|
+
const keepKeySdk = await KeepKeySdk.create(configKeepKey);
|
|
624
|
+
const features = await keepKeySdk.system.info.getFeatures();
|
|
625
|
+
|
|
626
|
+
this.keepkeyApiKey = configKeepKey.apiKey;
|
|
627
|
+
this.keepKeySdk = keepKeySdk;
|
|
628
|
+
this.context = 'keepkey:' + features.label + '.json';
|
|
629
|
+
} catch (e) {
|
|
630
|
+
console.error('⚠️ [INIT] KeepKey SDK initialization failed:', e);
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
console.log('👁️ [VIEW-ONLY] Skipping KeepKey SDK initialization');
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
console.log('👁️ [VIEW-ONLY] Skipping vault endpoint detection');
|
|
637
|
+
this.keepkeyEndpoint = null;
|
|
586
638
|
}
|
|
587
639
|
|
|
588
640
|
// Initialize WebSocket events
|
|
@@ -1934,7 +1986,7 @@ export class SDK {
|
|
|
1934
1986
|
console.time('GetPortfolioBalances Response Time');
|
|
1935
1987
|
|
|
1936
1988
|
try {
|
|
1937
|
-
//
|
|
1989
|
+
// Wrap assetQuery in object with pubkeys field (API expects { pubkeys: [...] })
|
|
1938
1990
|
let marketInfo = await this.pioneer.GetPortfolioBalances({ pubkeys: assetQuery });
|
|
1939
1991
|
const apiCallTime = performance.now() - apiCallStart;
|
|
1940
1992
|
console.timeEnd('GetPortfolioBalances Response Time');
|
|
@@ -2088,12 +2140,17 @@ export class SDK {
|
|
|
2088
2140
|
console.log(tag, `Calling batch GetCharts with ${pubkeysForBatch.length} pubkeys`);
|
|
2089
2141
|
|
|
2090
2142
|
// Single batch call to get ALL charts data
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2143
|
+
let newBalances = [];
|
|
2144
|
+
try {
|
|
2145
|
+
const chartsResponse = await this.pioneer.GetCharts({
|
|
2146
|
+
pubkeys: pubkeysForBatch
|
|
2147
|
+
});
|
|
2148
|
+
newBalances = chartsResponse?.data?.balances || [];
|
|
2149
|
+
console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
|
|
2150
|
+
} catch (chartsError: any) {
|
|
2151
|
+
// GetCharts API may not be available - gracefully continue without charts data
|
|
2152
|
+
console.warn(tag, `GetCharts API not available (${chartsError.message}), continuing without charts data`);
|
|
2153
|
+
}
|
|
2097
2154
|
|
|
2098
2155
|
// Deduplicate balances using a Map with `identifier` as the key
|
|
2099
2156
|
const uniqueBalances = new Map(
|
|
@@ -330,7 +330,29 @@ export async function createUnsignedEvmTx(
|
|
|
330
330
|
amountWei = balance - gasFee - buffer;
|
|
331
331
|
console.log(tag, 'isMax calculation - balance:', balance.toString(), 'gasFee:', gasFee.toString(), 'buffer:', buffer.toString(), 'amountWei:', amountWei.toString());
|
|
332
332
|
} else {
|
|
333
|
-
|
|
333
|
+
// Validate and convert amount to number
|
|
334
|
+
// Handle both string and number inputs
|
|
335
|
+
const amountNum = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
336
|
+
|
|
337
|
+
// Validate amount is a valid number
|
|
338
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Invalid amount for native gas transfer: "${amount}" (type: ${typeof amount}). ` +
|
|
341
|
+
`Amount must be a valid positive number.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const amountCalculation = amountNum * 1e18;
|
|
346
|
+
|
|
347
|
+
// Validate calculation result before BigInt conversion
|
|
348
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`Invalid amount calculation for native gas transfer. ` +
|
|
351
|
+
`Input: ${amountNum}`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
334
356
|
const totalNeeded = amountWei + gasFee;
|
|
335
357
|
|
|
336
358
|
if (totalNeeded > balance) {
|
|
@@ -517,6 +539,7 @@ export async function createUnsignedEvmTx(
|
|
|
517
539
|
|
|
518
540
|
unsignedTx = {
|
|
519
541
|
chainId,
|
|
542
|
+
from: address, // Required for simulation (insight endpoint validation)
|
|
520
543
|
nonce: toHex(nonce),
|
|
521
544
|
gas: toHex(gasLimit),
|
|
522
545
|
gasPrice: toHex(gasPrice),
|
|
@@ -577,12 +600,37 @@ export async function createUnsignedEvmTx(
|
|
|
577
600
|
const tokenBalance = BigInt(Math.round(tokenBalanceData.data * Number(tokenMultiplier)));
|
|
578
601
|
amountWei = tokenBalance;
|
|
579
602
|
} else {
|
|
603
|
+
// Validate and convert amount to number
|
|
604
|
+
// Handle both string and number inputs
|
|
605
|
+
const amountNum = typeof amount === 'string' ? parseFloat(amount) : amount;
|
|
606
|
+
|
|
607
|
+
// Validate amount is a valid number
|
|
608
|
+
if (isNaN(amountNum) || !isFinite(amountNum) || amountNum <= 0) {
|
|
609
|
+
throw new Error(
|
|
610
|
+
`Invalid amount for ERC20 transfer: "${amount}" (type: ${typeof amount}). ` +
|
|
611
|
+
`Amount must be a valid positive number.`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
580
615
|
// Use BigInt math to avoid precision loss
|
|
581
|
-
|
|
616
|
+
const amountCalculation = amountNum * Number(tokenMultiplier);
|
|
617
|
+
|
|
618
|
+
// Validate calculation result before BigInt conversion
|
|
619
|
+
if (isNaN(amountCalculation) || !isFinite(amountCalculation)) {
|
|
620
|
+
throw new Error(
|
|
621
|
+
`Invalid amount calculation for ERC20 transfer. ` +
|
|
622
|
+
`Input: ${amountNum}, Decimals: ${tokenDecimals}, Multiplier: ${tokenMultiplier}`
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
amountWei = BigInt(Math.round(amountCalculation));
|
|
582
627
|
console.log(tag, 'Token amount calculation:', {
|
|
583
628
|
inputAmount: amount,
|
|
629
|
+
inputType: typeof amount,
|
|
630
|
+
parsedAmount: amountNum,
|
|
584
631
|
decimals: tokenDecimals,
|
|
585
|
-
multiplier: tokenMultiplier,
|
|
632
|
+
multiplier: tokenMultiplier.toString(),
|
|
633
|
+
calculation: amountCalculation,
|
|
586
634
|
resultWei: amountWei.toString(),
|
|
587
635
|
});
|
|
588
636
|
}
|