Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14362740
D10912.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
58 KB
Subscribers
None
D10912.id.diff
View Options
diff --git a/web/cashtab/src/components/Common/Atoms.js b/web/cashtab/src/components/Common/Atoms.js
--- a/web/cashtab/src/components/Common/Atoms.js
+++ b/web/cashtab/src/components/Common/Atoms.js
@@ -1,5 +1,9 @@
import styled from 'styled-components';
+export const WarningFont = styled.div`
+ color: ${props => props.theme.wallet.text.primary};
+`;
+
export const LoadingCtn = styled.div`
width: 100%;
display: flex;
diff --git a/web/cashtab/src/components/Common/Notifications.js b/web/cashtab/src/components/Common/Notifications.js
--- a/web/cashtab/src/components/Common/Notifications.js
+++ b/web/cashtab/src/components/Common/Notifications.js
@@ -88,6 +88,23 @@
});
};
+const burnTokenNotification = link => {
+ const notificationStyle = getDeviceNotificationStyle();
+ notification.success({
+ message: 'Success',
+ description: (
+ <a href={link} target="_blank" rel="noopener noreferrer">
+ <Paragraph>
+ eToken burn successful. Click to view in block explorer.
+ </Paragraph>
+ </a>
+ ),
+ duration: currency.notificationDurationLong,
+ icon: <TokenReceivedNotificationIcon />,
+ style: notificationStyle,
+ });
+};
+
const xecReceivedNotification = (
balances,
previousBalances,
@@ -189,4 +206,5 @@
errorNotification,
messageSignedNotification,
generalNotification,
+ burnTokenNotification,
};
diff --git a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap
--- a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap
+++ b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap
@@ -2,10 +2,10 @@
exports[`Configure with a wallet 1`] = `
<div
- className="sc-gZMcBi iCoKwX"
+ className="sc-gqjmRU dYraLr"
>
<div
- className="sc-gPEVay MsPfq"
+ className="sc-iRbamj iLHUMS"
>
<h2>
<span
@@ -75,7 +75,7 @@
</div>
</div>
<div
- className="ant-collapse ant-collapse-icon-position-left sc-kgoBCf bSSwhO"
+ className="ant-collapse ant-collapse-icon-position-left sc-kGXeez iwFpHC"
role={null}
>
<div
@@ -113,7 +113,7 @@
</div>
</div>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
<h2>
<span
@@ -138,7 +138,7 @@
Manage Wallets
</h2>
<button
- className="sc-iAyFgw fHBaAb"
+ className="sc-hSdWYo eFTHFJ"
onClick={[Function]}
>
<span
@@ -166,7 +166,7 @@
New Wallet
</button>
<button
- className="sc-hSdWYo hVIBck"
+ className="sc-eHgmQL hjwBux"
onClick={[Function]}
>
<span
@@ -191,7 +191,7 @@
Import Wallet
</button>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
<h2>
<span
@@ -216,7 +216,7 @@
Fiat Currency
</h2>
<div
- className="sc-hMqMXs EtsmJ"
+ className="sc-kEYyzF gVORrF"
>
<div
className="ant-select select-after ant-select-single ant-select-show-arrow"
@@ -308,7 +308,7 @@
</div>
</div>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
<h2>
<span
@@ -333,7 +333,7 @@
General Settings
</h2>
<div
- className="sc-jlyJG sRRwV"
+ className="sc-gipzik fOiixB"
>
<div
className="title"
@@ -396,7 +396,7 @@
</button>
</div>
<div
- className="sc-jlyJG sRRwV"
+ className="sc-gipzik fOiixB"
>
<div
className="SendConfirm"
@@ -459,11 +459,11 @@
</button>
</div>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
[
<a
- className="sc-cvbbAY jGCCOB"
+ className="sc-jWBwVP kAmQdZ"
href="https://docs.cashtab.com/docs/"
rel="noreferrer"
target="_blank"
@@ -478,10 +478,10 @@
exports[`Configure without a wallet 1`] = `
<div
- className="sc-gZMcBi iCoKwX"
+ className="sc-gqjmRU dYraLr"
>
<div
- className="sc-gPEVay MsPfq"
+ className="sc-iRbamj iLHUMS"
>
<h2>
<span
@@ -551,7 +551,7 @@
</div>
</div>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
<h2>
<span
@@ -576,7 +576,7 @@
Manage Wallets
</h2>
<button
- className="sc-iAyFgw fHBaAb"
+ className="sc-hSdWYo eFTHFJ"
onClick={[Function]}
>
<span
@@ -604,7 +604,7 @@
New Wallet
</button>
<button
- className="sc-hSdWYo hVIBck"
+ className="sc-eHgmQL hjwBux"
onClick={[Function]}
>
<span
@@ -629,7 +629,7 @@
Import Wallet
</button>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
<h2>
<span
@@ -654,7 +654,7 @@
Fiat Currency
</h2>
<div
- className="sc-hMqMXs EtsmJ"
+ className="sc-kEYyzF gVORrF"
>
<div
className="ant-select select-after ant-select-single ant-select-show-arrow"
@@ -746,7 +746,7 @@
</div>
</div>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
<h2>
<span
@@ -771,7 +771,7 @@
General Settings
</h2>
<div
- className="sc-jlyJG sRRwV"
+ className="sc-gipzik fOiixB"
>
<div
className="title"
@@ -834,7 +834,7 @@
</button>
</div>
<div
- className="sc-jlyJG sRRwV"
+ className="sc-gipzik fOiixB"
>
<div
className="SendConfirm"
@@ -897,11 +897,11 @@
</button>
</div>
<div
- className="sc-iRbamj gnaEEo"
+ className="sc-jlyJG fPAUMB"
/>
[
<a
- className="sc-cvbbAY jGCCOB"
+ className="sc-jWBwVP kAmQdZ"
href="https://docs.cashtab.com/docs/"
rel="noreferrer"
target="_blank"
diff --git a/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
--- a/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
+++ b/web/cashtab/src/components/Home/__tests__/__snapshots__/Home.test.js.snap
@@ -3,15 +3,15 @@
exports[`Wallet with BCH balances 1`] = `
Array [
<div
- className="sc-ksYbfQ EFpnD"
+ className="sc-hmzhuo cYAKWr"
>
<h4
- className="sc-uJMKN cKiOJP"
+ className="sc-bbmXgH cXxYjE"
>
MigrationTestAlpha
</h4>
<div
- className="sc-frDJqD eedyCO"
+ className="sc-kvZOFW cxOlCC"
>
0
@@ -19,26 +19,26 @@
</div>
</div>,
<div
- className="sc-kgAjT fyrqRY"
+ className="sc-cJSrbW hStxoF"
>
<div
- className="sc-bbmXgH cDnIaX"
+ className="sc-gGBfsJ jUIZZs"
>
<button
- className="sc-gGBfsJ nhSkV"
+ className="sc-jnlKLf izRvRI"
onClick={[Function]}
>
Transactions
</button>
<button
- className="sc-gGBfsJ cUGrfZ"
+ className="sc-jnlKLf lblEKN"
onClick={[Function]}
>
eTokens
</button>
</div>
<div
- className="sc-jnlKLf bEDump"
+ className="sc-fYxtnH fiPSMX"
>
<div />
<span
@@ -65,10 +65,10 @@
to send to others
</div>
<div
- className="sc-jnlKLf iofjAr"
+ className="sc-fYxtnH hfLMVU"
>
<a
- className="sc-ktHwxA bInOhz"
+ className="sc-cIShpX fxEPvd"
href="/tokens"
onClick={[Function]}
>
@@ -87,15 +87,15 @@
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
<div
- className="sc-ksYbfQ EFpnD"
+ className="sc-hmzhuo cYAKWr"
>
<h4
- className="sc-uJMKN cKiOJP"
+ className="sc-bbmXgH cXxYjE"
>
MigrationTestAlpha
</h4>
<div
- className="sc-frDJqD eedyCO"
+ className="sc-kvZOFW cxOlCC"
>
0
@@ -103,26 +103,26 @@
</div>
</div>,
<div
- className="sc-kgAjT fyrqRY"
+ className="sc-cJSrbW hStxoF"
>
<div
- className="sc-bbmXgH cDnIaX"
+ className="sc-gGBfsJ jUIZZs"
>
<button
- className="sc-gGBfsJ nhSkV"
+ className="sc-jnlKLf izRvRI"
onClick={[Function]}
>
Transactions
</button>
<button
- className="sc-gGBfsJ cUGrfZ"
+ className="sc-jnlKLf lblEKN"
onClick={[Function]}
>
eTokens
</button>
</div>
<div
- className="sc-jnlKLf bEDump"
+ className="sc-fYxtnH fiPSMX"
>
<div />
<span
@@ -149,10 +149,10 @@
to send to others
</div>
<div
- className="sc-jnlKLf iofjAr"
+ className="sc-fYxtnH hfLMVU"
>
<a
- className="sc-ktHwxA bInOhz"
+ className="sc-cIShpX fxEPvd"
href="/tokens"
onClick={[Function]}
>
@@ -171,15 +171,15 @@
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
<div
- className="sc-ksYbfQ EFpnD"
+ className="sc-hmzhuo cYAKWr"
>
<h4
- className="sc-uJMKN cKiOJP"
+ className="sc-bbmXgH cXxYjE"
>
MigrationTestAlpha
</h4>
<div
- className="sc-frDJqD eedyCO"
+ className="sc-kvZOFW cxOlCC"
>
0.06
@@ -187,26 +187,26 @@
</div>
</div>,
<div
- className="sc-kgAjT fyrqRY"
+ className="sc-cJSrbW hStxoF"
>
<div
- className="sc-bbmXgH cDnIaX"
+ className="sc-gGBfsJ jUIZZs"
>
<button
- className="sc-gGBfsJ nhSkV"
+ className="sc-jnlKLf izRvRI"
onClick={[Function]}
>
Transactions
</button>
<button
- className="sc-gGBfsJ cUGrfZ"
+ className="sc-jnlKLf lblEKN"
onClick={[Function]}
>
eTokens
</button>
</div>
<div
- className="sc-jnlKLf bEDump"
+ className="sc-fYxtnH fiPSMX"
>
<div />
<span
@@ -233,10 +233,10 @@
to send to others
</div>
<div
- className="sc-jnlKLf iofjAr"
+ className="sc-fYxtnH hfLMVU"
>
<a
- className="sc-ktHwxA bInOhz"
+ className="sc-cIShpX fxEPvd"
href="/tokens"
onClick={[Function]}
>
@@ -274,15 +274,15 @@
exports[`Wallet without BCH balance 1`] = `
Array [
<div
- className="sc-ksYbfQ EFpnD"
+ className="sc-hmzhuo cYAKWr"
>
<h4
- className="sc-uJMKN cKiOJP"
+ className="sc-bbmXgH cXxYjE"
>
MigrationTestAlpha
</h4>
<div
- className="sc-frDJqD eedyCO"
+ className="sc-kvZOFW cxOlCC"
>
0
@@ -290,26 +290,26 @@
</div>
</div>,
<div
- className="sc-kgAjT fyrqRY"
+ className="sc-cJSrbW hStxoF"
>
<div
- className="sc-bbmXgH cDnIaX"
+ className="sc-gGBfsJ jUIZZs"
>
<button
- className="sc-gGBfsJ nhSkV"
+ className="sc-jnlKLf izRvRI"
onClick={[Function]}
>
Transactions
</button>
<button
- className="sc-gGBfsJ cUGrfZ"
+ className="sc-jnlKLf lblEKN"
onClick={[Function]}
>
eTokens
</button>
</div>
<div
- className="sc-jnlKLf bEDump"
+ className="sc-fYxtnH fiPSMX"
>
<div />
<span
@@ -336,10 +336,10 @@
to send to others
</div>
<div
- className="sc-jnlKLf iofjAr"
+ className="sc-fYxtnH hfLMVU"
>
<a
- className="sc-ktHwxA bInOhz"
+ className="sc-cIShpX fxEPvd"
href="/tokens"
onClick={[Function]}
>
diff --git a/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap b/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
--- a/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
+++ b/web/cashtab/src/components/Receive/__tests__/__snapshots__/Receive.test.js.snap
@@ -2,7 +2,7 @@
exports[`Wallet with BCH balances 1`] = `
<div
- className="sc-jAaTju kgZCSk"
+ className="sc-jDwBTQ hNLNlN"
>
<h2>
Receive
@@ -92,16 +92,16 @@
</div>
</div>
<div
- className="sc-jDwBTQ hksRrJ"
+ className="sc-gPEVay kBKxVA"
>
<div
- className="sc-gPEVay hSBCgm"
+ className="sc-iRbamj kypBeM"
onClick={[Function]}
>
XEC
</div>
<div
- className="sc-gPEVay hSBCgm nonactiveBtn"
+ className="sc-iRbamj kypBeM nonactiveBtn"
onClick={[Function]}
>
eToken
@@ -112,7 +112,7 @@
exports[`Wallet with BCH balances and tokens 1`] = `
<div
- className="sc-jAaTju kgZCSk"
+ className="sc-jDwBTQ hNLNlN"
>
<h2>
Receive
@@ -202,16 +202,16 @@
</div>
</div>
<div
- className="sc-jDwBTQ hksRrJ"
+ className="sc-gPEVay kBKxVA"
>
<div
- className="sc-gPEVay hSBCgm"
+ className="sc-iRbamj kypBeM"
onClick={[Function]}
>
XEC
</div>
<div
- className="sc-gPEVay hSBCgm nonactiveBtn"
+ className="sc-iRbamj kypBeM nonactiveBtn"
onClick={[Function]}
>
eToken
@@ -222,7 +222,7 @@
exports[`Wallet with BCH balances and tokens and state field 1`] = `
<div
- className="sc-jAaTju kgZCSk"
+ className="sc-jDwBTQ hNLNlN"
>
<h2>
Receive
@@ -312,16 +312,16 @@
</div>
</div>
<div
- className="sc-jDwBTQ hksRrJ"
+ className="sc-gPEVay kBKxVA"
>
<div
- className="sc-gPEVay hSBCgm"
+ className="sc-iRbamj kypBeM"
onClick={[Function]}
>
XEC
</div>
<div
- className="sc-gPEVay hSBCgm nonactiveBtn"
+ className="sc-iRbamj kypBeM nonactiveBtn"
onClick={[Function]}
>
eToken
@@ -332,7 +332,7 @@
exports[`Wallet without BCH balance 1`] = `
<div
- className="sc-jAaTju kgZCSk"
+ className="sc-jDwBTQ hNLNlN"
>
<h2>
Receive
@@ -422,16 +422,16 @@
</div>
</div>
<div
- className="sc-jDwBTQ hksRrJ"
+ className="sc-gPEVay kBKxVA"
>
<div
- className="sc-gPEVay hSBCgm"
+ className="sc-iRbamj kypBeM"
onClick={[Function]}
>
XEC
</div>
<div
- className="sc-gPEVay hSBCgm nonactiveBtn"
+ className="sc-iRbamj kypBeM nonactiveBtn"
onClick={[Function]}
>
eToken
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
@@ -8,15 +8,18 @@
Col,
Alert,
Descriptions,
- Popover,
Modal,
+ Button,
+ Input,
} from 'antd';
import PrimaryButton, {
SecondaryButton,
} from '@components/Common/PrimaryButton';
+import { FireTwoTone } from '@ant-design/icons';
import {
DestinationAmount,
DestinationAddressSingle,
+ AntdFormWrapper,
} from '@components/Common/EnhancedInputs';
import useBCH from '@hooks/useBCH';
import { SidePaddingCtn } from '@components/Common/Atoms';
@@ -34,13 +37,19 @@
import {
sendTokenNotification,
errorNotification,
+ burnTokenNotification,
} from '@components/Common/Notifications';
-import { isValidXecAddress, isValidEtokenAddress } from '@utils/validation';
+import {
+ isValidXecAddress,
+ isValidEtokenAddress,
+ isValidEtokenBurnAmount,
+} from '@utils/validation';
import { formatDate } from '@utils/formatting';
import styled, { css } from 'styled-components';
import TokenIcon from '@components/Tokens/TokenIcon';
const AntdDescriptionsCss = css`
.ant-descriptions-item-label,
+ .ant-input-number,
.ant-descriptions-item-content {
background-color: ${props => props.theme.contrast} !important;
color: ${props => props.theme.dropdownText};
@@ -61,6 +70,13 @@
const [queryStringText, setQueryStringText] = useState(null);
const [sendTokenAddressError, setSendTokenAddressError] = useState(false);
const [sendTokenAmountError, setSendTokenAmountError] = useState(false);
+ const [eTokenBurnAmount, setETokenBurnAmount] = useState(new BigNumber(1));
+ const [showConfirmBurnEtoken, setShowConfirmBurnEtoken] = useState(false);
+ const [burnTokenAmountError, setBurnTokenAmountError] = useState(false);
+ const [burnConfirmationValid, setBurnConfirmationValid] = useState(null);
+ const [confirmationOfEtokenToBeBurnt, setConfirmationOfEtokenToBeBurnt] =
+ useState('');
+
// Get device window width
// If this is less than 769, the page will open with QR scanner open
const { width } = useWindowDimensions();
@@ -73,7 +89,8 @@
address: '',
});
- const { getBCH, getRestUrl, sendToken, getTokenStats } = useBCH();
+ const { getBCH, getRestUrl, sendToken, getTokenStats, burnEtoken } =
+ useBCH();
// jestBCH is only ever specified for unit tests, otherwise app will use getBCH();
const BCH = jestBCH ? jestBCH : getBCH();
@@ -197,7 +214,6 @@
const addressInfo = parseAddressForParams(addressString);
/*
Model
-
addressInfo =
{
address: '',
@@ -265,6 +281,94 @@
setIsModalVisible(false);
};
+ const handleEtokenBurnAmountChange = e => {
+ const { value } = e.target;
+ const burnAmount = new BigNumber(value);
+ setETokenBurnAmount(burnAmount);
+
+ let error = false;
+ if (!isValidEtokenBurnAmount(burnAmount, token.balance)) {
+ error = 'Burn amount must be between 1 and ' + token.balance;
+ }
+
+ setBurnTokenAmountError(error);
+ };
+
+ const onMaxBurn = () => {
+ setETokenBurnAmount(token.balance);
+
+ // trigger validation on the inserted max value
+ handleEtokenBurnAmountChange({
+ target: {
+ value: token.balance,
+ },
+ });
+ };
+
+ async function burn() {
+ if (
+ !burnConfirmationValid ||
+ burnConfirmationValid === null ||
+ !eTokenBurnAmount
+ ) {
+ return;
+ }
+
+ // Event("Category", "Action", "Label")
+ Event('SendToken.js', 'Burn eToken', tokenId);
+
+ passLoadingStatus(true);
+
+ try {
+ const link = await burnEtoken(BCH, wallet, slpBalancesAndUtxos, {
+ tokenId: tokenId,
+ amount: eTokenBurnAmount,
+ });
+ burnTokenNotification(link);
+ clearInputForms();
+ setShowConfirmBurnEtoken(false);
+ passLoadingStatus(false);
+ setConfirmationOfEtokenToBeBurnt('');
+ } catch (e) {
+ setShowConfirmBurnEtoken(false);
+ passLoadingStatus(false);
+ setConfirmationOfEtokenToBeBurnt('');
+ let message;
+
+ if (!e.error && !e.message) {
+ message = `Transaction failed: no response from ${getRestUrl()}.`;
+ } else if (/dust/.test(e.error)) {
+ message = 'Unable to burn due to insufficient eToken utxos.';
+ } else if (
+ /Could not communicate with full node or other external service/.test(
+ e.error,
+ )
+ ) {
+ message = 'Could not communicate with API. Please try again.';
+ } else {
+ message = e.message || e.error || JSON.stringify(e);
+ }
+ errorNotification(e, message, 'Burning eToken');
+ }
+ }
+
+ const handleBurnConfirmationInput = e => {
+ const { value } = e.target;
+
+ if (value && value === `burn ${token.info.tokenTicker}`) {
+ setBurnConfirmationValid(true);
+ } else {
+ setBurnConfirmationValid(false);
+ }
+ setConfirmationOfEtokenToBeBurnt(value);
+ };
+
+ const handleBurnAmountInput = () => {
+ if (!burnTokenAmountError) {
+ setShowConfirmBurnEtoken(true);
+ }
+ };
+
useEffect(() => {
// If the balance has changed, unlock the UI
// This is redundant, if backend has refreshed in 1.75s timeout below, UI will already be unlocked
@@ -290,6 +394,45 @@
{!token && <Redirect to="/" />}
{token && (
<SidePaddingCtn>
+ {/* eToken burn modal */}
+ <Modal
+ title={`Are you sure you want to burn ${eTokenBurnAmount.toString()} x ${
+ token.info.tokenTicker
+ } eTokens?`}
+ visible={showConfirmBurnEtoken}
+ onOk={burn}
+ okText={'Confirm'}
+ onCancel={() => setShowConfirmBurnEtoken(false)}
+ >
+ <AntdFormWrapper>
+ <Form style={{ width: 'auto' }}>
+ <Form.Item
+ validateStatus={
+ burnConfirmationValid === null ||
+ burnConfirmationValid
+ ? ''
+ : 'error'
+ }
+ help={
+ burnConfirmationValid === null ||
+ burnConfirmationValid
+ ? ''
+ : 'Your confirmation phrase must match exactly'
+ }
+ >
+ <Input
+ prefix={<FireTwoTone />}
+ placeholder={`Type "burn ${token.info.tokenTicker}" to confirm`}
+ name="etokenToBeBurnt"
+ value={confirmationOfEtokenToBeBurnt}
+ onChange={e =>
+ handleBurnConfirmationInput(e)
+ }
+ />
+ </Form.Item>
+ </Form>
+ </AntdFormWrapper>
+ </Modal>
<BalanceHeader
balance={token.balance}
ticker={token.info.tokenTicker}
@@ -464,6 +607,51 @@
<Descriptions.Item label="Circulating Supply">
{tokenStats.circulatingSupply.toLocaleString()}
</Descriptions.Item>
+ <Descriptions.Item label="Burn eToken">
+ <DestinationAmount
+ validateStatus={
+ burnTokenAmountError
+ ? 'error'
+ : ''
+ }
+ help={
+ burnTokenAmountError
+ ? burnTokenAmountError
+ : ''
+ }
+ onMax={onMaxBurn}
+ inputProps={{
+ placeholder:
+ 'Amount',
+ suffix: token.info
+ .tokenTicker,
+ onChange: e =>
+ handleEtokenBurnAmountChange(
+ e,
+ ),
+ initialvalue: 1,
+ value: eTokenBurnAmount,
+ prefix: (
+ <TokenIcon
+ size={32}
+ tokenId={
+ tokenId
+ }
+ />
+ ),
+ }}
+ />
+ <Button
+ type="primary"
+ onClick={
+ handleBurnAmountInput
+ }
+ danger
+ >
+ Burn
+ {token.info.tokenTicker}
+ </Button>
+ </Descriptions.Item>
</>
)}
</Descriptions>
@@ -480,7 +668,6 @@
/*
passLoadingStatus must receive a default prop that is a function
in order to pass the rendering unit test in SendToken.test.js
-
status => {console.log(status)} is an arbitrary stub function
*/
diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
--- a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
+++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
@@ -3,15 +3,15 @@
exports[`Wallet with BCH balances 1`] = `
Array [
<div
- className="sc-eNQAEJ bPMOWW"
+ className="sc-hMqMXs kpJXFt"
>
<h4
- className="sc-jWBwVP fTCVMO"
+ className="sc-brqgnP leSzSb"
>
MigrationTestAlpha
</h4>
<div
- className="sc-kkGfuU ihmkkm"
+ className="sc-iAyFgw hxUtff"
>
You currently have 0
XEC
@@ -20,7 +20,7 @@
</div>
</div>,
<div
- className="sc-ckVGcZ jBULtA"
+ className="sc-jKJlTe iSWneW"
>
<div
className="ant-row"
@@ -43,10 +43,10 @@
}
>
<div
- className="sc-jDwBTQ jNUfeO"
+ className="sc-gPEVay jXcfMx"
>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Send to
</label>
@@ -165,7 +165,7 @@
</div>
</div>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Amount
</label>
@@ -346,17 +346,17 @@
</div>
</div>
<div
- className="sc-jAaTju kplAPX"
+ className="sc-jDwBTQ bSLSig"
>
<h3
- className="sc-gPEVay kbHIAs"
+ className="sc-iRbamj fwDCLP"
>
0
XEC
</h3>
<div
- className="sc-eHgmQL bSQRhv"
+ className="sc-cvbbAY cLZxXS"
>
=
@@ -474,15 +474,15 @@
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
<div
- className="sc-eNQAEJ bPMOWW"
+ className="sc-hMqMXs kpJXFt"
>
<h4
- className="sc-jWBwVP fTCVMO"
+ className="sc-brqgnP leSzSb"
>
MigrationTestAlpha
</h4>
<div
- className="sc-kkGfuU ihmkkm"
+ className="sc-iAyFgw hxUtff"
>
You currently have 0
XEC
@@ -491,7 +491,7 @@
</div>
</div>,
<div
- className="sc-ckVGcZ jBULtA"
+ className="sc-jKJlTe iSWneW"
>
<div
className="ant-row"
@@ -514,10 +514,10 @@
}
>
<div
- className="sc-jDwBTQ jNUfeO"
+ className="sc-gPEVay jXcfMx"
>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Send to
</label>
@@ -636,7 +636,7 @@
</div>
</div>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Amount
</label>
@@ -817,17 +817,17 @@
</div>
</div>
<div
- className="sc-jAaTju kplAPX"
+ className="sc-jDwBTQ bSLSig"
>
<h3
- className="sc-gPEVay kbHIAs"
+ className="sc-iRbamj fwDCLP"
>
0
XEC
</h3>
<div
- className="sc-eHgmQL bSQRhv"
+ className="sc-cvbbAY cLZxXS"
>
=
@@ -945,15 +945,15 @@
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
<div
- className="sc-eNQAEJ bPMOWW"
+ className="sc-hMqMXs kpJXFt"
>
<h4
- className="sc-jWBwVP fTCVMO"
+ className="sc-brqgnP leSzSb"
>
MigrationTestAlpha
</h4>
<div
- className="sc-kEYyzF eqAzMl"
+ className="sc-kkGfuU fwBSar"
>
0.06
@@ -961,7 +961,7 @@
</div>
</div>,
<div
- className="sc-ckVGcZ jBULtA"
+ className="sc-jKJlTe iSWneW"
>
<div
className="ant-row"
@@ -984,10 +984,10 @@
}
>
<div
- className="sc-jDwBTQ jNUfeO"
+ className="sc-gPEVay jXcfMx"
>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Send to
</label>
@@ -1106,7 +1106,7 @@
</div>
</div>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Amount
</label>
@@ -1287,17 +1287,17 @@
</div>
</div>
<div
- className="sc-jAaTju kplAPX"
+ className="sc-jDwBTQ bSLSig"
>
<h3
- className="sc-gPEVay kbHIAs"
+ className="sc-iRbamj fwDCLP"
>
0
XEC
</h3>
<div
- className="sc-eHgmQL bSQRhv"
+ className="sc-cvbbAY cLZxXS"
>
=
@@ -1416,15 +1416,15 @@
exports[`Wallet without BCH balance 1`] = `
Array [
<div
- className="sc-eNQAEJ bPMOWW"
+ className="sc-hMqMXs kpJXFt"
>
<h4
- className="sc-jWBwVP fTCVMO"
+ className="sc-brqgnP leSzSb"
>
MigrationTestAlpha
</h4>
<div
- className="sc-kkGfuU ihmkkm"
+ className="sc-iAyFgw hxUtff"
>
You currently have 0
XEC
@@ -1433,7 +1433,7 @@
</div>
</div>,
<div
- className="sc-ckVGcZ jBULtA"
+ className="sc-jKJlTe iSWneW"
>
<div
className="ant-row"
@@ -1456,10 +1456,10 @@
}
>
<div
- className="sc-jDwBTQ jNUfeO"
+ className="sc-gPEVay jXcfMx"
>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Send to
</label>
@@ -1578,7 +1578,7 @@
</div>
</div>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Amount
</label>
@@ -1759,17 +1759,17 @@
</div>
</div>
<div
- className="sc-jAaTju kplAPX"
+ className="sc-jDwBTQ bSLSig"
>
<h3
- className="sc-gPEVay kbHIAs"
+ className="sc-iRbamj fwDCLP"
>
0
XEC
</h3>
<div
- className="sc-eHgmQL bSQRhv"
+ className="sc-cvbbAY cLZxXS"
>
=
@@ -1887,10 +1887,10 @@
exports[`Without wallet defined 1`] = `
Array [
<div
- className="sc-eNQAEJ bPMOWW"
+ className="sc-hMqMXs kpJXFt"
>
<div
- className="sc-kkGfuU ihmkkm"
+ className="sc-iAyFgw hxUtff"
>
You currently have 0
XEC
@@ -1899,7 +1899,7 @@
</div>
</div>,
<div
- className="sc-ckVGcZ jBULtA"
+ className="sc-jKJlTe iSWneW"
>
<div
className="ant-row"
@@ -1922,10 +1922,10 @@
}
>
<div
- className="sc-jDwBTQ jNUfeO"
+ className="sc-gPEVay jXcfMx"
>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Send to
</label>
@@ -2044,7 +2044,7 @@
</div>
</div>
<label
- className="sc-jKJlTe biUgjm"
+ className="sc-eNQAEJ kESMRD"
>
Amount
</label>
@@ -2225,17 +2225,17 @@
</div>
</div>
<div
- className="sc-jAaTju kplAPX"
+ className="sc-jDwBTQ bSLSig"
>
<h3
- className="sc-gPEVay kbHIAs"
+ className="sc-iRbamj fwDCLP"
>
0
XEC
</h3>
<div
- className="sc-eHgmQL bSQRhv"
+ className="sc-cvbbAY cLZxXS"
>
=
diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap
--- a/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap
+++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/SendToken.test.js.snap
@@ -4,10 +4,10 @@
exports[`Wallet with BCH balances and tokens and state field 1`] = `
<div
- className="sc-kgoBCf hlSlAt"
+ className="sc-kGXeez djtubK"
>
<div
- className="sc-ckVGcZ lkrhUi"
+ className="sc-jKJlTe hrFzvR"
>
6.001
diff --git a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap
--- a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap
+++ b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/CreateTokenForm.test.js.snap
@@ -2,7 +2,7 @@
exports[`Wallet with BCH balances and tokens and state field 1`] = `
<div
- className="sc-cMljjf fowfXL"
+ className="sc-jAaTju hKYuOV"
>
<h3>
Create a Token
@@ -21,7 +21,7 @@
}
>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Token Name
</label>
@@ -64,7 +64,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Ticker
</label>
@@ -107,7 +107,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Decimals
</label>
@@ -150,7 +150,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Supply
</label>
@@ -193,7 +193,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Document URL
</label>
@@ -236,7 +236,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Add Image
</label>
diff --git a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap
--- a/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap
+++ b/web/cashtab/src/components/Tokens/__tests__/__snapshots__/Tokens.test.js.snap
@@ -3,15 +3,15 @@
exports[`Wallet with BCH balances 1`] = `
Array [
<div
- className="sc-kkGfuU hVVqAi"
+ className="sc-iAyFgw chWhWK"
>
<h4
- className="sc-jDwBTQ fiRxiA"
+ className="sc-gPEVay jiKoLH"
>
MigrationTestAlpha
</h4>
<div
- className="sc-hSdWYo bXDUps"
+ className="sc-eHgmQL znFwg"
>
0
@@ -19,17 +19,17 @@
</div>
</div>,
<div
- className="sc-hMqMXs kjfrok"
+ className="sc-kEYyzF gZfJyA"
>
<div
- className="sc-cMljjf fowfXL"
+ className="sc-jAaTju hKYuOV"
>
<h3>
Create a Token
</h3>
</div>
<p
- className="sc-jWBwVP dkPIf"
+ className="sc-brqgnP kPbEse"
>
You need at least
@@ -50,15 +50,15 @@
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
<div
- className="sc-kkGfuU hVVqAi"
+ className="sc-iAyFgw chWhWK"
>
<h4
- className="sc-jDwBTQ fiRxiA"
+ className="sc-gPEVay jiKoLH"
>
MigrationTestAlpha
</h4>
<div
- className="sc-hSdWYo bXDUps"
+ className="sc-eHgmQL znFwg"
>
0
@@ -66,17 +66,17 @@
</div>
</div>,
<div
- className="sc-hMqMXs kjfrok"
+ className="sc-kEYyzF gZfJyA"
>
<div
- className="sc-cMljjf fowfXL"
+ className="sc-jAaTju hKYuOV"
>
<h3>
Create a Token
</h3>
</div>
<p
- className="sc-jWBwVP dkPIf"
+ className="sc-brqgnP kPbEse"
>
You need at least
@@ -97,15 +97,15 @@
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
<div
- className="sc-kkGfuU hVVqAi"
+ className="sc-iAyFgw chWhWK"
>
<h4
- className="sc-jDwBTQ fiRxiA"
+ className="sc-gPEVay jiKoLH"
>
MigrationTestAlpha
</h4>
<div
- className="sc-hSdWYo bXDUps"
+ className="sc-eHgmQL znFwg"
>
0.06
@@ -113,10 +113,10 @@
</div>
</div>,
<div
- className="sc-hMqMXs kjfrok"
+ className="sc-kEYyzF gZfJyA"
>
<div
- className="sc-cMljjf fowfXL"
+ className="sc-jAaTju hKYuOV"
>
<h3>
Create a Token
@@ -135,7 +135,7 @@
}
>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Token Name
</label>
@@ -178,7 +178,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Ticker
</label>
@@ -221,7 +221,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Decimals
</label>
@@ -264,7 +264,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Supply
</label>
@@ -307,7 +307,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Document URL
</label>
@@ -350,7 +350,7 @@
</div>
</div>
<label
- className="sc-kEYyzF gSQyNg"
+ className="sc-kkGfuU ekDVdV"
>
Add Image
</label>
@@ -493,15 +493,15 @@
exports[`Wallet without BCH balance 1`] = `
Array [
<div
- className="sc-kkGfuU hVVqAi"
+ className="sc-iAyFgw chWhWK"
>
<h4
- className="sc-jDwBTQ fiRxiA"
+ className="sc-gPEVay jiKoLH"
>
MigrationTestAlpha
</h4>
<div
- className="sc-hSdWYo bXDUps"
+ className="sc-eHgmQL znFwg"
>
0
@@ -509,17 +509,17 @@
</div>
</div>,
<div
- className="sc-hMqMXs kjfrok"
+ className="sc-kEYyzF gZfJyA"
>
<div
- className="sc-cMljjf fowfXL"
+ className="sc-jAaTju hKYuOV"
>
<h3>
Create a Token
</h3>
</div>
<p
- className="sc-jWBwVP dkPIf"
+ className="sc-brqgnP kPbEse"
>
You need at least
@@ -540,10 +540,10 @@
exports[`Without wallet defined 1`] = `
Array [
<div
- className="sc-kkGfuU hVVqAi"
+ className="sc-iAyFgw chWhWK"
>
<div
- className="sc-hSdWYo bXDUps"
+ className="sc-eHgmQL znFwg"
>
0
@@ -551,17 +551,17 @@
</div>
</div>,
<div
- className="sc-hMqMXs kjfrok"
+ className="sc-kEYyzF gZfJyA"
>
<div
- className="sc-cMljjf fowfXL"
+ className="sc-jAaTju hKYuOV"
>
<h3>
Create a Token
</h3>
</div>
<p
- className="sc-jWBwVP dkPIf"
+ className="sc-brqgnP kPbEse"
>
You need at least
diff --git a/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js b/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js
--- a/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js
+++ b/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js
@@ -113,3 +113,21 @@
txid: '5adc33b5c0509b31c6da359177b19467c443bdc4dd37c283c0f87244c0ad63af',
},
];
+export const mockBurnEtokenTx = [
+ {
+ amountReceived: 0,
+ amountSent: 0,
+ blocktime: 1643286535,
+ confirmations: 3,
+ destinationAddress: undefined,
+ decryptionSuccess: false,
+ height: undefined,
+ isCashtabMessage: false,
+ isEncryptedMessage: false,
+ opReturnMessage: '',
+ outgoingTx: false,
+ replyAddress: null,
+ tokenTx: true,
+ txid: '8b569d64a7e51d1d3cf1cf2b99d8b34451bbebc7df6b67232e5b770418b0428c',
+ },
+];
diff --git a/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js b/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js
--- a/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js
+++ b/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js
@@ -869,4 +869,74 @@
time: 1635509252,
blocktime: 1635509252,
},
+ {
+ blockhash:
+ '0000000000000000005aa0636bbc6b0117417d1db091b911259023885b5c0d12',
+ blocktime: 1643286535,
+ confirmations: 3,
+ hash: '8b569d64a7e51d1d3cf1cf2b99d8b34451bbebc7df6b67232e5b770418b0428c',
+ hex: '0200000002682a61fe5fe1f6468f5ebd9b6bfecd87d37281709e44222583f1868912f9b9ea020000006b483045022100d9bcfbeb1f27565b4cd3609eded0ce36cbac6472aa4fba0551cb9f5eae7ca3460220712263d13a6033a133eb67a96dba1ee626cfc6024ccd66825963692e2abd1144412102394542bf928bc707dcc156acf72e87c9d2fef77eaefc5f6b836d9ceeb0fc6a3effffffff5d4905ee2f09b0c3c131fe1b862f9882c2b8d79c9abf4a0351bbd03bacffd40a020000006a473044022059c39ed0798da7a4788355120d09737468ab182940ec78c3de1a2a23995c99aa02201bde53d7155892a145966149eedba665fbe02475a34b15a84c5d9a3d4b787d97412102394542bf928bc707dcc156acf72e87c9d2fef77eaefc5f6b836d9ceeb0fc6a3effffffff030000000000000000376a04534c500001010453454e44200203c768a66eba24affb19db1375b19388b6a0f9e1103b772de4d9f8f63ba79e08000000000000196422020000000000001976a9140b7d35fda03544a08e65464d54cfae4257eb6db788aca80a0000000000001976a9140b7d35fda03544a08e65464d54cfae4257eb6db788ac00000000',
+ locktime: 0,
+ size: 437,
+ time: 1643286535,
+ txid: '8b569d64a7e51d1d3cf1cf2b99d8b34451bbebc7df6b67232e5b770418b0428c',
+ version: 2,
+ vin: [
+ {
+ scriptSig: {
+ asm: '3045022100d9bcfbeb1f27565b4cd3609eded0ce36cbac6472aa4fba0551cb9f5eae7ca3460220712263d13a6033a133eb67a96dba1ee626cfc6024ccd66825963692e2abd1144[ALL|FORKID] 02394542bf928bc707dcc156acf72e87c9d2fef77eaefc5f6b836d9ceeb0fc6a3e',
+ hex: '483045022100d9bcfbeb1f27565b4cd3609eded0ce36cbac6472aa4fba0551cb9f5eae7ca3460220712263d13a6033a133eb67a96dba1ee626cfc6024ccd66825963692e2abd1144412102394542bf928bc707dcc156acf72e87c9d2fef77eaefc5f6b836d9ceeb0fc6a3e',
+ },
+ sequence: 4294967295,
+ txid: 'eab9f9128986f1832522449e708172d387cdfe6b9bbd5e8f46f6e15ffe612a68',
+ vout: 2,
+ },
+ {
+ scriptSig: {
+ asm: '3044022059c39ed0798da7a4788355120d09737468ab182940ec78c3de1a2a23995c99aa02201bde53d7155892a145966149eedba665fbe02475a34b15a84c5d9a3d4b787d97[ALL|FORKID] 02394542bf928bc707dcc156acf72e87c9d2fef77eaefc5f6b836d9ceeb0fc6a3e',
+ hex: '473044022059c39ed0798da7a4788355120d09737468ab182940ec78c3de1a2a23995c99aa02201bde53d7155892a145966149eedba665fbe02475a34b15a84c5d9a3d4b787d97412102394542bf928bc707dcc156acf72e87c9d2fef77eaefc5f6b836d9ceeb0fc6a3e',
+ },
+ sequence: 4294967295,
+ txid: '0ad4ffac3bd0bb51034abf9a9cd7b8c282982f861bfe31c1c3b0092fee05495d',
+ vout: 2,
+ },
+ ],
+ vout: [
+ {
+ n: 0,
+ scriptPubKey: {
+ asm: 'OP_RETURN 5262419 1 1145980243 0203c768a66eba24affb19db1375b19388b6a0f9e1103b772de4d9f8f63ba79e 0000000000001964',
+ hex: '6a04534c500001010453454e44200203c768a66eba24affb19db1375b19388b6a0f9e1103b772de4d9f8f63ba79e080000000000001964',
+ type: 'nulldata',
+ },
+ value: 0,
+ },
+ {
+ n: 1,
+ scriptPubKey: {
+ addresses: [
+ 'bitcoincash:qq9h6d0a5q65fgywv4ry64x04ep906mdku7ymranw3',
+ ],
+ asm: 'OP_DUP OP_HASH160 0b7d35fda03544a08e65464d54cfae4257eb6db7 OP_EQUALVERIFY OP_CHECKSIG',
+ hex: '76a9140b7d35fda03544a08e65464d54cfae4257eb6db788ac',
+ reqSigs: 1,
+ type: 'pubkeyhash',
+ },
+ value: 0.00000546,
+ },
+ {
+ n: 2,
+ scriptPubKey: {
+ addresses: [
+ 'bitcoincash:qq9h6d0a5q65fgywv4ry64x04ep906mdku7ymranw3',
+ ],
+ asm: 'OP_DUP OP_HASH160 0b7d35fda03544a08e65464d54cfae4257eb6db7 OP_EQUALVERIFY OP_CHECKSIG',
+ hex: '76a9140b7d35fda03544a08e65464d54cfae4257eb6db788ac',
+ reqSigs: 1,
+ type: 'pubkeyhash',
+ },
+ value: 0.00002728,
+ },
+ ],
+ },
];
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 @@
mockReceivedTokenTx,
mockSentOpReturnMessageTx,
mockReceivedOpReturnMessageTx,
+ mockBurnEtokenTx,
} from '../__mocks__/mockParsedTxs';
import BCHJS from '@psf/bch-js'; // TODO: should be removed when external lib not needed anymore
import { currency } from '../../components/Common/Ticker';
@@ -293,6 +294,27 @@
expect(nullValuesSendBch).toBe(null);
});
+ it("throws error attempting to burn an eToken ID that is not within the wallet's utxo", async () => {
+ const { burnEtoken } = useBCH();
+ const BCH = new BCHJS();
+ const { wallet } = sendBCHMock;
+ const burnAmount = 10;
+ const eTokenId = '0203c768a66eba24affNOTVALID103b772de4d9f8f63ba79e';
+ const expectedError =
+ 'No token UTXOs for the specified token could be found.';
+
+ let thrownError;
+ try {
+ await burnEtoken(BCH, wallet, mockReturnGetSlpBalancesAndUtxos, {
+ eTokenId,
+ burnAmount,
+ });
+ } catch (err) {
+ thrownError = err;
+ }
+ expect(thrownError).toStrictEqual(new Error(expectedError));
+ });
+
it('receives errors from the network and parses it', async () => {
const { sendXec } = useBCH();
const BCH = new BCHJS();
@@ -458,6 +480,18 @@
).toStrictEqual(mockSentTokenTx);
});
+ it(`Correctly parses a "burn ${currency.tokenTicker}" transaction`, async () => {
+ const { parseTxData } = useBCH();
+ const BCH = new BCHJS();
+ expect(
+ await parseTxData(
+ BCH,
+ [mockTxDataWithPassthrough[13]],
+ mockPublicKeys,
+ ),
+ ).toStrictEqual(mockBurnEtokenTx);
+ });
+
it(`Correctly parses a "receive ${currency.tokenTicker}" transaction`, async () => {
const { parseTxData } = useBCH();
const BCH = new BCHJS();
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
@@ -1152,6 +1152,162 @@
return link;
};
+ const burnEtoken = async (
+ BCH,
+ wallet,
+ slpBalancesAndUtxos,
+ { tokenId, amount },
+ ) => {
+ // Handle error of user having no XEC
+ if (slpBalancesAndUtxos.nonSlpUtxos.length === 0) {
+ throw new Error(`You need some ${currency.ticker} to burn eTokens`);
+ }
+ const largestBchUtxo = slpBalancesAndUtxos.nonSlpUtxos.reduce(
+ (previous, current) =>
+ previous.value > current.value ? previous : current,
+ );
+
+ const bchECPair = BCH.ECPair.fromWIF(largestBchUtxo.wif);
+ const tokenUtxos = slpBalancesAndUtxos.slpUtxos.filter(utxo => {
+ if (
+ utxo && // UTXO is associated with a token.
+ utxo.tokenId === tokenId && // UTXO matches the token ID.
+ utxo.utxoType === 'token' // UTXO is not a minting baton.
+ ) {
+ return true;
+ }
+ return false;
+ });
+
+ if (tokenUtxos.length === 0) {
+ throw new Error(
+ 'No token UTXOs for the specified token could be found.',
+ );
+ }
+
+ // BEGIN transaction construction.
+
+ // instance of transaction builder
+ let transactionBuilder;
+ if (process.env.REACT_APP_NETWORK === 'mainnet') {
+ transactionBuilder = new BCH.TransactionBuilder();
+ } else transactionBuilder = new BCH.TransactionBuilder('testnet');
+
+ const originalAmount = largestBchUtxo.value;
+ transactionBuilder.addInput(
+ largestBchUtxo.tx_hash,
+ largestBchUtxo.tx_pos,
+ );
+
+ let finalTokenAmountBurnt = new BigNumber(0);
+ let tokenAmountBeingBurnt = new BigNumber(amount);
+
+ let tokenUtxosBeingBurnt = [];
+ for (let i = 0; i < tokenUtxos.length; i++) {
+ finalTokenAmountBurnt = finalTokenAmountBurnt.plus(
+ new BigNumber(tokenUtxos[i].tokenQty),
+ );
+ transactionBuilder.addInput(
+ tokenUtxos[i].tx_hash,
+ tokenUtxos[i].tx_pos,
+ );
+ tokenUtxosBeingBurnt.push(tokenUtxos[i]);
+ if (tokenAmountBeingBurnt.lte(finalTokenAmountBurnt)) {
+ break;
+ }
+ }
+
+ const slpBurnObj = BCH.SLP.TokenType1.generateBurnOpReturn(
+ tokenUtxosBeingBurnt,
+ tokenAmountBeingBurnt,
+ );
+
+ if (!slpBurnObj) {
+ throw new Error(`Invalid eToken burn transaction.`);
+ }
+
+ // Add OP_RETURN as first output.
+ transactionBuilder.addOutput(slpBurnObj, 0);
+
+ // Send dust transaction representing tokens being burnt.
+ transactionBuilder.addOutput(
+ BCH.SLP.Address.toLegacyAddress(largestBchUtxo.address),
+ currency.etokenSats,
+ );
+
+ // get byte count to calculate fee. paying 1 sat
+ const txFee = calcFee(
+ BCH,
+ tokenUtxosBeingBurnt,
+ 3,
+ currency.defaultFee,
+ );
+
+ // amount to send back to the address requesting the burn. It's the original amount - 1 sat/byte for tx size
+ const remainder = originalAmount - txFee - currency.etokenSats * 2;
+ if (remainder < 1) {
+ throw new Error('Selected UTXO does not have enough satoshis');
+ }
+
+ // Send it back from whence it came
+ transactionBuilder.addOutput(
+ BCH.Address.toLegacyAddress(largestBchUtxo.address),
+ remainder,
+ );
+
+ // Sign the transaction with the private key for the XEC UTXO paying the fees.
+ let redeemScript;
+ transactionBuilder.sign(
+ 0,
+ bchECPair,
+ redeemScript,
+ transactionBuilder.hashTypes.SIGHASH_ALL,
+ originalAmount,
+ );
+
+ // Sign each token UTXO being consumed.
+ for (let i = 0; i < tokenUtxosBeingBurnt.length; i++) {
+ const thisUtxo = tokenUtxosBeingBurnt[i];
+ const accounts = [wallet.Path245, wallet.Path145, wallet.Path1899];
+ const utxoEcPair = BCH.ECPair.fromWIF(
+ accounts
+ .filter(acc => acc.cashAddress === thisUtxo.address)
+ .pop().fundingWif,
+ );
+
+ transactionBuilder.sign(
+ 1 + i,
+ utxoEcPair,
+ redeemScript,
+ transactionBuilder.hashTypes.SIGHASH_ALL,
+ thisUtxo.value,
+ );
+ }
+
+ // build tx
+ const tx = transactionBuilder.build();
+
+ // output rawhex
+ const hex = tx.toHex();
+ // console.log(`Transaction raw hex: `, hex);
+
+ // END transaction construction.
+
+ const txidStr = await BCH.RawTransactions.sendRawTransaction([hex]);
+ if (txidStr && txidStr[0]) {
+ console.log(`${currency.tokenTicker} txid`, txidStr[0]);
+ }
+
+ let link;
+ if (process.env.REACT_APP_NETWORK === `mainnet`) {
+ link = `${currency.tokenExplorerUrl}/tx/${txidStr}`;
+ } else {
+ link = `${currency.blockExplorerUrlTestnet}/tx/${txidStr}`;
+ }
+
+ return link;
+ };
+
const signPkMessage = async (BCH, pk, message) => {
try {
let signature = await BCH.BitcoinCash.signMessageWithPrivKey(
@@ -1514,5 +1670,6 @@
getTokenStats,
handleEncryptedOpReturn,
getRecipientPublicKey,
+ burnEtoken,
};
}
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
@@ -14,6 +14,7 @@
isValidSendToMany,
isValidUtxo,
isValidBchApiUtxoObject,
+ isValidEtokenBurnAmount,
} from '../validation';
import { currency } from '@components/Common/Ticker.js';
import { fromSmallestDenomination } from '@utils/cashMethods';
@@ -441,4 +442,36 @@
it(`isValidBchApiUtxoObject returns true for valid bch-api utxos object`, () => {
expect(isValidBchApiUtxoObject(utxosAfterSentTxIncremental)).toBe(true);
});
+ it(`isValidEtokenBurnAmount rejects null`, () => {
+ const testEtokenBurnAmount = null;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount)).toBe(false);
+ });
+ it(`isValidEtokenBurnAmount rejects undefined`, () => {
+ const testEtokenBurnAmount = undefined;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount)).toBe(false);
+ });
+ it(`isValidEtokenBurnAmount rejects a burn amount that is 0`, () => {
+ const testEtokenBurnAmount = 0;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount, 100)).toBe(false);
+ });
+ it(`isValidEtokenBurnAmount rejects a burn amount that is negative`, () => {
+ const testEtokenBurnAmount = -50;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount, 100)).toBe(false);
+ });
+ it(`isValidEtokenBurnAmount rejects a burn amount that is more than the maxAmount param`, () => {
+ const testEtokenBurnAmount = 1000;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount, 100)).toBe(false);
+ });
+ it(`isValidEtokenBurnAmount accepts a valid burn amount`, () => {
+ const testEtokenBurnAmount = 50;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount, 100)).toBe(true);
+ });
+ it(`isValidEtokenBurnAmount accepts a valid burn amount with decimal points`, () => {
+ const testEtokenBurnAmount = 10.545454;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount, 100)).toBe(true);
+ });
+ it(`isValidEtokenBurnAmount accepts a valid burn amount that is the same as the maxAmount`, () => {
+ const testEtokenBurnAmount = 100;
+ expect(isValidEtokenBurnAmount(testEtokenBurnAmount, 100)).toBe(true);
+ });
});
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
@@ -304,3 +304,15 @@
return isValidBchApiUtxoObject;
};
+
+export const isValidEtokenBurnAmount = (tokenBurnAmount, maxAmount) => {
+ // A valid eToken burn amount must be between 1 and the wallet's token balance
+ return (
+ tokenBurnAmount !== null &&
+ maxAmount !== null &&
+ typeof tokenBurnAmount !== 'undefined' &&
+ typeof maxAmount !== 'undefined' &&
+ new BigNumber(tokenBurnAmount).gt(0) &&
+ new BigNumber(tokenBurnAmount).lte(maxAmount)
+ );
+};
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, May 12, 01:44 (3 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5777057
Default Alt Text
D10912.id.diff (58 KB)
Attached To
D10912: [Cashtab] Token burn functionality
Event Timeline
Log In to Comment