diff --git a/web/cashtab/src/components/Common/Ticker.js b/web/cashtab/src/components/Common/Ticker.js --- a/web/cashtab/src/components/Common/Ticker.js +++ b/web/cashtab/src/components/Common/Ticker.js @@ -11,7 +11,7 @@ prefixes: ['bitcoincash', 'ecash'], coingeckoId: 'bitcoin-cash-abc-2', defaultFee: 5.01, - dust: '0.00000546', // The minimum amount of BCHA that can be sent by the app + dustSats: 546, cashDecimals: 8, blockExplorerUrl: 'https://explorer.bitcoinabc.org', tokenExplorerUrl: 'https://explorer.be.cash', diff --git a/web/cashtab/src/components/Tokens/Tokens.js b/web/cashtab/src/components/Tokens/Tokens.js --- a/web/cashtab/src/components/Tokens/Tokens.js +++ b/web/cashtab/src/components/Tokens/Tokens.js @@ -2,7 +2,11 @@ import { LoadingOutlined } from '@ant-design/icons'; import { CashLoader } from '@components/Common/CustomIcons'; import { WalletContext } from '@utils/context'; -import { formatBalance, isValidStoredWallet } from '@utils/cashMethods'; +import { + formatBalance, + isValidStoredWallet, + fromSmallestDenomination, +} from '@utils/cashMethods'; import CreateTokenForm from '@components/Tokens/CreateTokenForm'; import { currency } from '@components/Common/Ticker.js'; import TokenList from '@components/Wallet/TokenList'; @@ -100,14 +104,23 @@ BCH={BCH} getRestUrl={getRestUrl} createToken={createToken} - disabled={balances.totalBalanceInSatoshis < 546} + disabled={ + balances.totalBalanceInSatoshis < currency.dustSats + } /> - {balances.totalBalanceInSatoshis < 546 && ( + {balances.totalBalanceInSatoshis < currency.dustSats && ( - You need at least {currency.dust} {currency.ticker}{' '} - ($ - {(currency.dust * fiatPrice).toFixed(4)} USD) to - create a token + You need at least{' '} + {fromSmallestDenomination( + currency.dustSats, + ).toString()}{' '} + {currency.ticker} ($ + {( + fromSmallestDenomination( + currency.dustSats, + ).toString() * fiatPrice + ).toFixed(4)}{' '} + USD) to create a token )} diff --git a/web/cashtab/src/hooks/__tests__/migrations.test.js b/web/cashtab/src/hooks/__tests__/migrations.test.js --- a/web/cashtab/src/hooks/__tests__/migrations.test.js +++ b/web/cashtab/src/hooks/__tests__/migrations.test.js @@ -1,17 +1,20 @@ import { currency } from '../../components/Common/Ticker'; import BigNumber from 'bignumber.js'; import BCHJS from '@psf/bch-js'; -import useBCH from '../useBCH'; import { fromSmallestDenomination, toSmallestDenomination, } from '@utils/cashMethods'; describe('Testing functions for upgrading Cashtab', () => { - it('Replacement currency.dust parameter parsing matches legacy DUST parameter', () => { - expect(parseFloat(new BigNumber(currency.dust).toFixed(8))).toBe( - 0.00000546, - ); + it('Replacement currency.dustSats parameter parsing matches legacy DUST parameter', () => { + expect( + parseFloat( + new BigNumber( + fromSmallestDenomination(currency.dustSats).toString(), + ).toFixed(8), + ), + ).toBe(0.00000546); }); it('Replicate 8-decimal return value from instance of toSatoshi in TransactionBuilder with toSmallestDenomination', () => { const BCH = new BCHJS(); @@ -29,12 +32,11 @@ }); it('Replicate 8-decimal return value from instance of toSatoshi in remainder comparison with toSmallestDenomination', () => { const BCH = new BCHJS(); - // note: do not specify 8 decimals as this test SHOULD fail when currency.dust changes or cashDecimals changes if not updated expect( - parseFloat(toSmallestDenomination(new BigNumber(currency.dust))), + parseFloat(toSmallestDenomination(new BigNumber('0.00000546'), 8)), ).toBe( BCH.BitcoinCash.toSatoshi( - parseFloat(new BigNumber(currency.dust).toFixed(8)), + parseFloat(new BigNumber('0.00000546').toFixed(8)), ), ); }); @@ -126,4 +128,10 @@ BCH.BitcoinCash.toBitcoinCash(testSendAmount), ); }); + + it(`Converts dust limit in satoshis to dust limit in current app setting`, () => { + expect(fromSmallestDenomination(currency.dustSats).toString()).toBe( + '0.00000546', + ); + }); }); diff --git a/web/cashtab/src/hooks/__tests__/useBCH.test.js b/web/cashtab/src/hooks/__tests__/useBCH.test.js --- a/web/cashtab/src/hooks/__tests__/useBCH.test.js +++ b/web/cashtab/src/hooks/__tests__/useBCH.test.js @@ -27,6 +27,7 @@ import BCHJS from '@psf/bch-js'; // TODO: should be removed when external lib not needed anymore import { currency } from '../../components/Common/Ticker'; import BigNumber from 'bignumber.js'; +import { fromSmallestDenomination } from '@utils/cashMethods'; describe('useBCH hook', () => { it('gets Rest Api Url on testnet', () => { @@ -212,7 +213,9 @@ wallet, utxos, destinationAddress, - new BigNumber(currency.dust) + new BigNumber( + fromSmallestDenomination(currency.dustSats).toString(), + ) .minus(new BigNumber('0.00000001')) .toString(), 1.01, 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 @@ -3,6 +3,7 @@ import { toSmallestDenomination, + fromSmallestDenomination, batchArray, flattenBatchedHydratedUtxos, isValidStoredWallet, @@ -517,7 +518,9 @@ else transactionBuilder = new BCH.TransactionBuilder('testnet'); let originalAmount = new BigNumber(0); - const tokenOutputDust = new BigNumber(currency.dust); + const tokenOutputDust = new BigNumber( + fromSmallestDenomination(currency.dustSats).toString(), + ); let txFee = 0; for (let i = 0; i < utxos.length; i++) { const utxo = utxos[i]; @@ -560,11 +563,7 @@ ); // Send change to own address - if ( - remainder.gte( - toSmallestDenomination(new BigNumber(currency.dust)), - ) - ) { + if (remainder.gte(new BigNumber(currency.dustSats))) { transactionBuilder.addOutput( CREATION_ADDR, parseInt(remainder), @@ -832,7 +831,13 @@ const value = new BigNumber(sendAmount); // If user is attempting to send less than minimum accepted by the backend - if (value.lt(new BigNumber(currency.dust))) { + if ( + value.lt( + new BigNumber( + fromSmallestDenomination(currency.dustSats).toString(), + ), + ) + ) { // Throw the same error given by the backend attempting to broadcast such a tx throw new Error('dust'); } @@ -893,11 +898,7 @@ parseInt(toSmallestDenomination(value)), ); - if ( - remainder.gte( - toSmallestDenomination(new BigNumber(currency.dust)), - ) - ) { + if (remainder.gte(new BigNumber(currency.dustSats))) { transactionBuilder.addOutput( REMAINDER_ADDR, parseInt(remainder), 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 @@ -8,6 +8,7 @@ isValidTokenDocumentUrl, } from '../validation'; import { currency } from '@components/Common/Ticker.js'; +import { fromSmallestDenomination } from '@utils/cashMethods'; describe('Validation utils', () => { it(`Returns 'false' if ${currency.ticker} send amount is a valid send amount`, () => { @@ -49,19 +50,34 @@ expectedValidationError, ); }); - it(`Returns error if ${currency.ticker} send amount is less than ${currency.dust} minimum`, () => { - const expectedValidationError = `Send amount must be at least ${currency.dust} ${currency.ticker}`; + it(`Returns error if ${ + currency.ticker + } send amount is less than ${fromSmallestDenomination( + currency.dustSats, + ).toString()} minimum`, () => { + const expectedValidationError = `Send amount must be at least ${fromSmallestDenomination( + currency.dustSats, + ).toString()} ${currency.ticker}`; expect( shouldRejectAmountInput( - (currency.dust - 0.00000001).toString(), + ( + fromSmallestDenomination(currency.dustSats).toString() - + 0.00000001 + ).toString(), currency.ticker, 20.0, 3, ), ).toBe(expectedValidationError); }); - it(`Returns error if ${currency.ticker} send amount is less than ${currency.dust} minimum in fiat currency`, () => { - const expectedValidationError = `Send amount must be at least ${currency.dust} ${currency.ticker}`; + it(`Returns error if ${ + currency.ticker + } send amount is less than ${fromSmallestDenomination( + currency.dustSats, + ).toString()} minimum in fiat currency`, () => { + const expectedValidationError = `Send amount must be at least ${fromSmallestDenomination( + currency.dustSats, + ).toString()} ${currency.ticker}`; expect( shouldRejectAmountInput('0.0000005', 'USD', 14.63, 0.52574662), ).toBe(expectedValidationError); 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 @@ -1,5 +1,6 @@ import BigNumber from 'bignumber.js'; import { currency } from '@components/Common/Ticker.js'; +import { fromSmallestDenomination } from '@utils/cashMethods'; // Validate cash amount export const shouldRejectAmountInput = ( @@ -22,8 +23,12 @@ error = 'Amount must be a number'; } else if (testedAmount.lte(0)) { error = 'Amount must be greater than 0'; - } else if (testedAmount.lt(currency.dust)) { - error = `Send amount must be at least ${currency.dust} ${currency.ticker}`; + } else if ( + testedAmount.lt(fromSmallestDenomination(currency.dustSats).toString()) + ) { + error = `Send amount must be at least ${fromSmallestDenomination( + currency.dustSats, + ).toString()} ${currency.ticker}`; } else if (testedAmount.gt(totalCashBalance)) { error = `Amount cannot exceed your ${currency.ticker} balance`; } else if (!isNaN(testedAmount) && testedAmount.toString().includes('.')) {