Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13711206
D14325.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Subscribers
None
D14325.diff
View Options
diff --git a/cashtab/package-lock.json b/cashtab/package-lock.json
--- a/cashtab/package-lock.json
+++ b/cashtab/package-lock.json
@@ -41,7 +41,7 @@
"css-minimizer-webpack-plugin": "^3.2.0",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
- "ecashaddrjs": "^1.3.0",
+ "ecashaddrjs": "^1.5.2",
"ecies-lite": "^1.0.7",
"eslint": "^8.3.0",
"eslint-config-react-app": "^7.0.0",
@@ -7693,9 +7693,9 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
},
"node_modules/ecashaddrjs": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/ecashaddrjs/-/ecashaddrjs-1.3.0.tgz",
- "integrity": "sha512-J0HN4g6GD/AhMUahCu0KD/Lon0lbSKjXjjeRZ0r9HbD2qZUldcLuuykqA/sgpKGf1z4g0dYsSD1oXaaIYHShAA==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/ecashaddrjs/-/ecashaddrjs-1.5.2.tgz",
+ "integrity": "sha512-zzpDxdVR6SmIym/s1OUo5gEzkH86ztHKbdzuFGet8vUpV0nIejOcW8JiTiq74PHAcIKVb9bQ7ZU7F1Ryw6F6xA==",
"dependencies": {
"big-integer": "1.6.36",
"bs58check": "^3.0.1"
@@ -25438,9 +25438,9 @@
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
},
"ecashaddrjs": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/ecashaddrjs/-/ecashaddrjs-1.3.0.tgz",
- "integrity": "sha512-J0HN4g6GD/AhMUahCu0KD/Lon0lbSKjXjjeRZ0r9HbD2qZUldcLuuykqA/sgpKGf1z4g0dYsSD1oXaaIYHShAA==",
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/ecashaddrjs/-/ecashaddrjs-1.5.2.tgz",
+ "integrity": "sha512-zzpDxdVR6SmIym/s1OUo5gEzkH86ztHKbdzuFGet8vUpV0nIejOcW8JiTiq74PHAcIKVb9bQ7ZU7F1Ryw6F6xA==",
"requires": {
"big-integer": "1.6.36",
"bs58check": "^3.0.1"
diff --git a/cashtab/package.json b/cashtab/package.json
--- a/cashtab/package.json
+++ b/cashtab/package.json
@@ -36,7 +36,7 @@
"css-minimizer-webpack-plugin": "^3.2.0",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
- "ecashaddrjs": "^1.3.0",
+ "ecashaddrjs": "^1.5.2",
"ecies-lite": "^1.0.7",
"eslint": "^8.3.0",
"eslint-config-react-app": "^7.0.0",
diff --git a/cashtab/src/components/Common/Notifications.js b/cashtab/src/components/Common/Notifications.js
--- a/cashtab/src/components/Common/Notifications.js
+++ b/cashtab/src/components/Common/Notifications.js
@@ -232,12 +232,17 @@
});
};
-const generalNotification = (data, msgStr) => {
+const generalNotification = (
+ data,
+ msgStr,
+ duration = currency.notificationDurationShort,
+) => {
const notificationStyle = getDeviceNotificationStyle();
notification.success({
message: msgStr,
description: data,
style: notificationStyle,
+ duration: duration,
});
};
diff --git a/cashtab/src/utils/__mocks__/chronikMock.js b/cashtab/src/utils/__mocks__/chronikMock.js
--- a/cashtab/src/utils/__mocks__/chronikMock.js
+++ b/cashtab/src/utils/__mocks__/chronikMock.js
@@ -14,10 +14,16 @@
self._url = `https://${url}`;
self._wsUrl = `wss://${url}`;
+ // API call mock return objects
+ // Can be set with self.setMock
+ self.mockedResponses = {
+ // empty for now, will include other API mocks as needed
+ };
+
// Flags to check if methods have been called
self.wsSubscribeCalled = false;
self.wsWaitForOpenCalled = false;
-
+ self.manuallyClosed = false;
// public chronik methods
// todo see latest failing unit test
self.ws = function (wsObj) {
@@ -45,6 +51,9 @@
}
};
+ self.wsClose = function () {
+ self.manuallyClosed = true;
+ };
// Allow user to set expected chronik call response
self.setMock = function (call, options) {
// e.g. ('block', {input: '', output: ''})
diff --git a/cashtab/src/utils/__tests__/chronik.test.js b/cashtab/src/utils/__tests__/chronik.test.js
--- a/cashtab/src/utils/__tests__/chronik.test.js
+++ b/cashtab/src/utils/__tests__/chronik.test.js
@@ -8,6 +8,7 @@
sortAndTrimChronikTxHistory,
parseChronikTx,
getMintAddress,
+ txConfListener,
} from 'utils/chronik';
import {
mockChronikUtxos,
@@ -68,6 +69,37 @@
} from '../__mocks__/chronikMintTxs';
import { ChronikClient } from 'chronik-client';
import { when } from 'jest-when';
+import { MockChronikClient } from 'utils/__mocks__/chronikMock';
+
+it(`txConfListener successfully subscribes to the given address' confirmation event`, async () => {
+ // Initialize mock chronik
+ const address = 'ecash:qp3c268rd5946l2f5m5es4x25f7ewu4sjvpy52pqa8';
+ const txid =
+ 'f7d71433af9a4e0081ea60349becf2a60efed8890df7c3e8e079b3427f51d5ea';
+ const mockedChronik = new MockChronikClient();
+
+ // Create websocket subscription
+ await txConfListener(mockedChronik, address, txid, '');
+
+ // Mock the 'Confirmed' ws event
+ const wsMsg = {
+ type: 'Confirmed',
+ txid: txid,
+ };
+ // Tell mockedChronik what response we expect
+ mockedChronik.setMock('ws', {
+ output: wsMsg,
+ });
+ mockedChronik.wsClose();
+
+ // Verify subscription functions were called
+ expect(mockedChronik.wsWaitForOpenCalled).toStrictEqual(true);
+ expect(mockedChronik.wsSubscribeCalled).toStrictEqual(true);
+ expect(mockedChronik.manuallyClosed).toStrictEqual(true);
+ // Verify ws confirmation event on the given txid
+ expect(mockedChronik.mockedResponses.ws.type).toStrictEqual('Confirmed');
+ expect(mockedChronik.mockedResponses.ws.txid).toStrictEqual(txid);
+});
it(`getTokenStats successfully returns a token stats object`, async () => {
// Initialize chronik
diff --git a/cashtab/src/utils/__tests__/transactions.test.js b/cashtab/src/utils/__tests__/transactions.test.js
--- a/cashtab/src/utils/__tests__/transactions.test.js
+++ b/cashtab/src/utils/__tests__/transactions.test.js
@@ -109,10 +109,8 @@
it('Broadcasts a v0 alias registration tx for an 8-byte alias to a p2pkh address', async () => {
const mockedChronik = new MockChronikClient();
-
const mockTxid =
'1272c4a9bf5829c9dba1efb252e753ed20e3cdd49b6e75a778befc7a87eaf7d0';
-
mockedChronik.broadcastTx = jest
.fn()
.mockResolvedValue({ txid: mockTxid });
@@ -134,13 +132,21 @@
551,
),
).toStrictEqual(expectedResult);
+
+ // Mock the 'Confirmed' ws event to exit the ws reconnections
+ const wsMsg = {
+ type: 'Confirmed',
+ txid: mockTxid,
+ };
+ // Tell mockedChronik what response we expect
+ mockedChronik.setMock('ws', {
+ output: wsMsg,
+ });
});
it('Broadcasts a v0 alias registration tx for a 21-byte alias to a p2pkh address', async () => {
const mockedChronik = new MockChronikClient();
-
const mockTxid =
'912582a1dc11b568f14f8ebae15cbb0ce53bdb973e137e7dc7c9b261327e6cab';
-
mockedChronik.broadcastTx = jest
.fn()
.mockResolvedValue({ txid: mockTxid });
@@ -161,13 +167,21 @@
551,
),
).toStrictEqual(expectedResult);
+
+ // Mock the 'Confirmed' ws event to exit the ws reconnections
+ const wsMsg = {
+ type: 'Confirmed',
+ txid: mockTxid,
+ };
+ // Tell mockedChronik what response we expect
+ mockedChronik.setMock('ws', {
+ output: wsMsg,
+ });
});
it('Broadcasts a v0 alias registration tx for a 16-byte alias to a p2pkh address', async () => {
const mockedChronik = new MockChronikClient();
-
const mockTxid =
'8783d7064ce22e8390c9fa94ef9a4d5bb0184e401ef5a9fbf60b68294e275c80';
-
mockedChronik.broadcastTx = jest
.fn()
.mockResolvedValue({ txid: mockTxid });
@@ -188,6 +202,16 @@
551,
),
).toStrictEqual(expectedResult);
+
+ // Mock the 'Confirmed' ws event to exit the ws reconnections
+ const wsMsg = {
+ type: 'Confirmed',
+ txid: mockTxid,
+ };
+ // Tell mockedChronik what response we expect
+ mockedChronik.setMock('ws', {
+ output: wsMsg,
+ });
});
it(`Throws error if called trying to send one base unit ${currency.ticker} more than available in utxo set`, async () => {
diff --git a/cashtab/src/utils/chronik.js b/cashtab/src/utils/chronik.js
--- a/cashtab/src/utils/chronik.js
+++ b/cashtab/src/utils/chronik.js
@@ -13,6 +13,54 @@
import wif from 'wif';
import { opReturn as opreturnConfig } from 'config/opreturn';
import { chronik as chronikConfig } from 'config/chronik';
+import { generalNotification } from 'components/Common/Notifications';
+import ecashaddr from 'ecashaddrjs';
+
+/**
+ * Subscribes to a given hash160 key and listens for confirmation on the txid
+ *
+ * @param {string} chronik the chronik-client instance
+ * @param {string} address the eCash address of the active wallet
+ * @param {string} txid the txid of the alias registration tx
+ * @param {string} message the message to be rendered upon detection of confirmation on the tx
+ * @throws {error} err chronik websocket subscription errors
+ */
+export const txConfListener = async (chronik, address, txid, message) => {
+ const { type, hash } = ecashaddr.decode(address, true);
+ try {
+ const ws = chronik.ws({
+ onMessage: msg => {
+ if (msg.type === 'Confirmed' && msg.txid === txid) {
+ console.log(
+ `txConfListener: ${txid} has received one confirmation`,
+ );
+ // Sets a perm-notification until user closes it
+ generalNotification(message, 'Confirmed', 0);
+ // Confirmation received, unsubscribe and close websocket
+ ws.unsubscribe(type, hash);
+ ws.close();
+ }
+ },
+ onReconnect: e => {
+ // Fired before a reconnect attempt is made:
+ console.log(
+ 'txConfListener: Reconnecting websocket, disconnection cause: ',
+ e,
+ );
+ },
+ });
+
+ // Wait for WS to be connected:
+ await ws.waitForOpen();
+
+ // Subscribe to scripts
+ ws.subscribe(type, hash);
+ } catch (err) {
+ console.log(
+ 'txConfListener: Error in chronik websocket subscription: ' + err,
+ );
+ }
+};
export const getTxHistoryPage = async (chronik, hash160, page = 0) => {
let txHistoryPage;
diff --git a/cashtab/src/utils/transactions.js b/cashtab/src/utils/transactions.js
--- a/cashtab/src/utils/transactions.js
+++ b/cashtab/src/utils/transactions.js
@@ -18,6 +18,7 @@
import ecies from 'ecies-lite';
import * as utxolib from '@bitgo/utxo-lib';
import { explorer } from 'config/explorer';
+import { txConfListener } from 'utils/chronik';
const SEND_XEC_ERRORS = {
INSUFFICIENT_FUNDS: 0,
@@ -409,6 +410,16 @@
}
const explorerLink = `${explorer.blockExplorerUrl}/tx/${broadcastResponse.txid}`;
+
+ // Open a dedicated websocket to listen for 1 conf on this alias registration tx
+ // Note: not an async call as this can listen in the background and does not need to complete first
+ txConfListener(
+ chronik,
+ wallet.Path1899.cashAddress,
+ broadcastResponse.txid,
+ `Alias registration for ${aliasName} has received 1 confirmation, please check the Alias page`,
+ );
+
// return the explorer link for the broadcasted tx
return { explorerLink, txid: broadcastResponse.txid, rawTxHex };
} catch (err) {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 26, 10:55 (12 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5573339
Default Alt Text
D14325.diff (12 KB)
Attached To
D14325: [Cashtab][Alias] Add websocket listener for 1 conf on alias registration txs
Event Timeline
Log In to Comment