Page MenuHomePhabricator

D12025.id.diff
No OneTemporary

D12025.id.diff

diff --git a/web/cashtab/src/components/Send/SendToken.js b/web/cashtab/src/components/Send/SendToken.js
--- a/web/cashtab/src/components/Send/SendToken.js
+++ b/web/cashtab/src/components/Send/SendToken.js
@@ -46,6 +46,7 @@
isValidEtokenAddress,
isValidEtokenBurnAmount,
} from 'utils/validation';
+import { getTokenStats } from 'utils/chronik';
import { formatDate } from 'utils/formatting';
import styled, { css } from 'styled-components';
import TokenIcon from 'components/Tokens/TokenIcon';
@@ -93,7 +94,7 @@
`;
const SendToken = ({ tokenId, jestBCH, passLoadingStatus }) => {
- const { BCH, wallet, apiError, cashtabSettings } =
+ const { BCH, wallet, apiError, cashtabSettings, chronik } =
React.useContext(WalletContext);
const walletState = getWalletState(wallet);
const { tokens } = walletState;
@@ -130,7 +131,7 @@
address: '',
});
- const { getRestUrl, sendToken, getTokenStats, burnToken } = useBCH();
+ const { getRestUrl, sendToken, burnToken } = useBCH();
useEffect(() => {
// jestBCH is only ever specified for unit tests, otherwise app will use getBCH();
@@ -142,7 +143,7 @@
// Fetch token stats if you do not have them and API did not return an error
if (tokenStats === null) {
- getTokenStats(bchObj, tokenId).then(
+ getTokenStats(chronik, tokenId).then(
result => {
setTokenStats(result);
},
@@ -650,13 +651,19 @@
{tokenStats && (
<>
<Descriptions.Item label="Document URI">
- {tokenStats.documentUri}
+ {
+ tokenStats.slpTxData
+ .genesisInfo
+ .tokenDocumentUrl
+ }
</Descriptions.Item>
<Descriptions.Item label="Genesis Date">
- {tokenStats.timestampUnix !==
- null
+ {tokenStats.block &&
+ tokenStats.block
+ .timestamp !== null
? formatDate(
- tokenStats.timestampUnix,
+ tokenStats.block
+ .timestamp,
navigator.language,
)
: 'Just now (Genesis tx confirming)'}
@@ -667,16 +674,40 @@
: 'Yes'}
</Descriptions.Item>
<Descriptions.Item label="Initial Quantity">
- {tokenStats.initialTokenQty.toLocaleString()}
+ {new BigNumber(
+ tokenStats.initialTokenQuantity,
+ )
+ .toFormat(
+ token.info.decimals,
+ )
+ .toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="Total Burned">
- {tokenStats.totalBurned.toLocaleString()}
+ {new BigNumber(
+ tokenStats.tokenStats.totalBurned,
+ )
+ .toFormat(
+ token.info.decimals,
+ )
+ .toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="Total Minted">
- {tokenStats.totalMinted.toLocaleString()}
+ {new BigNumber(
+ tokenStats.tokenStats.totalMinted,
+ )
+ .toFormat(
+ token.info.decimals,
+ )
+ .toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="Circulating Supply">
- {tokenStats.circulatingSupply.toLocaleString()}
+ {new BigNumber(
+ tokenStats.circulatingSupply,
+ )
+ .toFormat(
+ token.info.decimals,
+ )
+ .toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="Burn eToken">
<DestinationAmount
diff --git a/web/cashtab/src/hooks/useBCH.js b/web/cashtab/src/hooks/useBCH.js
--- a/web/cashtab/src/hooks/useBCH.js
+++ b/web/cashtab/src/hooks/useBCH.js
@@ -1,6 +1,5 @@
import BigNumber from 'bignumber.js';
import { currency } from 'components/Common/Ticker';
-import { isValidTokenStats } from 'utils/validation';
import SlpWallet from 'minimal-slp-wallet';
import {
fromXecToSatoshis,
@@ -707,22 +706,6 @@
}
};
- // No unit tests for this function as it is only an API wrapper
- // Return false if do not get a valid response
- const getTokenStats = async (BCH, tokenId) => {
- let tokenStats;
- try {
- tokenStats = await BCH.SLP.Utils.tokenStats(tokenId);
- if (isValidTokenStats(tokenStats)) {
- return tokenStats;
- }
- } catch (err) {
- console.log(`Error fetching token stats for tokenId ${tokenId}`);
- console.log(err);
- return false;
- }
- };
-
const sendToken = async (
BCH,
wallet,
@@ -1418,7 +1401,6 @@
sendXec,
sendToken,
createToken,
- getTokenStats,
handleEncryptedOpReturn,
getRecipientPublicKey,
burnToken,
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
@@ -1399,6 +1399,7 @@
return {
BCH,
+ chronik,
wallet,
fiatPrice,
loading,
diff --git a/web/cashtab/src/utils/__mocks__/mockChronikTokenStats.js b/web/cashtab/src/utils/__mocks__/mockChronikTokenStats.js
new file mode 100644
--- /dev/null
+++ b/web/cashtab/src/utils/__mocks__/mockChronikTokenStats.js
@@ -0,0 +1,64 @@
+import BigNumber from 'bignumber.js';
+
+export const mockChronikTokenResponse = {
+ slpTxData: {
+ slpMeta: {
+ tokenType: 'FUNGIBLE',
+ txType: 'GENESIS',
+ tokenId:
+ '3c14fcdc3fce9738d213c1ab9d9ff18234fecab9d1ad5a77d3f7b95964269f4a',
+ },
+ genesisInfo: {
+ tokenTicker: 'VVS',
+ tokenName: 'ethantest',
+ tokenDocumentUrl: 'https://cashtab.com/',
+ tokenDocumentHash: '',
+ decimals: 3,
+ },
+ },
+ tokenStats: {
+ totalMinted: '21000000000',
+ totalBurned: '3056',
+ },
+ block: {
+ height: 758409,
+ hash: '00000000000000000f305eafc05bffd14de4acf52787596b5927199c9cab37da',
+ timestamp: '1663859004',
+ },
+ timeFirstSeen: '1663858438',
+ initialTokenQuantity: '21000000000',
+ containsBaton: false,
+ network: 'XEC',
+};
+
+export const mockGetTokenStatsReturn = {
+ slpTxData: {
+ slpMeta: {
+ tokenType: 'FUNGIBLE',
+ txType: 'GENESIS',
+ tokenId:
+ '3c14fcdc3fce9738d213c1ab9d9ff18234fecab9d1ad5a77d3f7b95964269f4a',
+ },
+ genesisInfo: {
+ tokenTicker: 'VVS',
+ tokenName: 'ethantest',
+ tokenDocumentUrl: 'https://cashtab.com/',
+ tokenDocumentHash: '',
+ decimals: 3,
+ },
+ },
+ tokenStats: {
+ totalMinted: '21000000',
+ totalBurned: '3.056',
+ },
+ block: {
+ height: 758409,
+ hash: '00000000000000000f305eafc05bffd14de4acf52787596b5927199c9cab37da',
+ timestamp: '1663859004',
+ },
+ timeFirstSeen: '1663858438',
+ initialTokenQuantity: '21000000',
+ containsBaton: false,
+ network: 'XEC',
+ circulatingSupply: '20999996.944',
+};
diff --git a/web/cashtab/src/utils/__tests__/chronik.test.js b/web/cashtab/src/utils/__tests__/chronik.test.js
--- a/web/cashtab/src/utils/__tests__/chronik.test.js
+++ b/web/cashtab/src/utils/__tests__/chronik.test.js
@@ -1,8 +1,10 @@
+import BigNumber from 'bignumber.js';
import {
organizeUtxosByType,
getPreliminaryTokensArray,
finalizeTokensArray,
finalizeSlpUtxos,
+ getTokenStats,
} from 'utils/chronik';
import {
mockChronikUtxos,
@@ -19,9 +21,33 @@
mockFinalizedSlpUtxos,
mockTokenInfoById,
} from '../__mocks__/chronikUtxos';
+import {
+ mockChronikTokenResponse,
+ mockGetTokenStatsReturn,
+} from '../__mocks__/mockChronikTokenStats';
import { ChronikClient } from 'chronik-client';
import { when } from 'jest-when';
+it(`getTokenStats successfully returns a token stats object`, async () => {
+ // Initialize chronik
+ const chronik = new ChronikClient(
+ 'https://FakeChronikUrlToEnsureMocksOnly.com',
+ );
+ const tokenId =
+ 'bb8e9f685a06a2071d82f757ce19201b4c8e5e96fbe186960a3d65aec83eab20';
+ /*
+ Mock the API response from chronik.token('tokenId') called
+ in getTokenStats()
+ */
+ chronik.token = jest.fn();
+ when(chronik.token)
+ .calledWith(tokenId)
+ .mockResolvedValue(mockChronikTokenResponse);
+ expect(await getTokenStats(chronik, tokenId)).toStrictEqual(
+ mockGetTokenStatsReturn,
+ );
+});
+
it(`organizeUtxosByType successfully splits a chronikUtxos array into slpUtxos and nonSlpUtxos`, () => {
expect(organizeUtxosByType(mockChronikUtxos)).toStrictEqual(
mockOrganizedUtxosByType,
diff --git a/web/cashtab/src/utils/__tests__/validation.test.js b/web/cashtab/src/utils/__tests__/validation.test.js
--- a/web/cashtab/src/utils/__tests__/validation.test.js
+++ b/web/cashtab/src/utils/__tests__/validation.test.js
@@ -6,7 +6,6 @@
isValidTokenDecimals,
isValidTokenInitialQty,
isValidTokenDocumentUrl,
- isValidTokenStats,
isValidCashtabSettings,
isValidXecAddress,
isValidNewWalletNameLength,
@@ -250,18 +249,6 @@
it(`Rejects a domain input as numbers ${currency.tokenTicker} token document URL`, () => {
expect(isValidTokenDocumentUrl(12345)).toBe(false);
});
- it(`Correctly validates token stats for token created before the ${currency.ticker} fork`, () => {
- expect(isValidTokenStats(stStatsValid)).toBe(true);
- });
- it(`Correctly validates token stats for token created after the ${currency.ticker} fork`, () => {
- expect(isValidTokenStats(noCovidStatsValid)).toBe(true);
- });
- it(`Correctly validates token stats for token with no minting baton`, () => {
- expect(isValidTokenStats(cGenStatsValid)).toBe(true);
- });
- it(`Recognizes a token stats object with missing required keys as invalid`, () => {
- expect(isValidTokenStats(noCovidStatsInvalid)).toBe(false);
- });
it(`Recognizes the default cashtabCache object as valid`, () => {
expect(isValidCashtabCache(currency.defaultCashtabCache)).toBe(true);
});
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,5 +1,49 @@
// Chronik methods
import BigNumber from 'bignumber.js';
+
+// Return false if do not get a valid response
+export const getTokenStats = async (chronik, tokenId) => {
+ try {
+ // token attributes available via chronik's token() method
+ let tokenResponseObj = await chronik.token(tokenId);
+ const tokenDecimals = tokenResponseObj.slpTxData.genesisInfo.decimals;
+
+ // 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();
+
+ return tokenResponseObj;
+ } catch (err) {
+ console.log(
+ `Error fetching token stats for tokenId ${tokenId}: ` + err,
+ );
+ return false;
+ }
+};
+
/*
Note: chronik.script('p2pkh', hash160).utxos(); is not readily mockable in jest
Hence it is necessary to keep this out of any functions that require unit testing
diff --git a/web/cashtab/src/utils/validation.js b/web/cashtab/src/utils/validation.js
--- a/web/cashtab/src/utils/validation.js
+++ b/web/cashtab/src/utils/validation.js
@@ -105,19 +105,6 @@
);
};
-export const isValidTokenStats = tokenStats => {
- return (
- typeof tokenStats === 'object' &&
- 'timestampUnix' in tokenStats &&
- 'documentUri' in tokenStats &&
- 'containsBaton' in tokenStats &&
- 'initialTokenQty' in tokenStats &&
- 'totalMinted' in tokenStats &&
- 'totalBurned' in tokenStats &&
- 'circulatingSupply' in tokenStats
- );
-};
-
export const isValidCashtabSettings = settings => {
try {
let isValidSettingParams = true;

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 26, 10:41 (5 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5571085
Default Alt Text
D12025.id.diff (15 KB)

Event Timeline