Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13711177
D12025.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Subscribers
None
D12025.id.diff
View Options
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
Details
Attached
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)
Attached To
D12025: [Cashtab] [Chronik] getTokenStats refactor
Event Timeline
Log In to Comment