diff --git a/web/cashtab/src/components/Common/Ticker.js b/web/cashtab/src/components/Common/Ticker.js
index 509be36bb..ea4d4a5e3 100644
--- a/web/cashtab/src/components/Common/Ticker.js
+++ b/web/cashtab/src/components/Common/Ticker.js
@@ -1,202 +1,260 @@
import mainLogo from '@assets/logo_primary.png';
import tokenLogo from '@assets/logo_secondary.png';
import cashaddr from 'ecashaddrjs';
import BigNumber from 'bignumber.js';
export const currency = {
name: 'eCash',
ticker: 'XEC',
appUrl: 'cashtab.com',
logo: mainLogo,
legacyPrefix: 'bitcoincash',
prefixes: ['ecash'],
coingeckoId: 'ecash',
defaultFee: 2.01,
dustSats: 550,
etokenSats: 546,
cashDecimals: 2,
blockExplorerUrl: 'https://explorer.bitcoinabc.org',
tokenExplorerUrl: 'https://explorer.be.cash',
blockExplorerUrlTestnet: 'https://texplorer.bitcoinabc.org',
tokenName: 'eToken',
tokenTicker: 'eToken',
tokenIconSubmitApi: 'https://icons.etokens.cash/new',
tokenLogo: tokenLogo,
tokenPrefixes: ['etoken'],
tokenIconsUrl: 'https://etoken-icons.s3.us-west-2.amazonaws.com/32',
txHistoryCount: 5,
hydrateUtxoBatchSize: 20,
defaultSettings: { fiatCurrency: 'usd' },
notificationDurationShort: 3,
notificationDurationLong: 5,
newTokenDefaultUrl: 'https://cashtab.com/',
+ opReturn: {
+ opReturnPrefixHex: '6a',
+ opReturnPushDataHex: '04',
+ opReturnAppPrefixLengthHex: '04',
+ appPrefixesHex: {
+ eToken: '534c5000',
+ cashtab: '00746162',
+ },
+ },
settingsValidation: {
fiatCurrency: [
'usd',
'idr',
'krw',
'cny',
'zar',
'vnd',
'cad',
'nok',
'eur',
'gbp',
'jpy',
'try',
'rub',
'inr',
'brl',
'php',
'ils',
'clp',
],
},
fiatCurrencies: {
usd: { name: 'US Dollar', symbol: '$', slug: 'usd' },
brl: { name: 'Brazilian Real', symbol: 'R$', slug: 'brl' },
gbp: { name: 'British Pound', symbol: '£', slug: 'gbp' },
cad: { name: 'Canadian Dollar', symbol: '$', slug: 'cad' },
clp: { name: 'Chilean Peso', symbol: '$', slug: 'clp' },
cny: { name: 'Chinese Yuan', symbol: '元', slug: 'cny' },
eur: { name: 'Euro', symbol: '€', slug: 'eur' },
inr: { name: 'Indian Rupee', symbol: '₹', slug: 'inr' },
idr: { name: 'Indonesian Rupiah', symbol: 'Rp', slug: 'idr' },
ils: { name: 'Israeli Shekel', symbol: '₪', slug: 'ils' },
jpy: { name: 'Japanese Yen', symbol: '¥', slug: 'jpy' },
krw: { name: 'Korean Won', symbol: '₩', slug: 'krw' },
nok: { name: 'Norwegian Krone', symbol: 'kr', slug: 'nok' },
php: { name: 'Philippine Peso', symbol: '₱', slug: 'php' },
rub: { name: 'Russian Ruble', symbol: 'р.', slug: 'rub' },
zar: { name: 'South African Rand', symbol: 'R', slug: 'zar' },
try: { name: 'Turkish Lira', symbol: '₺', slug: 'try' },
vnd: { name: 'Vietnamese đồng', symbol: 'đ', slug: 'vnd' },
},
};
+export function getETokenEncodingSubstring() {
+ let encodingStr =
+ currency.opReturn.opReturnPrefixHex + // 6a
+ currency.opReturn.opReturnAppPrefixLengthHex + // 04
+ currency.opReturn.appPrefixesHex.eToken; // 534c5000
+
+ return encodingStr;
+}
+
+export function getCashtabEncodingSubstring() {
+ let encodingStr =
+ currency.opReturn.opReturnPrefixHex + // 6a
+ currency.opReturn.opReturnAppPrefixLengthHex + // 04
+ currency.opReturn.appPrefixesHex.cashtab; // 00746162
+
+ return encodingStr;
+}
+
+export function isCashtabOutput(hexStr) {
+ if (!hexStr || typeof hexStr !== 'string') {
+ return false;
+ }
+ return hexStr.startsWith(getCashtabEncodingSubstring());
+}
+
+export function isEtokenOutput(hexStr) {
+ if (!hexStr || typeof hexStr !== 'string') {
+ return false;
+ }
+ return hexStr.startsWith(getETokenEncodingSubstring());
+}
+
+export function extractCashtabMessage(hexSubstring) {
+ if (!hexSubstring || typeof hexSubstring !== 'string') {
+ return '';
+ }
+ let substring = hexSubstring.replace(getCashtabEncodingSubstring(), ''); // remove the cashtab encoding
+ substring = substring.slice(2); // remove the 2 bytes indicating the size of the next element on the stack e.g. a0 -> 160 bytes
+ return substring;
+}
+
+export function extractExternalMessage(hexSubstring) {
+ if (!hexSubstring || typeof hexSubstring !== 'string') {
+ return '';
+ }
+ let substring = hexSubstring.slice(4); // remove the preceding OP_RETURN prefixes
+ return substring;
+}
+
export function isValidCashPrefix(addressString) {
// Note that this function validates prefix only
// Check for prefix included in currency.prefixes array
// For now, validation is handled by converting to bitcoincash: prefix and checksum
// and relying on legacy validation methods of bitcoincash: prefix addresses
// Also accept an address with no prefix, as some exchanges provide these
for (let i = 0; i < currency.prefixes.length; i += 1) {
// If the addressString being tested starts with an accepted prefix or no prefix at all
if (
addressString.startsWith(currency.prefixes[i] + ':') ||
!addressString.includes(':')
) {
return true;
}
}
return false;
}
export function isValidTokenPrefix(addressString) {
// Check for prefix included in currency.tokenPrefixes array
// For now, validation is handled by converting to simpleledger: prefix and checksum
// and relying on legacy validation methods of simpleledger: prefix addresses
// For token addresses, do not accept an address with no prefix
for (let i = 0; i < currency.tokenPrefixes.length; i += 1) {
if (addressString.startsWith(currency.tokenPrefixes[i] + ':')) {
return true;
}
}
return false;
}
export function toLegacy(address) {
let testedAddress;
let legacyAddress;
try {
if (isValidCashPrefix(address)) {
// Prefix-less addresses may be valid, but the cashaddr.decode function used below
// will throw an error without a prefix. Hence, must ensure prefix to use that function.
const hasPrefix = address.includes(':');
if (!hasPrefix) {
testedAddress = currency.legacyPrefix + ':' + address;
} else {
testedAddress = address;
}
// Note: an `ecash:` checksum address with no prefix will not be validated by
// parseAddress in Send.js
// Only handle the case of prefixless address that is valid `bitcoincash:` address
const { type, hash } = cashaddr.decode(testedAddress);
legacyAddress = cashaddr.encode(currency.legacyPrefix, type, hash);
} else {
console.log(`Error: ${address} is not a cash address`);
throw new Error(
'Address prefix is not a valid cash address with a prefix from the Ticker.prefixes array',
);
}
} catch (err) {
return err;
}
return legacyAddress;
}
export function parseAddress(BCH, addressString, isToken = false) {
// Build return obj
const addressInfo = {
address: '',
isValid: false,
queryString: null,
amount: null,
};
// Parse address string for parameters
const paramCheck = addressString.split('?');
let cleanAddress = paramCheck[0];
addressInfo.address = cleanAddress;
// Validate address
let isValidAddress;
try {
isValidAddress = BCH.Address.isCashAddress(cleanAddress);
// Only accept addresses with ecash: prefix
const { prefix } = cashaddr.decode(cleanAddress);
// If the address does not have a valid prefix or token prefix
if (
(!isToken && !currency.prefixes.includes(prefix)) ||
(isToken && !currency.tokenPrefixes.includes(prefix))
) {
// then it is not a valid destination address for XEC sends
isValidAddress = false;
}
} catch (err) {
isValidAddress = false;
}
addressInfo.isValid = isValidAddress;
// Check for parameters
// only the amount param is currently supported
let queryString = null;
let amount = null;
if (paramCheck.length > 1) {
queryString = paramCheck[1];
addressInfo.queryString = queryString;
const addrParams = new URLSearchParams(queryString);
if (addrParams.has('amount')) {
// Amount in satoshis
try {
amount = new BigNumber(parseInt(addrParams.get('amount')))
.div(10 ** currency.cashDecimals)
.toString();
} catch (err) {
amount = null;
}
}
}
addressInfo.amount = amount;
return addressInfo;
}
diff --git a/web/cashtab/src/components/Common/__tests__/Ticker.test.js b/web/cashtab/src/components/Common/__tests__/Ticker.test.js
index 9d7aa3cba..97f978f22 100644
--- a/web/cashtab/src/components/Common/__tests__/Ticker.test.js
+++ b/web/cashtab/src/components/Common/__tests__/Ticker.test.js
@@ -1,108 +1,191 @@
import { ValidationError } from 'ecashaddrjs';
-import { isValidCashPrefix, isValidTokenPrefix, toLegacy } from '../Ticker';
+import {
+ isValidCashPrefix,
+ isValidTokenPrefix,
+ toLegacy,
+ isCashtabOutput,
+ isEtokenOutput,
+ extractCashtabMessage,
+ extractExternalMessage,
+ getETokenEncodingSubstring,
+ getCashtabEncodingSubstring,
+} from '../Ticker';
test('Rejects cash address with bitcoincash: prefix', async () => {
const result = isValidCashPrefix(
'bitcoincash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gjykk3wa0',
);
expect(result).toStrictEqual(false);
});
test('Correctly validates cash address with bitcoincash: checksum but no prefix', async () => {
const result = isValidCashPrefix(
'qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
);
expect(result).toStrictEqual(true);
});
test('Correctly validates cash address with ecash: checksum but no prefix', async () => {
const result = isValidCashPrefix(
'qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25mc',
);
expect(result).toStrictEqual(true);
});
test('Correctly validates cash address with ecash: prefix', async () => {
const result = isValidCashPrefix(
'ecash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25mc',
);
expect(result).toStrictEqual(true);
});
test('Rejects token address with simpleledger: prefix', async () => {
const result = isValidTokenPrefix(
'simpleledger:qpmytrdsakt0axrrlswvaj069nat3p9s7c8w5tu8gm',
);
expect(result).toStrictEqual(false);
});
test('Does not accept a valid token address without a prefix', async () => {
const result = isValidTokenPrefix(
'qpmytrdsakt0axrrlswvaj069nat3p9s7c8w5tu8gm',
);
expect(result).toStrictEqual(false);
});
test('Correctly validates token address with etoken: prefix (prefix only, not checksum)', async () => {
const result = isValidTokenPrefix(
'etoken:qpmytrdsakt0axrrlswvaj069nat3p9s7c8w5tu8gm',
);
expect(result).toStrictEqual(true);
});
test('Recognizes unaccepted token prefix (prefix only, not checksum)', async () => {
const result = isValidTokenPrefix(
'wtftoken:qpmytrdsakt0axrrlswvaj069nat3p9s7c8w5tu8gm',
);
expect(result).toStrictEqual(false);
});
test('Knows that acceptable cash prefixes are not tokens', async () => {
const result = isValidTokenPrefix(
'ecash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25mc',
);
expect(result).toStrictEqual(false);
});
test('Address with unlisted prefix is invalid', async () => {
const result = isValidCashPrefix(
'ecashdoge:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25mc',
);
expect(result).toStrictEqual(false);
});
test('toLegacy() converts a valid ecash: prefix address to a valid bitcoincash: prefix address', async () => {
const result = toLegacy('ecash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25mc');
expect(result).toStrictEqual(
'bitcoincash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gjykk3wa0',
);
});
test('toLegacy() accepts a valid BCH address with no prefix and returns with prefix', async () => {
const result = toLegacy('qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gjykk3wa0');
expect(result).toStrictEqual(
'bitcoincash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gjykk3wa0',
);
});
test('toLegacy throws error if input address has invalid checksum', async () => {
const result = toLegacy('ecash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25m');
expect(result).toStrictEqual(
new ValidationError(
'Invalid checksum: ecash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25m.',
),
);
});
test('toLegacy throws error if input address has invalid prefix', async () => {
const result = toLegacy(
'notecash:qqd3qn4zazjhygk5a2vzw2gvqgqwempr4gtfza25mc',
);
expect(result).toStrictEqual(
new Error(
'Address prefix is not a valid cash address with a prefix from the Ticker.prefixes array',
),
);
});
+
+test('getCashtabEncodingSubstring() returns the appropriate substring for cashtab message outputs', async () => {
+ const result = getCashtabEncodingSubstring();
+ expect(result).toStrictEqual('6a0400746162');
+});
+
+test('getETokenEncodingSubstring() returns the appropriate substring for eToken outputs', async () => {
+ const result = getETokenEncodingSubstring();
+ expect(result).toStrictEqual('6a04534c5000');
+});
+
+test('isCashtabOutput() correctly validates a cashtab message output hex', async () => {
+ const result = isCashtabOutput('6a04007461620b63617368746162756c6172');
+ expect(result).toStrictEqual(true);
+});
+
+test('isCashtabOutput() correctly invalidates an external message output hex', async () => {
+ const result = isCashtabOutput('6a0c7069616e6f74656e6e697332');
+ expect(result).toStrictEqual(false);
+});
+
+test('isCashtabOutput() correctly handles null input', async () => {
+ const result = isCashtabOutput(null);
+ expect(result).toStrictEqual(false);
+});
+
+test('isCashtabOutput() correctly handles non-string input', async () => {
+ const result = isCashtabOutput(7623723323);
+ expect(result).toStrictEqual(false);
+});
+
+test('isCashtabOutput() correctly invalidates an external message output hex', async () => {
+ const result = isCashtabOutput(
+ '6a202731afddf3b83747943f0e650b938ea0670dcae2e08c415f53bd4c6acfd15e09',
+ );
+ expect(result).toStrictEqual(false);
+});
+
+test('isEtokenOutput() correctly validates an eToken output hex', async () => {
+ const result = isEtokenOutput(
+ '6a04534c500001010453454e442069b8431ddecf775393b1b36aa1d0ddcd7b342f1157b9671a03747378ed35ea0d08000000000000012c080000000000002008',
+ );
+ expect(result).toStrictEqual(true);
+});
+
+test('isEtokenOutput() correctly invalidates an eToken output hex', async () => {
+ const result = isEtokenOutput(
+ '5434c500001010453454e442069b8431ddecf775393b1b36aa1d0ddcd7b342f1157b9671a03747378ed35ea0d08000000000000012c080000000000002008',
+ );
+ expect(result).toStrictEqual(false);
+});
+
+test('isEtokenOutput() correctly handles null input', async () => {
+ const result = isEtokenOutput(null);
+ expect(result).toStrictEqual(false);
+});
+
+test('isEtokenOutput() correctly handles non-string input', async () => {
+ const result = isEtokenOutput(7623723323);
+ expect(result).toStrictEqual(false);
+});
+
+test('extractCashtabMessage() correctly extracts a Cashtab message', async () => {
+ const result = extractCashtabMessage(
+ '6a04007461620b63617368746162756c6172',
+ );
+ expect(result).toStrictEqual('63617368746162756c6172');
+});
+
+test('extractExternalMessage() correctly extracts an external message', async () => {
+ const result = extractExternalMessage('6a0d62696e676f656c65637472756d');
+ expect(result).toStrictEqual('62696e676f656c65637472756d');
+});
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
index 1f2ac3565..797543c54 100644
--- a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
+++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap
@@ -1,2199 +1,2199 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
Signatures
,
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
Signatures
,
,
]
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
0.06
XEC
,
$
NaN
USD
,
,
Signatures
,
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
Signatures
,
,
]
`;
exports[`Without wallet defined 1`] = `
Array [
You currently have 0
XEC
Deposit some funds to use this feature
,
,
Signatures
,
,
]
`;
diff --git a/web/cashtab/src/components/Wallet/Tx.js b/web/cashtab/src/components/Wallet/Tx.js
index d3e88ee33..97949c4b0 100644
--- a/web/cashtab/src/components/Wallet/Tx.js
+++ b/web/cashtab/src/components/Wallet/Tx.js
@@ -1,404 +1,423 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import {
ArrowUpOutlined,
ArrowDownOutlined,
ExperimentOutlined,
ExclamationOutlined,
} from '@ant-design/icons';
import { currency } from '@components/Common/Ticker';
import makeBlockie from 'ethereum-blockies-base64';
import { Img } from 'react-image';
import { formatBalance, fromLegacyDecimals } from '@utils/cashMethods';
const SentTx = styled(ArrowUpOutlined)`
color: ${props => props.theme.secondary} !important;
`;
const ReceivedTx = styled(ArrowDownOutlined)`
color: ${props => props.theme.primary} !important;
`;
const GenesisTx = styled(ExperimentOutlined)`
color: ${props => props.theme.primary} !important;
`;
const UnparsedTx = styled(ExclamationOutlined)`
color: ${props => props.theme.primary} !important;
`;
const DateType = styled.div`
text-align: left;
padding: 12px;
@media screen and (max-width: 500px) {
font-size: 0.8rem;
}
`;
-const OpReturnType = styled.div`
+const OpReturnType = styled.span`
text-align: left;
width: 300%;
- max-height: 130px;
+ max-height: 170px;
padding: 3px;
- padding-left: 14px;
- padding-right: 25px;
+ margin: auto;
word-break: break-word;
- overflow: hidden;
- text-overflow: ellipsis;
+ padding-left: 13px;
+ padding-right: 30px;
`;
const SentLabel = styled.span`
font-weight: bold;
color: ${props => props.theme.secondary} !important;
`;
const ReceivedLabel = styled.span`
font-weight: bold;
color: ${props => props.theme.primary} !important;
`;
+const CashtabMessageLabel = styled.span`
+ text-align: left;
+ font-weight: bold;
+ color: ${props => props.theme.primary} !important;
+ white-space: nowrap;
+`;
+const MessageLabel = styled.span`
+ text-align: left;
+ font-weight: bold;
+ color: ${props => props.theme.secondary} !important;
+ white-space: nowrap;
+`;
const TxIcon = styled.div`
svg {
width: 32px;
height: 32px;
}
height: 32px;
width: 32px;
@media screen and (max-width: 500px) {
svg {
width: 24px;
height: 24px;
}
height: 24px;
width: 24px;
}
`;
const TxInfo = styled.div`
padding: 12px;
font-size: 1rem;
text-align: right;
color: ${props =>
props.outgoing ? props.theme.secondary : props.theme.primary};
@media screen and (max-width: 500px) {
font-size: 0.8rem;
}
`;
const TxFiatPrice = styled.span`
font-size: 0.8rem;
`;
const TokenInfo = styled.div`
display: grid;
grid-template-rows: 50%;
grid-template-columns: 24px auto;
padding: 12px;
font-size: 1rem;
color: ${props =>
props.outgoing ? props.theme.secondary : props.theme.primary};
@media screen and (max-width: 500px) {
font-size: 0.8rem;
grid-template-columns: 16px auto;
}
`;
const TxTokenIcon = styled.div`
img {
height: 24px;
width: 24px;
}
@media screen and (max-width: 500px) {
img {
height: 16px;
width: 16px;
}
}
grid-column-start: 1;
grid-column-end: span 1;
grid-row-start: 1;
grid-row-end: span 2;
align-self: center;
`;
const TokenTxAmt = styled.div`
padding-left: 12px;
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
const TokenName = styled.div`
padding-left: 12px;
font-size: 0.8rem;
@media screen and (max-width: 500px) {
font-size: 0.6rem;
}
text-align: right;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
const TxWrapper = styled.div`
display: grid;
grid-template-columns: 36px 30% 50%;
justify-content: space-between;
align-items: center;
padding: 15px 25px;
border-radius: 16px;
background: ${props => props.theme.tokenListItem.background};
margin-bottom: 12px;
box-shadow: ${props => props.theme.tokenListItem.boxShadow};
:hover {
transform: translateY(-2px);
box-shadow: rgb(136 172 243 / 25%) 0px 10px 30px,
rgb(0 0 0 / 3%) 0px 1px 1px, rgb(0 51 167 / 10%) 0px 10px 20px;
transition: all 0.8s cubic-bezier(0.075, 0.82, 0.165, 1) 0s;
}
@media screen and (max-width: 500px) {
grid-template-columns: 24px 30% 50%;
padding: 12px 12px;
}
`;
const Tx = ({ data, fiatPrice, fiatCurrency }) => {
const txDate =
typeof data.blocktime === 'undefined'
? new Date().toLocaleDateString()
: new Date(data.blocktime * 1000).toLocaleDateString();
// if data only includes height and txid, then the tx could not be parsed by cashtab
// render as such but keep link to block explorer
let unparsedTx = false;
if (!Object.keys(data).includes('outgoingTx')) {
unparsedTx = true;
}
return (
<>
{unparsedTx ? (
Unparsed
{txDate}
Open in Explorer
) : (
{data.outgoingTx ? (
<>
{data.tokenTx &&
data.tokenInfo.transactionType === 'GENESIS' ? (
) : (
)}
>
) : (
)}
{data.outgoingTx ? (
<>
{data.tokenTx &&
data.tokenInfo.transactionType === 'GENESIS' ? (
Genesis
) : (
Sent
)}
>
) : (
Received
)}
{txDate}
{data.tokenTx ? (
{data.tokenTx && data.tokenInfo ? (
<>
{currency.tokenIconsUrl !== '' ? (
}
/>
) : (
)}
{data.outgoingTx ? (
<>
{data.tokenInfo.transactionType ===
'GENESIS' ? (
<>
+{' '}
{data.tokenInfo.qtyReceived.toString()}
{
data.tokenInfo
.tokenTicker
}
{
data.tokenInfo
.tokenName
}
>
) : (
<>
-{' '}
{data.tokenInfo.qtySent.toString()}
{
data.tokenInfo
.tokenTicker
}
{
data.tokenInfo
.tokenName
}
>
)}
>
) : (
<>
+{' '}
{data.tokenInfo.qtyReceived.toString()}
{data.tokenInfo.tokenTicker}
{data.tokenInfo.tokenName}
>
)}
>
) : (
Token Tx
)}
) : (
<>
{data.outgoingTx ? (
<>
-{' '}
{formatBalance(
fromLegacyDecimals(data.amountSent),
)}{' '}
{currency.ticker}
{fiatPrice !== null &&
!isNaN(data.amountSent) && (
-{' '}
{
currency.fiatCurrencies[
fiatCurrency
].symbol
}
{(
fromLegacyDecimals(
data.amountSent,
) * fiatPrice
).toFixed(2)}{' '}
{
currency.fiatCurrencies
.fiatCurrency
}
)}
>
) : (
<>
+{' '}
{formatBalance(
fromLegacyDecimals(
data.amountReceived,
),
)}{' '}
{currency.ticker}
{fiatPrice !== null &&
!isNaN(data.amountReceived) && (
+{' '}
{
currency.fiatCurrencies[
fiatCurrency
].symbol
}
{(
fromLegacyDecimals(
data.amountReceived,
) * fiatPrice
).toFixed(2)}{' '}
{
currency.fiatCurrencies
.fiatCurrency
}
)}
>
)}
>
)}
{data.opReturnMessage && (
<>
- Message:
+ {data.isCashtabMessage ? (
+
+ Cashtab Message
+
+ ) : (
+
+ External Message
+
+ )}
{data.opReturnMessage
? Buffer.from(
data.opReturnMessage,
).toString()
: ''}
>
)}
)}
>
);
};
Tx.propTypes = {
data: PropTypes.object,
fiatPrice: PropTypes.number,
fiatCurrency: PropTypes.string,
};
export default Tx;
diff --git a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
index 42daaa070..1e617dd3b 100644
--- a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
+++ b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap
@@ -1,622 +1,622 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP @generated
exports[`Wallet with BCH balances 1`] = `
Array [
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
,
0
XEC
,
,
,
]
`;
exports[`Wallet with BCH balances and tokens 1`] = `
Array [
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
,
0
XEC
,
,
,
]
`;
exports[`Wallet with BCH balances and tokens and state field 1`] = `
Array [
0.06
XEC
,
$
NaN
USD
,
,
,
]
`;
exports[`Wallet without BCH balance 1`] = `
Array [
🎉
Congratulations on your new wallet!
🎉
Start using the wallet immediately to receive
XEC
payments, or load it up with
XEC
to send to others
,
0
XEC
,
,
,
]
`;
exports[`Without wallet defined 1`] = `
Array [
Welcome to Cashtab!
,
Cashtab is an
open source,
non-custodial web wallet for
eCash
.
Want to learn more?
Check out the Cashtab documentation.
,
,
,
]
`;
diff --git a/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js b/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js
index b7bf2b4e2..5ce2860e7 100644
--- a/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js
+++ b/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js
@@ -1,91 +1,97 @@
// Expected result of applying parseTxData to mockTxDataWityhPassthrough[0]
export const mockSentCashTx = [
{
amountReceived: 0,
amountSent: 0.000042,
blocktime: 1614380741,
confirmations: 2721,
destinationAddress:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
height: 674993,
outgoingTx: true,
+ isCashtabMessage: false,
opReturnMessage: '',
tokenTx: false,
txid: '089f2188d5771a7de0589def2b8d6c1a1f33f45b6de26d9a0ef32782f019ecf1',
},
];
export const mockReceivedCashTx = [
{
amountReceived: 3,
amountSent: 0,
blocktime: 1612567121,
confirmations: 5637,
destinationAddress:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
height: 672077,
outgoingTx: false,
+ isCashtabMessage: false,
opReturnMessage: '',
tokenTx: false,
txid: '42d39fbe068a40fe691f987b22fdf04b80f94d71d2fec20a58125e7b1a06d2a9',
},
];
export const mockSentTokenTx = [
{
amountReceived: 0,
amountSent: 0.00000546,
blocktime: 1614027278,
confirmations: 3270,
destinationAddress:
'bitcoincash:qzj5zu6fgg8v2we82gh76xnrk9njcregluzgaztm45',
height: 674444,
outgoingTx: true,
+ isCashtabMessage: false,
opReturnMessage: '',
tokenTx: true,
txid: 'ffe3a7500dbcc98021ad581c98d9947054d1950a7f3416664715066d3d20ad72',
},
];
export const mockReceivedTokenTx = [
{
amountReceived: 0.00000546,
amountSent: 0,
blocktime: 1613859311,
confirmations: 3571,
destinationAddress:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
height: 674143,
outgoingTx: false,
+ isCashtabMessage: false,
opReturnMessage: '',
tokenTx: true,
txid: '618d0dd8c0c5fa5a34c6515c865dd72bb76f8311cd6ee9aef153bab20dabc0e6',
},
];
export const mockSentOpReturnMessageTx = [
{
amountReceived: 0,
amountSent: 0,
blocktime: 1635507345,
confirmations: 59,
destinationAddress: undefined,
height: undefined,
- opReturnMessage: new Buffer('testing message 12'),
+ opReturnMessage: new Buffer('bingoelectrum'),
outgoingTx: false,
tokenTx: false,
+ isCashtabMessage: false,
txid: 'dd35690b0cefd24dcc08acba8694ecd49293f365a81372cb66c8f1c1291d97c5',
},
];
export const mockReceivedOpReturnMessageTx = [
{
amountReceived: 0,
amountSent: 0,
blocktime: 1635511136,
confirmations: 70,
destinationAddress: undefined,
height: undefined,
- opReturnMessage: new Buffer('testing message 13'),
+ opReturnMessage: new Buffer('cashtabular'),
outgoingTx: false,
tokenTx: false,
+ isCashtabMessage: true,
txid: '5adc33b5c0509b31c6da359177b19467c443bdc4dd37c283c0f87244c0ad63af',
},
];
diff --git a/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js b/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js
index 0c310c22c..e7fffc2c7 100644
--- a/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js
+++ b/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js
@@ -1,869 +1,870 @@
export default [
{
txid: '089f2188d5771a7de0589def2b8d6c1a1f33f45b6de26d9a0ef32782f019ecf1',
hash: '089f2188d5771a7de0589def2b8d6c1a1f33f45b6de26d9a0ef32782f019ecf1',
version: 2,
size: 225,
locktime: 0,
vin: [
{
txid: 'b96da810b15deb312ad4508a165033ca8ffa282f88e5b7b0e79be09a0b0424f9',
vout: 1,
scriptSig: {
asm: '3044022064084d72b1bb7ca148d1950cf07494ffb397cb3df53b72afa8bd844b80369ecd02203ae21f14ba5019f38bc0b80b99e7c8cc1d5d3360ca7bab56be28ef583fe5c6a6[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '473044022064084d72b1bb7ca148d1950cf07494ffb397cb3df53b72afa8bd844b80369ecd02203ae21f14ba5019f38bc0b80b99e7c8cc1d5d3360ca7bab56be28ef583fe5c6a6412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
],
vout: [
{
value: 0.000042,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
{
value: 0.6244967,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
],
hex: '0200000001f924040b9ae09be7b0b7e5882f28fa8fca3350168a50d42a31eb5db110a86db9010000006a473044022064084d72b1bb7ca148d1950cf07494ffb397cb3df53b72afa8bd844b80369ecd02203ae21f14ba5019f38bc0b80b99e7c8cc1d5d3360ca7bab56be28ef583fe5c6a6412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffff0268100000000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac06e8b803000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00000000',
blockhash:
'000000000000000087dd4ca6308e835edfba871fee36d3e53ad3c9545c4b1719',
confirmations: 2721,
time: 1614380741,
blocktime: 1614380741,
height: 674993,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'ffe3a7500dbcc98021ad581c98d9947054d1950a7f3416664715066d3d20ad72',
hash: 'ffe3a7500dbcc98021ad581c98d9947054d1950a7f3416664715066d3d20ad72',
version: 2,
size: 480,
locktime: 0,
vin: [
{
txid: 'b980b35b794ad73d8aae312385e82d9be8086e7b743e1c6a468db8db8ac74bd8',
vout: 3,
scriptSig: {
asm: '30440220538de8f61d716c899e6a2cd78ca46162edaaa5f0d000ebbbc875608e5639170a02206a7fc8f7c16cef1c56667a8da6d5e480f440ecf43238879ad9f8785a0473a72b[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '4730440220538de8f61d716c899e6a2cd78ca46162edaaa5f0d000ebbbc875608e5639170a02206a7fc8f7c16cef1c56667a8da6d5e480f440ecf43238879ad9f8785a0473a72b412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'b980b35b794ad73d8aae312385e82d9be8086e7b743e1c6a468db8db8ac74bd8',
vout: 2,
scriptSig: {
asm: '3045022100ce03e19bd181b903adc6f192d4ad0900e6816f6e62282cefff05c22cf36a647602202b296a2ed1805f0b0a9aa5f99158685298e7a0aff406fedb8abb8e0afaf48ca4[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '483045022100ce03e19bd181b903adc6f192d4ad0900e6816f6e62282cefff05c22cf36a647602202b296a2ed1805f0b0a9aa5f99158685298e7a0aff406fedb8abb8e0afaf48ca4412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 1145980243 50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e 0000000000000003 000000000000005e',
hex: '6a04534c500001010453454e442050d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e08000000000000000308000000000000005e',
type: 'nulldata',
},
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 a5417349420ec53b27522fed1a63b1672c0f28ff OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914a5417349420ec53b27522fed1a63b1672c0f28ff88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qzj5zu6fgg8v2we82gh76xnrk9njcregluzgaztm45',
],
},
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
{
value: 4.99996074,
n: 3,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
],
hex: '0200000002d84bc78adbb88d466a1c3e747b6e08e89b2de8852331ae8a3dd74a795bb380b9030000006a4730440220538de8f61d716c899e6a2cd78ca46162edaaa5f0d000ebbbc875608e5639170a02206a7fc8f7c16cef1c56667a8da6d5e480f440ecf43238879ad9f8785a0473a72b412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffffd84bc78adbb88d466a1c3e747b6e08e89b2de8852331ae8a3dd74a795bb380b9020000006b483045022100ce03e19bd181b903adc6f192d4ad0900e6816f6e62282cefff05c22cf36a647602202b296a2ed1805f0b0a9aa5f99158685298e7a0aff406fedb8abb8e0afaf48ca4412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffff040000000000000000406a04534c500001010453454e442050d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e08000000000000000308000000000000005e22020000000000001976a914a5417349420ec53b27522fed1a63b1672c0f28ff88ac22020000000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688acaa55cd1d000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00000000',
blockhash:
'00000000000000007053867de29516374a23d7adfb08ccb47cfbea0e98a49e5b',
confirmations: 3270,
time: 1614027278,
blocktime: 1614027278,
height: 674444,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'b980b35b794ad73d8aae312385e82d9be8086e7b743e1c6a468db8db8ac74bd8',
hash: 'b980b35b794ad73d8aae312385e82d9be8086e7b743e1c6a468db8db8ac74bd8',
version: 2,
size: 479,
locktime: 0,
vin: [
{
txid: 'ec9c20c2c5cd5aa4c9261a9f97e68734b175962c4b3d9edc996dd415dd03c2e7',
vout: 0,
scriptSig: {
asm: '3044022075cb93e60ffb792b2715d96f3d31033e8f385bb9bfeadf99f7b1055d749a33cc022028292ee8ffaed64cbc6f9b680db36e031250672a6b0c5cfd23f9a61977d52ed7[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '473044022075cb93e60ffb792b2715d96f3d31033e8f385bb9bfeadf99f7b1055d749a33cc022028292ee8ffaed64cbc6f9b680db36e031250672a6b0c5cfd23f9a61977d52ed7412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: '618d0dd8c0c5fa5a34c6515c865dd72bb76f8311cd6ee9aef153bab20dabc0e6',
vout: 1,
scriptSig: {
asm: '304402203ea0558cd917eb8f6c286e79ffcc5dd1f5accb66c2e5836628d6be6f9d03ca260220120a6da92b6f44bdfcd3ef7b08263d3f73d99ff4b1f83b8f998ff1355f3f0d2e[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '47304402203ea0558cd917eb8f6c286e79ffcc5dd1f5accb66c2e5836628d6be6f9d03ca260220120a6da92b6f44bdfcd3ef7b08263d3f73d99ff4b1f83b8f998ff1355f3f0d2e412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 1145980243 50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e 0000000000000003 0000000000000061',
hex: '6a04534c500001010453454e442050d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e080000000000000003080000000000000061',
type: 'nulldata',
},
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 d4fa9121bcd065dd93e58831569cf51ef5a74f61 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914d4fa9121bcd065dd93e58831569cf51ef5a74f6188ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qr204yfphngxthvnukyrz45u7500tf60vyea48xwmd',
],
},
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
{
value: 4.99998974,
n: 3,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
],
hex: '0200000002e7c203dd15d46d99dc9e3d4b2c9675b13487e6979f1a26c9a45acdc5c2209cec000000006a473044022075cb93e60ffb792b2715d96f3d31033e8f385bb9bfeadf99f7b1055d749a33cc022028292ee8ffaed64cbc6f9b680db36e031250672a6b0c5cfd23f9a61977d52ed7412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffffe6c0ab0db2ba53f1aee96ecd11836fb72bd75d865c51c6345afac5c0d80d8d61010000006a47304402203ea0558cd917eb8f6c286e79ffcc5dd1f5accb66c2e5836628d6be6f9d03ca260220120a6da92b6f44bdfcd3ef7b08263d3f73d99ff4b1f83b8f998ff1355f3f0d2e412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffff040000000000000000406a04534c500001010453454e442050d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e08000000000000000308000000000000006122020000000000001976a914d4fa9121bcd065dd93e58831569cf51ef5a74f6188ac22020000000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688acfe60cd1d000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00000000',
blockhash:
'0000000000000000a9f812d56e2249b7c462ce499a0852bdfe20bb46c1bb9f92',
confirmations: 3278,
time: 1614021424,
blocktime: 1614021424,
height: 674436,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: '618d0dd8c0c5fa5a34c6515c865dd72bb76f8311cd6ee9aef153bab20dabc0e6',
hash: '618d0dd8c0c5fa5a34c6515c865dd72bb76f8311cd6ee9aef153bab20dabc0e6',
version: 2,
size: 436,
locktime: 0,
vin: [
{
txid: '50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e',
vout: 3,
scriptSig: {
asm: '30440220664f988b86035ddcdff6e9c3b8e140712eca297750d056e41577a0bf0059e7ff022030982b3fcab1cab5d6086bc935e941e7d22efbb0ad5ccca0268515c5c8306089[ALL|FORKID] 034509251caa5f01e2787c436949eb94d71dcc451bcde5791ae5b7109255f5f0a3',
hex: '4730440220664f988b86035ddcdff6e9c3b8e140712eca297750d056e41577a0bf0059e7ff022030982b3fcab1cab5d6086bc935e941e7d22efbb0ad5ccca0268515c5c83060894121034509251caa5f01e2787c436949eb94d71dcc451bcde5791ae5b7109255f5f0a3',
},
sequence: 4294967295,
address:
'bitcoincash:qzudj5fd9t0cknnsc3wzdd4sp46u9r42jcnqnwfss0',
},
{
txid: '50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e',
vout: 1,
scriptSig: {
asm: '304402203ce88e0a95d5581ad567c0468c87a08027aa5ecdecd614a168d833d7ecc02c1c022013ddd81147b44ad5488107d5c4d535f7f59e9fa46840451d39422aace284b2b7[ALL|FORKID] 034509251caa5f01e2787c436949eb94d71dcc451bcde5791ae5b7109255f5f0a3',
hex: '47304402203ce88e0a95d5581ad567c0468c87a08027aa5ecdecd614a168d833d7ecc02c1c022013ddd81147b44ad5488107d5c4d535f7f59e9fa46840451d39422aace284b2b74121034509251caa5f01e2787c436949eb94d71dcc451bcde5791ae5b7109255f5f0a3',
},
sequence: 4294967295,
address:
'bitcoincash:qzudj5fd9t0cknnsc3wzdd4sp46u9r42jcnqnwfss0',
},
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 1145980243 50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e 0000000000000064',
hex: '6a04534c500001010453454e442050d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e080000000000000064',
type: 'nulldata',
},
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
{
value: 0.00088064,
n: 2,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 b8d9512d2adf8b4e70c45c26b6b00d75c28eaa96 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914b8d9512d2adf8b4e70c45c26b6b00d75c28eaa9688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qzudj5fd9t0cknnsc3wzdd4sp46u9r42jcnqnwfss0',
],
},
},
],
hex: '02000000020e41711243954c8ffe409761e994272af43ced6f56c8c6afa7cd55622c29d850030000006a4730440220664f988b86035ddcdff6e9c3b8e140712eca297750d056e41577a0bf0059e7ff022030982b3fcab1cab5d6086bc935e941e7d22efbb0ad5ccca0268515c5c83060894121034509251caa5f01e2787c436949eb94d71dcc451bcde5791ae5b7109255f5f0a3ffffffff0e41711243954c8ffe409761e994272af43ced6f56c8c6afa7cd55622c29d850010000006a47304402203ce88e0a95d5581ad567c0468c87a08027aa5ecdecd614a168d833d7ecc02c1c022013ddd81147b44ad5488107d5c4d535f7f59e9fa46840451d39422aace284b2b74121034509251caa5f01e2787c436949eb94d71dcc451bcde5791ae5b7109255f5f0a3ffffffff030000000000000000376a04534c500001010453454e442050d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e08000000000000006422020000000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00580100000000001976a914b8d9512d2adf8b4e70c45c26b6b00d75c28eaa9688ac00000000',
blockhash:
'000000000000000034c77993a35c74fe2dddace27198681ca1e89e928d0c2fff',
confirmations: 3571,
time: 1613859311,
blocktime: 1613859311,
height: 674143,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'f90631b48521a4147dd9dd7091ce936eddc0c3e6221ec87fa4fabacc453a0b95',
hash: 'f90631b48521a4147dd9dd7091ce936eddc0c3e6221ec87fa4fabacc453a0b95',
version: 2,
size: 437,
locktime: 0,
vin: [
{
txid: 'db464f77ac97deabc28df07a7e4a2e261c854a8ec4dc959b89b10531966f6cbf',
vout: 0,
scriptSig: {
asm: '3044022065622a7aa065f56abe84f3589c983a768e3ef5d72c9352991d6b584a2a16dcb802200c1c0065106207715a024624ed951e851d4f742c55a704e9531bebd2ef84fc14[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '473044022065622a7aa065f56abe84f3589c983a768e3ef5d72c9352991d6b584a2a16dcb802200c1c0065106207715a024624ed951e851d4f742c55a704e9531bebd2ef84fc1441210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: 'acbb66f826211f40b89e84d9bd2143dfb541d67e1e3c664b17ccd3ba66327a9e',
vout: 1,
scriptSig: {
asm: '3045022100b475cf7d1eaf37641d2107f13be0ef9acbd17b252ed3f9ae349edfdcd6a97cf402202bf2852dfa905e6d50c96a622d2838408ceb979245a4342d5096acc938135804[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '483045022100b475cf7d1eaf37641d2107f13be0ef9acbd17b252ed3f9ae349edfdcd6a97cf402202bf2852dfa905e6d50c96a622d2838408ceb979245a4342d5096acc93813580441210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 1145980243 bfddfcfc9fb9a8d61ed74fa94b5e32ccc03305797eea461658303df5805578ef 0000000000000001',
hex: '6a04534c500001010453454e4420bfddfcfc9fb9a8d61ed74fa94b5e32ccc03305797eea461658303df5805578ef080000000000000001',
type: 'nulldata',
},
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
{
value: 9.99997101,
n: 2,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
],
hex: '0200000002bf6c6f963105b1899b95dcc48e4a851c262e4a7e7af08dc2abde97ac774f46db000000006a473044022065622a7aa065f56abe84f3589c983a768e3ef5d72c9352991d6b584a2a16dcb802200c1c0065106207715a024624ed951e851d4f742c55a704e9531bebd2ef84fc1441210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffff9e7a3266bad3cc174b663c1e7ed641b5df4321bdd9849eb8401f2126f866bbac010000006b483045022100b475cf7d1eaf37641d2107f13be0ef9acbd17b252ed3f9ae349edfdcd6a97cf402202bf2852dfa905e6d50c96a622d2838408ceb979245a4342d5096acc93813580441210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffff030000000000000000376a04534c500001010453454e4420bfddfcfc9fb9a8d61ed74fa94b5e32ccc03305797eea461658303df5805578ef08000000000000000122020000000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688acadbe9a3b000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac00000000',
blockhash:
'0000000000000000132378b84a7477b7d601faedec302264bde1e89b1480e364',
confirmations: 5013,
time: 1612966022,
blocktime: 1612966022,
height: 672701,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: '42d39fbe068a40fe691f987b22fdf04b80f94d71d2fec20a58125e7b1a06d2a9',
hash: '42d39fbe068a40fe691f987b22fdf04b80f94d71d2fec20a58125e7b1a06d2a9',
version: 2,
size: 226,
locktime: 0,
vin: [
{
txid: '5e0436c6741e226d05c5b7e7e23de8213d3583e2669e50a80b908bf4cb471317',
vout: 1,
scriptSig: {
asm: '3045022100f8a8eca8f5d6149511c518d41015512f8164a5be6f01e9efd609db9a429f4872022059121e122043b43eae77b5e132b8f798a290e6eed8a2026a0656540cd1bd752b[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '483045022100f8a8eca8f5d6149511c518d41015512f8164a5be6f01e9efd609db9a429f4872022059121e122043b43eae77b5e132b8f798a290e6eed8a2026a0656540cd1bd752b41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
],
vout: [
{
value: 3,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
{
value: 6.9999586,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
],
hex: '0200000001171347cbf48b900ba8509e66e283353d21e83de2e7b7c5056d221e74c636045e010000006b483045022100f8a8eca8f5d6149511c518d41015512f8164a5be6f01e9efd609db9a429f4872022059121e122043b43eae77b5e132b8f798a290e6eed8a2026a0656540cd1bd752b41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffff0200a3e111000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688acd416b929000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac00000000',
blockhash:
'00000000000000008f563edf8604e537fe0d1e80f1c7c2d97dd094824f804ba3',
confirmations: 5637,
time: 1612567121,
blocktime: 1612567121,
height: 672077,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'b96da810b15deb312ad4508a165033ca8ffa282f88e5b7b0e79be09a0b0424f9',
hash: 'b96da810b15deb312ad4508a165033ca8ffa282f88e5b7b0e79be09a0b0424f9',
version: 2,
size: 226,
locktime: 0,
vin: [
{
txid: '9ad75af97f0617a3729c2bd31bf7c4b380230e661cc921a3c6be0febc75a3e49',
vout: 1,
scriptSig: {
asm: '3045022100d59e6fad4d1d57796f229a7d4aa3b01fc3241132dae9bc406c66fa33d7aef21c022036a5f432d6d99f65848ac12c00bde2b5ba7e63a9f9a74349d9ab8ec39db26f8e[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '483045022100d59e6fad4d1d57796f229a7d4aa3b01fc3241132dae9bc406c66fa33d7aef21c022036a5f432d6d99f65848ac12c00bde2b5ba7e63a9f9a74349d9ab8ec39db26f8e412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
],
vout: [
{
value: 0.12345,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
{
value: 0.62455003,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
],
hex: '0200000001493e5ac7eb0fbec6a321c91c660e2380b3c4f71bd32b9c72a317067ff95ad79a010000006b483045022100d59e6fad4d1d57796f229a7d4aa3b01fc3241132dae9bc406c66fa33d7aef21c022036a5f432d6d99f65848ac12c00bde2b5ba7e63a9f9a74349d9ab8ec39db26f8e412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffff02a85ebc00000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88acdbfcb803000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00000000',
blockhash:
'00000000000000008f563edf8604e537fe0d1e80f1c7c2d97dd094824f804ba3',
confirmations: 5637,
time: 1612567121,
blocktime: 1612567121,
height: 672077,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'db464f77ac97deabc28df07a7e4a2e261c854a8ec4dc959b89b10531966f6cbf',
hash: 'db464f77ac97deabc28df07a7e4a2e261c854a8ec4dc959b89b10531966f6cbf',
version: 2,
size: 225,
locktime: 0,
vin: [
{
txid: '1452267e57429edcfdcb1184b24becea6ddf8f8a4f8e130dad6248545d9f8e75',
vout: 1,
scriptSig: {
asm: '30440220184921bfce634a57b5220f06b11b64c0cb7e67ecd9c634335e3e933e35a7a969022038b2074e1d75aa4f6945d150bae5b8a1d426f4284da2b96336fa0fc741eb6de7[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '4730440220184921bfce634a57b5220f06b11b64c0cb7e67ecd9c634335e3e933e35a7a969022038b2074e1d75aa4f6945d150bae5b8a1d426f4284da2b96336fa0fc741eb6de7412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
],
vout: [
{
value: 10.00000001,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
{
value: 2.8830607,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
],
hex: '0200000001758e9f5d544862ad0d138e4f8a8fdf6deaec4bb28411cbfddc9e42577e265214010000006a4730440220184921bfce634a57b5220f06b11b64c0cb7e67ecd9c634335e3e933e35a7a969022038b2074e1d75aa4f6945d150bae5b8a1d426f4284da2b96336fa0fc741eb6de7412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffff0201ca9a3b000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac96332f11000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00000000',
blockhash:
'00000000000000008f563edf8604e537fe0d1e80f1c7c2d97dd094824f804ba3',
confirmations: 5637,
time: 1612567121,
blocktime: 1612567121,
height: 672077,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'e32c20137e590f253b8d198608f7fffd428fc0bd7a9a0675bb6af091d1cb2ea4',
hash: 'e32c20137e590f253b8d198608f7fffd428fc0bd7a9a0675bb6af091d1cb2ea4',
version: 2,
size: 373,
locktime: 0,
vin: [
{
txid: 'f63e890423b3bffa6e01be2dcb4942940c2e8a1985926411558a22d1b5dd0e29',
vout: 1,
scriptSig: {
asm: '3045022100c7f51ff0888c182a1a60c08904d8116c9d2e31cb7d2fd5b63c2bf9fd7b246fc102202ee786d2052448621c4a04d18d13c83ac5ee27008dd079e8ba954f8197ff3c6c[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '483045022100c7f51ff0888c182a1a60c08904d8116c9d2e31cb7d2fd5b63c2bf9fd7b246fc102202ee786d2052448621c4a04d18d13c83ac5ee27008dd079e8ba954f8197ff3c6c412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: '42d39fbe068a40fe691f987b22fdf04b80f94d71d2fec20a58125e7b1a06d2a9',
vout: 0,
scriptSig: {
asm: '304402201bbfcd0c120ace9b8c7a6f5e77b61236bb1128e2a757f85ba80101885e9c1212022046fed4006dcd6a236034dede77c566acf74824d14b3ee3da884e9bd93884ff93[ALL|FORKID] 02c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
hex: '47304402201bbfcd0c120ace9b8c7a6f5e77b61236bb1128e2a757f85ba80101885e9c1212022046fed4006dcd6a236034dede77c566acf74824d14b3ee3da884e9bd93884ff93412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795',
},
sequence: 4294967295,
address:
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
],
vout: [
{
value: 1.8725994,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
{
value: 1.49237053,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
],
hex: '0200000002290eddb5d1228a5511649285198a2e0c944249cb2dbe016efabfb32304893ef6010000006b483045022100c7f51ff0888c182a1a60c08904d8116c9d2e31cb7d2fd5b63c2bf9fd7b246fc102202ee786d2052448621c4a04d18d13c83ac5ee27008dd079e8ba954f8197ff3c6c412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffffa9d2061a7b5e12580ac2fed2714df9804bf0fd227b981f69fe408a06be9fd342000000006a47304402201bbfcd0c120ace9b8c7a6f5e77b61236bb1128e2a757f85ba80101885e9c1212022046fed4006dcd6a236034dede77c566acf74824d14b3ee3da884e9bd93884ff93412102c237f49dd4c812f27b09d69d4c8a4da12744fda8ad63ce151fed2a3f41fd8795ffffffff02245c290b000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac3d2de508000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac00000000',
blockhash:
'00000000000000008f563edf8604e537fe0d1e80f1c7c2d97dd094824f804ba3',
confirmations: 5637,
time: 1612567121,
blocktime: 1612567121,
height: 672077,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'ec9c20c2c5cd5aa4c9261a9f97e68734b175962c4b3d9edc996dd415dd03c2e7',
hash: 'ec9c20c2c5cd5aa4c9261a9f97e68734b175962c4b3d9edc996dd415dd03c2e7',
version: 2,
size: 1405,
locktime: 0,
vin: [
{
txid: '3507d73b0bb82421d64ae79f469943e56f15d7db954ad235f48ede33c718d860',
vout: 0,
scriptSig: {
asm: '3044022000cc5b79e5da60cf4935f3a172089cd9b631b678462ee29091dc610816d059c4022002e3b6f32e825ac04d2907453d6d647a32a995c798df1c68401cc461f6bfbd3a[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '473044022000cc5b79e5da60cf4935f3a172089cd9b631b678462ee29091dc610816d059c4022002e3b6f32e825ac04d2907453d6d647a32a995c798df1c68401cc461f6bfbd3a41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: '8f73a718d907d94e60c5f73f299bd01dc5b1c163c4ebc26b5304e37a1a7f34af',
vout: 0,
scriptSig: {
asm: '3045022100ac50553448f2a5fab1177ed0bc64541b2dba063d04f2d69a8a1d216fb1435e5802202c7f6abd1685a6d81f14ac3bdb0874d214a5f4260719f9c5dc519ac5d8dffd37[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '483045022100ac50553448f2a5fab1177ed0bc64541b2dba063d04f2d69a8a1d216fb1435e5802202c7f6abd1685a6d81f14ac3bdb0874d214a5f4260719f9c5dc519ac5d8dffd3741210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: 'd38b76bacd6aa75ad2d6fcfd994533e54d0541435970eace49486fde9d6ee2e3',
vout: 0,
scriptSig: {
asm: '304502210091836c6cb4c786bd3b74b73e579ddf8b843ba51841e5675fa53608449b67371802203de75f32b684cfe2d2e9cd424ea6eb4f49248e6698365c9364ebf84cd6e50eab[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '48304502210091836c6cb4c786bd3b74b73e579ddf8b843ba51841e5675fa53608449b67371802203de75f32b684cfe2d2e9cd424ea6eb4f49248e6698365c9364ebf84cd6e50eab41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: 'd47607a72d6bc093556fa7f2cec9d67719bd627751d5d27bc53c4eb8eb6f54e5',
vout: 0,
scriptSig: {
asm: '3045022100e7727d9d26c645282553aef27947ad6795bc89b505ad089d617b6f696399352802206c736524a1410ed3e30cf1127f7f02c9a249392f8f8e7c670250472909d1c0d6[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '483045022100e7727d9d26c645282553aef27947ad6795bc89b505ad089d617b6f696399352802206c736524a1410ed3e30cf1127f7f02c9a249392f8f8e7c670250472909d1c0d641210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: '33f246811f794c4b64098a64c698ae5811054b13e289256a18e2d142beef57e7',
vout: 1,
scriptSig: {
asm: '304402203fe78ad5aaeefab7b3b2277eefc4a2ace9c2e92694b46bf4a76927bf2b82017102200ded59336aba269a54865d9fdd99e72081c0318ccbc37bc0fc0c72b60ae35382[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '47304402203fe78ad5aaeefab7b3b2277eefc4a2ace9c2e92694b46bf4a76927bf2b82017102200ded59336aba269a54865d9fdd99e72081c0318ccbc37bc0fc0c72b60ae3538241210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: '25f915d2912524ad602c882211ccaf479d6bf87ef7c24d1be0f325cec3727257',
vout: 0,
scriptSig: {
asm: '30440220670af03605b9495c8ecee357889ceeb137dadaa1662136fdc55c28fe9434e3c60220285195a62811941745a9f93e136e59c96b81d5b0d9525f3d16d001bc0f6fa9bb[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '4730440220670af03605b9495c8ecee357889ceeb137dadaa1662136fdc55c28fe9434e3c60220285195a62811941745a9f93e136e59c96b81d5b0d9525f3d16d001bc0f6fa9bb41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: 'c9044b4d7438d006a722ef85474c8127265eced4f72c7d71c2f714444bc0e1f2',
vout: 0,
scriptSig: {
asm: '304402203f822a0b207ed49e6918663133a18037c24498c2f770c2649333a32f523e259d02203afc42a79d0da123b67f814effeee7c05c7996ea829b3cfa46c5c2e74209c096[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '47304402203f822a0b207ed49e6918663133a18037c24498c2f770c2649333a32f523e259d02203afc42a79d0da123b67f814effeee7c05c7996ea829b3cfa46c5c2e74209c09641210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: '045306f0019ae0d977de7ff17dd55e861b3fe94458693ee2b94ce5dd7003aab9',
vout: 0,
scriptSig: {
asm: '3045022100a7a2cf838a13a19f0e443ca35ac5ee3d55f70edca992f98402a84d4ab5ae1ad90220644a02c746eae7b44a4600199ecbf69f3b0f0bdf8479f461c482d67ef4a84e76[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '483045022100a7a2cf838a13a19f0e443ca35ac5ee3d55f70edca992f98402a84d4ab5ae1ad90220644a02c746eae7b44a4600199ecbf69f3b0f0bdf8479f461c482d67ef4a84e7641210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
{
txid: '1452267e57429edcfdcb1184b24becea6ddf8f8a4f8e130dad6248545d9f8e75',
vout: 0,
scriptSig: {
asm: '30440220290701c797eb52ad6721db615c7d6f623c0200be0e6d6802df68c527655475450220446c4a4da9a0df5efcb57711ad61cf6167dfdda937bd0477189be8afedaedd05[ALL|FORKID] 0352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
hex: '4730440220290701c797eb52ad6721db615c7d6f623c0200be0e6d6802df68c527655475450220446c4a4da9a0df5efcb57711ad61cf6167dfdda937bd0477189be8afedaedd0541210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22d',
},
sequence: 4294967295,
address:
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
},
],
vout: [
{
value: 5.00001874,
n: 0,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 76458db0ed96fe9863fc1ccec9fa2cfab884b0f6 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
],
},
},
{
value: 7.52551634,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 6e1da64f04fc29dbe0b8d33a341e05e3afc586eb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl',
],
},
},
],
hex: '020000000960d818c733de8ef435d24a95dbd7156fe54399469fe74ad62124b80b3bd70735000000006a473044022000cc5b79e5da60cf4935f3a172089cd9b631b678462ee29091dc610816d059c4022002e3b6f32e825ac04d2907453d6d647a32a995c798df1c68401cc461f6bfbd3a41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffffaf347f1a7ae304536bc2ebc463c1b1c51dd09b293ff7c5604ed907d918a7738f000000006b483045022100ac50553448f2a5fab1177ed0bc64541b2dba063d04f2d69a8a1d216fb1435e5802202c7f6abd1685a6d81f14ac3bdb0874d214a5f4260719f9c5dc519ac5d8dffd3741210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffffe3e26e9dde6f4849ceea70594341054de5334599fdfcd6d25aa76acdba768bd3000000006b48304502210091836c6cb4c786bd3b74b73e579ddf8b843ba51841e5675fa53608449b67371802203de75f32b684cfe2d2e9cd424ea6eb4f49248e6698365c9364ebf84cd6e50eab41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffffe5546febb84e3cc57bd2d5517762bd1977d6c9cef2a76f5593c06b2da70776d4000000006b483045022100e7727d9d26c645282553aef27947ad6795bc89b505ad089d617b6f696399352802206c736524a1410ed3e30cf1127f7f02c9a249392f8f8e7c670250472909d1c0d641210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffffe757efbe42d1e2186a2589e2134b051158ae98c6648a09644b4c791f8146f233010000006a47304402203fe78ad5aaeefab7b3b2277eefc4a2ace9c2e92694b46bf4a76927bf2b82017102200ded59336aba269a54865d9fdd99e72081c0318ccbc37bc0fc0c72b60ae3538241210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffff577272c3ce25f3e01b4dc2f77ef86b9d47afcc1122882c60ad242591d215f925000000006a4730440220670af03605b9495c8ecee357889ceeb137dadaa1662136fdc55c28fe9434e3c60220285195a62811941745a9f93e136e59c96b81d5b0d9525f3d16d001bc0f6fa9bb41210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dfffffffff2e1c04b4414f7c2717d2cf7d4ce5e2627814c4785ef22a706d038744d4b04c9000000006a47304402203f822a0b207ed49e6918663133a18037c24498c2f770c2649333a32f523e259d02203afc42a79d0da123b67f814effeee7c05c7996ea829b3cfa46c5c2e74209c09641210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffffb9aa0370dde54cb9e23e695844e93f1b865ed57df17fde77d9e09a01f0065304000000006b483045022100a7a2cf838a13a19f0e443ca35ac5ee3d55f70edca992f98402a84d4ab5ae1ad90220644a02c746eae7b44a4600199ecbf69f3b0f0bdf8479f461c482d67ef4a84e7641210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffff758e9f5d544862ad0d138e4f8a8fdf6deaec4bb28411cbfddc9e42577e265214000000006a4730440220290701c797eb52ad6721db615c7d6f623c0200be0e6d6802df68c527655475450220446c4a4da9a0df5efcb57711ad61cf6167dfdda937bd0477189be8afedaedd0541210352cbc218d193ceaf4fb38a772856380173db7a908905e3190841b3174c7ae22dffffffff02526ccd1d000000001976a91476458db0ed96fe9863fc1ccec9fa2cfab884b0f688acd206db2c000000001976a9146e1da64f04fc29dbe0b8d33a341e05e3afc586eb88ac00000000',
blockhash:
'00000000000000008f563edf8604e537fe0d1e80f1c7c2d97dd094824f804ba3',
confirmations: 5637,
time: 1612567121,
blocktime: 1612567121,
height: 672077,
address: 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9',
},
{
txid: 'dd35690b0cefd24dcc08acba8694ecd49293f365a81372cb66c8f1c1291d97c5',
hash: 'dd35690b0cefd24dcc08acba8694ecd49293f365a81372cb66c8f1c1291d97c5',
version: 2,
size: 224,
locktime: 0,
vin: [
{
txid: 'd3e1b8a65f9d50363cad9a496f7cecab59c9415dd9bcfd6f56c0c5dd4dffa7af',
vout: 1,
scriptSig: {
asm: '3045022100fb14c794778e33aa66b861e85650f07e802da8b257cc37ac9dc1ac6346a0171d022051d79d2fc81bcb5bc3c7c7025d4222ecc2060cbdbf71a6fb2c7856b2eeaef7dc[ALL|FORKID] 02e4af47715f4db1d2a8d686be40c42bba5e70d715e470314181730e797be2324b',
hex: '483045022100fb14c794778e33aa66b861e85650f07e802da8b257cc37ac9dc1ac6346a0171d022051d79d2fc81bcb5bc3c7c7025d4222ecc2060cbdbf71a6fb2c7856b2eeaef7dc412102e4af47715f4db1d2a8d686be40c42bba5e70d715e470314181730e797be2324b',
},
sequence: 4294967295,
address:
'bitcoincash:qzekdmmurl75aazj6uj4vc68yrxgws0pms30lsw8de',
value: 0.61760311,
},
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
- asm: 'OP_RETURN 621 74657374696e67206d657373616765203132',
- hex: '6a026d021274657374696e67206d657373616765203132',
+ asm: 'OP_RETURN 62696e676f656c65637472756d',
+ hex: '6a0d62696e676f656c65637472756d',
type: 'nulldata',
},
},
{
value: 0.61759811,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 09401a690d52252acd1152c2ddd36c5081dff574 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91409401a690d52252acd1152c2ddd36c5081dff57488ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qqy5qxnfp4fz22kdz9fv9hwnd3ggrhl4wsekqyswf0',
],
},
},
],
hex: '0200000001afa7ff4dddc5c0566ffdbcd95d41c959abec7c6f499aad3c36509d5fa6b8e1d3010000006b483045022100fb14c794778e33aa66b861e85650f07e802da8b257cc37ac9dc1ac6346a0171d022051d79d2fc81bcb5bc3c7c7025d4222ecc2060cbdbf71a6fb2c7856b2eeaef7dc412102e4af47715f4db1d2a8d686be40c42bba5e70d715e470314181730e797be2324bffffffff020000000000000000176a026d021274657374696e67206d6573736167652031324361ae03000000001976a91409401a690d52252acd1152c2ddd36c5081dff57488ac00000000',
blockhash:
'00000000000000000cbd73d616ecdd107a92d33aee5406ce05141231a76d408a',
confirmations: 59,
time: 1635507345,
blocktime: 1635507345,
},
{
txid: '5adc33b5c0509b31c6da359177b19467c443bdc4dd37c283c0f87244c0ad63af',
hash: '5adc33b5c0509b31c6da359177b19467c443bdc4dd37c283c0f87244c0ad63af',
version: 2,
size: 223,
locktime: 0,
vin: [
{
txid: '2e1c5d1060bf5216678e31ed52d2ca564b81a34ac1a10749c5e124d25ec3c7a2',
vout: 0,
scriptSig: {
asm: '304402205f6f73369ee558a8dd149480dda3d5417aab3c9bc2c4ff97aaebfc2768ceaded022052d7e2cfa0743205db27ac0cd29bcae110f1ca00aeedb6f88694901a7379dc65[ALL|FORKID] 0320b7867e815a2b00fa935a44a4c348299f7171995c8470d8221e6485da521164',
hex: '47304402205f6f73369ee558a8dd149480dda3d5417aab3c9bc2c4ff97aaebfc2768ceaded022052d7e2cfa0743205db27ac0cd29bcae110f1ca00aeedb6f88694901a7379dc6541210320b7867e815a2b00fa935a44a4c348299f7171995c8470d8221e6485da521164',
},
sequence: 4294967295,
address:
'bitcoincash:qrxkkzsmrxcjmz8x90fx2uztt83cuu0u25vrmw66jk',
value: 0.00005,
},
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
- asm: 'OP_RETURN 621 74657374696e67206d657373616765203133',
- hex: '6a026d021274657374696e67206d657373616765203133',
+ asm: 'OP_RETURN 1650553856 63617368746162756c6172',
+ hex: '6a04007461620b63617368746162756c6172',
type: 'nulldata',
},
+ value: '0',
},
{
value: 0.000045,
n: 1,
scriptPubKey: {
asm: 'OP_DUP OP_HASH160 b366ef7c1ffd4ef452d72556634720cc8741e1dc OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914b366ef7c1ffd4ef452d72556634720cc8741e1dc88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: [
'bitcoincash:qzekdmmurl75aazj6uj4vc68yrxgws0pms30lsw8de',
],
},
},
],
hex: '0200000001a2c7c35ed224e1c54907a1c14aa3814b56cad252ed318e671652bf60105d1c2e000000006a47304402205f6f73369ee558a8dd149480dda3d5417aab3c9bc2c4ff97aaebfc2768ceaded022052d7e2cfa0743205db27ac0cd29bcae110f1ca00aeedb6f88694901a7379dc6541210320b7867e815a2b00fa935a44a4c348299f7171995c8470d8221e6485da521164ffffffff020000000000000000176a026d021274657374696e67206d65737361676520313394110000000000001976a914b366ef7c1ffd4ef452d72556634720cc8741e1dc88ac00000000',
blockhash:
'000000000000000012c00755aab6cdef0806ebe24da10d78574c67558d3d816b',
confirmations: 70,
time: 1635511136,
blocktime: 1635511136,
},
];
diff --git a/web/cashtab/src/hooks/useBCH.js b/web/cashtab/src/hooks/useBCH.js
index ca266e8a0..5176e507e 100644
--- a/web/cashtab/src/hooks/useBCH.js
+++ b/web/cashtab/src/hooks/useBCH.js
@@ -1,1136 +1,1155 @@
import BigNumber from 'bignumber.js';
-import { currency } from '@components/Common/Ticker';
+import {
+ currency,
+ isCashtabOutput,
+ isEtokenOutput,
+ extractCashtabMessage,
+ extractExternalMessage,
+} from '@components/Common/Ticker';
import { isValidTokenStats } from '@utils/validation';
import SlpWallet from 'minimal-slp-wallet';
import {
toSmallestDenomination,
fromSmallestDenomination,
batchArray,
flattenBatchedHydratedUtxos,
isValidStoredWallet,
checkNullUtxosForTokenStatus,
confirmNonEtokenUtxos,
} from '@utils/cashMethods';
export default function useBCH() {
const SEND_BCH_ERRORS = {
INSUFFICIENT_FUNDS: 0,
NETWORK_ERROR: 1,
INSUFFICIENT_PRIORITY: 66, // ~insufficient fee
DOUBLE_SPENDING: 18,
MAX_UNCONFIRMED_TXS: 64,
};
const getRestUrl = (apiIndex = 0) => {
const apiString =
process.env.REACT_APP_NETWORK === `mainnet`
? process.env.REACT_APP_BCHA_APIS
: process.env.REACT_APP_BCHA_APIS_TEST;
const apiArray = apiString.split(',');
return apiArray[apiIndex];
};
- // filter out prefixes for OP_RETURN encoded messages
- // Note: only for use with encoded message strings
- const removeOpReturnPrefixes = asmStr => {
- if (asmStr.includes(' 621')) {
- //strip out the 621 (6d02) prefix if exists
- asmStr = asmStr.replace(' 621', '');
- }
- return asmStr;
- };
-
const flattenTransactions = (
txHistory,
txCount = currency.txHistoryCount,
) => {
/*
Convert txHistory, format
[{address: '', transactions: [{height: '', tx_hash: ''}, ...{}]}, {}, {}]
to flatTxHistory
[{txid: '', blockheight: '', address: ''}]
sorted by blockheight, newest transactions to oldest transactions
*/
let flatTxHistory = [];
let includedTxids = [];
for (let i = 0; i < txHistory.length; i += 1) {
const { address, transactions } = txHistory[i];
for (let j = transactions.length - 1; j >= 0; j -= 1) {
let flatTx = {};
flatTx.address = address;
// If tx is unconfirmed, give arbitrarily high blockheight
flatTx.height =
transactions[j].height <= 0
? 10000000
: transactions[j].height;
flatTx.txid = transactions[j].tx_hash;
// Only add this tx if the same transaction is not already in the array
// This edge case can happen with older wallets, txs can be on multiple paths
if (!includedTxids.includes(flatTx.txid)) {
includedTxids.push(flatTx.txid);
flatTxHistory.push(flatTx);
}
}
}
// Sort with most recent transaction at index 0
flatTxHistory.sort((a, b) => b.height - a.height);
// Only return 10
return flatTxHistory.splice(0, txCount);
};
const parseTxData = txData => {
/*
Desired output
[
{
txid: '',
type: send, receive
receivingAddress: '',
quantity: amount bcha
token: true/false
tokenInfo: {
tokenId:
tokenQty:
txType: mint, send, other
}
opReturnMessage: 'message extracted from asm' or ''
}
]
*/
const parsedTxHistory = [];
for (let i = 0; i < txData.length; i += 1) {
const tx = txData[i];
const parsedTx = {};
// Move over info that does not need to be calculated
parsedTx.txid = tx.txid;
parsedTx.height = tx.height;
let destinationAddress = tx.address;
// If this tx had too many inputs to be parsed by getTxDataWithPassThrough, skip it
// When this occurs, the tx will only have txid and height
// So, it will not have 'vin'
if (!Object.keys(tx).includes('vin')) {
// Populate as a limited-info tx that can be expanded in a block explorer
parsedTxHistory.push(parsedTx);
continue;
}
parsedTx.confirmations = tx.confirmations;
parsedTx.blocktime = tx.blocktime;
let amountSent = 0;
let amountReceived = 0;
let opReturnMessage = '';
+ let isCashtabMessage = false;
// Assume an incoming transaction
let outgoingTx = false;
let tokenTx = false;
+ let substring = '';
// If vin includes tx address, this is an outgoing tx
// Note that with bch-input data, we do not have input amounts
for (let j = 0; j < tx.vin.length; j += 1) {
const thisInput = tx.vin[j];
if (thisInput.address === tx.address) {
// This is an outgoing transaction
outgoingTx = true;
}
}
// Iterate over vout to find how much was sent or received
for (let j = 0; j < tx.vout.length; j += 1) {
const thisOutput = tx.vout[j];
// If there is no addresses object in the output, it's either an OP_RETURN msg or token tx
if (
!Object.keys(thisOutput.scriptPubKey).includes('addresses')
) {
- let asm = thisOutput.scriptPubKey.asm;
- if (asm.includes('OP_RETURN 5262419')) {
- // assume this is an eToken tx for now
- // future diffs will add additional NFT parsing logic in this segment
+ let hex = thisOutput.scriptPubKey.hex;
+
+ if (isEtokenOutput(hex)) {
+ // this is an eToken transaction
tokenTx = true;
+ } else if (isCashtabOutput(hex)) {
+ // this is a cashtab.com generated message
+ try {
+ substring = extractCashtabMessage(hex);
+ opReturnMessage = Buffer.from(substring, 'hex');
+ isCashtabMessage = true;
+ } catch (err) {
+ // soft error if an unexpected or invalid cashtab hex is encountered
+ opReturnMessage = '';
+ console.log(
+ 'useBCH.parsedTxHistory() error: invalid cashtab msg hex: ' +
+ substring,
+ );
+ }
} else {
- // if this is not an eToken tx and does not contain addresses, then assume encoded message
- asm = removeOpReturnPrefixes(asm);
- let msgBody = asm.substr(asm.indexOf(' ') + 1); // extract everything after the OP_RETURN opcode
+ // this is an externally generated message
try {
- opReturnMessage = Buffer.from(msgBody, 'hex');
+ substring = extractExternalMessage(hex);
+ opReturnMessage = Buffer.from(substring, 'hex');
} catch (err) {
+ // soft error if an unexpected or invalid cashtab hex is encountered
opReturnMessage = '';
+ console.log(
+ 'useBCH.parsedTxHistory() error: invalid external msg hex: ' +
+ substring,
+ );
}
}
continue; // skipping the remainder of tx data parsing logic in both token and OP_RETURN tx cases
}
if (
thisOutput.scriptPubKey.addresses &&
thisOutput.scriptPubKey.addresses[0] === tx.address
) {
if (outgoingTx) {
// This amount is change
continue;
}
amountReceived += thisOutput.value;
} else if (outgoingTx) {
amountSent += thisOutput.value;
// Assume there's only one destination address, i.e. it was sent by a Cashtab wallet
destinationAddress = thisOutput.scriptPubKey.addresses[0];
}
}
// Construct parsedTx
parsedTx.amountSent = amountSent;
parsedTx.amountReceived = amountReceived;
parsedTx.tokenTx = tokenTx;
parsedTx.outgoingTx = outgoingTx;
parsedTx.destinationAddress = destinationAddress;
parsedTx.opReturnMessage = opReturnMessage;
-
+ parsedTx.isCashtabMessage = isCashtabMessage;
parsedTxHistory.push(parsedTx);
}
return parsedTxHistory;
};
const getTxHistory = async (BCH, addresses) => {
let txHistoryResponse;
try {
//console.log(`API Call: BCH.Electrumx.utxo(addresses)`);
//console.log(addresses);
txHistoryResponse = await BCH.Electrumx.transactions(addresses);
//console.log(`BCH.Electrumx.transactions(addresses) succeeded`);
//console.log(`txHistoryResponse`, txHistoryResponse);
if (txHistoryResponse.success && txHistoryResponse.transactions) {
return txHistoryResponse.transactions;
} else {
// eslint-disable-next-line no-throw-literal
throw new Error('Error in getTxHistory');
}
} catch (err) {
console.log(`Error in BCH.Electrumx.transactions(addresses):`);
console.log(err);
return err;
}
};
const getTxDataWithPassThrough = async (BCH, flatTx) => {
// necessary as BCH.RawTransactions.getTxData does not return address or blockheight
let txDataWithPassThrough = {};
try {
txDataWithPassThrough = await BCH.RawTransactions.getTxData(
flatTx.txid,
);
} catch (err) {
console.log(
`Error in BCH.RawTransactions.getTxData(${flatTx.txid})`,
);
console.log(err);
// Include txid if you don't get it from the attempted response
txDataWithPassThrough.txid = flatTx.txid;
}
txDataWithPassThrough.height = flatTx.height;
txDataWithPassThrough.address = flatTx.address;
return txDataWithPassThrough;
};
const getTxData = async (BCH, txHistory) => {
// Flatten tx history
let flatTxs = flattenTransactions(txHistory);
// Build array of promises to get tx data for all 10 transactions
let txDataPromises = [];
for (let i = 0; i < flatTxs.length; i += 1) {
const txDataPromise = await getTxDataWithPassThrough(
BCH,
flatTxs[i],
);
txDataPromises.push(txDataPromise);
}
// Get txData for the 10 most recent transactions
let txDataPromiseResponse;
try {
txDataPromiseResponse = await Promise.all(txDataPromises);
const parsed = parseTxData(txDataPromiseResponse);
return parsed;
} catch (err) {
console.log(`Error in Promise.all(txDataPromises):`);
console.log(err);
return err;
}
};
const parseTokenInfoForTxHistory = (BCH, parsedTx, tokenInfo) => {
// Address at which the eToken was received
const { destinationAddress } = parsedTx;
// Here in cashtab, destinationAddress is in bitcoincash: format
// In the API response of tokenInfo, this will be in simpleledger: format
// So, must convert to simpleledger
const receivingSlpAddress =
BCH.SLP.Address.toSLPAddress(destinationAddress);
const { transactionType, sendInputsFull, sendOutputsFull } = tokenInfo;
const sendingTokenAddresses = [];
// Scan over inputs to find out originating addresses
for (let i = 0; i < sendInputsFull.length; i += 1) {
const sendingAddress = sendInputsFull[i].address;
sendingTokenAddresses.push(sendingAddress);
}
// Scan over outputs to find out how much was sent
let qtySent = new BigNumber(0);
let qtyReceived = new BigNumber(0);
for (let i = 0; i < sendOutputsFull.length; i += 1) {
if (sendingTokenAddresses.includes(sendOutputsFull[i].address)) {
// token change and should be ignored, unless it's a genesis transaction
// then this is the amount created
if (transactionType === 'GENESIS') {
qtyReceived = qtyReceived.plus(
new BigNumber(sendOutputsFull[i].amount),
);
}
continue;
}
if (parsedTx.outgoingTx) {
qtySent = qtySent.plus(
new BigNumber(sendOutputsFull[i].amount),
);
} else {
// Only if this matches the receiving address
if (sendOutputsFull[i].address === receivingSlpAddress) {
qtyReceived = qtyReceived.plus(
new BigNumber(sendOutputsFull[i].amount),
);
}
}
}
const cashtabTokenInfo = {};
cashtabTokenInfo.qtySent = qtySent.toString();
cashtabTokenInfo.qtyReceived = qtyReceived.toString();
cashtabTokenInfo.tokenId = tokenInfo.tokenIdHex;
cashtabTokenInfo.tokenName = tokenInfo.tokenName;
cashtabTokenInfo.tokenTicker = tokenInfo.tokenTicker;
cashtabTokenInfo.transactionType = transactionType;
return cashtabTokenInfo;
};
const addTokenTxDataToSingleTx = async (BCH, parsedTx) => {
// Accept one parsedTx
// If it's not a token tx, just return it as is and do not parse for token data
if (!parsedTx.tokenTx) {
return parsedTx;
}
// If it could be a token tx, do an API call to get token info and return it
let tokenData;
try {
tokenData = await BCH.SLP.Utils.txDetails(parsedTx.txid);
} catch (err) {
console.log(
`Error in parsing BCH.SLP.Utils.txDetails(${parsedTx.txid})`,
);
console.log(err);
// This is not a token tx
parsedTx.tokenTx = false;
return parsedTx;
}
const { tokenInfo } = tokenData;
parsedTx.tokenInfo = parseTokenInfoForTxHistory(
BCH,
parsedTx,
tokenInfo,
);
return parsedTx;
};
const addTokenTxData = async (BCH, parsedTxs) => {
// Collect all txids for token transactions into array of promises
// Promise.all to get their tx history
// Add a tokeninfo object to parsedTxs for token txs
// Get txData for the 10 most recent transactions
// Build array of promises to get tx data for all 10 transactions
let tokenTxDataPromises = [];
for (let i = 0; i < parsedTxs.length; i += 1) {
const txDataPromise = await addTokenTxDataToSingleTx(
BCH,
parsedTxs[i],
);
tokenTxDataPromises.push(txDataPromise);
}
let tokenTxDataPromiseResponse;
try {
tokenTxDataPromiseResponse = await Promise.all(tokenTxDataPromises);
return tokenTxDataPromiseResponse;
} catch (err) {
console.log(`Error in Promise.all(tokenTxDataPromises):`);
console.log(err);
return err;
}
};
// Split out the BCH.Electrumx.utxo(addresses) call from the getSlpBalancesandUtxos function
// If utxo set has not changed, you do not need to hydrate the utxo set
// This drastically reduces calls to the API
const getUtxos = async (BCH, addresses) => {
let utxosResponse;
try {
//console.log(`API Call: BCH.Electrumx.utxo(addresses)`);
//console.log(addresses);
utxosResponse = await BCH.Electrumx.utxo(addresses);
//console.log(`BCH.Electrumx.utxo(addresses) succeeded`);
//console.log(`utxosResponse`, utxosResponse);
return utxosResponse.utxos;
} catch (err) {
console.log(`Error in BCH.Electrumx.utxo(addresses):`);
return err;
}
};
const getHydratedUtxoDetails = async (BCH, utxos) => {
const hydrateUtxosPromises = [];
for (let i = 0; i < utxos.length; i += 1) {
let thisAddress = utxos[i].address;
let theseUtxos = utxos[i].utxos;
const batchedUtxos = batchArray(
theseUtxos,
currency.hydrateUtxoBatchSize,
);
// Iterate over each utxo in this address field
for (let j = 0; j < batchedUtxos.length; j += 1) {
const utxoSetForThisPromise = [
{ utxos: batchedUtxos[j], address: thisAddress },
];
const thisPromise = BCH.SLP.Utils.hydrateUtxos(
utxoSetForThisPromise,
);
hydrateUtxosPromises.push(thisPromise);
}
}
let hydratedUtxoDetails;
try {
hydratedUtxoDetails = await Promise.all(hydrateUtxosPromises);
const flattenedBatchedHydratedUtxos =
flattenBatchedHydratedUtxos(hydratedUtxoDetails);
return flattenedBatchedHydratedUtxos;
} catch (err) {
console.log(`Error in Promise.all(hydrateUtxosPromises)`);
console.log(err);
return err;
}
};
const fetchTxDataForNullUtxos = async (BCH, nullUtxos) => {
// Check nullUtxos. If they aren't eToken txs, count them
console.log(
`Null utxos found, checking OP_RETURN fields to confirm they are not eToken txs.`,
);
const txids = [];
for (let i = 0; i < nullUtxos.length; i += 1) {
// Batch API call to get their OP_RETURN asm info
txids.push(nullUtxos[i].tx_hash);
}
let nullUtxoTxData;
try {
nullUtxoTxData = await BCH.Electrumx.txData(txids);
console.log(`nullUtxoTxData`, nullUtxoTxData.transactions);
// Scan tx data for each utxo to confirm they are not eToken txs
const txDataResults = nullUtxoTxData.transactions;
const nonEtokenUtxos = checkNullUtxosForTokenStatus(txDataResults);
return nonEtokenUtxos;
} catch (err) {
console.log(`Error in checkNullUtxosForTokenStatus(nullUtxos)`);
console.log(`nullUtxos`, nullUtxos);
// If error, ignore these utxos, will be updated next utxo set refresh
return [];
}
};
const getSlpBalancesAndUtxos = async (BCH, hydratedUtxoDetails) => {
let hydratedUtxos = [];
for (let i = 0; i < hydratedUtxoDetails.slpUtxos.length; i += 1) {
const hydratedUtxosAtAddress = hydratedUtxoDetails.slpUtxos[i];
for (let j = 0; j < hydratedUtxosAtAddress.utxos.length; j += 1) {
const hydratedUtxo = hydratedUtxosAtAddress.utxos[j];
hydratedUtxo.address = hydratedUtxosAtAddress.address;
hydratedUtxos.push(hydratedUtxo);
}
}
//console.log(`hydratedUtxos`, hydratedUtxos);
// WARNING
// If you hit rate limits, your above utxos object will come back with `isValid` as null, but otherwise ok
// You need to throw an error before setting nonSlpUtxos and slpUtxos in this case
const nullUtxos = hydratedUtxos.filter(utxo => utxo.isValid === null);
if (nullUtxos.length > 0) {
console.log(`${nullUtxos.length} null utxos found!`);
console.log('nullUtxos', nullUtxos);
const nullNonEtokenUtxos = await fetchTxDataForNullUtxos(
BCH,
nullUtxos,
);
// Set isValid === false for nullUtxos that are confirmed non-eToken
hydratedUtxos = confirmNonEtokenUtxos(
hydratedUtxos,
nullNonEtokenUtxos,
);
}
// Prevent app from treating slpUtxos as nonSlpUtxos
// Must enforce === false as api will occasionally return utxo.isValid === null
// Do not classify any utxos that include token information as nonSlpUtxos
const nonSlpUtxos = hydratedUtxos.filter(
utxo =>
utxo.isValid === false &&
utxo.value !== currency.etokenSats &&
!utxo.tokenName,
);
// To be included in slpUtxos, the utxo must
// have utxo.isValid = true
// If utxo has a utxo.tokenQty field, i.e. not a minting baton, then utxo.tokenQty !== '0'
const slpUtxos = hydratedUtxos.filter(
utxo => utxo.isValid && !(utxo.tokenQty === '0'),
);
let tokensById = {};
slpUtxos.forEach(slpUtxo => {
let token = tokensById[slpUtxo.tokenId];
if (token) {
// Minting baton does nto have a slpUtxo.tokenQty type
if (slpUtxo.tokenQty) {
token.balance = token.balance.plus(
new BigNumber(slpUtxo.tokenQty),
);
}
//token.hasBaton = slpUtxo.transactionType === "genesis";
if (slpUtxo.utxoType && !token.hasBaton) {
token.hasBaton = slpUtxo.utxoType === 'minting-baton';
}
// Examples of slpUtxo
/*
Genesis transaction:
{
address: "bitcoincash:qrhzv5t79e2afc3rdutcu0d3q20gl7ul3ue58whah6"
decimals: 9
height: 617564
isValid: true
satoshis: 546
tokenDocumentHash: ""
tokenDocumentUrl: "developer.bitcoin.com"
tokenId: "6c41f244676ecfcbe3b4fabee2c72c2dadf8d74f8849afabc8a549157db69199"
tokenName: "PiticoLaunch"
tokenTicker: "PTCL"
tokenType: 1
tx_hash: "6c41f244676ecfcbe3b4fabee2c72c2dadf8d74f8849afabc8a549157db69199"
tx_pos: 2
txid: "6c41f244676ecfcbe3b4fabee2c72c2dadf8d74f8849afabc8a549157db69199"
utxoType: "minting-baton"
value: 546
vout: 2
}
Send transaction:
{
address: "bitcoincash:qrhzv5t79e2afc3rdutcu0d3q20gl7ul3ue58whah6"
decimals: 9
height: 655115
isValid: true
satoshis: 546
tokenDocumentHash: ""
tokenDocumentUrl: "developer.bitcoin.com"
tokenId: "6c41f244676ecfcbe3b4fabee2c72c2dadf8d74f8849afabc8a549157db69199"
tokenName: "PiticoLaunch"
tokenQty: 1.123456789
tokenTicker: "PTCL"
tokenType: 1
transactionType: "send"
tx_hash: "dea400f963bc9f51e010f88533010f8d1f82fc2bcc485ff8500c3a82b25abd9e"
tx_pos: 1
txid: "dea400f963bc9f51e010f88533010f8d1f82fc2bcc485ff8500c3a82b25abd9e"
utxoType: "token"
value: 546
vout: 1
}
*/
} else {
token = {};
token.info = slpUtxo;
token.tokenId = slpUtxo.tokenId;
if (slpUtxo.tokenQty) {
token.balance = new BigNumber(slpUtxo.tokenQty);
} else {
token.balance = new BigNumber(0);
}
if (slpUtxo.utxoType) {
token.hasBaton = slpUtxo.utxoType === 'minting-baton';
} else {
token.hasBaton = false;
}
tokensById[slpUtxo.tokenId] = token;
}
});
const tokens = Object.values(tokensById);
// console.log(`tokens`, tokens);
return {
tokens,
nonSlpUtxos,
slpUtxos,
};
};
const calcFee = (
BCH,
utxos,
p2pkhOutputNumber = 2,
satoshisPerByte = currency.defaultFee,
) => {
const byteCount = BCH.BitcoinCash.getByteCount(
{ P2PKH: utxos.length },
{ P2PKH: p2pkhOutputNumber },
);
const txFee = Math.ceil(satoshisPerByte * byteCount);
return txFee;
};
const createToken = async (BCH, wallet, feeInSatsPerByte, configObj) => {
try {
// Throw error if wallet does not have utxo set in state
if (!isValidStoredWallet(wallet)) {
const walletError = new Error(`Invalid wallet`);
throw walletError;
}
const utxos = wallet.state.slpBalancesAndUtxos.nonSlpUtxos;
const CREATION_ADDR = wallet.Path1899.cashAddress;
const inputUtxos = [];
let transactionBuilder;
// instance of transaction builder
if (process.env.REACT_APP_NETWORK === `mainnet`)
transactionBuilder = new BCH.TransactionBuilder();
else transactionBuilder = new BCH.TransactionBuilder('testnet');
let originalAmount = new BigNumber(0);
let txFee = 0;
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i];
originalAmount = originalAmount.plus(new BigNumber(utxo.value));
const vout = utxo.vout;
const txid = utxo.txid;
// add input with txid and index of vout
transactionBuilder.addInput(txid, vout);
inputUtxos.push(utxo);
txFee = calcFee(BCH, inputUtxos, 3, feeInSatsPerByte);
if (
originalAmount
.minus(new BigNumber(currency.etokenSats))
.minus(new BigNumber(txFee))
.gte(0)
) {
break;
}
}
// amount to send back to the remainder address.
const remainder = originalAmount
.minus(new BigNumber(currency.etokenSats))
.minus(new BigNumber(txFee));
if (remainder.lt(0)) {
const error = new Error(`Insufficient funds`);
error.code = SEND_BCH_ERRORS.INSUFFICIENT_FUNDS;
throw error;
}
// Generate the OP_RETURN entry for an SLP GENESIS transaction.
const script =
BCH.SLP.TokenType1.generateGenesisOpReturn(configObj);
// OP_RETURN needs to be the first output in the transaction.
transactionBuilder.addOutput(script, 0);
// add output w/ address and amount to send
transactionBuilder.addOutput(CREATION_ADDR, currency.etokenSats);
// Send change to own address
if (remainder.gte(new BigNumber(currency.etokenSats))) {
transactionBuilder.addOutput(
CREATION_ADDR,
parseInt(remainder),
);
}
// Sign the transactions with the HD node.
for (let i = 0; i < inputUtxos.length; i++) {
const utxo = inputUtxos[i];
transactionBuilder.sign(
i,
BCH.ECPair.fromWIF(utxo.wif),
undefined,
transactionBuilder.hashTypes.SIGHASH_ALL,
utxo.value,
);
}
// build tx
const tx = transactionBuilder.build();
// output rawhex
const hex = tx.toHex();
// Broadcast transaction to the network
const txidStr = await BCH.RawTransactions.sendRawTransaction([hex]);
if (txidStr && txidStr[0]) {
console.log(`${currency.ticker} txid`, txidStr[0]);
}
let link;
if (process.env.REACT_APP_NETWORK === `mainnet`) {
link = `${currency.tokenExplorerUrl}/tx/${txidStr}`;
} else {
link = `${currency.blockExplorerUrlTestnet}/tx/${txidStr}`;
}
//console.log(`link`, link);
return link;
} catch (err) {
if (err.error === 'insufficient priority (code 66)') {
err.code = SEND_BCH_ERRORS.INSUFFICIENT_PRIORITY;
} else if (err.error === 'txn-mempool-conflict (code 18)') {
err.code = SEND_BCH_ERRORS.DOUBLE_SPENDING;
} else if (err.error === 'Network Error') {
err.code = SEND_BCH_ERRORS.NETWORK_ERROR;
} else if (
err.error ===
'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)'
) {
err.code = SEND_BCH_ERRORS.MAX_UNCONFIRMED_TXS;
}
console.log(`error: `, err);
throw err;
}
};
// No unit tests for this function as it is only an API wrapper
// Return false if do not get a valid response
const getTokenStats = async (BCH, tokenId) => {
let tokenStats;
try {
tokenStats = await BCH.SLP.Utils.tokenStats(tokenId);
if (isValidTokenStats(tokenStats)) {
return tokenStats;
}
} catch (err) {
console.log(`Error fetching token stats for tokenId ${tokenId}`);
console.log(err);
return false;
}
};
const sendToken = async (
BCH,
wallet,
slpBalancesAndUtxos,
{ tokenId, amount, tokenReceiverAddress },
) => {
// Handle error of user having no BCH
if (slpBalancesAndUtxos.nonSlpUtxos.length === 0) {
throw new Error(
`You need some ${currency.ticker} to send ${currency.tokenTicker}`,
);
}
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, index) => {
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 finalTokenAmountSent = new BigNumber(0);
let tokenAmountBeingSentToAddress = new BigNumber(amount);
let tokenUtxosBeingSpent = [];
for (let i = 0; i < tokenUtxos.length; i++) {
finalTokenAmountSent = finalTokenAmountSent.plus(
new BigNumber(tokenUtxos[i].tokenQty),
);
transactionBuilder.addInput(
tokenUtxos[i].tx_hash,
tokenUtxos[i].tx_pos,
);
tokenUtxosBeingSpent.push(tokenUtxos[i]);
if (tokenAmountBeingSentToAddress.lte(finalTokenAmountSent)) {
break;
}
}
const slpSendObj = BCH.SLP.TokenType1.generateSendOpReturn(
tokenUtxosBeingSpent,
tokenAmountBeingSentToAddress.toString(),
);
const slpData = slpSendObj.script;
// Add OP_RETURN as first output.
transactionBuilder.addOutput(slpData, 0);
// Send dust transaction representing tokens being sent.
transactionBuilder.addOutput(
BCH.SLP.Address.toLegacyAddress(tokenReceiverAddress),
currency.etokenSats,
);
// Return any token change back to the sender.
if (slpSendObj.outputs > 1) {
// Change goes back to where slp utxo came from
transactionBuilder.addOutput(
BCH.SLP.Address.toLegacyAddress(
tokenUtxosBeingSpent[0].address,
),
currency.etokenSats,
);
}
// get byte count to calculate fee. paying 1 sat
// Note: This may not be totally accurate. Just guessing on the byteCount size.
const txFee = calcFee(
BCH,
tokenUtxosBeingSpent,
5,
1.1 * currency.defaultFee,
);
// amount to send back to the sending address. 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');
}
// Last output: send the BCH change back to the wallet.
// Send it back from whence it came
transactionBuilder.addOutput(
BCH.Address.toLegacyAddress(largestBchUtxo.address),
remainder,
);
// Sign the transaction with the private key for the BCH 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 < tokenUtxosBeingSpent.length; i++) {
const thisUtxo = tokenUtxosBeingSpent[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.blockExplorerUrl}/tx/${txidStr}`;
} else {
link = `${currency.blockExplorerUrlTestnet}/tx/${txidStr}`;
}
//console.log(`link`, link);
return link;
};
const signPkMessage = async (BCH, pk, message) => {
try {
let signature = await BCH.BitcoinCash.signMessageWithPrivKey(
pk,
message,
);
return signature;
} catch (err) {
console.log(`useBCH.signPkMessage() error: `, err);
throw err;
}
};
const sendBch = async (
BCH,
wallet,
utxos,
destinationAddress,
sendAmount,
feeInSatsPerByte,
optionalOpReturnMsg,
) => {
try {
if (!sendAmount) {
return null;
}
const value = new BigNumber(sendAmount);
// If user is attempting to send less than minimum accepted by the backend
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');
}
const inputUtxos = [];
let transactionBuilder;
// instance of transaction builder
if (process.env.REACT_APP_NETWORK === `mainnet`)
transactionBuilder = new BCH.TransactionBuilder();
else transactionBuilder = new BCH.TransactionBuilder('testnet');
const satoshisToSend = toSmallestDenomination(value);
// Throw validation error if toSmallestDenomination returns false
if (!satoshisToSend) {
const error = new Error(
`Invalid decimal places for send amount`,
);
throw error;
}
// Start of building the OP_RETURN output.
// only build the OP_RETURN output if the user supplied it
if (
typeof optionalOpReturnMsg !== 'undefined' &&
optionalOpReturnMsg.trim() !== ''
) {
const script = [
- BCH.Script.opcodes.OP_RETURN,
- Buffer.from('6d02', 'hex'),
+ BCH.Script.opcodes.OP_RETURN, // 6a
+ Buffer.from(
+ currency.opReturn.appPrefixesHex.cashtab,
+ 'hex',
+ ), // 00746162
Buffer.from(optionalOpReturnMsg),
];
const data = BCH.Script.encode(script);
transactionBuilder.addOutput(data, 0);
}
// End of building the OP_RETURN output.
let originalAmount = new BigNumber(0);
let txFee = 0;
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i];
originalAmount = originalAmount.plus(utxo.value);
const vout = utxo.vout;
const txid = utxo.txid;
// add input with txid and index of vout
transactionBuilder.addInput(txid, vout);
inputUtxos.push(utxo);
txFee = calcFee(BCH, inputUtxos, 2, feeInSatsPerByte);
if (originalAmount.minus(satoshisToSend).minus(txFee).gte(0)) {
break;
}
}
// Get change address from sending utxos
// fall back to what is stored in wallet
let REMAINDER_ADDR;
// Validate address
let isValidChangeAddress;
try {
REMAINDER_ADDR = inputUtxos[0].address;
isValidChangeAddress =
BCH.Address.isCashAddress(REMAINDER_ADDR);
} catch (err) {
isValidChangeAddress = false;
}
if (!isValidChangeAddress) {
REMAINDER_ADDR = wallet.Path1899.cashAddress;
}
// amount to send back to the remainder address.
const remainder = originalAmount.minus(satoshisToSend).minus(txFee);
if (remainder.lt(0)) {
const error = new Error(`Insufficient funds`);
error.code = SEND_BCH_ERRORS.INSUFFICIENT_FUNDS;
throw error;
}
// add output w/ address and amount to send
transactionBuilder.addOutput(
BCH.Address.toCashAddress(destinationAddress),
parseInt(toSmallestDenomination(value)),
);
if (remainder.gte(new BigNumber(currency.dustSats))) {
transactionBuilder.addOutput(
REMAINDER_ADDR,
parseInt(remainder),
);
}
// Sign the transactions with the HD node.
for (let i = 0; i < inputUtxos.length; i++) {
const utxo = inputUtxos[i];
transactionBuilder.sign(
i,
BCH.ECPair.fromWIF(utxo.wif),
undefined,
transactionBuilder.hashTypes.SIGHASH_ALL,
utxo.value,
);
}
// build tx
const tx = transactionBuilder.build();
// output rawhex
const hex = tx.toHex();
// Broadcast transaction to the network
const txidStr = await BCH.RawTransactions.sendRawTransaction([hex]);
if (txidStr && txidStr[0]) {
console.log(`${currency.ticker} txid`, txidStr[0]);
}
let link;
if (process.env.REACT_APP_NETWORK === `mainnet`) {
link = `${currency.blockExplorerUrl}/tx/${txidStr}`;
} else {
link = `${currency.blockExplorerUrlTestnet}/tx/${txidStr}`;
}
//console.log(`link`, link);
return link;
} catch (err) {
if (err.error === 'insufficient priority (code 66)') {
err.code = SEND_BCH_ERRORS.INSUFFICIENT_PRIORITY;
} else if (err.error === 'txn-mempool-conflict (code 18)') {
err.code = SEND_BCH_ERRORS.DOUBLE_SPENDING;
} else if (err.error === 'Network Error') {
err.code = SEND_BCH_ERRORS.NETWORK_ERROR;
} else if (
err.error ===
'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)'
) {
err.code = SEND_BCH_ERRORS.MAX_UNCONFIRMED_TXS;
}
console.log(`error: `, err);
throw err;
}
};
const getBCH = (apiIndex = 0) => {
let ConstructedSlpWallet;
ConstructedSlpWallet = new SlpWallet('', {
restURL: getRestUrl(apiIndex),
});
return ConstructedSlpWallet.bchjs;
};
return {
getBCH,
calcFee,
getUtxos,
getHydratedUtxoDetails,
getSlpBalancesAndUtxos,
getTxHistory,
flattenTransactions,
parseTxData,
addTokenTxData,
parseTokenInfoForTxHistory,
getTxData,
getRestUrl,
signPkMessage,
sendBch,
sendToken,
createToken,
getTokenStats,
};
}