diff --git a/web/cashtab/src/components/Common/Ticker.js b/web/cashtab/src/components/Common/Ticker.js --- a/web/cashtab/src/components/Common/Ticker.js +++ b/web/cashtab/src/components/Common/Ticker.js @@ -29,6 +29,14 @@ notificationDurationShort: 3, notificationDurationLong: 5, newTokenDefaultUrl: 'https://cashtab.com/', + opReturn: { + eTokenPrefixAsm: '5262419', + opReturnPrefixHex: '6a', + opReturnPushDataHex: '04', + opreturnMessagePrefixAsm: '1130', + cashtabPrefixHex: '00746162', + cashtabPrefixAsm: '1650553856', + }, settingsValidation: { fiatCurrency: [ 'usd', diff --git a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap --- a/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap +++ b/web/cashtab/src/components/Send/__tests__/__snapshots__/Send.test.js.snap @@ -388,7 +388,7 @@ ,
Signatures
, @@ -826,7 +826,7 @@ ,
Signatures
, @@ -1272,7 +1272,7 @@ ,
Signatures
, @@ -1710,7 +1710,7 @@ ,
Signatures
, @@ -2148,7 +2148,7 @@ ,
Signatures
, diff --git a/web/cashtab/src/components/Wallet/Tx.js b/web/cashtab/src/components/Wallet/Tx.js --- a/web/cashtab/src/components/Wallet/Tx.js +++ b/web/cashtab/src/components/Wallet/Tx.js @@ -31,16 +31,15 @@ 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; @@ -50,6 +49,18 @@ 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; @@ -379,7 +390,15 @@ <>
- Message: + {data.isCashtabMessage ? ( + + Cashtab Message + + ) : ( + + External Message + + )}
{data.opReturnMessage ? Buffer.from( 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 --- a/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap +++ b/web/cashtab/src/components/Wallet/__tests__/__snapshots__/Wallet.test.js.snap @@ -3,7 +3,7 @@ exports[`Wallet with BCH balances 1`] = ` Array [
,
0 @@ -119,16 +119,16 @@
,
XEC
eToken @@ -140,7 +140,7 @@ exports[`Wallet with BCH balances and tokens 1`] = ` Array [
,
0 @@ -256,16 +256,16 @@
,
XEC
eToken @@ -277,14 +277,14 @@ exports[`Wallet with BCH balances and tokens and state field 1`] = ` Array [
0.06 XEC
,
$ NaN @@ -375,16 +375,16 @@
,
XEC
eToken @@ -396,7 +396,7 @@ exports[`Wallet without BCH balance 1`] = ` Array [
,
0 @@ -512,16 +512,16 @@
,
XEC
eToken diff --git a/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js b/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js --- a/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js +++ b/web/cashtab/src/hooks/__mocks__/mockParsedTxs.js @@ -9,6 +9,7 @@ 'bitcoincash:qphpmfj0qn7znklqhrfn5dq7qh36l3vxavu346vqcl', height: 674993, outgoingTx: true, + isCashtabMessage: false, opReturnMessage: '', tokenTx: false, txid: '089f2188d5771a7de0589def2b8d6c1a1f33f45b6de26d9a0ef32782f019ecf1', @@ -25,6 +26,7 @@ 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9', height: 672077, outgoingTx: false, + isCashtabMessage: false, opReturnMessage: '', tokenTx: false, txid: '42d39fbe068a40fe691f987b22fdf04b80f94d71d2fec20a58125e7b1a06d2a9', @@ -41,6 +43,7 @@ 'bitcoincash:qzj5zu6fgg8v2we82gh76xnrk9njcregluzgaztm45', height: 674444, outgoingTx: true, + isCashtabMessage: false, opReturnMessage: '', tokenTx: true, txid: 'ffe3a7500dbcc98021ad581c98d9947054d1950a7f3416664715066d3d20ad72', @@ -56,6 +59,7 @@ 'bitcoincash:qpmytrdsakt0axrrlswvaj069nat3p9s7ct4lsf8k9', height: 674143, outgoingTx: false, + isCashtabMessage: false, opReturnMessage: '', tokenTx: true, txid: '618d0dd8c0c5fa5a34c6515c865dd72bb76f8311cd6ee9aef153bab20dabc0e6', @@ -72,6 +76,7 @@ opReturnMessage: new Buffer('testing message 12'), outgoingTx: false, tokenTx: false, + isCashtabMessage: true, txid: 'dd35690b0cefd24dcc08acba8694ecd49293f365a81372cb66c8f1c1291d97c5', }, ]; @@ -86,6 +91,7 @@ opReturnMessage: new Buffer('testing message 13'), 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 --- a/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js +++ b/web/cashtab/src/hooks/__mocks__/mockTxDataWithPassthrough.js @@ -789,7 +789,7 @@ value: 0, n: 0, scriptPubKey: { - asm: 'OP_RETURN 621 74657374696e67206d657373616765203132', + asm: 'OP_RETURN 1130 1650553856 74657374696e67206d657373616765203132', hex: '6a026d021274657374696e67206d657373616765203132', type: 'nulldata', }, @@ -840,7 +840,7 @@ value: 0, n: 0, scriptPubKey: { - asm: 'OP_RETURN 621 74657374696e67206d657373616765203133', + asm: 'OP_RETURN 1130 1650553856 74657374696e67206d657373616765203133', hex: '6a026d021274657374696e67206d657373616765203133', type: 'nulldata', }, diff --git a/web/cashtab/src/hooks/__tests__/useBCH.test.js b/web/cashtab/src/hooks/__tests__/useBCH.test.js --- a/web/cashtab/src/hooks/__tests__/useBCH.test.js +++ b/web/cashtab/src/hooks/__tests__/useBCH.test.js @@ -1,5 +1,5 @@ /* eslint-disable no-native-reassign */ -import useBCH from '../useBCH'; +import useBCH, { removeOpReturnPrefixes } from '../useBCH'; import mockReturnGetHydratedUtxoDetails from '../__mocks__/mockReturnGetHydratedUtxoDetails'; import mockReturnGetSlpBalancesAndUtxos from '../__mocks__/mockReturnGetSlpBalancesAndUtxos'; import mockReturnGetHydratedUtxoDetailsWithZeroBalance from '../__mocks__/mockReturnGetHydratedUtxoDetailsWithZeroBalance'; @@ -414,4 +414,31 @@ mockReceivedOpReturnMessageTx, ); }); + + it(`Correctly removes an OP_RETURN message prefix from an ASM substring`, () => { + const { removeOpReturnPrefixes } = useBCH(); + expect( + removeOpReturnPrefixes('OP_RETURN 1130 REMAINDER'), + ).toStrictEqual('OP_RETURN REMAINDER'); + }); + + it(`Correctly returns an eToken OP_RETURN message prefix untouched`, () => { + const { removeOpReturnPrefixes } = useBCH(); + expect( + removeOpReturnPrefixes( + 'OP_RETURN 5262419 1 1145980243 69b8431ddecf775393b1b36aa1d0ddcd7b342f1157b9671a03747378ed35ea0d 00000000000000e6 000000000000010e', + ), + ).toStrictEqual( + 'OP_RETURN 5262419 1 1145980243 69b8431ddecf775393b1b36aa1d0ddcd7b342f1157b9671a03747378ed35ea0d 00000000000000e6 000000000000010e', + ); + }); + + it(`Correctly returns an invalid OP_RETURN message prefix untouched`, () => { + const { removeOpReturnPrefixes } = useBCH(); + expect( + removeOpReturnPrefixes( + 'OP_RETURN not a message its just gibberish REMAINDER', + ), + ).toStrictEqual('OP_RETURN not a message its just gibberish REMAINDER'); + }); }); diff --git a/web/cashtab/src/hooks/useBCH.js b/web/cashtab/src/hooks/useBCH.js --- a/web/cashtab/src/hooks/useBCH.js +++ b/web/cashtab/src/hooks/useBCH.js @@ -33,9 +33,10 @@ // 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', ''); + let msgPrefix = ' ' + currency.opReturn.opreturnMessagePrefixAsm; + if (asmStr.includes(msgPrefix)) { + //strip out the message prefix if exists + asmStr = asmStr.replace(msgPrefix, ''); } return asmStr; }; @@ -126,6 +127,7 @@ let amountSent = 0; let amountReceived = 0; let opReturnMessage = ''; + let isCashtabMessage = false; // Assume an incoming transaction let outgoingTx = false; let tokenTx = false; @@ -148,7 +150,9 @@ !Object.keys(thisOutput.scriptPubKey).includes('addresses') ) { let asm = thisOutput.scriptPubKey.asm; - if (asm.includes('OP_RETURN 5262419')) { + let eTokenSubstring = + 'OP_RETURN ' + currency.opReturn.eTokenPrefixAsm; + if (asm.includes(eTokenSubstring)) { // assume this is an eToken tx for now // future diffs will add additional NFT parsing logic in this segment tokenTx = true; @@ -156,9 +160,23 @@ // 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 + try { - opReturnMessage = Buffer.from(msgBody, 'hex'); + if ( + msgBody.split(' ')[0] === + currency.opReturn.cashtabPrefixAsm + ) { + // this is a Cashtab.com generated message + opReturnMessage = Buffer.from( + msgBody.substr(msgBody.indexOf(' ') + 1), + 'hex', + ); // extract everything after the Cashtab prefix + isCashtabMessage = true; + } else { + opReturnMessage = Buffer.from(msgBody, 'hex'); + } } catch (err) { + // if an unexpected or invalid hex is encountered opReturnMessage = ''; } } @@ -186,7 +204,7 @@ parsedTx.outgoingTx = outgoingTx; parsedTx.destinationAddress = destinationAddress; parsedTx.opReturnMessage = opReturnMessage; - + parsedTx.isCashtabMessage = isCashtabMessage; parsedTxHistory.push(parsedTx); } return parsedTxHistory; @@ -990,7 +1008,12 @@ ) { const script = [ BCH.Script.opcodes.OP_RETURN, - Buffer.from('6d02', 'hex'), + Buffer.from( + currency.opReturn.opReturnPrefixHex + + currency.opReturn.opReturnPushDataHex, + 'hex', + ), + Buffer.from(currency.opReturn.cashtabPrefixHex, 'hex'), Buffer.from(optionalOpReturnMsg), ]; const data = BCH.Script.encode(script); @@ -1132,5 +1155,6 @@ sendToken, createToken, getTokenStats, + removeOpReturnPrefixes, }; }