@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 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
- amountWei = BigInt(Math.round(amount * 1000000000000000000));
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
- amountWei = BigInt(Math.round(amount * Number(tokenMultiplier)));
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
- return Promise.resolve({});
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
- this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
3718
- const keepkeyEndpoint = this.keepkeyEndpoint;
3719
- try {
3720
- const configKeepKey = {
3721
- apiKey: this.keepkeyApiKey || "keepkey-api-key",
3722
- pairingInfo: {
3723
- name: "KeepKey SDK Demo App",
3724
- imageUrl: "https://pioneers.dev/coins/keepkey.png",
3725
- basePath: keepkeyEndpoint.basePath,
3726
- url: keepkeyEndpoint.baseUrl
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
- console.log("\uD83D\uDD11 [INIT] Initializing KeepKey SDK...");
3730
- const keepKeySdk = await import_keepkey_sdk.KeepKeySdk.create(configKeepKey);
3731
- const features = await keepKeySdk.system.info.getFeatures();
3732
- this.keepkeyApiKey = configKeepKey.apiKey;
3733
- this.keepKeySdk = keepKeySdk;
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
- const chartsResponse = await this.pioneer.GetCharts({
4767
- pubkeys: pubkeysForBatch
4768
- });
4769
- const newBalances = chartsResponse?.data?.balances || [];
4770
- console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
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
- amountWei = BigInt(Math.round(amount * 1000000000000000000));
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
- amountWei = BigInt(Math.round(amount * Number(tokenMultiplier)));
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
- return Promise.resolve({});
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
- this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
3894
- const keepkeyEndpoint = this.keepkeyEndpoint;
3895
- try {
3896
- const configKeepKey = {
3897
- apiKey: this.keepkeyApiKey || "keepkey-api-key",
3898
- pairingInfo: {
3899
- name: "KeepKey SDK Demo App",
3900
- imageUrl: "https://pioneers.dev/coins/keepkey.png",
3901
- basePath: keepkeyEndpoint.basePath,
3902
- url: keepkeyEndpoint.baseUrl
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
- console.log("\uD83D\uDD11 [INIT] Initializing KeepKey SDK...");
3906
- const keepKeySdk = await KeepKeySdk.create(configKeepKey);
3907
- const features = await keepKeySdk.system.info.getFeatures();
3908
- this.keepkeyApiKey = configKeepKey.apiKey;
3909
- this.keepKeySdk = keepKeySdk;
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
- const chartsResponse = await this.pioneer.GetCharts({
4943
- pubkeys: pubkeysForBatch
4944
- });
4945
- const newBalances = chartsResponse?.data?.balances || [];
4946
- console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
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
- amountWei = BigInt(Math.round(amount * 1000000000000000000));
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
- amountWei = BigInt(Math.round(amount * Number(tokenMultiplier)));
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
- return Promise.resolve({});
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
- this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
3894
- const keepkeyEndpoint = this.keepkeyEndpoint;
3895
- try {
3896
- const configKeepKey = {
3897
- apiKey: this.keepkeyApiKey || "keepkey-api-key",
3898
- pairingInfo: {
3899
- name: "KeepKey SDK Demo App",
3900
- imageUrl: "https://pioneers.dev/coins/keepkey.png",
3901
- basePath: keepkeyEndpoint.basePath,
3902
- url: keepkeyEndpoint.baseUrl
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
- console.log("\uD83D\uDD11 [INIT] Initializing KeepKey SDK...");
3906
- const keepKeySdk = await KeepKeySdk.create(configKeepKey);
3907
- const features = await keepKeySdk.system.info.getFeatures();
3908
- this.keepkeyApiKey = configKeepKey.apiKey;
3909
- this.keepKeySdk = keepKeySdk;
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
- const chartsResponse = await this.pioneer.GetCharts({
4943
- pubkeys: pubkeysForBatch
4944
- });
4945
- const newBalances = chartsResponse?.data?.balances || [];
4946
- console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "highlander",
3
3
  "name": "@pioneer-platform/pioneer-sdk",
4
- "version": "8.12.7",
4
+ "version": "8.13.0",
5
5
  "dependencies": {
6
6
  "@keepkey/keepkey-sdk": "^0.2.62",
7
7
  "@pioneer-platform/loggerdog": "^8.11.0",
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
- // Implementation will be added later
282
- return Promise.resolve({});
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
- this.keepkeyEndpoint = await detectKkApiAvailability(this.forceLocalhost);
563
- const keepkeyEndpoint = this.keepkeyEndpoint;
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
- // Initialize KeepKey SDK if available
566
- try {
567
- const configKeepKey = {
568
- apiKey: this.keepkeyApiKey || 'keepkey-api-key',
569
- pairingInfo: {
570
- name: 'KeepKey SDK Demo App',
571
- imageUrl: 'https://pioneers.dev/coins/keepkey.png',
572
- basePath: keepkeyEndpoint.basePath,
573
- url: keepkeyEndpoint.baseUrl,
574
- },
575
- };
576
-
577
- console.log('🔑 [INIT] Initializing KeepKey SDK...');
578
- const keepKeySdk = await KeepKeySdk.create(configKeepKey);
579
- const features = await keepKeySdk.system.info.getFeatures();
580
-
581
- this.keepkeyApiKey = configKeepKey.apiKey;
582
- this.keepKeySdk = keepKeySdk;
583
- this.context = 'keepkey:' + features.label + '.json';
584
- } catch (e) {
585
- console.error('⚠️ [INIT] KeepKey SDK initialization failed:', e);
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
- // FIX: Wrap assetQuery in object with pubkeys field to match server API
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
- const chartsResponse = await this.pioneer.GetCharts({
2092
- pubkeys: pubkeysForBatch
2093
- });
2094
-
2095
- const newBalances = chartsResponse?.data?.balances || [];
2096
- console.log(tag, `Received ${newBalances.length} balances from batch endpoint`);
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
- amountWei = BigInt(Math.round(amount * 1e18));
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
- amountWei = BigInt(Math.round(amount * Number(tokenMultiplier)));
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
  }