Page MenuHomePhabricator

D10628.id31268.diff
No OneTemporary

D10628.id31268.diff

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
@@ -34,9 +34,11 @@
opReturnPrefixHex: '6a',
opReturnPushDataHex: '04',
opReturnAppPrefixLengthHex: '04',
+ opPushDataOne: '4c',
appPrefixesHex: {
eToken: '534c5000',
cashtab: '00746162',
+ UnknownApp: '6e6f617070',
},
},
settingsValidation: {
@@ -93,53 +95,69 @@
},
};
-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) {
+export function parseOpReturn(hexStr) {
if (!hexStr || typeof hexStr !== 'string') {
return false;
}
- return hexStr.startsWith(getCashtabEncodingSubstring());
-}
+ /*
+ * @Return: resultArray is structured as follows:
+ * resultArray[0] is the protocol ID - not used for now, kept for future upgrades
+ * resultArray[1] is the transaction type i.e. eToken, cashtab message, external message
+ * resultArray[2] is the actual cashtab message
+ * resultArray[3 - n] are the additional messages for future protcols
+ */
+ let resultArray = [];
+ resultArray[0] = ''; // protocol ID not currently used but set aside for future scaling
+ let appPrefixParsed = false;
-export function isEtokenOutput(hexStr) {
- if (!hexStr || typeof hexStr !== 'string') {
- return false;
- }
- return hexStr.startsWith(getETokenEncodingSubstring());
-}
+ hexStr = hexStr.slice(2); // remove the first byte i.e. 6a
-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;
-}
+ let message = '';
+ let hexStrLength = hexStr.length;
+
+ for (let i = 0; hexStrLength !== 0; i++) {
+ // part 1: check the preceding byte value for the subsequent message
+ let byteValue = hexStr.substring(0, 2);
+ let msgByteSize = 0;
+ if (byteValue === currency.opReturn.opPushDataOne) {
+ // if this byte is 4c then the next byte is the message byte size - retrieve the message byte size only
+ msgByteSize = parseInt(hexStr.substring(2, 4), 16); // hex to base16 decimal
+ hexStr = hexStr.slice(4); // strip the 4c + message byte size info
+ } else {
+ // take the byte as the message byte size
+ msgByteSize = parseInt(hexStr.substring(0, 2), 16); // hex to base16 decimal
+ hexStr = hexStr.slice(2); // strip the message byte size info
+ }
+
+ // part 2: parse the subsequent message based on bytesize
+ message = hexStr.substring(0, msgByteSize * 2);
+ if (message === currency.opReturn.appPrefixesHex.eToken) {
+ // add the extracted prefix to array then exit loop
+ // array index is +1 because index 0 is reserved for Protocol ID
+ resultArray[i + 1] = currency.opReturn.appPrefixesHex.eToken;
+ appPrefixParsed = true;
+ break;
+ } else if (message === currency.opReturn.appPrefixesHex.cashtab) {
+ // add the extracted prefix to array
+ resultArray[i + 1] = currency.opReturn.appPrefixesHex.cashtab;
+ appPrefixParsed = true;
+ } else {
+ if (appPrefixParsed) {
+ // app prefix already parsed, so extract the external or cashtab message
+ resultArray[i + 1] = message;
+ } else {
+ // this is part of the initial app prefix parsing loop where it's not eToken or Cashtab message
+ resultArray[i + 1] =
+ currency.opReturn.appPrefixesHex.UnknownApp + message; // appending the message to the end to adhere to this iteration being the prefix indicator
+ appPrefixParsed = true;
+ }
+ }
-export function extractExternalMessage(hexSubstring) {
- if (!hexSubstring || typeof hexSubstring !== 'string') {
- return '';
+ // strip out the parsed message
+ hexStr = hexStr.slice(msgByteSize * 2);
+ hexStrLength = hexStr.length;
}
- let substring = hexSubstring.slice(4); // remove the preceding OP_RETURN prefixes
- return substring;
+ return resultArray;
}
export function isValidCashPrefix(addressString) {
diff --git a/web/cashtab/src/components/Common/__tests__/Ticker.test.js b/web/cashtab/src/components/Common/__tests__/Ticker.test.js
--- a/web/cashtab/src/components/Common/__tests__/Ticker.test.js
+++ b/web/cashtab/src/components/Common/__tests__/Ticker.test.js
@@ -3,12 +3,8 @@
isValidCashPrefix,
isValidTokenPrefix,
toLegacy,
- isCashtabOutput,
- isEtokenOutput,
- extractCashtabMessage,
- extractExternalMessage,
- getETokenEncodingSubstring,
- getCashtabEncodingSubstring,
+ parseOpReturn,
+ currency,
} from '../Ticker';
test('Rejects cash address with bitcoincash: prefix', async () => {
@@ -117,75 +113,110 @@
);
});
-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',
+test('parseOpReturn() successfully parses a short cashtab message', async () => {
+ const result = Buffer.from(
+ parseOpReturn('6a04007461620d6368726973746d617320657665')[2],
+ 'hex',
);
- expect(result).toStrictEqual(false);
+ expect(result).toStrictEqual(Buffer.from('christmas eve'));
});
-test('isEtokenOutput() correctly validates an eToken output hex', async () => {
- const result = isEtokenOutput(
- '6a04534c500001010453454e442069b8431ddecf775393b1b36aa1d0ddcd7b342f1157b9671a03747378ed35ea0d08000000000000012c080000000000002008',
+test('parseOpReturn() successfully parses a long cashtab message where an additional PUSHDATA1 is present', async () => {
+ const result = Buffer.from(
+ parseOpReturn(
+ '6a04007461624ca054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e',
+ )[2],
+ 'hex',
+ );
+ expect(result).toStrictEqual(
+ Buffer.from(
+ 'Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testin',
+ ),
);
- expect(result).toStrictEqual(true);
});
-test('isEtokenOutput() correctly invalidates an eToken output hex', async () => {
- const result = isEtokenOutput(
- '5434c500001010453454e442069b8431ddecf775393b1b36aa1d0ddcd7b342f1157b9671a03747378ed35ea0d08000000000000012c080000000000002008',
+test('parseOpReturn() successfully parses a short external message', async () => {
+ const result = Buffer.from(
+ parseOpReturn('6a1173686f727420656c65637472756d313132')[1].replace(
+ currency.opReturn.appPrefixesHex.UnknownApp,
+ '',
+ ),
+ 'hex',
);
- expect(result).toStrictEqual(false);
+ expect(result).toStrictEqual(Buffer.from('short electrum112'));
});
-test('isEtokenOutput() correctly handles null input', async () => {
- const result = isEtokenOutput(null);
- expect(result).toStrictEqual(false);
+test('parseOpReturn() successfully parses a long external message where an additional PUSHDATA1 is present', async () => {
+ const result = Buffer.from(
+ parseOpReturn(
+ '6a4cd9656c65637472756d6520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d6940',
+ )[1].replace(currency.opReturn.appPrefixesHex.UnknownApp, ''),
+ 'hex',
+ );
+ expect(result).toStrictEqual(
+ Buffer.from(
+ 'electrume 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limi@',
+ ),
+ );
});
-test('isEtokenOutput() correctly handles non-string input', async () => {
- const result = isEtokenOutput(7623723323);
- expect(result).toStrictEqual(false);
+test('parseOpReturn() successfully parses a short external message that is segmented into separate short parts', async () => {
+ const parsedArray = parseOpReturn(
+ '6a1173686f727420656c65637472756d3131321173686f727420656c65637472756d3131321173686f727420656c65637472756d313132',
+ );
+ let result = parsedArray[1].replace(
+ currency.opReturn.appPrefixesHex.UnknownApp,
+ '',
+ );
+ if (parsedArray.length > 1) {
+ for (let i = 2; i <= parsedArray.length; i++) {
+ result = result + parsedArray[i];
+ }
+ }
+ result = Buffer.from(result, 'hex');
+ expect(result).toStrictEqual(
+ Buffer.from('short electrum112short electrum112short electrum112'),
+ );
});
-test('extractCashtabMessage() correctly extracts a Cashtab message', async () => {
- const result = extractCashtabMessage(
- '6a04007461620b63617368746162756c6172',
+test('parseOpReturn() successfully parses a long external message that is segmented into separate long parts', async () => {
+ const parsedArray = parseOpReturn(
+ '6a4cd9656c65637472756d6520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69404cd9656c65637472756d6520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d6940',
+ );
+ let result = parsedArray[1].replace(
+ currency.opReturn.appPrefixesHex.UnknownApp,
+ '',
+ );
+ if (parsedArray.length > 1) {
+ for (let i = 2; i <= parsedArray.length; i++) {
+ result = result + parsedArray[i];
+ }
+ }
+ result = Buffer.from(result, 'hex');
+ expect(result).toStrictEqual(
+ Buffer.from(
+ 'electrume 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limi@electrume 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limi@',
+ ),
);
- expect(result).toStrictEqual('63617368746162756c6172');
});
-test('extractExternalMessage() correctly extracts an external message', async () => {
- const result = extractExternalMessage('6a0d62696e676f656c65637472756d');
- expect(result).toStrictEqual('62696e676f656c65637472756d');
+test('parseOpReturn() successfully parses a long external message that is segmented into separate mix of long and short parts', async () => {
+ const parsedArray = parseOpReturn(
+ '6a4cd9656c65637472756d6520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69404cd9656c65637472756d6520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69742054657374696e672074686520323535206c696d69401173686f727420656c65637472756d313132',
+ );
+ let result = parsedArray[1].replace(
+ currency.opReturn.appPrefixesHex.UnknownApp,
+ '',
+ );
+ if (parsedArray.length > 1) {
+ for (let i = 2; i <= parsedArray.length; i++) {
+ result = result + parsedArray[i];
+ }
+ }
+ result = Buffer.from(result, 'hex');
+ expect(result).toStrictEqual(
+ Buffer.from(
+ 'electrume 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limi@electrume 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limit Testing the 255 limi@short electrum112',
+ ),
+ );
});
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
@@ -5,6 +5,7 @@
isEtokenOutput,
extractCashtabMessage,
extractExternalMessage,
+ parseOpReturn,
} from '@components/Common/Ticker';
import { isValidTokenStats } from '@utils/validation';
import SlpWallet from 'minimal-slp-wallet';
@@ -152,29 +153,55 @@
!Object.keys(thisOutput.scriptPubKey).includes('addresses')
) {
let hex = thisOutput.scriptPubKey.hex;
+ let parsedOpReturnArray = parseOpReturn(hex);
+ let message = '';
+ let txType = parsedOpReturnArray[1];
- if (isEtokenOutput(hex)) {
+ if (txType === currency.opReturn.appPrefixesHex.eToken) {
// this is an eToken transaction
tokenTx = true;
- } else if (isCashtabOutput(hex)) {
- // this is a cashtab.com generated message
+ } else if (
+ txType === currency.opReturn.appPrefixesHex.cashtab
+ ) {
+ // this is a Cashtab message
try {
- substring = extractCashtabMessage(hex);
- opReturnMessage = Buffer.from(substring, 'hex');
+ opReturnMessage = Buffer.from(
+ parsedOpReturnArray[2],
+ '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,
+ parsedOpReturnArray[2],
);
}
- } else {
+ } else if (
+ txType.startsWith(
+ currency.opReturn.appPrefixesHex.UnknownApp,
+ )
+ ) {
// this is an externally generated message
+ txType = txType.replace(
+ currency.opReturn.appPrefixesHex.UnknownApp,
+ '',
+ );
+ message = txType; // remaining substring is the first part of the external message
+
+ // if there are more than one part to the external message
+ const arrayLength = parsedOpReturnArray.length;
+ for (let i = 2; i < arrayLength; i++) {
+ message = message + parsedOpReturnArray[i];
+ if (i > 2) {
+ // this is a tx from an external protocol, append double linebreaks
+ message = message + '<br /><br />';
+ }
+ }
+
try {
- substring = extractExternalMessage(hex);
- opReturnMessage = Buffer.from(substring, 'hex');
+ opReturnMessage = Buffer.from(message, 'hex');
} catch (err) {
// soft error if an unexpected or invalid cashtab hex is encountered
opReturnMessage = '';
@@ -183,6 +210,10 @@
substring,
);
}
+ } else {
+ console.log(
+ 'parseTx.parseOpReturn() error: app prefix is null',
+ );
}
continue; // skipping the remainder of tx data parsing logic in both token and OP_RETURN tx cases
}

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 26, 11:16 (4 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5573383
Default Alt Text
D10628.id31268.diff (19 KB)

Event Timeline