diff --git a/web/cashtab/src/hooks/useWallet.js b/web/cashtab/src/hooks/useWallet.js --- a/web/cashtab/src/hooks/useWallet.js +++ b/web/cashtab/src/hooks/useWallet.js @@ -12,6 +12,7 @@ checkWalletForTokenInfo, isActiveWebsocket, getWalletBalanceFromUtxos, + adjustTokenQtyForDecimals, } from 'utils/cashMethods'; import { isValidCashtabSettings, @@ -901,9 +902,10 @@ }; } // Calculate eToken amount with decimals - eTokenAmountReceived = new BigNumber( - parsedChronikTx.etokenAmount, - ).shiftedBy(-1 * incomingTokenInfo.decimals); + eTokenAmountReceived = adjustTokenQtyForDecimals( + new BigNumber(parsedChronikTx.etokenAmount), + incomingTokenInfo.decimals, + ); // Send this info to the notification function eTokenReceivedNotification( currency, diff --git a/web/cashtab/src/utils/__tests__/cashMethods.test.js b/web/cashtab/src/utils/__tests__/cashMethods.test.js --- a/web/cashtab/src/utils/__tests__/cashMethods.test.js +++ b/web/cashtab/src/utils/__tests__/cashMethods.test.js @@ -27,6 +27,7 @@ getWalletBalanceFromUtxos, signUtxosByAddress, getUtxoWif, + adjustTokenQtyForDecimals, } from 'utils/cashMethods'; import { currency } from 'components/Common/Ticker'; import { @@ -1455,4 +1456,65 @@ it(`isActiveWebsocket returns false for an active websocket connection with no subscriptions`, () => { expect(isActiveWebsocket(unsubscribedWebsocket)).toBe(false); }); + + it(`correctly adjusts token balance with decimals when passed 2 BigNumbers`, () => { + let tokenBalance = new BigNumber(6); + let tokenDecimals = new BigNumber(0); + let result = tokenBalance.shiftedBy(-1 * tokenDecimals); + expect( + JSON.stringify( + adjustTokenQtyForDecimals(tokenBalance, tokenDecimals), + ), + ).toBe(JSON.stringify(new BigNumber(result))); + }); + it(`correctly adjusts token balance with decimals when passed 1 BigNumber and 1 string`, () => { + let tokenBalance = '42816543209900000'; + let tokenDecimals = new BigNumber(9); + let result = new BigNumber(tokenBalance).shiftedBy(-1 * tokenDecimals); + + expect( + JSON.stringify( + adjustTokenQtyForDecimals(tokenBalance, tokenDecimals), + ), + ).toBe(JSON.stringify(new BigNumber(result))); + }); + it(`correctly adjusts token balance with decimals when passed 1 string and 1 number`, () => { + let tokenBalance = '999995000000000'; + let tokenDecimals = 9; + let result = new BigNumber(tokenBalance).shiftedBy( + -1 * new BigNumber(tokenDecimals), + ); + + expect( + JSON.stringify( + adjustTokenQtyForDecimals(tokenBalance, tokenDecimals), + ), + ).toBe(JSON.stringify(new BigNumber(result))); + }); + it(`correctly adjusts token balance with decimals when passed 2 strings`, () => { + let tokenBalance = '37013482000000000'; + let tokenDecimals = '9'; + let result = new BigNumber(tokenBalance).shiftedBy( + -1 * new BigNumber(tokenDecimals), + ); + + expect( + JSON.stringify( + adjustTokenQtyForDecimals(tokenBalance, tokenDecimals), + ), + ).toBe(JSON.stringify(new BigNumber(result))); + }); + it(`correctly adjusts token balance with decimals when passed 2 numbers`, () => { + let tokenBalance = 100000000000000; + let tokenDecimals = 8; + let result = new BigNumber(tokenBalance).shiftedBy( + -1 * new BigNumber(tokenDecimals), + ); + + expect( + JSON.stringify( + adjustTokenQtyForDecimals(tokenBalance, tokenDecimals), + ), + ).toBe(JSON.stringify(new BigNumber(result))); + }); }); diff --git a/web/cashtab/src/utils/cashMethods.js b/web/cashtab/src/utils/cashMethods.js --- a/web/cashtab/src/utils/cashMethods.js +++ b/web/cashtab/src/utils/cashMethods.js @@ -906,8 +906,7 @@ } // Convert from sats to XEC - xecAmount = xecAmount.shiftedBy(-1 * currency.cashDecimals); - + xecAmount = adjustTokenQtyForDecimals(xecAmount, currency.cashDecimals); // Convert from BigNumber to string xecAmount = xecAmount.toString(); etokenAmount = etokenAmount.toString(); @@ -965,3 +964,30 @@ ws._subs.length > 0 ); }; + +// This function accepts BigNumber, string or number as params +export const adjustTokenQtyForDecimals = (tokenBalance, tokenDecimals) => { + let tokenQtyAdjustedWithDecimals; + let convertedTokenDecimals; + let convertedTokenBalance; + if (BigNumber.isBigNumber(tokenDecimals)) { + convertedTokenDecimals = tokenDecimals; + } + if (BigNumber.isBigNumber(tokenBalance)) { + convertedTokenBalance = tokenBalance; + } + if ( + typeof tokenDecimals === 'string' || + typeof tokenDecimals === 'number' + ) { + convertedTokenDecimals = new BigNumber(tokenDecimals); + } + if (typeof tokenBalance === 'string' || typeof tokenBalance === 'number') { + convertedTokenBalance = new BigNumber(tokenBalance); + } + + tokenQtyAdjustedWithDecimals = convertedTokenBalance.shiftedBy( + -1 * convertedTokenDecimals, + ); + return new BigNumber(tokenQtyAdjustedWithDecimals); +}; diff --git a/web/cashtab/src/utils/chronik.js b/web/cashtab/src/utils/chronik.js --- a/web/cashtab/src/utils/chronik.js +++ b/web/cashtab/src/utils/chronik.js @@ -1,6 +1,6 @@ // Chronik methods import BigNumber from 'bignumber.js'; - +import { adjustTokenQtyForDecimals } from './cashMethods'; // Return false if do not get a valid response export const getTokenStats = async (chronik, tokenId) => { try { @@ -10,30 +10,26 @@ // additional arithmetic to account for token decimals // circulating supply not provided by chronik, calculate via totalMinted - totalBurned - tokenResponseObj.circulatingSupply = new BigNumber( - tokenResponseObj.tokenStats.totalMinted, - ) - .minus(new BigNumber(tokenResponseObj.tokenStats.totalBurned)) - .shiftedBy(-1 * tokenDecimals) - .toString(); - - tokenResponseObj.tokenStats.totalMinted = new BigNumber( - tokenResponseObj.tokenStats.totalMinted, - ) - .shiftedBy(-1 * tokenDecimals) - .toString(); - - tokenResponseObj.initialTokenQuantity = new BigNumber( - tokenResponseObj.initialTokenQuantity, - ) - .shiftedBy(-1 * tokenDecimals) - .toString(); - - tokenResponseObj.tokenStats.totalBurned = new BigNumber( - tokenResponseObj.tokenStats.totalBurned, - ) - .shiftedBy(-1 * tokenDecimals) - .toString(); + tokenResponseObj.circulatingSupply = adjustTokenQtyForDecimals( + new BigNumber(tokenResponseObj.tokenStats.totalMinted).minus( + new BigNumber(tokenResponseObj.tokenStats.totalBurned), + ), + tokenDecimals, + ).toString(); + + tokenResponseObj.tokenStats.totalMinted = adjustTokenQtyForDecimals( + new BigNumber(tokenResponseObj.tokenStats.totalMinted), + tokenDecimals, + ).toString(); + + tokenResponseObj.initialTokenQuantity = adjustTokenQtyForDecimals( + new BigNumber(tokenResponseObj.initialTokenQuantity), + tokenDecimals, + ).toString(); + tokenResponseObj.tokenStats.totalBurned = adjustTokenQtyForDecimals( + new BigNumber(tokenResponseObj.tokenStats.totalBurned), + tokenDecimals, + ).toString(); return tokenResponseObj; } catch (err) { @@ -236,8 +232,10 @@ thisToken.info = thisTokenInfo; // Update balance according to decimals - thisToken.balance = thisToken.balance.shiftedBy(-1 * thisTokenDecimals); - + thisToken.balance = adjustTokenQtyForDecimals( + thisToken.balance, + thisTokenDecimals, + ); // Now that you have the metadata and the correct balance, // preliminaryTokenInfo is finalTokenInfo finalTokenArray.push(thisToken); @@ -321,9 +319,10 @@ const thisTokenId = thisUtxo.slpMeta.tokenId; const { decimals } = tokenInfoById[thisTokenId]; // Update balance according to decimals - thisUtxo.tokenQty = new BigNumber(thisUtxo.slpToken.amount) - .shiftedBy(-1 * decimals) - .toString(); + thisUtxo.tokenQty = adjustTokenQtyForDecimals( + new BigNumber(thisUtxo.slpToken.amount), + decimals, + ).toString(); // SLP utxos also require tokenId and decimals directly in the utxo object // This is bad organization but necessary until bch-js is refactored // https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/src/slp/tokentype1.js#L217