Page MenuHomePhabricator

D10912.id.diff
No OneTemporary

D10912.id.diff

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&nbsp;
+ {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

Mime Type
text/plain
Expires
Mon, May 12, 01:44 (21 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5777057
Default Alt Text
D10912.id.diff (58 KB)

Event Timeline