diff --git a/apps/alias-server/test/chronik.test.js b/apps/alias-server/test/chronik.test.js
--- a/apps/alias-server/test/chronik.test.js
+++ b/apps/alias-server/test/chronik.test.js
@@ -13,7 +13,9 @@
     unconfirmedTxs,
     unconfirmedTxsAfterConfirmation,
 } = require('./mocks/txHistoryMocks');
-const { MockChronikClient } = require('../../../modules/mock-chronik-client');
+const {
+    MockChronikClient,
+} = require('../../../modules/mock-chronik-client/dist');
 
 // todo make txsperpage a param and test different values
 describe('alias-server chronik.js', () => {
@@ -31,9 +33,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, allTxHistoryFromChronik);
+        mockedChronik.setTxHistoryByScript(type, hash, allTxHistoryFromChronik);
 
         const result = await getUnprocessedTxHistory(
             mockedChronik,
@@ -59,9 +60,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, allTxHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, allTxHistory);
 
         const result = await getUnprocessedTxHistory(
             mockedChronik,
@@ -86,9 +86,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, allTxHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, allTxHistory);
 
         const result = await getUnprocessedTxHistory(
             mockedChronik,
diff --git a/apps/alias-server/test/chronikWsHandler.test.js b/apps/alias-server/test/chronikWsHandler.test.js
--- a/apps/alias-server/test/chronikWsHandler.test.js
+++ b/apps/alias-server/test/chronikWsHandler.test.js
@@ -10,7 +10,9 @@
     initializeWebsocket,
     parseWebsocketMessage,
 } = require('../src/chronikWsHandler');
-const { MockChronikClient } = require('../../../modules/mock-chronik-client');
+const {
+    MockChronikClient,
+} = require('../../../modules/mock-chronik-client/dist');
 // Mock mongodb
 const {
     initializeDb,
@@ -86,7 +88,7 @@
         const telegramBot = null;
         const channelId = null;
 
-        await initializeWebsocket(
+        const ws = await initializeWebsocket(
             mockedChronik,
             wsTestAddress,
             db,
@@ -96,9 +98,9 @@
         );
 
         // Confirm websocket opened
-        assert.strictEqual(mockedChronik.wsWaitForOpenCalled, true);
+        assert.strictEqual(ws.waitForOpenCalled, true);
         // Confirm subscribe was called
-        assert.deepEqual(mockedChronik.wsSubscribeCalled, true);
+        assert.equal(ws.subs.scripts.length, 1);
     });
     it('initializeWebsocket returns expected websocket object for a p2sh address', async function () {
         const wsTestAddress =
@@ -109,7 +111,7 @@
         const telegramBot = null;
         const channelId = null;
 
-        await initializeWebsocket(
+        const ws = await initializeWebsocket(
             mockedChronik,
             wsTestAddress,
             db,
@@ -119,9 +121,9 @@
         );
 
         // Confirm websocket opened
-        assert.strictEqual(mockedChronik.wsWaitForOpenCalled, true);
+        assert.strictEqual(ws.waitForOpenCalled, true);
         // Confirm subscribe was called
-        assert.deepEqual(mockedChronik.wsSubscribeCalled, true);
+        assert.equal(ws.subs.scripts.length, 1);
     });
     it('parseWebsocketMessage correctly processes a chronik websocket BLK_FINALIZED message if block is avalanche finalized', async function () {
         // Initialize chronik mock
@@ -142,9 +144,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         const result = await parseWebsocketMessage(
             mockedChronik,
@@ -184,9 +185,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         // Initialize mocks for second call to parseWebsocketMessage
         const nextMockedChronik = new MockChronikClient();
@@ -199,10 +199,9 @@
 
         // Add tx history to nextMockedChronik
         // Set the script
-        nextMockedChronik.setScript(type, hash);
         // Set the mock tx history
         // For now, assume it's the same as before, i.e. no new txs found
-        nextMockedChronik.setTxHistory(type, hash, generated.txHistory);
+        nextMockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         const firstCallPromise = parseWebsocketMessage(
             mockedChronik,
@@ -262,10 +261,7 @@
         const mockedChronik = new MockChronikClient();
 
         // Mock chronik response
-        mockedChronik.setMock('tx', {
-            input: incomingTxid,
-            output: pendingTxObject,
-        });
+        mockedChronik.setTx(incomingTxid, pendingTxObject);
         const result = await parseWebsocketMessage(
             mockedChronik,
             db,
@@ -303,10 +299,7 @@
         const mockedChronik = new MockChronikClient();
 
         // Mock chronik response
-        mockedChronik.setMock('tx', {
-            input: incomingTxid,
-            output: pendingTxObject,
-        });
+        mockedChronik.setTx(incomingTxid, pendingTxObject);
         const result = await parseWebsocketMessage(
             mockedChronik,
             db,
diff --git a/apps/alias-server/test/events.test.js b/apps/alias-server/test/events.test.js
--- a/apps/alias-server/test/events.test.js
+++ b/apps/alias-server/test/events.test.js
@@ -12,7 +12,9 @@
     handleBlockFinalized,
     handleAddedToMempool,
 } = require('../src/events');
-const { MockChronikClient } = require('../../../modules/mock-chronik-client');
+const {
+    MockChronikClient,
+} = require('../../../modules/mock-chronik-client/dist');
 // Mock mongodb
 const {
     initializeDb,
@@ -90,10 +92,7 @@
         };
 
         // Tell mockedChronik what response we expect
-        mockedChronik.setMock('blockchainInfo', {
-            input: null,
-            output: mockBlockchaininfoResponse,
-        });
+        mockedChronik.setBlockchainInfo(mockBlockchaininfoResponse);
 
         // Add tx history to mockedChronik
         // Set the script
@@ -101,17 +100,15 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         // Mock chronik block call for a finalized chaintip
-        mockedChronik.setMock('block', {
-            input: mockBlockchaininfoResponse.tipHash,
-            output: {
-                blockInfo: { isFinal: true },
-            },
-        });
+        mockedChronik.setBlock(
+            mockBlockchaininfoResponse.tipHash,
+
+            { blockInfo: { isFinal: true } },
+        );
 
         const db = testDb;
         const telegramBot = null;
@@ -171,10 +168,7 @@
         const mockedChronik = new MockChronikClient();
 
         // Tell mockedChronik what response we expect
-        mockedChronik.setMock('blockchainInfo', {
-            input: null,
-            output: new Error('some chronik error'),
-        });
+        mockedChronik.setBlockchainInfo(new Error('some chronik error'));
 
         const db = null;
         const telegramBot = null;
@@ -204,17 +198,11 @@
         };
 
         // Tell mockedChronik what response we expect
-        mockedChronik.setMock('blockchainInfo', {
-            input: null,
-            output: mockBlockchaininfoResponse,
-        });
+        mockedChronik.setBlockchainInfo(mockBlockchaininfoResponse);
 
         // Mock chronik block call for unfinalized chaintip
-        mockedChronik.setMock('block', {
-            input: mockBlockchaininfoResponse.tipHash,
-            output: {
-                blockInfo: { isFinal: false },
-            },
+        mockedChronik.setBlock(mockBlockchaininfoResponse.tipHash, {
+            blockInfo: { isFinal: false },
         });
 
         const db = null;
@@ -248,9 +236,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         const telegramBot = null;
         const channelId = null;
@@ -289,9 +276,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         const telegramBot = null;
         const channelId = null;
@@ -332,9 +318,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         const telegramBot = null;
         const channelId = null;
@@ -368,10 +353,7 @@
         const mockedChronik = new MockChronikClient();
 
         // Mock an error from the chronik.tx call
-        mockedChronik.setMock('tx', {
-            input: incomingTxid,
-            output: new Error('Some chronik error'),
-        });
+        mockedChronik.setTx(incomingTxid, new Error('Some chronik error'));
 
         await assert.rejects(
             async () => {
@@ -408,10 +390,7 @@
         const mockedChronik = new MockChronikClient();
 
         // Mock chronik response
-        mockedChronik.setMock('tx', {
-            input: incomingTxid,
-            output: pendingTxObject,
-        });
+        mockedChronik.setTx(incomingTxid, pendingTxObject);
 
         assert.strictEqual(
             await handleAddedToMempool(
diff --git a/apps/alias-server/test/main.test.js b/apps/alias-server/test/main.test.js
--- a/apps/alias-server/test/main.test.js
+++ b/apps/alias-server/test/main.test.js
@@ -17,7 +17,9 @@
 const { MongoClient } = require('mongodb');
 const { MongoMemoryServer } = require('mongodb-memory-server');
 // Mock chronik
-const { MockChronikClient } = require('../../../modules/mock-chronik-client');
+const {
+    MockChronikClient,
+} = require('../../../modules/mock-chronik-client/dist');
 const NodeCache = require('node-cache');
 
 describe('alias-server main.js', async function () {
@@ -68,17 +70,11 @@
         };
 
         // Tell mockedChronik what response we expect
-        mockedChronik.setMock('blockchainInfo', {
-            input: null,
-            output: mockBlockchaininfoResponse,
-        });
+        mockedChronik.setBlockchainInfo(mockBlockchaininfoResponse);
 
         // Mock chronik block call for a finalized chaintip
-        mockedChronik.setMock('block', {
-            input: mockBlockchaininfoResponse.tipHash,
-            output: {
-                blockInfo: { isFinal: true },
-            },
+        mockedChronik.setBlock(mockBlockchaininfoResponse.tipHash, {
+            blockInfo: { isFinal: true },
         });
 
         // Add tx history to mockedChronik
@@ -87,9 +83,8 @@
             aliasConstants.registrationAddress,
             true,
         );
-        mockedChronik.setScript(type, hash);
         // Set the mock tx history
-        mockedChronik.setTxHistory(type, hash, generated.txHistory);
+        mockedChronik.setTxHistoryByScript(type, hash, generated.txHistory);
 
         // Define params
         const chronik = mockedChronik;
@@ -107,7 +102,7 @@
             returnMocks,
         );
         // Confirm websocket opened
-        assert.strictEqual(mockedChronik.wsWaitForOpenCalled, true);
+        assert.strictEqual(result.aliasWebsocket.waitForOpenCalled, true);
         // Check that startup was called
         assert.deepEqual(
             result.appStartup,
diff --git a/apps/ecash-herald/src/main.ts b/apps/ecash-herald/src/main.ts
--- a/apps/ecash-herald/src/main.ts
+++ b/apps/ecash-herald/src/main.ts
@@ -25,7 +25,7 @@
     });
     // Initialize websocket connection
     try {
-        await initializeWebsocket(
+        return await initializeWebsocket(
             chronik,
             telegramBot,
             telegramChannelId,
diff --git a/apps/ecash-herald/test/chronik.test.ts b/apps/ecash-herald/test/chronik.test.ts
--- a/apps/ecash-herald/test/chronik.test.ts
+++ b/apps/ecash-herald/test/chronik.test.ts
@@ -3,7 +3,13 @@
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 import assert from 'assert';
-import { GenesisInfo } from 'chronik-client';
+import {
+    ChronikClient,
+    GenesisInfo,
+    TokenInfo,
+    Tx,
+    Block,
+} from 'chronik-client';
 import { MockChronikClient } from '../../../modules/mock-chronik-client';
 import {
     getTokenInfoMap,
@@ -29,14 +35,11 @@
         // and build expected result
         const expectedTokenInfoMap: TokenInfoMap = new Map();
         mockTokenCalls.forEach((tokenInfo, tokenId) => {
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: tokenInfo,
-            });
+            mockedChronik.setToken(tokenId, tokenInfo);
             expectedTokenInfoMap.set(tokenId, tokenInfo.genesisInfo);
         });
         const tokenInfoMap = await getTokenInfoMap(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             new Set([alitaTokenId, bearNipTokenId, powTokenId]),
         );
 
@@ -52,15 +55,19 @@
 
         // Create a set of only one token id
         const tokenIdSet: Set<string> = new Set([alitaTokenId]);
-        mockedChronik.setMock('token', {
-            input: alitaTokenId,
-            output: mockTokenCalls.get(alitaTokenId),
-        });
+        mockedChronik.setToken(
+            alitaTokenId,
+            mockTokenCalls.get(alitaTokenId) as TokenInfo,
+        );
+
         expectedTokenInfoMap.set(
             alitaTokenId,
             mockTokenCalls.get(alitaTokenId)!.genesisInfo as GenesisInfo,
         );
-        const tokenInfoMap = await getTokenInfoMap(mockedChronik, tokenIdSet);
+        const tokenInfoMap = await getTokenInfoMap(
+            mockedChronik as unknown as ChronikClient,
+            tokenIdSet,
+        );
 
         assert.deepEqual(tokenInfoMap, expectedTokenInfoMap);
     });
@@ -71,20 +78,17 @@
         const tokenIdSet = new Set([alitaTokenId, bearNipTokenId, powTokenId]);
         // Tell mockedChronik what responses we expect
         // Include one error response
-        mockedChronik.setMock('tx', {
-            input: alitaTokenId,
-            output: mockTxCalls.get(alitaTokenId),
-        });
-        mockedChronik.setMock('tx', {
-            input: bearNipTokenId,
-            output: mockTxCalls.get(bearNipTokenId),
-        });
-        mockedChronik.setMock('tx', {
-            input: powTokenId,
-            output: new Error('some error'),
-        });
+        mockedChronik.setTx(alitaTokenId, mockTxCalls.get(alitaTokenId) as Tx);
+        mockedChronik.setTx(
+            bearNipTokenId,
+            mockTxCalls.get(bearNipTokenId) as Tx,
+        );
+        mockedChronik.setTx(powTokenId, mockTxCalls.get(powTokenId) as Tx);
 
-        const tokenInfoMap = await getTokenInfoMap(mockedChronik, tokenIdSet);
+        const tokenInfoMap = await getTokenInfoMap(
+            mockedChronik as unknown as ChronikClient,
+            tokenIdSet,
+        );
 
         assert.strictEqual(tokenInfoMap, false);
     });
@@ -95,17 +99,11 @@
         const setWithNonToken = new Set([alitaTokenId, swapTxid]);
         // Tell mockedChronik what responses we expect
         // Include one error response
-        mockedChronik.setMock('tx', {
-            input: alitaTokenId,
-            output: mockTxCalls.get(alitaTokenId),
-        });
-        mockedChronik.setMock('tx', {
-            input: swapTxid,
-            output: mockTxCalls.get(swapTxid),
-        });
+        mockedChronik.setTx(alitaTokenId, mockTxCalls.get(alitaTokenId) as Tx);
+        mockedChronik.setTx(swapTxid, mockTxCalls.get(swapTxid) as Tx);
 
         const tokenInfoMap = await getTokenInfoMap(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             setWithNonToken,
         );
 
@@ -122,8 +120,11 @@
         ];
         // Initialize chronik mock
         const mockedChronik = new MockChronikClient();
-        mockedChronik.setTxHistoryByBlock(MOCK_HEIGHT, MOCK_TXS);
-        const txsInBlock = await getAllBlockTxs(mockedChronik, MOCK_HEIGHT);
+        mockedChronik.setTxHistoryByBlock(MOCK_HEIGHT, MOCK_TXS as Tx[]);
+        const txsInBlock = await getAllBlockTxs(
+            mockedChronik as unknown as ChronikClient,
+            MOCK_HEIGHT,
+        );
         assert.deepEqual(txsInBlock, MOCK_TXS);
     });
     it('getAllBlockTxs can get all block txs if a block has more than one page of txs', async function () {
@@ -142,9 +143,13 @@
         ];
         // Initialize chronik mock
         const mockedChronik = new MockChronikClient();
-        mockedChronik.setTxHistoryByBlock(MOCK_HEIGHT, MOCK_TXS);
+        mockedChronik.setTxHistoryByBlock(MOCK_HEIGHT, MOCK_TXS as Tx[]);
         // Call with pageSize smaller than tx size
-        const txsInBlock = await getAllBlockTxs(mockedChronik, MOCK_HEIGHT, 2);
+        const txsInBlock = await getAllBlockTxs(
+            mockedChronik as unknown as ChronikClient,
+            MOCK_HEIGHT,
+            2,
+        );
         assert.deepEqual(txsInBlock, MOCK_TXS);
     });
     it('getBlocksAgoFromChaintipByTimestamp will get start and end blockheights for a given timestamp and window if we have fewer than expected blocks in that window', async function () {
@@ -153,8 +158,9 @@
 
         // Mock the chaintip
         const mockChaintip = 800000;
-        mockedChronik.setMock('blockchainInfo', {
-            output: { tipHeight: mockChaintip },
+        mockedChronik.setBlockchainInfo({
+            tipHeight: mockChaintip,
+            tipHash: 'hash',
         });
 
         // Arbitrary timestamp to test
@@ -169,26 +175,23 @@
             mockChaintip - secondsAgo / SECONDS_PER_BLOCK;
 
         // Guessed block is older than secondsAgo
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo - 1 } },
-        });
+        mockedChronik.setBlock(guessedBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo - 1 },
+        } as Block);
 
         // Set 2 newer blocks
         // So this one, at guessedBlockheight + 1, is in the window
         const expectedStartBlockheight = guessedBlockheight + 1;
-        mockedChronik.setMock('block', {
-            input: expectedStartBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo + 2 } },
-        });
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight + 2,
-            output: { blockInfo: { timestamp: now - secondsAgo + 3 } },
-        });
+        mockedChronik.setBlock(expectedStartBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo + 2 },
+        } as Block);
+        mockedChronik.setBlock(guessedBlockheight + 2, {
+            blockInfo: { timestamp: now - secondsAgo + 3 },
+        } as Block);
 
         assert.deepEqual(
             await getBlocksAgoFromChaintipByTimestamp(
-                mockedChronik,
+                mockedChronik as unknown as ChronikClient,
                 now,
                 secondsAgo,
             ),
@@ -204,8 +207,9 @@
 
         // Mock the chaintip
         const mockChaintip = 800000;
-        mockedChronik.setMock('blockchainInfo', {
-            output: { tipHeight: mockChaintip },
+        mockedChronik.setBlockchainInfo({
+            tipHeight: mockChaintip,
+            tipHash: 'hash',
         });
 
         // Arbitrary timestamp to test
@@ -220,26 +224,23 @@
             mockChaintip - secondsAgo / SECONDS_PER_BLOCK;
 
         // Guessed block is NEWER than secondsAgo
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo + 5 } },
-        });
+        mockedChronik.setBlock(guessedBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo + 5 },
+        } as Block);
 
         // Set 3 older blocks, with 1 still in the window
         const expectedStartBlockheight = guessedBlockheight - 1;
-        mockedChronik.setMock('block', {
-            input: expectedStartBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo + 4 } },
-        });
+        mockedChronik.setBlock(expectedStartBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo + 4 },
+        } as Block);
         // Since this block is out of the window, we expect the start height to be proceeding block
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight - 2,
-            output: { blockInfo: { timestamp: now - secondsAgo - 3 } },
-        });
+        mockedChronik.setBlock(guessedBlockheight - 2, {
+            blockInfo: { timestamp: now - secondsAgo - 3 },
+        } as Block);
 
         assert.deepEqual(
             await getBlocksAgoFromChaintipByTimestamp(
-                mockedChronik,
+                mockedChronik as unknown as ChronikClient,
                 now,
                 secondsAgo,
             ),
@@ -255,8 +256,9 @@
 
         // Mock the chaintip
         const mockChaintip = 800000;
-        mockedChronik.setMock('blockchainInfo', {
-            output: { tipHeight: mockChaintip },
+        mockedChronik.setBlockchainInfo({
+            tipHeight: mockChaintip,
+            tipHash: 'hash',
         });
 
         // Arbitrary timestamp to test
@@ -271,20 +273,18 @@
             mockChaintip - secondsAgo / SECONDS_PER_BLOCK;
 
         // Guessed block is exactly secondsAgo
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo } },
-        });
+        mockedChronik.setBlock(guessedBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo },
+        } as Block);
 
         // Set 1 older block
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight - 1,
-            output: { blockInfo: { timestamp: now - secondsAgo - 1 } },
-        });
+        mockedChronik.setBlock(guessedBlockheight - 1, {
+            blockInfo: { timestamp: now - secondsAgo - 1 },
+        } as Block);
 
         assert.deepEqual(
             await getBlocksAgoFromChaintipByTimestamp(
-                mockedChronik,
+                mockedChronik as unknown as ChronikClient,
                 now,
                 secondsAgo,
             ),
@@ -300,8 +300,9 @@
 
         // Mock the chaintip
         const mockChaintip = 800000;
-        mockedChronik.setMock('blockchainInfo', {
-            output: { tipHeight: mockChaintip },
+        mockedChronik.setBlockchainInfo({
+            tipHeight: mockChaintip,
+            tipHash: 'hash',
         });
 
         // Arbitrary timestamp to test
@@ -316,23 +317,21 @@
             mockChaintip - secondsAgo / SECONDS_PER_BLOCK;
 
         // Guessed block is older than secondsAgo
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo - 1 } },
-        });
+        mockedChronik.setBlock(guessedBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo - 1 },
+        } as Block);
 
         // Set 100 newer blocks, all of them still outside the window
         for (let i = 0; i < 200; i += 1) {
-            mockedChronik.setMock('block', {
-                input: guessedBlockheight + 1 + i,
-                output: { blockInfo: { timestamp: now - secondsAgo - 1 } },
-            });
+            mockedChronik.setBlock(guessedBlockheight + 1 + i, {
+                blockInfo: { timestamp: now - secondsAgo - 1 },
+            } as Block);
         }
 
         await assert.rejects(
             async () => {
                 await getBlocksAgoFromChaintipByTimestamp(
-                    mockedChronik,
+                    mockedChronik as unknown as ChronikClient,
                     now,
                     secondsAgo,
                 );
@@ -349,8 +348,9 @@
 
         // Mock the chaintip
         const mockChaintip = 800000;
-        mockedChronik.setMock('blockchainInfo', {
-            output: { tipHeight: mockChaintip },
+        mockedChronik.setBlockchainInfo({
+            tipHeight: mockChaintip,
+            tipHash: 'hash',
         });
 
         // Arbitrary timestamp to test
@@ -365,23 +365,21 @@
             mockChaintip - secondsAgo / SECONDS_PER_BLOCK;
 
         // Guessed block is newer than secondsAgo, i.e. within the window
-        mockedChronik.setMock('block', {
-            input: guessedBlockheight,
-            output: { blockInfo: { timestamp: now - secondsAgo + 5 } },
-        });
+        mockedChronik.setBlock(guessedBlockheight, {
+            blockInfo: { timestamp: now - secondsAgo + 5 },
+        } as Block);
 
         // Set 200 older blocks, all of them still within the window
         for (let i = 0; i < 200; i += 1) {
-            mockedChronik.setMock('block', {
-                input: guessedBlockheight - 1 - i,
-                output: { blockInfo: { timestamp: now - secondsAgo + 5 } },
-            });
+            mockedChronik.setBlock(guessedBlockheight - 1 - i, {
+                blockInfo: { timestamp: now - secondsAgo + 5 },
+            } as Block);
         }
 
         await assert.rejects(
             async () => {
                 await getBlocksAgoFromChaintipByTimestamp(
-                    mockedChronik,
+                    mockedChronik as unknown as ChronikClient,
                     now,
                     secondsAgo,
                 );
diff --git a/apps/ecash-herald/test/chronikWsHandler.test.ts b/apps/ecash-herald/test/chronikWsHandler.test.ts
--- a/apps/ecash-herald/test/chronikWsHandler.test.ts
+++ b/apps/ecash-herald/test/chronikWsHandler.test.ts
@@ -13,12 +13,15 @@
     initializeWebsocket,
     parseWebsocketMessage,
 } from '../src/chronikWsHandler';
-import { MockChronikClient } from '../../../modules/mock-chronik-client';
+import {
+    MockChronikClient,
+    MockWsEndpoint,
+} from '../../../modules/mock-chronik-client';
 import { MockTelegramBot, mockChannelId } from './mocks/telegramBotMock';
 import axios from 'axios';
 import MockAdapter from 'axios-mock-adapter';
 import { caching, MemoryCache } from 'cache-manager';
-import { WsMsgClient } from 'chronik-client';
+import { ChronikClient, TokenInfo, WsMsgClient, Tx } from 'chronik-client';
 import { StoredMock } from '../src/events';
 const block: StoredMock = JSON.parse(
     JSON.stringify(unrevivedBlock),
@@ -41,14 +44,17 @@
         const channelId = mockChannelId;
 
         const result = await initializeWebsocket(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             memoryCache,
         );
 
         // Confirm websocket opened
-        assert.strictEqual(mockedChronik.wsWaitForOpenCalled, true);
+        assert.strictEqual(
+            (result as unknown as MockWsEndpoint).waitForOpenCalled,
+            true,
+        );
         // Confirm subscribed to blocks
         assert.deepEqual(result.subs.blocks, true);
     });
@@ -59,14 +65,17 @@
         const channelId = mockChannelId;
 
         const result = await initializeWebsocket(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             memoryCache,
         );
 
         // Confirm websocket opened
-        assert.strictEqual(mockedChronik.wsWaitForOpenCalled, true);
+        assert.strictEqual(
+            (result as unknown as MockWsEndpoint).waitForOpenCalled,
+            true,
+        );
         // Confirm subscribed to blocks
         assert.deepEqual(result.subs.blocks, true);
     });
@@ -94,7 +103,7 @@
         for (let i = 0; i < unsupportedWebsocketMsgs.length; i += 1) {
             const thisUnsupportedMsg = unsupportedWebsocketMsgs[i];
             const result = await parseWebsocketMessage(
-                mockedChronik,
+                mockedChronik as unknown as ChronikClient,
                 thisUnsupportedMsg as WsMsgClient,
                 telegramBot,
                 channelId,
@@ -125,8 +134,11 @@
             const { type, hash } =
                 cashaddr.getTypeAndHashFromOutputScript(outputScript);
             const { utxos } = info;
-            mockedChronik.setScript(type, hash);
-            mockedChronik.setUtxos(type, hash, { outputScript, utxos });
+            mockedChronik.setUtxosByScript(
+                type as 'p2pkh' | 'p2sh',
+                hash,
+                utxos,
+            );
         });
 
         // Tell mockedChronik what response we expect for chronik.tx
@@ -137,12 +149,9 @@
             // Instead of saving all the chronik responses as mocks, which would be very large
             // Just set them as mocks based on tokenInfoMap, which contains the info we need
             tokenIds.forEach(tokenId => {
-                mockedChronik.setMock('token', {
-                    input: tokenId,
-                    output: {
-                        genesisInfo: tokenInfoMap.get(tokenId),
-                    },
-                });
+                mockedChronik.setToken(tokenId, {
+                    genesisInfo: tokenInfoMap.get(tokenId),
+                } as TokenInfo);
             });
         }
         const thisBlockExpectedMsgs = thisBlock.blockSummaryTgMsgs;
@@ -182,7 +191,7 @@
         ).reply(200, thisBlock.activeStakers);
 
         const result = await parseWebsocketMessage(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             mockWsMsg as WsMsgClient,
             telegramBot,
             channelId,
@@ -226,18 +235,12 @@
             tokenIds.forEach(tokenId => {
                 // If this is the first one, set an error response
                 if (index === 0) {
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: new Error('some error'),
-                    });
+                    mockedChronik.setToken(tokenId, new Error('some error'));
                 } else {
                     index += 1;
-                    mockedChronik.setMock('tx', {
-                        input: tokenId,
-                        output: {
-                            genesisInfo: tokenInfoMap.get(tokenId),
-                        },
-                    });
+                    mockedChronik.setTx(tokenId, {
+                        genesisInfo: tokenInfoMap.get(tokenId),
+                    } as unknown as Tx);
                 }
             });
         }
@@ -271,7 +274,7 @@
         });
 
         const result = await parseWebsocketMessage(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             mockWsMsg as WsMsgClient,
             telegramBot,
             channelId,
@@ -318,18 +321,12 @@
             tokenIds.forEach(tokenId => {
                 // If this is the first one, set an error response
                 if (index === 0) {
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: new Error('some error'),
-                    });
+                    mockedChronik.setToken(tokenId, new Error('some error'));
                 } else {
                     index += 1;
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: {
-                            genesisInfo: tokenInfoMap.get(tokenId),
-                        },
-                    });
+                    mockedChronik.setToken(tokenId, {
+                        genesisInfo: tokenInfoMap.get(tokenId),
+                    } as unknown as TokenInfo);
                 }
             });
         }
@@ -360,7 +357,7 @@
         });
 
         const result = await parseWebsocketMessage(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             mockWsMsg as WsMsgClient,
             telegramBot,
             channelId,
@@ -391,7 +388,7 @@
         const channelId = mockChannelId;
 
         const result = await parseWebsocketMessage(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             mockWsMsg as WsMsgClient,
             telegramBot,
             channelId,
diff --git a/apps/ecash-herald/test/events.test.ts b/apps/ecash-herald/test/events.test.ts
--- a/apps/ecash-herald/test/events.test.ts
+++ b/apps/ecash-herald/test/events.test.ts
@@ -20,6 +20,7 @@
 import MockAdapter from 'axios-mock-adapter';
 import { caching, MemoryCache } from 'cache-manager';
 import FakeTimers, { InstalledClock } from '@sinonjs/fake-timers';
+import { ChronikClient, TokenInfo } from 'chronik-client';
 const block: StoredMock = JSON.parse(
     JSON.stringify(unrevivedBlock),
     jsonReviver,
@@ -61,8 +62,11 @@
             const { type, hash } =
                 cashaddr.getTypeAndHashFromOutputScript(outputScript);
             const { utxos } = info;
-            mockedChronik.setScript(type, hash);
-            mockedChronik.setUtxos(type, hash, { outputScript, utxos });
+            mockedChronik.setUtxosByScript(
+                type as 'p2pkh' | 'p2sh',
+                hash,
+                utxos,
+            );
         });
 
         // Tell mockedChronik what response we expect for chronik.tx
@@ -73,12 +77,9 @@
             // Instead of saving all the chronik responses as mocks, which would be very large
             // Just set them as mocks based on tokenInfoMap, which contains the info we need
             tokenIds.forEach(tokenId => {
-                mockedChronik.setMock('token', {
-                    input: tokenId,
-                    output: {
-                        genesisInfo: tokenInfoMap.get(tokenId),
-                    },
-                });
+                mockedChronik.setToken(tokenId, {
+                    genesisInfo: tokenInfoMap.get(tokenId),
+                } as TokenInfo);
             });
         }
 
@@ -104,7 +105,7 @@
         ).reply(200, thisBlock.activeStakers);
 
         const result = await handleBlockFinalized(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             thisBlock.parsedBlock.hash,
@@ -152,18 +153,12 @@
             tokenIds.forEach(tokenId => {
                 // If this is the first one, set an error response
                 if (index === 0) {
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: new Error('some error'),
-                    });
+                    mockedChronik.setToken(tokenId, new Error('some error'));
                 } else {
                     index += 1;
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: {
-                            genesisInfo: tokenInfoMap.get(tokenId),
-                        },
-                    });
+                    mockedChronik.setToken(tokenId, {
+                        genesisInfo: tokenInfoMap.get(tokenId),
+                    } as TokenInfo);
                 }
             });
         }
@@ -182,7 +177,7 @@
         mock.onGet(getCoingeckoApiUrl(config)).reply(500, { error: 'error' });
 
         const result = await handleBlockFinalized(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             thisBlock.parsedBlock.hash,
@@ -223,7 +218,7 @@
         const channelId = mockChannelId;
 
         const result = await handleBlockFinalized(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             thisBlock.parsedBlock.hash,
@@ -274,18 +269,12 @@
             tokenIds.forEach(tokenId => {
                 // If this is the first one, set an error response
                 if (index === 0) {
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: new Error('some error'),
-                    });
+                    mockedChronik.setToken(tokenId, new Error('some error'));
                 } else {
                     index += 1;
-                    mockedChronik.setMock('token', {
-                        input: tokenId,
-                        output: {
-                            genesisInfo: tokenInfoMap.get(tokenId),
-                        },
-                    });
+                    mockedChronik.setToken(tokenId, {
+                        genesisInfo: tokenInfoMap.get(tokenId),
+                    } as TokenInfo);
                 }
             });
         }
@@ -298,7 +287,7 @@
         const channelId = mockChannelId;
 
         const result = await handleBlockFinalized(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             thisBlock.parsedBlock.hash,
@@ -319,7 +308,7 @@
         const channelId = mockChannelId;
 
         const result = await handleBlockInvalidated(
-            mockedChronik,
+            mockedChronik as unknown as ChronikClient,
             telegramBot,
             channelId,
             thisBlock.blockTxs[0].block!.hash,
diff --git a/apps/ecash-herald/test/fixtures/invalidatedBlocks.ts b/apps/ecash-herald/test/fixtures/invalidatedBlocks.ts
--- a/apps/ecash-herald/test/fixtures/invalidatedBlocks.ts
+++ b/apps/ecash-herald/test/fixtures/invalidatedBlocks.ts
@@ -149,7 +149,8 @@
             scriptHex: '76a914b03bb6f8567bade53cc3a716e0414c1082a8530088ac',
         },
         mockedBlock: {
-            865428: {
+            height: 865428,
+            block: {
                 blockInfo: {
                     hash: '00000000000000000692216bd4f235fc2cd98872640ba6a3bec0130cbfe59a14',
                 },
diff --git a/apps/ecash-herald/test/main.test.ts b/apps/ecash-herald/test/main.test.ts
--- a/apps/ecash-herald/test/main.test.ts
+++ b/apps/ecash-herald/test/main.test.ts
@@ -4,8 +4,12 @@
 
 import assert from 'assert';
 import { main } from '../src/main';
-import { MockChronikClient } from '../../../modules/mock-chronik-client';
+import {
+    MockChronikClient,
+    MockWsEndpoint,
+} from '../../../modules/mock-chronik-client';
 import { MockTelegramBot, mockChannelId } from './mocks/telegramBotMock';
+import { ChronikClient } from 'chronik-client';
 
 describe('ecash-herald main.js', async function () {
     it('main() starts the app on successful websocket connection', async function () {
@@ -13,9 +17,13 @@
         const mockedChronik = new MockChronikClient();
         const channelId = mockChannelId;
 
-        await main(mockedChronik, new MockTelegramBot(), channelId);
+        const ws = await main(
+            mockedChronik as unknown as ChronikClient,
+            new MockTelegramBot(),
+            channelId,
+        );
 
         // Confirm websocket opened
-        assert.strictEqual(mockedChronik.wsWaitForOpenCalled, true);
+        assert.strictEqual((ws as MockWsEndpoint).waitForOpenCalled, true);
     });
 });
diff --git a/apps/ecash-herald/test/parse.test.ts b/apps/ecash-herald/test/parse.test.ts
--- a/apps/ecash-herald/test/parse.test.ts
+++ b/apps/ecash-herald/test/parse.test.ts
@@ -13,7 +13,7 @@
 import memoFixtures from './mocks/memo';
 import { consumeNextPush } from 'ecash-script';
 import { MockChronikClient } from '../../../modules/mock-chronik-client';
-import { TxOutput } from 'chronik-client';
+import { Block, ChronikClient, TxOutput } from 'chronik-client';
 import { caching } from 'cache-manager';
 import { StoredMock } from '../src/events';
 import {
@@ -338,7 +338,12 @@
             } = invalidatedBlocksTestFixtures[i];
 
             const mockedChronik = new MockChronikClient();
-            mockedChronik.mockedResponses.block = mockedBlock;
+            if (typeof mockedBlock.height !== 'undefined') {
+                mockedChronik.setBlock(
+                    mockedBlock.height,
+                    mockedBlock.block as Block,
+                );
+            }
 
             const testMemoryCache = await caching('memory', {
                 max: 100,
@@ -348,7 +353,7 @@
 
             assert.strictEqual(
                 await guessRejectReason(
-                    mockedChronik,
+                    mockedChronik as unknown as ChronikClient,
                     height,
                     coinbaseData,
                     testMemoryCache,
diff --git a/apps/token-server/src/chronik/clientHandler.test.ts b/apps/token-server/src/chronik/clientHandler.test.ts
--- a/apps/token-server/src/chronik/clientHandler.test.ts
+++ b/apps/token-server/src/chronik/clientHandler.test.ts
@@ -6,6 +6,7 @@
 import { MockChronikClient } from '../../../../modules/mock-chronik-client';
 import { getHistoryAfterTimestamp } from '../../src/chronik/clientHandler';
 import vectors from '../../test/vectors';
+import { ChronikClient } from 'chronik-client';
 
 describe('chronik/clientHandler.js', function () {
     describe('We can get all tx history from after a given timestamp', function () {
@@ -23,12 +24,11 @@
 
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(address);
             mockedChronik.setTxHistoryByAddress(address, mocks.history);
             it(description, async function () {
                 assert.deepEqual(
                     await getHistoryAfterTimestamp(
-                        mockedChronik,
+                        mockedChronik as unknown as ChronikClient,
                         address,
                         timestamp,
                         pageSize,
@@ -44,13 +44,12 @@
 
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(address);
             mockedChronik.setTxHistoryByAddress(address, mocks.history);
 
             it(description, async function () {
                 await assert.rejects(
                     getHistoryAfterTimestamp(
-                        mockedChronik,
+                        mockedChronik as unknown as ChronikClient,
                         address,
                         timestamp,
                         pageSize,
diff --git a/apps/token-server/src/routes.test.ts b/apps/token-server/src/routes.test.ts
--- a/apps/token-server/src/routes.test.ts
+++ b/apps/token-server/src/routes.test.ts
@@ -25,6 +25,7 @@
 import { initializeDb, initialBlacklist } from '../src/db';
 import axios from 'axios';
 import MockAdapter from 'axios-mock-adapter';
+import { ChronikClient, ScriptUtxo, Tx } from 'chronik-client';
 
 // Clone initialBlacklist before initializing the database
 // initializeDb(initialBlacklist) will modify the entries by adding an "_id" key
@@ -74,7 +75,6 @@
     // Seen ~ 2x before the amount of time required
     const eligibleTimeFirstSeen =
         Math.ceil(Date.now() / 1000) - 2 * config.eligibilityResetSeconds;
-    mockedChronikClient.setAddress(ELIGIBLE_ADDRESS);
     mockedChronikClient.setTxHistoryByAddress(ELIGIBLE_ADDRESS, [
         {
             timeFirstSeen: eligibleTimeFirstSeen,
@@ -85,39 +85,32 @@
                     token: { tokenId: config.rewardsTokenId },
                 },
             ],
-        },
+        } as Tx,
     ]);
 
-    mockedChronikClient.setAddress(SERVER_WALLET_ADDRESS);
-    mockedChronikClient.setUtxosByAddress(SERVER_WALLET_ADDRESS, {
-        outputScript: SERVER_WALLET_OUTPUTSCRIPT,
-        utxos: [
-            { ...MOCK_SCRIPT_UTXO, value: 10000 },
-            {
-                ...MOCK_SPENDABLE_TOKEN_UTXO,
-                outpoint: { ...MOCK_OUTPOINT, outIdx: 1 },
-                token: {
-                    ...MOCK_UTXO_TOKEN,
-                    tokenId: config.rewardsTokenId,
-                    // Note, can change this to '10' or something less than config.rewardAmountTokenSats
-                    // to test behavior of server if it is out of tokens
-                    // Bad ROI on adding this test outright as we need lots of scripting
-                    // to overcome the need for multiple mocked server wallets
-                    amount: config.rewardAmountTokenSats,
-                },
+    mockedChronikClient.setUtxosByAddress(SERVER_WALLET_ADDRESS, [
+        { ...MOCK_SCRIPT_UTXO, value: 10000 },
+        {
+            ...MOCK_SPENDABLE_TOKEN_UTXO,
+            outpoint: { ...MOCK_OUTPOINT, outIdx: 1 },
+            token: {
+                ...MOCK_UTXO_TOKEN,
+                tokenId: config.rewardsTokenId,
+                // Note, can change this to '10' or something less than config.rewardAmountTokenSats
+                // to test behavior of server if it is out of tokens
+                // Bad ROI on adding this test outright as we need lots of scripting
+                // to overcome the need for multiple mocked server wallets
+                amount: config.rewardAmountTokenSats,
             },
-        ],
-    });
-    mockedChronikClient.setMock('broadcastTx', {
-        input: '02000000021111111111111111111111111111111111111111111111111111111111111111010000006441aa58606dc2133b1547da04323797794c8ae8a245518c82b6a360db52f9451b33b301eeb18c5851fd98989a7c24b384bfb49c18e37d1ffdf4e6bc42c30575913041210228363bacbd9e52c1e515e715633fd2376d58671cda418e05685447a4a49b0645ffffffff111111111111111111111111111111111111111111111111111111111111111100000000644168bf907b93ffc6f1dad8378ca5de1a35e4b3d3fae7f151fed92eabffa301ba01dce9d79108e4a4374414f5ac7364d99ef5ff506ef5a69cc58e91e4871e4f27f541210228363bacbd9e52c1e515e715633fd2376d58671cda418e05685447a4a49b0645ffffffff030000000000000000376a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000271022020000000000001976a9146ffbe7c7d7bd01295eb1e371de9550339bdcf9fd88ac68250000000000001976a91476fb100532b1fe23b26930e7001dff7989d2db5588ac00000000',
-        output: {
-            txid: '1b3cb86a06c64afdbad89ac3660ee724cbb8a5a1b099763b993d63b1285bb404',
         },
-    });
+    ] as ScriptUtxo[]);
+    mockedChronikClient.setBroadcastTx(
+        '02000000021111111111111111111111111111111111111111111111111111111111111111010000006441aa58606dc2133b1547da04323797794c8ae8a245518c82b6a360db52f9451b33b301eeb18c5851fd98989a7c24b384bfb49c18e37d1ffdf4e6bc42c30575913041210228363bacbd9e52c1e515e715633fd2376d58671cda418e05685447a4a49b0645ffffffff111111111111111111111111111111111111111111111111111111111111111100000000644168bf907b93ffc6f1dad8378ca5de1a35e4b3d3fae7f151fed92eabffa301ba01dce9d79108e4a4374414f5ac7364d99ef5ff506ef5a69cc58e91e4871e4f27f541210228363bacbd9e52c1e515e715633fd2376d58671cda418e05685447a4a49b0645ffffffff030000000000000000376a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000271022020000000000001976a9146ffbe7c7d7bd01295eb1e371de9550339bdcf9fd88ac68250000000000001976a91476fb100532b1fe23b26930e7001dff7989d2db5588ac00000000',
+        '1b3cb86a06c64afdbad89ac3660ee724cbb8a5a1b099763b993d63b1285bb404',
+    );
     // Set an ineligible mock
     // Seen just now
     const ineligibleTimeFirstSeen = Math.ceil(Date.now() / 1000);
-    mockedChronikClient.setAddress(INELIGIBLE_ADDRESS);
     mockedChronikClient.setTxHistoryByAddress(INELIGIBLE_ADDRESS, [
         {
             timeFirstSeen: ineligibleTimeFirstSeen,
@@ -128,10 +121,9 @@
                     token: { tokenId: config.rewardsTokenId },
                 },
             ],
-        },
+        } as Tx,
     ]);
     // Mock chronik throwing an error
-    mockedChronikClient.setAddress(ERROR_ADDRESS);
     mockedChronikClient.setTxHistoryByAddress(
         ERROR_ADDRESS,
         new Error('some chronik error'),
@@ -140,24 +132,22 @@
     // Address with no tx history
     // i.e. eligible for an XEC airdrop
     const NEW_ADDRESS = 'ecash:qrfkcnzdm0dvkrc20dhcf7qv23vt736ynuujzxnzs6';
-    mockedChronikClient.setAddress(NEW_ADDRESS);
     mockedChronikClient.setTxHistoryByAddress(NEW_ADDRESS, []);
 
     // Address with tx history
     // i.e. not eligible for an XEC airdrop
     const USED_ADDRESS = 'ecash:qrplfw9x5hrdnra3t42s3543gh3vtg8xgyr4t4lrun';
-    mockedChronikClient.setAddress(USED_ADDRESS);
-    mockedChronikClient.setTxHistoryByAddress(USED_ADDRESS, [{ isTx: true }]);
+    mockedChronikClient.setTxHistoryByAddress(USED_ADDRESS, [
+        { isTx: true },
+    ] as unknown as Tx[]);
 
     // Mock an XEC airdrop tx
     const expectedXecAirdropTxid =
         'd19c496e82bd160c841968ec0d2b61bf64cb884b002835649594cd973967d33b';
-    mockedChronikClient.setMock('broadcastTx', {
-        input: '02000000011111111111111111111111111111111111111111111111111111111111111111000000006441d51a04ca0cba7e791ceb0d39f19b45162756087e7058cf5dec770cbcabbc89598b5b6f966a3609b01a34b1e5b6853c46f843bd8b3507c0dbd6acc4329182b88841210228363bacbd9e52c1e515e715633fd2376d58671cda418e05685447a4a49b0645ffffffff0268100000000000001976a914d36c4c4ddbdacb0f0a7b6f84f80c5458bf47449f88accd150000000000001976a91476fb100532b1fe23b26930e7001dff7989d2db5588ac00000000',
-        output: {
-            txid: expectedXecAirdropTxid,
-        },
-    });
+    mockedChronikClient.setBroadcastTx(
+        '02000000011111111111111111111111111111111111111111111111111111111111111111000000006441d51a04ca0cba7e791ceb0d39f19b45162756087e7058cf5dec770cbcabbc89598b5b6f966a3609b01a34b1e5b6853c46f843bd8b3507c0dbd6acc4329182b88841210228363bacbd9e52c1e515e715633fd2376d58671cda418e05685447a4a49b0645ffffffff0268100000000000001976a914d36c4c4ddbdacb0f0a7b6f84f80c5458bf47449f88accd150000000000001976a91476fb100532b1fe23b26930e7001dff7989d2db5588ac00000000',
+        expectedXecAirdropTxid,
+    );
 
     // Mock a stub telegram bot
     const mockedTgBot = { sendPhoto: () => {} };
@@ -179,7 +169,7 @@
         app = startExpressServer(
             TEST_PORT,
             testDb,
-            mockedChronikClient,
+            mockedChronikClient as unknown as ChronikClient,
             mockedTgBot as unknown as TelegramBot,
             fs,
             ecc,
@@ -204,7 +194,7 @@
         badDbApp = startExpressServer(
             TEST_PORT_BAD_DB,
             {} as unknown as Db,
-            mockedChronikClient,
+            mockedChronikClient as unknown as ChronikClient,
             mockedTgBot as unknown as TelegramBot,
             fs,
             ecc,
diff --git a/apps/token-server/src/transactions.test.ts b/apps/token-server/src/transactions.test.ts
--- a/apps/token-server/src/transactions.test.ts
+++ b/apps/token-server/src/transactions.test.ts
@@ -11,6 +11,7 @@
 import { MockChronikClient } from '../../../modules/mock-chronik-client';
 import vectors from '../test/vectors';
 import { Ecc, initWasm } from 'ecash-lib';
+import { ChronikClient } from 'chronik-client';
 
 describe('transactions.ts', function () {
     let ecc: Ecc;
@@ -84,19 +85,12 @@
             } = vector;
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(wallet.address);
-            mockedChronik.setUtxosByAddress(wallet.address, {
-                outputScript: 'outputScript',
-                utxos,
-            });
-            mockedChronik.setMock('broadcastTx', {
-                input: returned.hex,
-                output: { txid: returned.response.txid },
-            });
+            mockedChronik.setUtxosByAddress(wallet.address, utxos);
+            mockedChronik.setBroadcastTx(returned.hex, returned.response.txid);
             it(description, async function () {
                 assert.deepEqual(
                     await sendReward(
-                        mockedChronik,
+                        mockedChronik as unknown as ChronikClient,
                         ecc,
                         wallet,
                         tokenId,
@@ -119,17 +113,11 @@
             } = vector;
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(wallet.address);
-            mockedChronik.setUtxosByAddress(
-                wallet.address,
-                utxos instanceof Error
-                    ? utxos
-                    : { outputScript: 'outputScript', utxos },
-            );
+            mockedChronik.setUtxosByAddress(wallet.address, utxos);
             it(description, async function () {
                 await assert.rejects(
                     sendReward(
-                        mockedChronik,
+                        mockedChronik as unknown as ChronikClient,
                         ecc,
                         wallet,
                         tokenId,
@@ -154,19 +142,12 @@
             } = vector;
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(wallet.address);
-            mockedChronik.setUtxosByAddress(wallet.address, {
-                outputScript: 'outputScript',
-                utxos,
-            });
-            mockedChronik.setMock('broadcastTx', {
-                input: returned.hex,
-                output: { txid: returned.response.txid },
-            });
+            mockedChronik.setUtxosByAddress(wallet.address, utxos);
+            mockedChronik.setBroadcastTx(returned.hex, returned.response.txid);
             it(description, async function () {
                 assert.deepEqual(
                     await sendXecAirdrop(
-                        mockedChronik,
+                        mockedChronik as unknown as ChronikClient,
                         ecc,
                         wallet,
                         xecAirdropAmountSats,
@@ -187,17 +168,11 @@
             } = vector;
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(wallet.address);
-            mockedChronik.setUtxosByAddress(
-                wallet.address,
-                utxos instanceof Error
-                    ? utxos
-                    : { outputScript: 'outputScript', utxos },
-            );
+            mockedChronik.setUtxosByAddress(wallet.address, utxos);
             it(description, async function () {
                 await assert.rejects(
                     sendXecAirdrop(
-                        mockedChronik,
+                        mockedChronik as unknown as ChronikClient,
                         ecc,
                         wallet,
                         xecAirdropAmountSats,
diff --git a/apps/token-server/src/wallet.test.ts b/apps/token-server/src/wallet.test.ts
--- a/apps/token-server/src/wallet.test.ts
+++ b/apps/token-server/src/wallet.test.ts
@@ -6,6 +6,7 @@
 import { getWalletFromSeed, syncWallet } from '../src/wallet';
 import vectors from '../test/vectors';
 import { MockChronikClient } from '../../../modules/mock-chronik-client';
+import { ChronikClient } from 'chronik-client';
 
 describe('wallet.ts', function () {
     describe('We can generate an ecash address and its wif from a valid bip39 mnemonic', function () {
@@ -30,15 +31,14 @@
 
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(wallet.address);
-            mockedChronik.setUtxosByAddress(wallet.address, {
-                outputScript: 'outputScript',
-                utxos: mockUtxos,
-            });
+            mockedChronik.setUtxosByAddress(wallet.address, mockUtxos);
 
             it(description, async function () {
                 // We call syncWallet on wallet
-                await syncWallet(mockedChronik, wallet);
+                await syncWallet(
+                    mockedChronik as unknown as ChronikClient,
+                    wallet,
+                );
                 // The wallet object is now synced, we do not need to rely on it being returned from the function
                 assert.deepEqual(wallet, returned);
             });
@@ -47,10 +47,15 @@
             const { description, wallet, error } = vector;
             // Set mocks in chronik-client
             const mockedChronik = new MockChronikClient();
-            mockedChronik.setAddress(wallet.address);
             mockedChronik.setUtxosByAddress(wallet.address, error);
             it(description, async function () {
-                await assert.rejects(syncWallet(mockedChronik, wallet), error);
+                await assert.rejects(
+                    syncWallet(
+                        mockedChronik as unknown as ChronikClient,
+                        wallet,
+                    ),
+                    error,
+                );
             });
         });
     });
diff --git a/apps/token-server/tsconfig.json b/apps/token-server/tsconfig.json
--- a/apps/token-server/tsconfig.json
+++ b/apps/token-server/tsconfig.json
@@ -8,5 +8,5 @@
         "strict": true,
         "alwaysStrict": true,
         "skipLibCheck": true
-    },
+    }
 }
diff --git a/cashtab/src/chronik/__tests__/index.test.js b/cashtab/src/chronik/__tests__/index.test.js
--- a/cashtab/src/chronik/__tests__/index.test.js
+++ b/cashtab/src/chronik/__tests__/index.test.js
@@ -90,16 +90,10 @@
             const mockedChronik = new MockChronikClient();
 
             // Set mock for chronik.token(tokenId)
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: tokenInfo,
-            });
+            mockedChronik.setToken(tokenId, tokenInfo);
 
             // Set mock for chronik.tx(tokenId)
-            mockedChronik.setMock('tx', {
-                input: tokenId,
-                output: genesisTx,
-            });
+            mockedChronik.setTx(tokenId, genesisTx);
 
             it(`getTokenGenesisInfo: ${description}`, async () => {
                 expect(
@@ -114,16 +108,10 @@
             const mockedChronik = new MockChronikClient();
 
             // Set mock for chronik.token(tokenId)
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: tokenInfo,
-            });
+            mockedChronik.setToken(tokenId, tokenInfo);
 
             // Set mock for chronik.tx(tokenId)
-            mockedChronik.setMock('tx', {
-                input: tokenId,
-                output: genesisTx,
-            });
+            mockedChronik.setTx(tokenId, genesisTx);
 
             it(`getTokenGenesisInfo: ${description}`, async () => {
                 await expect(
@@ -139,16 +127,10 @@
         for (const tokenId of tokenIds) {
             const { token, tx } = chronikTokenMocks[tokenId];
             // Set mock for chronik.token(tokenId)
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: token,
-            });
+            mockedChronik.setToken(tokenId, token);
 
             // Set mock for chronik.tx(tokenId)
-            mockedChronik.setMock('tx', {
-                input: tokenId,
-                output: tx,
-            });
+            mockedChronik.setTx(tokenId, tx);
         }
 
         // Initialize an empty token cache
@@ -174,16 +156,10 @@
         for (const tokenId of tokenIds) {
             const { token, tx } = chronikTokenMocks[tokenId];
             // Set mock for chronik.token(tokenId)
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: token,
-            });
+            mockedChronik.setToken(tokenId, token);
 
             // Set mock for chronik.tx(tokenId)
-            mockedChronik.setMock('tx', {
-                input: tokenId,
-                output: tx,
-            });
+            mockedChronik.setTx(tokenId, tx);
         }
 
         // Initialize an empty token cache
@@ -247,16 +223,8 @@
 
         // Set tx history for all paths
         const mockedChronik = new MockChronikClient();
-        mockedChronik.setAddress(defaultAddress);
-        mockedChronik.setUtxosByAddress(defaultAddress, {
-            outputScript: 'string',
-            utxos: [{ value: 546 }],
-        });
-        mockedChronik.setAddress(secondaryAddress);
-        mockedChronik.setUtxosByAddress(secondaryAddress, {
-            outputScript: 'string',
-            utxos: [{ value: 546 }],
-        });
+        mockedChronik.setUtxosByAddress(defaultAddress, [{ value: 546 }]);
+        mockedChronik.setUtxosByAddress(secondaryAddress, [{ value: 546 }]);
         expect(
             await getUtxos(mockedChronik, mockTxHistoryWallet),
         ).toStrictEqual([
@@ -271,16 +239,10 @@
         for (const tokenId of tokenIds) {
             const { token, tx } = tokensInHistory[tokenId];
             // Set mock for chronik.token(tokenId)
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: token,
-            });
+            mockedChronik.setToken(tokenId, token);
 
             // Set mock for chronik.tx(tokenId)
-            mockedChronik.setMock('tx', {
-                input: tokenId,
-                output: tx,
-            });
+            mockedChronik.setTx(tokenId, tx);
         }
 
         // Revive JSON wallet
@@ -292,12 +254,10 @@
         const secondaryAddress = mockTxHistoryWallet.paths.get(145).address;
 
         // Set tx history for all paths
-        mockedChronik.setAddress(defaultAddress);
         mockedChronik.setTxHistoryByAddress(
             defaultAddress,
             mockPath1899History,
         );
-        mockedChronik.setAddress(secondaryAddress);
         mockedChronik.setTxHistoryByAddress(
             secondaryAddress,
             mockPath145History,
@@ -325,10 +285,7 @@
         const mockedChronik = new MockChronikClient();
         for (const tokenId of tokenIds) {
             // Mock an error in getting cached token info
-            mockedChronik.setMock('token', {
-                input: tokenId,
-                output: new Error('Some chronik error'),
-            });
+            mockedChronik.setToken(tokenId, new Error('Some chronik error'));
         }
 
         // Revive JSON wallet
@@ -340,12 +297,10 @@
         const secondaryAddress = mockTxHistoryWallet.paths.get(145).address;
 
         // Set tx history for all paths
-        mockedChronik.setAddress(defaultAddress);
         mockedChronik.setTxHistoryByAddress(
             defaultAddress,
             mockPath1899History,
         );
-        mockedChronik.setAddress(secondaryAddress);
         mockedChronik.setTxHistoryByAddress(
             secondaryAddress,
             mockPath145History,
@@ -375,7 +330,6 @@
             const mockedChronik = new MockChronikClient();
             const tokenId =
                 '1111111111111111111111111111111111111111111111111111111111111111';
-            mockedChronik.setTokenId(tokenId);
             mockedChronik.setTxHistoryByTokenId(tokenId, [
                 { txid: 'deadbeef' },
             ]);
@@ -388,7 +342,6 @@
             const mockedChronik = new MockChronikClient();
             const tokenId =
                 '1111111111111111111111111111111111111111111111111111111111111111';
-            mockedChronik.setTokenId(tokenId);
             mockedChronik.setTxHistoryByTokenId(
                 tokenId,
                 [
@@ -411,7 +364,6 @@
             const mockedChronik = new MockChronikClient();
             const tokenId =
                 '1111111111111111111111111111111111111111111111111111111111111111';
-            mockedChronik.setTokenId(tokenId);
             mockedChronik.setTxHistoryByTokenId(tokenId, []);
             expect(
                 await getAllTxHistoryByTokenId(mockedChronik, tokenId),
diff --git a/cashtab/src/components/Agora/Collection/index.test.js b/cashtab/src/components/Agora/Collection/index.test.js
--- a/cashtab/src/components/Agora/Collection/index.test.js
+++ b/cashtab/src/components/Agora/Collection/index.test.js
@@ -152,14 +152,14 @@
             heismanCollectionCacheMocks,
             lkCacheMocks,
         ]) {
-            mockedChronik.setMock('token', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.token,
-            });
-            mockedChronik.setMock('tx', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.tx,
-            });
+            mockedChronik.setToken(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.token,
+            );
+            mockedChronik.setTx(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.tx,
+            );
         }
 
         // Must include CashtabNotification to test notification
@@ -239,14 +239,14 @@
             heismanCollectionCacheMocks,
             lkCacheMocks,
         ]) {
-            mockedChronik.setMock('token', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.token,
-            });
-            mockedChronik.setMock('tx', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.tx,
-            });
+            mockedChronik.setToken(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.token,
+            );
+            mockedChronik.setTx(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.tx,
+            );
         }
 
         // Must include CashtabNotification to test notification
@@ -326,11 +326,7 @@
             '0200000002f7bb552354b6f5076eb2664a8bcbbedc87b42f2ebfcb1480ee0a9141bbae63590000000064418bcbcf63745390a2ccc2a64657dc269db9dabb0e37faf002959ee4b10c688ec944d169e3cab07ab284e2962f97036c864390dd6cd14abfec9064b11529a3221a41210233f09cd4dc3381162f09975f90866f085350a5ec890d7fba5f6739c9c0ac2afdffffffff404b7bf6dde0308d82d627850eb1a3a2fca61f3275be83b6d579c47ed2550ed301000000f5414687cc36c08199e2f3bb4f36c91d7c54000f764b7fe4503311dbcb3080e64555fb718d12ba26b434c8dcbf511f14ac27c141bb834a0fa475613dc70dee4504ac41004cb0634c6b0000000000000000406a04534c500001410453454e4420be095430a16a024134bea079f235bcd2f79425c42659f9346416f626671f371c08000000000000000008000000000000000100f2052a010000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac7c7eaa7801327f7701207f7588520144807c7ea86f7bbb7501c17e7c67210233f09cd4dc3381162f09975f90866f085350a5ec890d7fba5f6739c9c0ac2afd68abacffffffff030000000000000000376a04534c500001410453454e4420be095430a16a024134bea079f235bcd2f79425c42659f9346416f626671f371c08000000000000000122020000000000001976a91403b830e4b9dce347f3495431e1f9d1005f4b420488ac67660600000000001976a91403b830e4b9dce347f3495431e1f9d1005f4b420488ac00000000';
         const mockCancelTxid =
             'df0737a3f86e8761e1b00197935c49b8589e20320ced101d640b874f7cded2b2';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: mockCancelHex,
-            output: { txid: mockCancelTxid },
-        });
+        mockedChronik.setBroadcastTx(mockCancelHex, mockCancelTxid);
 
         // Must include CashtabNotification to test notification
         render(
@@ -417,11 +413,7 @@
             '0200000002f327f712cf7e629089daa7e76aa6a22d695f09a8019cd6fce640f5d044d2114700000000644176b288e2343af4e8e2420d29aa16571eed88e05b30519f452eaddae4538f5ba2c59408cc58bd367458a8632e36dc7d969588040529c3e61b796467b4f3a6ab574121021e75febb8ae57a8805e80df93732ab7d5d8606377cb30c0f02444809cc085f39ffffffff404b7bf6dde0308d82d627850eb1a3a2fca61f3275be83b6d579c47ed2550ed301000000fd950121023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b140ea428c8709da0aa9a3f3c9e159347b63d813f4c821f8b33ceea43962ab616ef9fd663847058af843fb18e6837669dde335066ed5aacb4ef45882f50f81b371cf4c5a404b7bf6dde0308d82d627850eb1a3a2fca61f3275be83b6d579c47ed2550ed30100000001ac2202000000000000fffffffffc9d8ea3c199bf5bb3c08676d4c6aa244aa4f228b0df15847db7f0458f0031d300000000c10000002222020000000000001976a914f208ef75eb0dd778ea4540cbd966a830c7b94bb088ac514cb0634c6b0000000000000000406a04534c500001410453454e4420be095430a16a024134bea079f235bcd2f79425c42659f9346416f626671f371c08000000000000000008000000000000000100f2052a010000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac7c7eaa7801327f7701207f7588520144807c7ea86f7bbb7501c17e7c67210233f09cd4dc3381162f09975f90866f085350a5ec890d7fba5f6739c9c0ac2afd68abacffffffff030000000000000000406a04534c500001410453454e4420be095430a16a024134bea079f235bcd2f79425c42659f9346416f626671f371c08000000000000000008000000000000000100f2052a010000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac22020000000000001976a914f208ef75eb0dd778ea4540cbd966a830c7b94bb088ac00000000';
         const mockBuyTxid =
             '031e3291025cf485ec707f9ada7cb6229cb5a23fbcc4fec9c3f6a937d2e52913';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: mockBuyHex,
-            output: { txid: mockBuyTxid },
-        });
+        mockedChronik.setBroadcastTx(mockBuyHex, mockBuyTxid);
 
         // Need to juice the wallet with a big utxo as this NFT is $$$
         const affordItUTxo = {
diff --git a/cashtab/src/components/Agora/OrderBook/__tests__/index.test.js b/cashtab/src/components/Agora/OrderBook/__tests__/index.test.js
--- a/cashtab/src/components/Agora/OrderBook/__tests__/index.test.js
+++ b/cashtab/src/components/Agora/OrderBook/__tests__/index.test.js
@@ -279,11 +279,7 @@
             '0200000002f7bb552354b6f5076eb2664a8bcbbedc87b42f2ebfcb1480ee0a9141bbae63590000000064414e90dfcdd1508f599267d5b761db8268c164567032f6eb597677d010df4e67eb61e29721535f92070d3c77d7679d78a209122aabec6c7f8d536db072b7dda28241210233f09cd4dc3381162f09975f90866f085350a5ec890d7fba5f6739c9c0ac2afdffffffffbfd08cec4d74b7820cea750b36a0a69d88b6cec3c084caf29e9b866cd8999f6d01000000fdab010441475230075041525449414c4162790797e5a77ccb0326f5e85ad2ec334b17616a636bad4d21a9fa8ec73e6e249443ef7f598a513ee6023bf0f4090300e3f1f37e96c5ea39fe15db0f2f3a56b941004d58014c766a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb10800000000000000000001db4603000000000079150000000000008ec420000000000008b7023e0233f09cd4dc3381162f09975f90866f085350a5ec890d7fba5f6739c9c0ac2afd08b0caff7f00000000ab7b63817b6ea26976038ec420a2697603db46039700887d94527901377f75789263587e7803db4603965880bc007e7e68587e527903db4603965880bc007e7e825980bc7c7e01007e7b027815930279159657807e041976a914707501557f77a97e0288ac7e7e6b7d02220258800317a9147e024c7672587d807e7e7e01ab7e537901257f7702d6007f5c7f7701207f547f750408b7023e886b7ea97e01877e7c92647500687b8292697e6c6c7b7eaa88520144807c7ea86f7bbb7501c17e7c677501557f7768ad075041525449414c88044147523087ffffffff030000000000000000376a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000271022020000000000001976a91403b830e4b9dce347f3495431e1f9d1005f4b420488acaf650600000000001976a91403b830e4b9dce347f3495431e1f9d1005f4b420488ac00000000';
         const cancelTxid =
             '256ffa0a5e18f7c546673ff6c49fb4d483fe2cbae3b1269bc1000c4c6d950fa9';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: cancelHex,
-            output: { txid: cancelTxid },
-        });
+        mockedChronik.setBroadcastTx(cancelHex, cancelTxid);
 
         // Note we must include CashtabNotification to test toastify notification
         render(
@@ -426,11 +422,7 @@
             '593640fef02460656cf16385493523091338366a7688e9ce731f40c10000000384c420514d58014c766a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000000000019e17010000000000f70500000000000084c4200000000000ce731f40021e75febb8ae57a8805e80df93732ab7d5d8606377cb30c0f02444809cc085f3908a0a3ff7f00000000ab7b63817b6ea269760384c420a26976039e17019700887d94527901377f75789263587e78039e1701965880bc007e7e68587e5279039e1701965880bc007e7e825980bc7c7e01007e7b02f6059302f7059657807e041976a914707501557f77a97e0288ac7e7e6b7d02220258800317a9147e024c7672587d807e7e7e01ab7e537901257f7702d6007f5c7f7701207f547f7504ce731f40886b7ea97e01877e7c92647500687b8292697e6c6c7b7eaa88520144807c7ea86f7bbb7501c17e7c677501557f7768ad075041525449414c88044147523087fffffffff7bb552354b6f5076eb2664a8bcbbedc87b42f2ebfcb1480ee0a9141bbae6359000000006441ed5b343334ab7603062faac5469e7b8b1513cec8e8730c972f4759e4fed0ef9cbd0a50b944d7e8094192ba99fd5eea6e61f568ba12a6b542deca6eea77761d1841210233f09cd4dc3381162f09975f90866f085350a5ec890d7fba5f6739c9c0ac2afdffffffff050000000000000000496a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000000008000000000000751208000000000000001e007f0500000000001976a914f208ef75eb0dd778ea4540cbd966a830c7b94bb088ac220200000000000017a914211be508fb7608c0a3b3d7a36279894d0450e7378722020000000000001976a91403b830e4b9dce347f3495431e1f9d1005f4b420488ac9de20000000000001976a91403b830e4b9dce347f3495431e1f9d1005f4b420488acce731f40';
         const buyTxid =
             'eb298e786a91676f5b88b45d31d3979d6a8f96771ed99a69f3fa1aa1306238b0';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: buyHex,
-            output: { txid: buyTxid },
-        });
+        mockedChronik.setBroadcastTx(buyHex, buyTxid);
 
         // Note we must include CashtabNotification to test toastify notification
         render(
diff --git a/cashtab/src/components/Agora/__tests__/index.test.js b/cashtab/src/components/Agora/__tests__/index.test.js
--- a/cashtab/src/components/Agora/__tests__/index.test.js
+++ b/cashtab/src/components/Agora/__tests__/index.test.js
@@ -60,14 +60,14 @@
             bullCacheMocks,
             scamCacheMocks,
         ]) {
-            mockedChronik.setMock('token', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.token,
-            });
-            mockedChronik.setMock('tx', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.tx,
-            });
+            mockedChronik.setToken(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.token,
+            );
+            mockedChronik.setTx(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.tx,
+            );
         }
 
         // Mock the fetch call to Cashtab's price API
@@ -430,10 +430,7 @@
         const cancelTxid =
             'de8f638c5b11592825ff74f2ec59892f721bc1151486efe86d99a44bf05865bf';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: cancelHex,
-            output: { txid: cancelTxid },
-        });
+        mockedChronik.setBroadcastTx(cancelHex, cancelTxid);
 
         render(
             <CashtabTestWrapper
@@ -722,10 +719,7 @@
         const buyTxid =
             '6fbee4e0460e3730f000e2927d69d881b8a536b80fd43b839d32e34c3490ff00';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: buyHex,
-            output: { txid: buyTxid },
-        });
+        mockedChronik.setBroadcastTx(buyHex, buyTxid);
 
         render(
             <CashtabTestWrapper
@@ -874,14 +868,14 @@
         // Mock chronik calls used to build token cache to show
         // the user can load a page without having the token info cached
         for (const tokenCacheMock of [cachetCacheMocks, bullCacheMocks]) {
-            emptyWalletMockedChronik.setMock('token', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.token,
-            });
-            emptyWalletMockedChronik.setMock('tx', {
-                input: tokenCacheMock.token.tokenId,
-                output: tokenCacheMock.tx,
-            });
+            emptyWalletMockedChronik.setToken(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.token,
+            );
+            emptyWalletMockedChronik.setTx(
+                tokenCacheMock.token.tokenId,
+                tokenCacheMock.tx,
+            );
         }
 
         render(
diff --git a/cashtab/src/components/Airdrop/__tests__/Airdrop.test.js b/cashtab/src/components/Airdrop/__tests__/Airdrop.test.js
--- a/cashtab/src/components/Airdrop/__tests__/Airdrop.test.js
+++ b/cashtab/src/components/Airdrop/__tests__/Airdrop.test.js
@@ -65,20 +65,16 @@
         const airdropTokenId =
             '50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e';
         // Make sure the app can get this token's genesis info by calling a mock
-        mockedChronik.setMock('token', {
-            input: airdropTokenId,
-            output: easterEggTokenChronikTokenDetails,
-        });
+        mockedChronik.setToken(
+            airdropTokenId,
+            easterEggTokenChronikTokenDetails,
+        );
 
         // Set tx mock so we can get its minting address
-        mockedChronik.setMock('tx', {
-            input: airdropTokenId,
-            output: easterEggTokenChronikGenesisTx,
-        });
+        mockedChronik.setTx(airdropTokenId, easterEggTokenChronikGenesisTx);
 
         // Mock the chronik.tokenId(formData.tokenId).utxos(); call
-        mockedChronik.setTokenId(airdropTokenId);
-        mockedChronik.setUtxosByTokenId(airdropTokenId, tokenUtxos);
+        mockedChronik.setUtxosByTokenId(airdropTokenId, tokenUtxos.utxos);
 
         render(<CashtabTestWrapper chronik={mockedChronik} route="/airdrop" />);
 
@@ -169,20 +165,16 @@
         const airdropTokenId =
             'bef614aac85c0c866f4d39e4d12a96851267d38d1bca5bdd6488bbd42e28b6b1';
         // Make sure the app can get this token's genesis info by calling a mock
-        mockedChronik.setMock('token', {
-            input: airdropTokenId,
-            output: decimalsTokenInfo,
-        });
+        mockedChronik.setToken(airdropTokenId, decimalsTokenInfo);
 
         // Set tx mock so we can get its minting address
-        mockedChronik.setMock('tx', {
-            input: airdropTokenId,
-            output: decimalsTokenGenesis,
-        });
+        mockedChronik.setTx(airdropTokenId, decimalsTokenGenesis);
 
         // Mock the chronik.tokenId(formData.tokenId).utxos(); call
-        mockedChronik.setTokenId(airdropTokenId);
-        mockedChronik.setUtxosByTokenId(airdropTokenId, tokenUtxosDecimals);
+        mockedChronik.setUtxosByTokenId(
+            airdropTokenId,
+            tokenUtxosDecimals.utxos,
+        );
 
         render(<CashtabTestWrapper chronik={mockedChronik} route="/airdrop" />);
 
diff --git a/cashtab/src/components/App/__tests__/App.test.js b/cashtab/src/components/App/__tests__/App.test.js
--- a/cashtab/src/components/App/__tests__/App.test.js
+++ b/cashtab/src/components/App/__tests__/App.test.js
@@ -595,10 +595,10 @@
         );
 
         // Make sure the app can get this token's genesis info by calling a mock
-        mockedChronik.setMock('token', {
-            input: EASTER_EGG_TOKENID,
-            output: easterEggTokenChronikTokenDetails,
-        });
+        mockedChronik.setToken(
+            EASTER_EGG_TOKENID,
+            easterEggTokenChronikTokenDetails,
+        );
 
         render(<CashtabTestWrapper ecc={ecc} chronik={mockedChronik} />);
 
diff --git a/cashtab/src/components/App/fixtures/helpers.js b/cashtab/src/components/App/fixtures/helpers.js
--- a/cashtab/src/components/App/fixtures/helpers.js
+++ b/cashtab/src/components/App/fixtures/helpers.js
@@ -171,62 +171,50 @@
 ) => {
     // mock chronik endpoint returns
     const CASHTAB_TESTS_TIPHEIGHT = 800000;
-    chronikClient.setMock('blockchainInfo', {
-        output: apiError
-            ? new Error('Error fetching blockchainInfo')
-            : { tipHeight: CASHTAB_TESTS_TIPHEIGHT },
-    });
-
-    // Mock scriptutxos to match context
-    chronikClient.setAddress(wallet.Path1899.cashAddress);
-    chronikClient.setUtxosByAddress(
-        wallet.Path1899.cashAddress,
-        apiError
-            ? new Error('Error fetching utxos')
-            : {
-                  outputScript: `76a914${wallet.Path1899.hash160}88ac`,
-                  utxos: wallet.state.nonSlpUtxos.concat(wallet.state.slpUtxos),
-              },
-    );
-
-    // We set legacy paths to contain no utxos
-    chronikClient.setAddress(wallet.Path145.cashAddress);
-    chronikClient.setUtxosByAddress(
-        wallet.Path145.cashAddress,
-        apiError
-            ? new Error('Error fetching utxos')
-            : {
-                  outputScript: `76a914${wallet.Path145.hash160}88ac`,
-                  utxos: [],
-              },
-    );
-    chronikClient.setAddress(wallet.Path245.cashAddress);
-    chronikClient.setUtxosByAddress(
-        wallet.Path245.cashAddress,
-        apiError
-            ? new Error('Error fetching utxos')
-            : {
-                  outputScript: `76a914${wallet.Path245.hash160}88ac`,
-                  utxos: [],
-              },
-    );
-
-    // TX history mocks
-    chronikClient.setTxHistoryByAddress(
-        wallet.Path1899.cashAddress,
-        apiError
-            ? new Error('Error fetching history')
-            : wallet.state.parsedTxHistory,
-    );
-    // We set legacy paths to contain no utxos
-    chronikClient.setTxHistoryByAddress(
-        wallet.Path145.cashAddress,
-        apiError ? new Error('Error fetching history') : [],
-    );
-    chronikClient.setTxHistoryByAddress(
-        wallet.Path245.cashAddress,
-        apiError ? new Error('Error fetching history') : [],
-    );
+    if (apiError) {
+        chronikClient.setBlockchainInfo(
+            new Error('Error fetching blockchainInfo'),
+        );
+        chronikClient.setUtxosByAddress(
+            wallet.Path1899.cashAddress,
+            new Error('Error fetching utxos'),
+        );
+        chronikClient.setTxHistoryByAddress(
+            wallet.Path1899.cashAddress,
+            new Error('Error fetching history'),
+        );
+        chronikClient.setUtxosByAddress(
+            wallet.Path145.cashAddress,
+            new Error('Error fetching utxos'),
+        );
+        chronikClient.setUtxosByAddress(
+            wallet.Path245.cashAddress,
+            new Error('Error fetching utxos'),
+        );
+        // We set legacy paths to contain no utxos
+        chronikClient.setTxHistoryByAddress(
+            wallet.Path145.cashAddress,
+            new Error('Error fetching history'),
+        );
+        chronikClient.setTxHistoryByAddress(
+            wallet.Path245.cashAddress,
+            new Error('Error fetching history'),
+        );
+    } else {
+        chronikClient.setBlockchainInfo({ tipHeight: CASHTAB_TESTS_TIPHEIGHT });
+        chronikClient.setUtxosByAddress(
+            wallet.Path1899.cashAddress,
+            wallet.state.nonSlpUtxos.concat(wallet.state.slpUtxos),
+        );
+        chronikClient.setTxHistoryByAddress(
+            wallet.Path1899.cashAddress,
+            wallet.state.parsedTxHistory,
+        );
+        chronikClient.setUtxosByAddress(wallet.Path145.cashAddress, []);
+        chronikClient.setUtxosByAddress(wallet.Path245.cashAddress, []);
+        chronikClient.setTxHistoryByAddress(wallet.Path145.cashAddress, []);
+        chronikClient.setTxHistoryByAddress(wallet.Path245.cashAddress, []);
+    }
 };
 
 /**
@@ -243,11 +231,13 @@
 ) => {
     // mock chronik endpoint returns
     const CASHTAB_TESTS_TIPHEIGHT = 800000;
-    chronikClient.setMock('blockchainInfo', {
-        output: apiError
-            ? new Error('Error fetching blockchainInfo')
-            : { tipHeight: CASHTAB_TESTS_TIPHEIGHT },
-    });
+    if (apiError) {
+        chronikClient.setBlockchainInfo(
+            new Error('Error fetching blockchainInfo'),
+        );
+    } else {
+        chronikClient.setBlockchainInfo({ tipHeight: CASHTAB_TESTS_TIPHEIGHT });
+    }
 
     // If you are mocking a legacy wallet to test a migration, return prepareMockedChronikCallsForLegacyWallet
     if (!('paths' in wallet)) {
@@ -261,32 +251,31 @@
     // Iterate over paths to create chronik mocks
     for (const path of wallet.paths) {
         // Mock scriptutxos to match context
-        chronikClient.setAddress(path.address);
-        chronikClient.setUtxosByAddress(
-            path.address,
-            apiError
-                ? new Error('Error fetching utxos')
-                : {
-                      outputScript: `76a914${path.hash}88ac`,
-                      utxos:
-                          path.path === 1899
-                              ? wallet.state.nonSlpUtxos.concat(
-                                    wallet.state.slpUtxos,
-                                )
-                              : [],
-                  },
-        );
-
-        // Mock tx history
-        chronikClient.setTxHistoryByAddress(
-            path.address,
-            apiError
-                ? new Error('Error fetching history')
-                : path.path === 1899
-                ? wallet.state.parsedTxHistory
-                : // No tx history at legacy paths
-                  [],
-        );
+        if (apiError) {
+            chronikClient.setUtxosByAddress(
+                path.address,
+                new Error('Error fetching utxos'),
+            );
+            chronikClient.setTxHistoryByAddress(
+                path.address,
+                new Error('Error fetching history'),
+            );
+        } else {
+            if (path.path === 1899) {
+                chronikClient.setUtxosByAddress(
+                    path.address,
+                    wallet.state.nonSlpUtxos.concat(wallet.state.slpUtxos),
+                );
+                chronikClient.setTxHistoryByAddress(
+                    path.address,
+                    wallet.state.parsedTxHistory,
+                );
+            } else {
+                // No history or utxos at legacy paths
+                chronikClient.setUtxosByAddress(path.address, []);
+                chronikClient.setTxHistoryByAddress(path.address, []);
+            }
+        }
     }
 };
 
@@ -304,18 +293,20 @@
 ) => {
     // mock chronik endpoint returns
     const CASHTAB_TESTS_TIPHEIGHT = 800000;
-    chronikClient.setMock('blockchainInfo', {
-        output: apiError
-            ? new Error('Error fetching blockchainInfo')
-            : { tipHeight: CASHTAB_TESTS_TIPHEIGHT },
-    });
-    // Mock an avalanche-finalized block details
-    chronikClient.setMock('block', {
-        input: CASHTAB_TESTS_TIPHEIGHT,
-        output: apiError
-            ? new Error('Error fetching block')
-            : { blockInfo: { isFinal: true } },
-    });
+    if (apiError) {
+        chronikClient.setBlockchainInfo(
+            new Error('Error fetching blockchainInfo'),
+        );
+        chronikClient.setBlock(
+            CASHTAB_TESTS_TIPHEIGHT,
+            new Error('Error fetching block'),
+        );
+    } else {
+        chronikClient.setBlockchainInfo({ tipHeight: CASHTAB_TESTS_TIPHEIGHT });
+        chronikClient.setBlock(CASHTAB_TESTS_TIPHEIGHT, {
+            blockInfo: { isFinal: true },
+        });
+    }
 
     // Mock token calls
     // This info is same shape for all wallets supported in these functions
@@ -357,10 +348,8 @@
                 timestamp: 1678408305,
             },
         };
-        chronikClient.setMock('token', {
-            input: tokenId,
-            output: mockedTokenResponse,
-        });
+        chronikClient.setToken(tokenId, mockedTokenResponse);
+
         const mockedTxResponse = {
             txid: tokenId,
             version: 2,
@@ -445,10 +434,7 @@
                 timestamp: 1678408305,
             },
         };
-        chronikClient.setMock('tx', {
-            input: tokenId,
-            output: mockedTxResponse,
-        });
+        chronikClient.setTx(tokenId, mockedTxResponse);
     }
 
     // If you are mocking a legacy wallet to test a migration, return prepareMockedChronikCallsForLegacyWallet
@@ -471,32 +457,30 @@
     // Iterate over paths to create chronik mocks
     wallet.paths.forEach((pathInfo, path) => {
         // Mock scriptutxos to match context
-        chronikClient.setAddress(pathInfo.address);
-        chronikClient.setUtxosByAddress(
-            pathInfo.address,
-            apiError
-                ? new Error('Error fetching utxos')
-                : {
-                      outputScript: `76a914${pathInfo.hash}88ac`,
-                      utxos:
-                          path === 1899
-                              ? wallet.state.nonSlpUtxos.concat(
-                                    wallet.state.slpUtxos,
-                                )
-                              : [],
-                  },
-        );
-
-        // Mock tx history
-        chronikClient.setTxHistoryByAddress(
-            pathInfo.address,
-            apiError
-                ? new Error('Error fetching history')
-                : path === 1899
-                ? wallet.state.parsedTxHistory
-                : // No tx history at legacy paths
-                  [],
-        );
+        if (apiError) {
+            chronikClient.setUtxosByAddress(
+                pathInfo.address,
+                new Error('Error fetching utxos'),
+            );
+            chronikClient.setTxHistoryByAddress(
+                pathInfo.address,
+                new Error('Error fetching history'),
+            );
+        } else {
+            if (path === 1899) {
+                chronikClient.setUtxosByAddress(
+                    pathInfo.address,
+                    wallet.state.nonSlpUtxos.concat(wallet.state.slpUtxos),
+                );
+                chronikClient.setTxHistoryByAddress(
+                    pathInfo.address,
+                    wallet.state.parsedTxHistory,
+                );
+            } else {
+                chronikClient.setUtxosByAddress(pathInfo.address, []);
+                chronikClient.setTxHistoryByAddress(pathInfo.address, []);
+            }
+        }
     });
 };
 
diff --git a/cashtab/src/components/Configure/__tests__/Configure.test.js b/cashtab/src/components/Configure/__tests__/Configure.test.js
--- a/cashtab/src/components/Configure/__tests__/Configure.test.js
+++ b/cashtab/src/components/Configure/__tests__/Configure.test.js
@@ -144,10 +144,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064416a14b2f97b4b24a409799b68a6da2d34ada9fa0f305eeffe11d9234cd8ee17dcb033de65840107dd52c35207fb2d2a88eadac270e582a8bc7cd66df4437800234121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff027c150000000000001976a9146ffbe7c7d7bd01295eb1e371de9550339bdcf9fd88acdb6c0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '5f334f32bec07b1029ae579460c704e33ba05b91e3bc2bba9ee215bc585cd6ab';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(<CashtabTestWrapper chronik={mockedChronik} ecc={ecc} />);
 
@@ -239,14 +236,14 @@
         );
 
         // Make sure the app can get this token's genesis info by calling a mock
-        mockedChronik.setMock('token', {
-            input: appConfig.vipTokens.cachet.tokenId,
-            output: cachetTokenAndTx.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: appConfig.vipTokens.cachet.tokenId,
-            output: cachetTokenAndTx.tx,
-        });
+        mockedChronik.setToken(
+            appConfig.vipTokens.cachet.tokenId,
+            cachetTokenAndTx.token,
+        );
+        mockedChronik.setTx(
+            appConfig.vipTokens.cachet.tokenId,
+            cachetTokenAndTx.tx,
+        );
 
         render(<CashtabTestWrapper chronik={mockedChronik} ecc={ecc} />);
 
@@ -305,24 +302,21 @@
         );
 
         // Make sure the app can get this token's genesis info by calling a mock
-        mockedChronik.setMock('token', {
-            input: appConfig.vipTokens.cachet.tokenId,
-            output: cachetTokenAndTx.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: appConfig.vipTokens.cachet.tokenId,
-            output: cachetTokenAndTx.tx,
-        });
+        mockedChronik.setToken(
+            appConfig.vipTokens.cachet.tokenId,
+            cachetTokenAndTx.token,
+        );
+        mockedChronik.setTx(
+            appConfig.vipTokens.cachet.tokenId,
+            cachetTokenAndTx.tx,
+        );
 
         // Can verify in Electrum that this tx is sent at 1.0 sat/byte
         const hex =
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441a8ae2e6e418b09c8a189547c7412a551617d2f26e55ee5af787ef9ad3f583f6086995640fc06039a04e113dc3d18ce3c51b817f59d31dbb8193dcfa4b7a862664121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff027c150000000000001976a9146ffbe7c7d7bd01295eb1e371de9550339bdcf9fd88acb96d0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             'c16de907537369994417459369faad6595842d569b7b4a9544288ac8a4c81dbb';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(<CashtabTestWrapper chronik={mockedChronik} ecc={ecc} />);
 
@@ -403,24 +397,21 @@
         );
 
         // Make sure the app can get this token's genesis info by calling a mock
-        mockedChronik.setMock('token', {
-            input: appConfig.vipTokens.grumpy.tokenId,
-            output: vipTokenChronikTokenMocks.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: appConfig.vipTokens.grumpy.tokenId,
-            output: vipTokenChronikTokenMocks.tx,
-        });
+        mockedChronik.setToken(
+            appConfig.vipTokens.grumpy.tokenId,
+            vipTokenChronikTokenMocks.token,
+        );
+        mockedChronik.setTx(
+            appConfig.vipTokens.grumpy.tokenId,
+            vipTokenChronikTokenMocks.tx,
+        );
 
         // Can verify in Electrum that this tx is sent at 1.0 sat/byte
         const hex =
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441a8ae2e6e418b09c8a189547c7412a551617d2f26e55ee5af787ef9ad3f583f6086995640fc06039a04e113dc3d18ce3c51b817f59d31dbb8193dcfa4b7a862664121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff027c150000000000001976a9146ffbe7c7d7bd01295eb1e371de9550339bdcf9fd88acb96d0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             'c16de907537369994417459369faad6595842d569b7b4a9544288ac8a4c81dbb';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Can verify in Electrum that this tx is sent at 1.0 sat/byte
         const tokenSendHex =
@@ -428,10 +419,7 @@
         const tokenSendTxid =
             'a9981db09af60875966df3f47a80588d0975fec799c658b702b22633604904d1';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: tokenSendHex,
-            output: { txid: tokenSendTxid },
-        });
+        mockedChronik.setBroadcastTx(tokenSendHex, tokenSendTxid);
 
         render(<CashtabTestWrapper chronik={mockedChronik} ecc={ecc} />);
 
diff --git a/cashtab/src/components/Etokens/__tests__/CreateTokenForm.test.js b/cashtab/src/components/Etokens/__tests__/CreateTokenForm.test.js
--- a/cashtab/src/components/Etokens/__tests__/CreateTokenForm.test.js
+++ b/cashtab/src/components/Etokens/__tests__/CreateTokenForm.test.js
@@ -66,10 +66,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a2102000000644199e5a5dcfea45a68137f07bfe749195897767030687bd3f4b4dbcf2b2ddf2711af47b13f376523031b3c3c975a00e12b46d46f057fd5e144b79a95eee71479e84121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000466a04534c500001010747454e4553495303544b450a7465737420746f6b656e1768747470733a2f2f7777772e636173687461622e636f6d4c0001024c0008000000000393870022020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac977f0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '71626fb3bd4be7713096107af225eff0f9243c5374ca50fe3bf9a736e14b9f9c';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
         render(
             <CashtabTestWrapper
                 chronik={mockedChronik}
@@ -161,26 +158,16 @@
         );
 
         // Mock the not-yet-created token's tokeninfo and utxo calls to test the redirect
-        mockedChronik.setMock('token', {
-            input: createdTokenId,
-            output: MOCK_CHRONIK_TOKEN_CALL,
-        });
-        mockedChronik.setMock('tx', {
-            input: createdTokenId,
-            output: MOCK_CHRONIK_GENESIS_TX_CALL,
-        });
-        mockedChronik.setTokenId(createdTokenId);
-        mockedChronik.setUtxosByTokenId(createdTokenId, {
-            utxos: [MOCK_UTXO_FOR_BALANCE],
-        });
+        mockedChronik.setToken(createdTokenId, MOCK_CHRONIK_TOKEN_CALL);
+        mockedChronik.setTx(createdTokenId, MOCK_CHRONIK_GENESIS_TX_CALL);
+        mockedChronik.setUtxosByTokenId(createdTokenId, [
+            MOCK_UTXO_FOR_BALANCE,
+        ]);
         // Add tx mock to mockedChronik
         const hex =
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441ff86eb97dad643075e75ed273334cee9aef1b938436dc350bcb48f73d129ce6a9d9ea40e749303e7bcbd27a082f1ee03080582f00f1ec80f202166bff431a0334121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000466a04534c500001010747454e4553495303544b450a7465737420746f6b656e1768747470733a2f2f7777772e636173687461622e636f6d4c000102010208000000000393870022020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac22020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac307d0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid: createdTokenId },
-        });
+        mockedChronik.setBroadcastTx(hex, createdTokenId);
         render(
             <CashtabTestWrapper
                 chronik={mockedChronik}
@@ -271,10 +258,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064415594de73e7f09dc4bd7622b136921d8b883c131559e9ba9212185fb5f7db1fe062715183484097a5f7cf71d75af3b9b3b2768f7e011550893376ef9ec150887b4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff0300000000000000006e6a04534c500001810747454e45534953033448432454686520466f75722048616c662d436f696e73206f66204a696e2d71756120283448432925656e2e77696b6970656469612e6f72672f77696b692f5461692d50616e5f286e6f76656c294c0001004c0008000000000000000422020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac467f0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '4517dc895499f2090ae04eeb28e2d2f0a0790baf99568f7e52436df45ca766c3';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Load component with create-nft-collection route
         render(
@@ -373,26 +357,16 @@
         );
 
         // Mock the not-yet-created token's tokeninfo and utxo calls to test the redirect
-        mockedChronik.setMock('token', {
-            input: createdTokenId,
-            output: MOCK_CHRONIK_TOKEN_CALL,
-        });
-        mockedChronik.setMock('tx', {
-            input: createdTokenId,
-            output: MOCK_CHRONIK_GENESIS_TX_CALL,
-        });
-        mockedChronik.setTokenId(createdTokenId);
-        mockedChronik.setUtxosByTokenId(createdTokenId, {
-            utxos: [MOCK_UTXO_FOR_BALANCE],
-        });
+        mockedChronik.setToken(createdTokenId, MOCK_CHRONIK_TOKEN_CALL);
+        mockedChronik.setTx(createdTokenId, MOCK_CHRONIK_GENESIS_TX_CALL);
+        mockedChronik.setUtxosByTokenId(createdTokenId, [
+            MOCK_UTXO_FOR_BALANCE,
+        ]);
         // Add tx mock to mockedChronik
         const hex =
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064417055f05736401020a4eec59c8c9cb2e76bdbcfca5e2a9b1468e1dcf5ef5534febab436728463762b015f9564fa33cc870de0cfcafa7a906b663bc8ba58816c644121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000646a504c60534c5032000747454e4553495303544b450a7465737420746f6b656e1768747470733a2f2f7777772e636173687461622e636f6d0021031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02d02010087930300000122020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac22020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188acf47c0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid: createdTokenId },
-        });
+        mockedChronik.setBroadcastTx(hex, createdTokenId);
         render(
             <CashtabTestWrapper
                 chronik={mockedChronik}
diff --git a/cashtab/src/components/Etokens/__tests__/Token.test.js b/cashtab/src/components/Etokens/__tests__/Token.test.js
--- a/cashtab/src/components/Etokens/__tests__/Token.test.js
+++ b/cashtab/src/components/Etokens/__tests__/Token.test.js
@@ -64,19 +64,12 @@
             localforage,
         );
         // Set chronik mocks required for cache preparation and supply calc
-        mockedChronik.setMock('token', {
-            input: slp1FixedBear.tokenId,
-            output: slp1FixedBear.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: slp1FixedBear.tokenId,
-            output: slp1FixedBear.tx,
-        });
-        mockedChronik.setTokenId(slp1FixedBear.tokenId);
-        mockedChronik.setUtxosByTokenId(slp1FixedBear.tokenId, {
-            tokenId: slp1FixedBear.tokenId,
-            utxos: slp1FixedBear.utxos,
-        });
+        mockedChronik.setToken(slp1FixedBear.tokenId, slp1FixedBear.token);
+        mockedChronik.setTx(slp1FixedBear.tokenId, slp1FixedBear.tx);
+        mockedChronik.setUtxosByTokenId(
+            slp1FixedBear.tokenId,
+            slp1FixedBear.utxos,
+        );
 
         // Set up userEvent
         user = userEvent.setup();
@@ -588,10 +581,7 @@
         const txid =
             '6de2d27d40bced679a8b8e55c85230ed8da0977c30ad31247fefc0b1eba0976e';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -640,10 +630,7 @@
         const txid =
             'f3023fd2265ed98438f5d4d01d31a1d94633b496e03d4aad5acd8da240e38736';
 
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -794,29 +781,22 @@
             localforage,
         );
         // Set mock tokeninfo call
-        mintMockedChronik.setMock('token', {
-            input: slp1FixedCachet.tokenId,
-            output: slp1FixedCachet.token,
-        });
-        mintMockedChronik.setMock('tx', {
-            input: slp1FixedCachet.tokenId,
-            output: slp1FixedCachet.tx,
-        });
-        mockedChronik.setTokenId(slp1FixedCachet.tokenId);
-        mockedChronik.setUtxosByTokenId(slp1FixedCachet.tokenId, {
-            tokenId: slp1FixedCachet.tokenId,
-            utxos: slp1FixedCachet.utxos,
-        });
+        mintMockedChronik.setToken(
+            slp1FixedCachet.tokenId,
+            slp1FixedCachet.token,
+        );
+        mintMockedChronik.setTx(slp1FixedCachet.tokenId, slp1FixedCachet.tx);
+        mintMockedChronik.setUtxosByTokenId(
+            slp1FixedCachet.tokenId,
+            slp1FixedCachet.utxos,
+        );
 
         const hex =
             '02000000028ec326590f3e42afae0e458995599c4c892af8e749efc7cc6bcfca8b0f2a5b4b020000006441672ba8ac8941cc69b6f49f80da73046e65a125376dc0311b5467d678350924d598d5750cd2c19dd8b42016cef9629969373336ce2eb50c1d741985a652449db44121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dfffffffffe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441dfb3546c5e588030696f1e4a1ef00d039743514be0304505415ad9de4cf4ea0b4e9d0fda1ba3869241825e269867f6a45251477057a68ba39883eb4d25008cd64121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000396a04534c50000101044d494e5420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1010208000000000000273122020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac22020000000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac517e0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '567114b4adbb5e8969a587ac58866c0ccf0c91ded1fd0d96d75f8cb7aeb6f33a';
 
-        mintMockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mintMockedChronik.setBroadcastTx(hex, txid);
         render(
             <CashtabTestWrapper
                 chronik={mintMockedChronik}
@@ -860,19 +840,9 @@
     it('For an uncached token with no balance, we show a spinner while loading the token info, then show an info screen and open agora offers', async () => {
         // Set mock tokeninfo call
         const CACHET_TOKENID = slp1FixedCachet.tokenId;
-        mockedChronik.setMock('token', {
-            input: CACHET_TOKENID,
-            output: slp1FixedCachet.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: CACHET_TOKENID,
-            output: slp1FixedCachet.tx,
-        });
-        mockedChronik.setTokenId(CACHET_TOKENID);
-        mockedChronik.setUtxosByTokenId(CACHET_TOKENID, {
-            tokenId: slp1FixedCachet.tokenId,
-            utxos: slp1FixedCachet.utxos,
-        });
+        mockedChronik.setToken(CACHET_TOKENID, slp1FixedCachet.token);
+        mockedChronik.setTx(CACHET_TOKENID, slp1FixedCachet.tx);
+        mockedChronik.setUtxosByTokenId(CACHET_TOKENID, slp1FixedCachet.utxos);
 
         render(
             <CashtabTestWrapper
@@ -912,19 +882,12 @@
     it('For an uncached token with no balance, we show a chronik query error if we are unable to fetch the token info', async () => {
         // Set mock tokeninfo call
         const CACHET_TOKENID = slp1FixedCachet.tokenId;
-        mockedChronik.setMock('token', {
-            input: CACHET_TOKENID,
-            output: new Error('some error'),
-        });
-        mockedChronik.setMock('tx', {
-            input: CACHET_TOKENID,
-            output: new Error('some error'),
-        });
-        mockedChronik.setTokenId(CACHET_TOKENID);
-        mockedChronik.setUtxosByTokenId(CACHET_TOKENID, {
-            tokenId: slp1FixedCachet.tokenId,
-            utxos: new Error('some error'),
-        });
+        mockedChronik.setToken(CACHET_TOKENID, new Error('some error'));
+        mockedChronik.setTx(CACHET_TOKENID, new Error('some error'));
+        mockedChronik.setUtxosByTokenId(
+            CACHET_TOKENID,
+            new Error('some error'),
+        );
 
         render(
             <CashtabTestWrapper
diff --git a/cashtab/src/components/Etokens/__tests__/TokenActions.test.js b/cashtab/src/components/Etokens/__tests__/TokenActions.test.js
--- a/cashtab/src/components/Etokens/__tests__/TokenActions.test.js
+++ b/cashtab/src/components/Etokens/__tests__/TokenActions.test.js
@@ -57,19 +57,9 @@
 
         // Build chronik mocks that Cashtab would use to add token info to cache
         for (const tokenMock of supportedTokens) {
-            mockedChronik.setMock('token', {
-                input: tokenMock.tokenId,
-                output: tokenMock.token,
-            });
-            mockedChronik.setMock('tx', {
-                input: tokenMock.tokenId,
-                output: tokenMock.tx,
-            });
-            mockedChronik.setTokenId(tokenMock.tokenId);
-            mockedChronik.setUtxosByTokenId(tokenMock.tokenId, {
-                tokenId: tokenMock.tokenId,
-                utxos: tokenMock.utxos,
-            });
+            mockedChronik.setToken(tokenMock.tokenId, tokenMock.token);
+            mockedChronik.setTx(tokenMock.tokenId, tokenMock.tx);
+            mockedChronik.setUtxosByTokenId(tokenMock.tokenId, tokenMock.utxos);
             // Set empty tx history to mock no existing NFTs
             mockedChronik.setTxHistoryByTokenId(tokenMock.tokenId, []);
         }
@@ -225,22 +215,14 @@
             '0200000002666de5d5852807a13612b6ea0373643266d435822daeb39c29e5d4b67e893cda0100000064414feb64ffdf50b0eb40a6fe0c34da65e94e0cbbbc2e58f2b290f3b2bf31480b34a57c4862ee177129dc8a1ce645573cd240e5e83d336d19ff22c3a7675bc903564121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf7926180300000064410f0461f0e843cc5b78196e3fdb3b89d64948629645f3b44ea960c2a5ac8f5835189697165a01cc259a0f4eff931c83e110019ee5c7721a43e0dde11ba04e068d4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000406a04534c500001010453454e442020a0b9337a78603c6681ed2bc541593375535dcd9979196620ce71f233f2f6f80800000019d80000000800000000001d9600060500000000000017a914e49e695e2f466e34447cb253567b8b277b60e3908722020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac2c2e0f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const adPrepTxid =
             '280b6fda5a11a94145f3b4203fb4f199d875d3621c8e4cc9d63501e73b9649bc';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: adPrepHex,
-            output: { txid: adPrepTxid },
-        });
+        mockedChronik.setBroadcastTx(adPrepHex, adPrepTxid);
 
         // SLP1 ad list
         const adListHex =
             '0200000001bc49963be70135d6c94c8e1c62d375d899f1b43f20b4f34541a9115ada6f0b2801000000dd0441475230075041525449414c41b11b013fb8140dcce13f93ee99584b1c6b547ee076ed63f9ec0a6c0068ad84c5420ecd608af68134366576bae4196a83f6a8f521c50dea4acc75dda6215c7fec414c8c4c766a04534c500001010453454e442020a0b9337a78603c6681ed2bc541593375535dcd9979196620ce71f233f2f6f80800000000000000000300dbf30400000000003dc7010000000000d226af0c000000002099c53f031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02d01557f77ad075041525449414c88044147523087ffffffff020000000000000000376a04534c500001010453454e442020a0b9337a78603c6681ed2bc541593375535dcd9979196620ce71f233f2f6f80800000019d8000000220200000000000017a91472df09389a835adb0e13e32bf1c91144ed107eef8700000000';
         const adListTxid =
             '823f652e22d154fc7bdd77ee9d9fa37c77e9649235f1430958bef68b7428b9ae';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: adListHex,
-            output: { txid: adListTxid },
-        });
+        mockedChronik.setBroadcastTx(adListHex, adListTxid);
 
         // Mock response for agora select params check
         // Note
@@ -250,9 +232,8 @@
         // Note that Date() and Math.random() must be mocked to keep this deterministic
         const EXPECTED_OFFER_P2SH = '72df09389a835adb0e13e32bf1c91144ed107eef';
 
-        mockedChronik.setScript('p2sh', EXPECTED_OFFER_P2SH);
         // We mock no existing utxos
-        mockedChronik.setUtxos('p2sh', EXPECTED_OFFER_P2SH, { utxos: [] });
+        mockedChronik.setUtxosByScript('p2sh', EXPECTED_OFFER_P2SH, []);
         const agora = new Agora(mockedChronik);
 
         render(
@@ -406,11 +387,7 @@
             '0200000002cc04a35686950a66845ebf8e37677fffcc5ee0e2b63e3f05822838273149660c010000006441878aa7e698097a4961646a2da44f701d8895cb065113fcf1d2e9f073afbc37025a5587e121bd0311a24a7af60445abfc4de7e3675a3a9f51cffddc875d88fca24121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf7926180300000064412f509f90f23f4b85b27452e0f25d33cef07ad8fef898e2d308c43fb0dfd6f7e00f7201336be4089171ddc094a24688882b518ec0c6958c904df12d0239a7342f4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff150000000000000000d96a04534c500001810453454e44200c66493127382882053f3eb6e2e05eccff7f67378ebf5e84660a958656a304cc08000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000000108000000000000005222020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac0d070f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             'cdc6afbf1ddd796388692ec9106816be1f9229ece11e545c1cbe6854ccf087ec';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
         render(
             <CashtabTestWrapper
                 chronik={mockedChronik}
@@ -534,19 +511,12 @@
 
         // Build chronik mocks that Cashtab would use to add token info to cache
         for (const tokenMock of supportedTokens) {
-            mintNftMockedChronik.setMock('token', {
-                input: tokenMock.tokenId,
-                output: tokenMock.token,
-            });
-            mintNftMockedChronik.setMock('tx', {
-                input: tokenMock.tokenId,
-                output: tokenMock.tx,
-            });
-            mintNftMockedChronik.setTokenId(tokenMock.tokenId);
-            mintNftMockedChronik.setUtxosByTokenId(tokenMock.tokenId, {
-                tokenId: tokenMock.tokenId,
-                utxos: tokenMock.utxos,
-            });
+            mintNftMockedChronik.setToken(tokenMock.tokenId, tokenMock.token);
+            mintNftMockedChronik.setTx(tokenMock.tokenId, tokenMock.tx);
+            mintNftMockedChronik.setUtxosByTokenId(
+                tokenMock.tokenId,
+                tokenMock.utxos,
+            );
             // Set empty tx history to mock no existing NFTs
             mintNftMockedChronik.setTxHistoryByTokenId(tokenMock.tokenId, []);
         }
@@ -556,10 +526,7 @@
         const txid =
             'd215995b67194576b66ef9c593a66d9255a3ec21e424ecfbb6046643b8e0dbe6';
 
-        mintNftMockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mintNftMockedChronik.setBroadcastTx(hex, txid);
         render(
             <CashtabTestWrapper
                 chronik={mintNftMockedChronik}
@@ -701,19 +668,15 @@
 
         // Build chronik mocks that Cashtab would use to add token info to cache
         for (const tokenMock of supportedTokens) {
-            renderChildNftsMockedChronik.setMock('token', {
-                input: tokenMock.tokenId,
-                output: tokenMock.token,
-            });
-            renderChildNftsMockedChronik.setMock('tx', {
-                input: tokenMock.tokenId,
-                output: tokenMock.tx,
-            });
-            renderChildNftsMockedChronik.setTokenId(tokenMock.tokenId);
-            renderChildNftsMockedChronik.setUtxosByTokenId(tokenMock.tokenId, {
-                tokenId: tokenMock.tokenId,
-                utxos: tokenMock.utxos,
-            });
+            renderChildNftsMockedChronik.setToken(
+                tokenMock.tokenId,
+                tokenMock.token,
+            );
+            renderChildNftsMockedChronik.setTx(tokenMock.tokenId, tokenMock.tx);
+            renderChildNftsMockedChronik.setUtxosByTokenId(
+                tokenMock.tokenId,
+                tokenMock.utxos,
+            );
             // Set tx history of parent tokenId to empty
             renderChildNftsMockedChronik.setTxHistoryByTokenId(
                 tokenMock.tokenId,
@@ -838,32 +801,21 @@
             '0200000002268322a2a8e67fe9efdaf15c9eb7397fb640ae32d8a245c2933f9eb967ff9b5d010000006441e4365d350b1dfee55e60cc2600ba094ed0e05c1d6a297bd3fe3f0721b88d9ec09b7d114cf0aab08a3b264153858f1a48f839f3639a8a8f9b11214038080cb9e34121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf7926180300000064411e9913b28017832fa38944675eb8815411fd210f9dfc8f0aa806bed055f52b6592488fdd1f9be942c19dcb98d7ddd7c55bc8b1233a64ad3dfa1c65eebbd48f254121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000376a04534c500001410453454e44205d9bff67b99e3f93c245a2d832ae40b67f39b79e5cf1daefe97fe6a8a22283260800000000000000019a0400000000000017a91407d2b0e6ec7b96cbfbe4a7d54e28d28fbcf65e408710310f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const adPrepTxid =
             '7b4f2b1cf9716ead03f91910bd0c08956c381987e1cb3cd9f9b4d555a7b9ba25';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: adPrepHex,
-            output: { txid: adPrepTxid },
-        });
+        mockedChronik.setBroadcastTx(adPrepHex, adPrepTxid);
 
         // NFT ad list
         const adListHex =
             '020000000125bab9a755d5b4f9d93ccbe18719386c95080cbd1019f903ad6e71f91c2b4f7b01000000a70441475230074f4e4553484f544106bd7c3cc4f6aca45a7f97644b8cb5e745dee224246f38605171e8f9e0d6e036af3ea4853b08e1baa92e091bd0ceabf83d4a246e07e6b0b008a3e091b111f22a414c56222b50fe00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac7521031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dad074f4e4553484f5488044147523087ffffffff020000000000000000376a04534c500001410453454e44205d9bff67b99e3f93c245a2d832ae40b67f39b79e5cf1daefe97fe6a8a2228326080000000000000001220200000000000017a914729833ae294590bbcf28bfbb9ad54c01b6cdb6288700000000';
         const adListTxid =
             '97cf0fed5062419ad456f22457cfeb3b15909f1de2350be48c53b24944e0de89';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: adListHex,
-            output: { txid: adListTxid },
-        });
+        mockedChronik.setBroadcastTx(adListHex, adListTxid);
 
         // NFT send
         const hex =
             '0200000002268322a2a8e67fe9efdaf15c9eb7397fb640ae32d8a245c2933f9eb967ff9b5d010000006441fff60607ba0fb6eda064075b321abc3980c249efcc0e91d4d95e464500a654476e59b76dd19bdd66f5d207a0d731550c93ce724a09e00a3bff3fcfbc08c970844121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf792618030000006441fe754300443dfb293619693087016c9d9a8437489d48cb7c0c3fcb6b5af6277833ff7156355aeb557145c4075b7917d90d79239ba7bf776a38fef935d8da2f7c4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000376a04534c500001410453454e44205d9bff67b99e3f93c245a2d832ae40b67f39b79e5cf1daefe97fe6a8a222832608000000000000000122020000000000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac84330f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             'daa5872d1ef95a05bd3ee59fc532aa7921a54b783a5af68c5aa9146f61d2e134';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
         render(
             <CashtabTestWrapper
                 chronik={mockedChronik}
@@ -1011,10 +963,7 @@
             '0200000002268322a2a8e67fe9efdaf15c9eb7397fb640ae32d8a245c2933f9eb967ff9b5d010000006441fff60607ba0fb6eda064075b321abc3980c249efcc0e91d4d95e464500a654476e59b76dd19bdd66f5d207a0d731550c93ce724a09e00a3bff3fcfbc08c970844121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf792618030000006441fe754300443dfb293619693087016c9d9a8437489d48cb7c0c3fcb6b5af6277833ff7156355aeb557145c4075b7917d90d79239ba7bf776a38fef935d8da2f7c4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000376a04534c500001410453454e44205d9bff67b99e3f93c245a2d832ae40b67f39b79e5cf1daefe97fe6a8a222832608000000000000000122020000000000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac84330f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             'daa5872d1ef95a05bd3ee59fc532aa7921a54b783a5af68c5aa9146f61d2e134';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
         render(
             <CashtabTestWrapper
                 chronik={mockedChronik}
@@ -1119,19 +1068,15 @@
 
         // Build chronik mocks that Cashtab would use to add token info to cache
         for (const tokenMock of supportedTokens) {
-            renderChildNftsMockedChronik.setMock('token', {
-                input: tokenMock.tokenId,
-                output: tokenMock.token,
-            });
-            renderChildNftsMockedChronik.setMock('tx', {
-                input: tokenMock.tokenId,
-                output: tokenMock.tx,
-            });
-            renderChildNftsMockedChronik.setTokenId(tokenMock.tokenId);
-            renderChildNftsMockedChronik.setUtxosByTokenId(tokenMock.tokenId, {
-                tokenId: tokenMock.tokenId,
-                utxos: tokenMock.utxos,
-            });
+            renderChildNftsMockedChronik.setToken(
+                tokenMock.tokenId,
+                tokenMock.token,
+            );
+            renderChildNftsMockedChronik.setTx(tokenMock.tokenId, tokenMock.tx);
+            renderChildNftsMockedChronik.setUtxosByTokenId(
+                tokenMock.tokenId,
+                tokenMock.utxos,
+            );
             // Set tx history of parent tokenId to empty
             renderChildNftsMockedChronik.setTxHistoryByTokenId(
                 tokenMock.tokenId,
@@ -1203,23 +1148,17 @@
         const heismanNftTokenId = heismanNftOneOffer.token.tokenId;
 
         // Mock the API calls for getting and caching this token's info
-        mockedChronik.setMock('token', {
-            input: heismanNftTokenId,
-            output: heismanNftOneCache.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: heismanNftTokenId,
-            output: heismanNftOneCache.tx,
-        });
+        mockedChronik.setToken(heismanNftTokenId, heismanNftOneCache.token);
+        mockedChronik.setTx(heismanNftTokenId, heismanNftOneCache.tx);
         // Also mock for the collection
-        mockedChronik.setMock('token', {
-            input: heismanCollectionCacheMocks.tokenId,
-            output: heismanCollectionCacheMocks.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: heismanCollectionCacheMocks.tokenId,
-            output: heismanCollectionCacheMocks.tx,
-        });
+        mockedChronik.setToken(
+            heismanCollectionCacheMocks.tokenId,
+            heismanCollectionCacheMocks.token,
+        );
+        mockedChronik.setTx(
+            heismanCollectionCacheMocks.tokenId,
+            heismanCollectionCacheMocks.tx,
+        );
 
         // Mock an error querying this NFT listing
         const mockedAgora = new MockAgora();
@@ -1255,23 +1194,17 @@
         const heismanNftTokenId = heismanNftOneOffer.token.tokenId;
 
         // Mock the API calls for getting and caching this token's info
-        mockedChronik.setMock('token', {
-            input: heismanNftTokenId,
-            output: heismanNftOneCache.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: heismanNftTokenId,
-            output: heismanNftOneCache.tx,
-        });
+        mockedChronik.setToken(heismanNftTokenId, heismanNftOneCache.token);
+        mockedChronik.setTx(heismanNftTokenId, heismanNftOneCache.tx);
         // Also mock for the collection
-        mockedChronik.setMock('token', {
-            input: heismanCollectionCacheMocks.tokenId,
-            output: heismanCollectionCacheMocks.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: heismanCollectionCacheMocks.tokenId,
-            output: heismanCollectionCacheMocks.tx,
-        });
+        mockedChronik.setToken(
+            heismanCollectionCacheMocks.tokenId,
+            heismanCollectionCacheMocks.token,
+        );
+        mockedChronik.setTx(
+            heismanCollectionCacheMocks.tokenId,
+            heismanCollectionCacheMocks.tx,
+        );
 
         // Mock an error querying this NFT listing
         const mockedAgora = new MockAgora();
@@ -1362,10 +1295,7 @@
             '020000000288bb5c0d60e11b4038b00af152f9792fa954571ffdd2413a85f1c26bfd930c25010000006441999a894cafbab21d590da6ce07e572935144c480bce48c4df3efb74e9ee2fd3a4de61a40f93c28775c7b135a6a9ccba7d880bd5776d289b6c8ae5752afee24b34121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf792618030000006441f6e2b2a66d8676854e281f5af375bc56d4f359cb4be1e178d330720384da79a5216bd7a132bfd44654835c95a8d81b099b03e953d4a720187255ef1c9a1b646e4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff0400000000000000003a6a5037534c5032000453454e4449884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c02102700000000301b0f00000022020000000000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac18310f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             '33313eaf3365d9bf440645c5fffa8ed91681d1e1464afe598a564cdc76855c04';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Mock NOT blacklisted
         when(fetch)
@@ -1461,10 +1391,7 @@
             '020000000288bb5c0d60e11b4038b00af152f9792fa954571ffdd2413a85f1c26bfd930c250100000064416f667f359f04e273d524eac5fdaede0bfaf483daaf74f2ab5ba849c3a126b36b059003ef22b647d5265b74938e50c40505c1ad56474d0af2930192994011b9c84121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf792618030000006441ed0c24a83ec9137bc2cc367f674b1932de280f3bc2fbfd9cd70b840e61ccf5fa272e714ba06d3060574df97bc135acae2367d00fdd67ce2bbf347193a871348c4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000656a5030534c503200044255524e49884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c10270000000031534c5032000453454e4449884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c01301b0f00000022020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac28330f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             'f71293a94bd444c0b82ce6a6a8a1d2ae182f6a848cd2382bb6ca496955184fdf';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Mock NOT blacklisted
         when(fetch)
@@ -1545,10 +1472,7 @@
             '020000000288bb5c0d60e11b4038b00af152f9792fa954571ffdd2413a85f1c26bfd930c250100000064413919d2894e681586f285af178ef2c8d86b2f008e31519b1592c76cae7bee17eb4bb1558db35b225a15a2ba1c1f3d86564e12adfa0d5c012427f096398cdff20e4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf79261803000000644126a0f23966db5ba3212e4d5c545a186d407af4d110335e521c867e63549ade8d25da8a911343d9bf9275bbb58255cd445a1b3fc14ae35a89b8964cfbe47299aa4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000336a5030534c503200044255524e49884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c40420f00000022020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac8c330f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             'f413a14acc391c2541f0dea477cf7ee07cf6256bc3b201d6b276272f2fdda407';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Mock NOT blacklisted
         when(fetch)
@@ -1669,19 +1593,12 @@
         );
 
         // Mock cache info
-        mintAlpMockedChronik.setMock('token', {
-            input: alpMocks.tokenId,
-            output: alpMocks.token,
-        });
-        mintAlpMockedChronik.setMock('tx', {
-            input: alpMocks.tokenId,
-            output: alpMocks.tx,
-        });
-        mintAlpMockedChronik.setTokenId(alpMocks.tokenId);
-        mintAlpMockedChronik.setUtxosByTokenId(alpMocks.tokenId, {
-            tokenId: alpMocks.tokenId,
-            utxos: alpMocks.utxos,
-        });
+        mintAlpMockedChronik.setToken(alpMocks.tokenId, alpMocks.token);
+        mintAlpMockedChronik.setTx(alpMocks.tokenId, alpMocks.tx);
+        mintAlpMockedChronik.setUtxosByTokenId(
+            alpMocks.tokenId,
+            alpMocks.utxos,
+        );
         // Set empty tx history
         mintAlpMockedChronik.setTxHistoryByTokenId(alpMocks.tokenId, []);
 
@@ -1690,10 +1607,7 @@
             '020000000288bb5c0d60e11b4038b00af152f9792fa954571ffdd2413a85f1c26bfd930c25020000006441acdadb019c561b7bfa761695503eb1250d3ae1f34e66eeb3c4c8fb561b4ec95291bde678871451316a8f0472922d25936dd341eb90eb6bb3ccde98b00a2138da4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf792618030000006441fc7a554a708c3e6a2fc72e7c96871521678d0a36e336a599b39eac6a36f4ecedcfd2a728c8e639b5946fde677f1afa9e31468531476dd66fce1adfc760e7e2ff4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000356a5032534c503200044d494e5449884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c01ffffffffffff0122020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac22310f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             '28c733455a50be334948600bcdf0817610b0321ceba3da52c7c7ffec995320f0';
-        mintAlpMockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mintAlpMockedChronik.setBroadcastTx(hex, txid);
 
         // Mock NOT blacklisted
         when(fetch)
@@ -1768,11 +1682,7 @@
             '020000000288bb5c0d60e11b4038b00af152f9792fa954571ffdd2413a85f1c26bfd930c25010000006441d32ae72fa880a40975a475147443a3a7fe10308178ad38d80e6a2428921732b0699849443d8e24124a8ee5b75f1e9f74628fdb8cd0c9704d8cd0c70df65828e94121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf7926180300000064415fc18bb026bc3122776e708b8cdba9225494c704c1feca7aefb36b592abed96568cd57cb3504769bc4019ec0f36990c28c57012cefe805e4d3b046cc308bc86b4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000866a504b41475230075041525449414c01009b630800000000005532000000000000d6b24701000000002099c53f031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02d37534c5032000453454e4449884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c0200420f000000400000000000220200000000000017a91450eb4978c85ec89b63e37e6b87409c9f5815c7058722020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac83300f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const offerTxid =
             'e00be7011ee5d585cbd54049570ea0754ab0d5c05acf6cb01c25afa3aa61663d';
-
-        mockedChronik.setMock('broadcastTx', {
-            input: offerHex,
-            output: { txid: offerTxid },
-        });
+        mockedChronik.setBroadcastTx(offerHex, offerTxid);
 
         // Mock response for agora select params check
         // Note
@@ -1782,9 +1692,8 @@
         // Note that Date() and Math.random() must be mocked to keep this deterministic
         const EXPECTED_OFFER_P2SH = '50eb4978c85ec89b63e37e6b87409c9f5815c705';
 
-        mockedChronik.setScript('p2sh', EXPECTED_OFFER_P2SH);
         // We mock no existing utxos
-        mockedChronik.setUtxos('p2sh', EXPECTED_OFFER_P2SH, { utxos: [] });
+        mockedChronik.setUtxosByScript('p2sh', EXPECTED_OFFER_P2SH, []);
 
         // Note that we cannot use mockedAgora to avoid agoraQueryErrors, as we need a proper
         // agora object to build the partial
diff --git a/cashtab/src/components/Send/__tests__/SendXec.test.js b/cashtab/src/components/Send/__tests__/SendXec.test.js
--- a/cashtab/src/components/Send/__tests__/SendXec.test.js
+++ b/cashtab/src/components/Send/__tests__/SendXec.test.js
@@ -1144,10 +1144,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064410fed2b69cf2c9f0ca92318461d707292347ef567d6866d3889b510d1d1ab8615451dd1d56608457d4f40e8eb97f61dad4f3dc9fbef57099105a6a3e32e0efe8e4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000296a04007461622263617368746162206d6573736167652077697468206f705f72657475726e5f726177a4060000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac4f7b0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '63e4bba044135367eb71c71bc78aee91ecce0551fbdfbdb975e668fb808547ed';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1283,10 +1280,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064418305d1f7771a13e90b0cb15adf1c0a39b4381d1ec1bc3bebfa67cbbb5c9461b1a9d0c5a66ca5935aad771cbe869c9002add8b9d9822724ce73db8d15554f26cb4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff0200000000000000003c6a040074616235486f772061626f75742061206c6f6e672d6973682043617368746162206d7367207769746820656d6f6a697320f09f8eaff09f988e11820e00000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac00000000';
         const txid =
             'b1d49881be9c810e4408881438efb9e910d9e03bcc12a7bfcd652a0952d06f42';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1386,10 +1380,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064410fed2b69cf2c9f0ca92318461d707292347ef567d6866d3889b510d1d1ab8615451dd1d56608457d4f40e8eb97f61dad4f3dc9fbef57099105a6a3e32e0efe8e4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000296a04007461622263617368746162206d6573736167652077697468206f705f72657475726e5f726177a4060000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac4f7b0e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '63e4bba044135367eb71c71bc78aee91ecce0551fbdfbdb975e668fb808547ed';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1526,10 +1517,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064413b207f573a7abb9ec0d149fa71cbde63272a0e6b58298a81bc39ac2001729205bd1e9284c9d5a268f7abe63c5c953bf932ac06c13e408341bde4e4807d7f2fe94121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff0260ae0a00000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388acf7d30300000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             'a6e905185097cc1ffb289ca366ff7322f8aaf95713d1e5d1a4e89663e609530f';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1592,10 +1580,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a210200000064411aa35b343dea193092b46c01b877677d595d854686655ae24a3387a10955656a7604dca1cae999239d8ae76dc16ec9db72560832e8a9b0fb09f4ba6e1fb384b14121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff03d0070000000000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88ac98080000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388acab710e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             'a306834359b3591c68dc3ee8227e5e10225e6ab3c5a7496ead6e119aaaf31635';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1655,10 +1640,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441e11f1d96347ab50d56b111e9fd5ef68f9f1c69c76e1f8a65ec1fa58bd2e0eaee7a33b2c961ed5e58231c793b5d8a2950e2def6e7613df1055e681bc8f25d0a5b4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff019c820e00000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac00000000';
         const txid =
             '521eda8ad78014c60374931dcc4e35f312847f9332e16cb846cd387a984e95d2';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1763,10 +1745,7 @@
             '0200000001fe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441d95dfbf01e233d19684fd525d1cc39eb82a53ebfc97b8f2f9160f418ce863f4360f9fd1d6c182abde1d582ed39c6998ec5e4cdbde1b09736f6abe390a6ab8d8f4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff040000000000000000296a04007461622263617368746162206d6573736167652077697468206f705f72657475726e5f726177a4060000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac40e20100000000001976a91495e79f51d4260bc0dc3ba7fb77c7be92d0fbdd1d88acca980c00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             'f153119862f52dbe765ed5d66a5ff848d0386c5d987af9bef5e49a7e62a2c889';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         render(
             <CashtabTestWrapper
@@ -1912,14 +1891,8 @@
         // Mock API calls for fetching this token info from cache
         const token_id = mockTokenId;
         // Set chronik mocks required for cache preparation and supply calc
-        mockedChronik.setMock('token', {
-            input: token_id,
-            output: new Error('some chronik error'),
-        });
-        mockedChronik.setMock('tx', {
-            input: token_id,
-            output: slp1FixedBear.tx,
-        });
+        mockedChronik.setToken(token_id, new Error('some chronik error'));
+        mockedChronik.setTx(token_id, slp1FixedBear.tx);
 
         render(
             <CashtabTestWrapper
@@ -2009,22 +1982,13 @@
             '02000000023023c2a02d7932e2f716016ab866249dd292387967dbd050ff200b8b8560073b010000006441bac61dbfa47bc7b92952caaa867c2c5fd11bde4cfa36c21b818dbb80c15b19a0c94845e916bc57bc5f35f32ca379bd48a6ee1dc4ded52794bcee231655b105f14121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dfffffffffe667fba52a1aa603a892126e492717eed3dad43bfea7365a7fdd08e051e8a21020000006441a59dcc96f885dcbf56d473ba74b3202adb00dbc1142e379efa3784b559d7be97aa3d777eb4001613f205191d177c9896f652132d397a65cdfa93c69657d59f1b4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000376a04534c500001010453454e44203fee3384150b030490b7bee095a63900f66a45f2d8e3002ae2cf17ce3ef4d10908000000000000000122020000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388acbb800e00000000001976a9143a5fb236934ec078b4507c303d3afd82067f8fc188ac00000000';
         const txid =
             '6de2d27d40bced679a8b8e55c85230ed8da0977c30ad31247fefc0b1eba0976e';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Mock API calls for fetching this token info from cache
         const token_id = slp1FixedBear.tokenId;
         // Set chronik mocks required for cache preparation and supply calc
-        mockedChronik.setMock('token', {
-            input: slp1FixedBear.tokenId,
-            output: slp1FixedBear.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: slp1FixedBear.tokenId,
-            output: slp1FixedBear.tx,
-        });
+        mockedChronik.setToken(slp1FixedBear.tokenId, slp1FixedBear.token);
+        mockedChronik.setTx(slp1FixedBear.tokenId, slp1FixedBear.tx);
 
         const { tokenName, tokenTicker } = slp1FixedBear.token.genesisInfo;
 
@@ -2129,22 +2093,12 @@
             '020000000288bb5c0d60e11b4038b00af152f9792fa954571ffdd2413a85f1c26bfd930c25010000006441fff980a72dab5fed2ef4b94c54c5b91dd2e4d22fab32bd8daa8ba8118fc45b121cceb8c43a869966219d1e6b1ebf6c34436287a349fbd132a11b8928cdf642784121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf792618030000006441c8203434106d39d750461d8a6939412f432220cba2e957f19a699e5ed57a4357bb257dfcde9aa5618d6b87721f939b69312c429eba28c056f06efad33b4875314121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff0400000000000000003a6a5037534c5032000453454e4449884c726ebb974b9b8345ee12b44cc48445562b970f776e307d16547ccdd77c02102700000000301b0f00000022020000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac22020000000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac18310f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             'cf78a0c9f88027ab90dec0fe2180ef4d4d45ab431e179ce262ea19502202da52';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
-
+        mockedChronik.setBroadcastTx(hex, txid);
         // Mock API calls for fetching this token info from cache
         const token_id = alpMocks.tokenId;
         // Set chronik mocks required for cache preparation and supply calc
-        mockedChronik.setMock('token', {
-            input: alpMocks.tokenId,
-            output: alpMocks.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: alpMocks.tokenId,
-            output: alpMocks.tx,
-        });
+        mockedChronik.setToken(alpMocks.tokenId, alpMocks.token);
+        mockedChronik.setTx(alpMocks.tokenId, alpMocks.tx);
 
         const { tokenName, tokenTicker } = alpMocks.token.genesisInfo;
 
@@ -2249,22 +2203,16 @@
             '0200000002268322a2a8e67fe9efdaf15c9eb7397fb640ae32d8a245c2933f9eb967ff9b5d0100000064415b08020f453b87695e24d8ea104fab2c98c1e944502582599e945b407a8900dc75bcd599cbbb2cd3216402e9d6b0b1329aec686033cd838c9555777eaad8c0704121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffef76d01776229a95c45696cf68f2f98c8332d0c53e3f24e73fd9c6deaf79261803000000644164f0fe5c018e1b2ef2ed49e0ff1d87e5fe116e32ca30db8422ae09cc825976abc705ae59faee5d3372638bc297cd70f77582f5d0c513bfabfd9146dfb916d3ad4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff030000000000000000376a04534c500001410453454e44205d9bff67b99e3f93c245a2d832ae40b67f39b79e5cf1daefe97fe6a8a222832608000000000000000122020000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac84330f00000000001976a91400549451e5c22b18686cacdf34dce649e5ec3be288ac00000000';
         const txid =
             '57f665440f7ab0686fece1d744484140d3013e301b45842f5e9371597871ea8c';
-        mockedChronik.setMock('broadcastTx', {
-            input: hex,
-            output: { txid },
-        });
+        mockedChronik.setBroadcastTx(hex, txid);
 
         // Mock API calls for fetching this token info from cache
         const token_id = slp1NftChildMocks.tokenId;
         // Set chronik mocks required for cache preparation and supply calc
-        mockedChronik.setMock('token', {
-            input: slp1NftChildMocks.tokenId,
-            output: slp1NftChildMocks.token,
-        });
-        mockedChronik.setMock('tx', {
-            input: slp1NftChildMocks.tokenId,
-            output: slp1NftChildMocks.tx,
-        });
+        mockedChronik.setToken(
+            slp1NftChildMocks.tokenId,
+            slp1NftChildMocks.token,
+        );
+        mockedChronik.setTx(slp1NftChildMocks.tokenId, slp1NftChildMocks.tx);
 
         const { tokenName, tokenTicker } = slp1NftChildMocks.token.genesisInfo;
 
diff --git a/cashtab/src/transactions/__tests__/index.test.js b/cashtab/src/transactions/__tests__/index.test.js
--- a/cashtab/src/transactions/__tests__/index.test.js
+++ b/cashtab/src/transactions/__tests__/index.test.js
@@ -51,10 +51,7 @@
             } = tx;
             it(`sendXec: ${description}`, async () => {
                 const chronik = new MockChronikClient();
-                chronik.setMock('broadcastTx', {
-                    input: hex,
-                    output: { txid },
-                });
+                chronik.setBroadcastTx(hex, txid);
                 expect(
                     await sendXec(
                         chronik,
@@ -79,10 +76,7 @@
                 if (typeof hex !== 'undefined') {
                     // For error cases that are not thrown until after the tx is successfully built,
                     // set a tx broadcast error that can be thrown by the broadcasting eCash node
-                    chronik.setMock('broadcastTx', {
-                        input: hex,
-                        output: new Error(msg),
-                    });
+                    chronik.setBroadcastTx(hex, new Error(msg));
                 }
 
                 await expect(
@@ -105,12 +99,7 @@
             '0200000003c31d0b990c5a707dca806648fe5036dbb3f9590b3e22e026392912edeef154680000000064417353fc52d6f47efffddf90656dcbd4313f476c292625e24c71660b7b075f36f2c163ff2c713ad8e593490cbbaa32b424c93671908731912de255327e394c65eb4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffc31d0b990c5a707dca806648fe5036dbb3f9590b3e22e026392912edeef15468010000006441ef045f01ba4b6dd75b470787de704c366ad8869369ae445f9fb744ee3ec220533324423ae6028e6fb72fbd3d7f6359eb9c1b35322ced7e2d263170a4092bebaa4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffffc31d0b990c5a707dca806648fe5036dbb3f9590b3e22e026392912edeef15468020000006441faa831725c8e38a909cbf3ba594360b2d51fd884397347f4c0ce0d1379096636db4126d7f9caf34f7480d3700ffbed062352ea7e0b2fac15f5a35df880b9154c4121031d4603bdc23aca9432f903e3cf5975a3f655cc3fa5057c61d00dfc1ca5dfd02dffffffff01d0070000000000001976a9144e532257c01b310b3b5c1fd947c79a72addf852388ac00000000';
         const txid =
             '73af2c7dcf70811ef6fa68c671673529289b1304e1cb3979f9792780f2b885ab';
-        chronik.setMock('broadcastTx', {
-            input: hex,
-            output: {
-                txid,
-            },
-        });
+        chronik.setBroadcastTx(hex, txid);
         const walletWithEdgeCaseUtxos = {
             ...wallet,
             state: {
@@ -303,14 +292,8 @@
             } = tx;
             it(`Build and broadcast an SLP V1 SEND and BURN tx from in-node chronik-client utxos: ${description}`, async () => {
                 const chronik = new MockChronikClient();
-                chronik.setMock('broadcastTx', {
-                    input: hex,
-                    output: { txid },
-                });
-                chronik.setMock('broadcastTx', {
-                    input: burn.hex,
-                    output: { txid: burn.txid },
-                });
+                chronik.setBroadcastTx(hex, txid);
+                chronik.setBroadcastTx(burn.hex, burn.txid);
 
                 // Get tokenInputs and sendAmounts
                 const tokenInputInfo = getSendTokenInputs(
@@ -377,10 +360,7 @@
             const { hex, txid } = rawTx;
             it(`sendXec: ${description}`, async () => {
                 const chronik = new MockChronikClient();
-                chronik.setMock('broadcastTx', {
-                    input: hex,
-                    output: { txid },
-                });
+                chronik.setBroadcastTx(hex, txid);
                 expect(
                     await sendXec(
                         chronik,
diff --git a/cashtab/src/wallet/__tests__/useWallet.test.js b/cashtab/src/wallet/__tests__/useWallet.test.js
--- a/cashtab/src/wallet/__tests__/useWallet.test.js
+++ b/cashtab/src/wallet/__tests__/useWallet.test.js
@@ -713,10 +713,8 @@
         // Mock chronik.tx response for this tx to be a new token tx
         const MOCK_TXID =
             '1111111111111111111111111111111111111111111111111111111111111111';
-        mockedChronik.setMock('tx', {
-            input: MOCK_TXID,
-            output: mockIncomingTokenTxDetails,
-        });
+        mockedChronik.setTx(MOCK_TXID, mockIncomingTokenTxDetails);
+
         const { result } = renderHook(() => useWallet(mockedChronik));
 
         // Wait for the wallet to load
diff --git a/contrib/teamcity/build-configurations.yml b/contrib/teamcity/build-configurations.yml
--- a/contrib/teamcity/build-configurations.yml
+++ b/contrib/teamcity/build-configurations.yml
@@ -88,15 +88,6 @@
         npm run build
       fi
 
-      # Install mock-chronik-client dependencies if this test uses them
-      if [ -z "${DEPENDS_MOCK_CHRONIK_CLIENT+x}" ] ; then
-        echo "Test does not depend on mock-chronik-client, skipping mock-chronik-client dependencies..."
-      else
-        echo "Test depends on mock-chronik-client. Installing mock-chronik-client dependencies..."
-        pushd "${TOPLEVEL}/modules/mock-chronik-client"
-        npm ci
-      fi
-
       # Build ecash-lib-wasm for ecash-lib's WebAssembly part
       if [ -z "${DEPENDS_ECASH_LIB_WASM+x}" ] ; then
         echo "Test does not depend on ecash-lib-wasm, skipping"
@@ -114,6 +105,16 @@
         npm ci
       fi
 
+      # Install mock-chronik-client dependencies if this test uses them
+      if [ -z "${DEPENDS_MOCK_CHRONIK_CLIENT+x}" ] ; then
+        echo "Test does not depend on mock-chronik-client, skipping mock-chronik-client dependencies..."
+      else
+        echo "Test depends on mock-chronik-client. Installing mock-chronik-client dependencies..."
+        pushd "${TOPLEVEL}/modules/mock-chronik-client"
+        npm ci
+        npm run build
+      fi
+
       if [ -z "${DEPENDS_ECASH_LIB+x}" ] ; then
         echo "Test does not depend on ecash-lib"
       else
diff --git a/mock-chronik-client.Dockerfile b/mock-chronik-client.Dockerfile
--- a/mock-chronik-client.Dockerfile
+++ b/mock-chronik-client.Dockerfile
@@ -13,6 +13,10 @@
 COPY modules/mock-chronik-client .
 # Install ecashaddrjs from npm, so that module users install it automatically
 RUN npm install ecashaddrjs@latest
+# Install chronik-client from npm, so that module users install it automatically
+# Note that in practice any user of chronik-client probably already has chronik-client installed
+# So it won't really be bloating their node_modules
+RUN npm install chronik-client@latest
 RUN npm ci
 
 # Publish the module
diff --git a/modules/mock-chronik-client/.eslintrc.js b/modules/mock-chronik-client/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/modules/mock-chronik-client/.eslintrc.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2024 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+const headerArray = [
+    {
+        pattern:
+            '^ Copyright \\(c\\) [2][0-9]{3}([-][2][0-9]{3})? The Bitcoin developers$',
+        template: ` Copyright (c) ${new Date().getFullYear()} The Bitcoin developers`,
+    },
+    ' Distributed under the MIT software license, see the accompanying',
+    ' file COPYING or http://www.opensource.org/licenses/mit-license.php.',
+];
+
+module.exports = {
+    env: {
+        node: true,
+        commonjs: true,
+        es2021: true,
+        mocha: true,
+    },
+    overrides: [],
+    extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
+    parserOptions: {
+        ecmaVersion: 'latest',
+        sourceType: 'module',
+        parser: '@typescript-eslint/parser',
+    },
+    plugins: ['header', '@typescript-eslint'],
+    rules: {
+        'header/header': [2, 'line', headerArray, 2],
+    },
+};
diff --git a/modules/mock-chronik-client/.gitignore b/modules/mock-chronik-client/.gitignore
--- a/modules/mock-chronik-client/.gitignore
+++ b/modules/mock-chronik-client/.gitignore
@@ -9,3 +9,5 @@
 
 # generated by testing CI locally
 test_results/
+
+dist/
diff --git a/modules/mock-chronik-client/README.md b/modules/mock-chronik-client/README.md
--- a/modules/mock-chronik-client/README.md
+++ b/modules/mock-chronik-client/README.md
@@ -168,3 +168,15 @@
 1.12.3
 
 -   Add dummy `plugin` method to allow construction of `new Agora()` from `ecash-agora` with a `MockChronikClient` [D17279](https://reviews.bitcoinabc.org/D17279)
+
+2.0.0
+
+[D17332](https://reviews.bitcoinabc.org/D17332)
+
+-   Full implementation of typescript
+-   Set history and utxos by script, address, or tokenId in one step (prev 2)
+-   Set history by lokadId in one step (prev 2)
+-   Better type checking
+-   Improved mock websocket (now it more closely follows the API of chronik-client)
+-   Add `broadcastTxs` method
+-   Add `chronikInfo` method
diff --git a/modules/mock-chronik-client/index.d.ts b/modules/mock-chronik-client/index.d.ts
deleted file mode 100644
--- a/modules/mock-chronik-client/index.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-declare var MockChronikClient: any;
-declare var MockAgora: any;
-export { MockAgora, MockChronikClient };
diff --git a/modules/mock-chronik-client/index.js b/modules/mock-chronik-client/index.js
deleted file mode 100644
--- a/modules/mock-chronik-client/index.js
+++ /dev/null
@@ -1,496 +0,0 @@
-// Copyright (c) 2023 The Bitcoin developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-'use strict';
-const cashaddr = require('ecashaddrjs');
-
-const CHRONIK_DEFAULT_PAGESIZE = 25;
-
-module.exports = {
-    MockAgora: class {
-        // Agora can make specialized chronik-client calls to a chronik-client instance
-        // running the agora plugin
-        // For the purposes of unit testing, we only need to re-create how this object
-        // is initialized and support getting and setting of expected responses
-        constructor() {
-            // Use self since it is not a reserved term in js
-            // Can access self from inside a method and still get the class
-            const self = this;
-            // API call mock return objects
-            // Can be set with self.setMock
-            self.mockedResponses = {
-                offeredGroupTokenIds: {},
-                offeredFungibleTokenIds: {},
-                activeOffersByPubKey: {},
-                activeOffersByGroupTokenId: {},
-                activeOffersByTokenId: {},
-            };
-
-            // Allow user to set supported agora query responses
-            self.setOfferedGroupTokenIds = function (response) {
-                self.mockedResponses.offeredGroupTokenIds = response;
-            };
-            self.setOfferedFungibleTokenIds = function (response) {
-                self.mockedResponses.offeredFungibleTokenIds = response;
-            };
-            self.setActiveOffersByPubKey = function (pubKey, response) {
-                self.mockedResponses.activeOffersByPubKey[pubKey] = response;
-            };
-            self.setActiveOffersByGroupTokenId = function (
-                groupTokenId,
-                response,
-            ) {
-                self.mockedResponses.activeOffersByGroupTokenId[groupTokenId] =
-                    response;
-            };
-            self.setActiveOffersByTokenId = function (tokenId, response) {
-                self.mockedResponses.activeOffersByTokenId[tokenId] = response;
-            };
-
-            // Checks whether the user set this mock response to be an error.
-            // If so, throw it to simulate an API error response.
-            function throwOrReturnValue(mockResponse) {
-                if (mockResponse instanceof Error) {
-                    throw mockResponse;
-                }
-                return mockResponse;
-            }
-
-            self.offeredGroupTokenIds = async function () {
-                return throwOrReturnValue(
-                    self.mockedResponses.offeredGroupTokenIds,
-                );
-            };
-            self.offeredFungibleTokenIds = async function () {
-                return throwOrReturnValue(
-                    self.mockedResponses.offeredFungibleTokenIds,
-                );
-            };
-            self.activeOffersByPubKey = async function (pubKey) {
-                return throwOrReturnValue(
-                    self.mockedResponses.activeOffersByPubKey[pubKey],
-                );
-            };
-            self.activeOffersByGroupTokenId = async function (groupTokenId) {
-                return throwOrReturnValue(
-                    self.mockedResponses.activeOffersByGroupTokenId[
-                        groupTokenId
-                    ],
-                );
-            };
-            self.activeOffersByTokenId = async function (tokenId) {
-                return throwOrReturnValue(
-                    self.mockedResponses.activeOffersByTokenId[tokenId],
-                );
-            };
-        }
-    },
-    MockChronikClient: class {
-        constructor() {
-            // Use self since it is not a reserved term in js
-            // Can access self from inside a method and still get the class
-            const self = this;
-
-            // We need to give mockedChronik a plugin function
-            // This is required for creating a new Agora(mockedChronik)
-            self.plugin = () => 'dummy plugin';
-
-            // API call mock return objects
-            // Can be set with self.setMock
-            self.mockedResponses = {
-                block: {},
-                blockTxs: {},
-                blockchainInfo: {},
-                txHistory: [],
-                tx: {},
-                token: {},
-                p2sh: {},
-                p2pkh: {},
-                broadcastTx: {},
-            };
-            self.mockedMethods = { p2pkh: {}, p2sh: {} };
-            self.manuallyClosed = false;
-
-            // API call mock functions
-            self.block = async function (blockHashOrHeight) {
-                return throwOrReturnValue(
-                    self.mockedResponses.block[blockHashOrHeight],
-                );
-            };
-            self.blockTxs = async function (
-                hashOrHeight,
-                pageNumber = 0,
-                pageSize = CHRONIK_DEFAULT_PAGESIZE,
-            ) {
-                if (
-                    self.mockedResponses[hashOrHeight].txHistory instanceof
-                    Error
-                ) {
-                    throw self.mockedResponses[hashOrHeight].txHistory;
-                }
-                return self.getTxHistory(
-                    pageNumber,
-                    pageSize,
-                    self.mockedResponses[hashOrHeight].txHistory,
-                );
-            };
-            self.tx = async function (txid) {
-                return throwOrReturnValue(self.mockedResponses.tx[txid]);
-            };
-            self.token = async function (tokenId) {
-                return throwOrReturnValue(self.mockedResponses.token[tokenId]);
-            };
-            self.broadcastTx = async function (txHex) {
-                return throwOrReturnValue(
-                    self.mockedResponses.broadcastTx[txHex],
-                );
-            };
-            self.blockchainInfo = async function () {
-                return throwOrReturnValue(self.mockedResponses.blockchainInfo);
-            };
-
-            // Return assigned script mocks
-            self.script = function (type, hash) {
-                return self.mockedMethods[type][hash];
-            };
-
-            // Return assigned address mocks
-            self.address = function (address) {
-                return self.mockedMethods[address];
-            };
-
-            // Return assigned tokenId mocks
-            self.tokenId = function (tokenId) {
-                return self.mockedMethods[tokenId];
-            };
-
-            // Return assigned lokadId mocks
-            self.lokadId = function (lokadId) {
-                return self.mockedMethods[lokadId];
-            };
-
-            // Checks whether the user set this mock response to be an error.
-            // If so, throw it to simulate an API error response.
-            function throwOrReturnValue(mockResponse) {
-                if (mockResponse instanceof Error) {
-                    throw mockResponse;
-                }
-                return mockResponse;
-            }
-
-            // Flags to check if ws methods have been called
-            self.wsSubscribeCalled = false;
-            self.wsWaitForOpenCalled = false;
-
-            // Websocket mocks
-            self.ws = function (wsObj) {
-                if (wsObj !== null) {
-                    const returnedWs = {
-                        onMessage: wsObj.onMessage, // may be undefined
-                        onConnect: wsObj.onConnect, // may be undefined
-                        onReconnect: wsObj.onReconnect, // may be undefined
-                        onEnd: wsObj.onEnd, // may be undefined
-                        autoReconnect: wsObj.autoReconnect || true, // default to true if unset
-                        manuallyClosed: false,
-                        subs: {
-                            blocks: false,
-                            tokens: [],
-                            lokadIds: [],
-                            scripts: [],
-                        },
-                        isSubscribedBlocks: false,
-                        waitForOpen: async function () {
-                            self.wsWaitForOpenCalled = true;
-                        },
-                        // Note: subscribe is a legacy NNG method
-                        subscribe: function (type, hash) {
-                            this.subs.scripts.push({
-                                scriptType: type,
-                                scriptPayload: hash,
-                            });
-                            self.wsSubscribeCalled = true;
-                        },
-                        // Note: unsubscribe is a legacy NNG method
-                        unsubscribe: function (type, hash) {
-                            const thisSubInSubsIndex =
-                                this.subs.scripts.findIndex(
-                                    sub =>
-                                        sub.scriptType === type &&
-                                        sub.scriptPayload === hash,
-                                );
-
-                            if (typeof thisSubInSubsIndex !== 'undefined') {
-                                // Remove from subs
-                                this.subs.scripts.splice(thisSubInSubsIndex, 1);
-                            }
-                            // Otherwise do nothing
-                        },
-                        subscribeToScript: function (type, hash) {
-                            // in-node only
-                            if (Array.isArray(this.subs)) {
-                                this.subs = { scripts: [] };
-                            }
-                            this.subs.scripts.push({
-                                scriptType: type,
-                                payload: hash,
-                            });
-                            self.wsSubscribeCalled = true;
-                        },
-                        unsubscribeFromScript: function (type, hash) {
-                            const thisSubInSubsIndex =
-                                this.subs.scripts.findIndex(
-                                    sub =>
-                                        sub.scriptType === type &&
-                                        sub.payload === hash,
-                                );
-
-                            if (typeof thisSubInSubsIndex !== 'undefined') {
-                                // Remove from subs
-                                this.subs.scripts.splice(thisSubInSubsIndex, 1);
-                            }
-                            // Otherwise do nothing
-                        },
-                        subscribeToAddress: function (address) {
-                            // in-node only
-                            if (Array.isArray(this.subs)) {
-                                this.subs = { scripts: [] };
-                            }
-                            const { type, hash } = cashaddr.decode(
-                                address,
-                                true,
-                            );
-                            this.subs.scripts.push({
-                                scriptType: type,
-                                payload: hash,
-                            });
-                        },
-                        unsubscribeFromAddress: function (address) {
-                            const { type, hash } = cashaddr.decode(
-                                address,
-                                true,
-                            );
-                            // Find the requested unsub script and remove it
-                            const unsubIndex = this.subs.scripts.findIndex(
-                                sub =>
-                                    sub.scriptType === type &&
-                                    sub.payload === hash,
-                            );
-                            if (unsubIndex === -1) {
-                                // If we cannot find this subscription in this.subs, throw an error
-                                // We do not want an app developer thinking they have unsubscribed from something
-                                throw new Error(
-                                    `No existing sub at ${type}, ${hash}`,
-                                );
-                            }
-
-                            // Remove the requested subscription from this.subs
-                            this.subs.scripts.splice(unsubIndex, 1);
-                        },
-                        subscribeToBlocks: function () {
-                            this.subs.blocks = true;
-                        },
-                        unsubscribeFromBlocks: function () {
-                            this.subs.blocks = false;
-                        },
-                    };
-                    return returnedWs;
-                }
-            };
-            self.wsClose = function () {
-                self.manuallyClosed = true;
-            };
-
-            // Allow user to set expected chronik call response
-            self.setMock = function (call, options) {
-                // e.g. ('block', {input: '', output: ''})
-                const { input, output } = options;
-                if (input) {
-                    self.mockedResponses[call][input] = output;
-                } else {
-                    self.mockedResponses[call] = output;
-                }
-            };
-
-            // script calls need to be set differently
-            self.setTxHistory = function (type, hash, txHistory) {
-                self.mockedResponses[type][hash].txHistory = txHistory;
-            };
-
-            self.setTxHistoryByAddress = function (address, txHistory) {
-                self.mockedResponses[address].txHistory = txHistory;
-            };
-
-            self.setTxHistoryByTokenId = function (tokenId, txHistory) {
-                self.mockedResponses[tokenId].txHistory = txHistory;
-            };
-
-            self.setTxHistoryByLokadId = function (lokadId, txHistory) {
-                self.mockedResponses[lokadId].txHistory = txHistory;
-            };
-
-            self.setTxHistoryByBlock = function (hashOrHeight, txHistory) {
-                // Set all expected tx history as array where it can be accessed by mock method
-                self.mockedResponses[hashOrHeight] = { txHistory };
-            };
-
-            /**
-             * Set utxos to custom response; must be called after setScript
-             * @param {string} type 'p2sh' or 'p2pkh'
-             * @param {string} hash hash of an eCash address
-             * @param {array} utxos mocked response of chronik.script(type,hash).utxos()
-             */
-            self.setUtxos = function (type, hash, utxos) {
-                self.mockedResponses[type][hash].utxos = utxos;
-            };
-
-            /**
-             * Set utxos to custom response; must be called after setAddress
-             * @param {string} address 'p2sh' or 'p2pkh' address
-             * @param {array} utxos mocked response of chronik.address(address).utxos()
-             */
-            self.setUtxosByAddress = function (address, utxos) {
-                self.mockedResponses[address].utxos = utxos;
-            };
-
-            /**
-             * Set utxos to custom response; must be called after setTokenId
-             * @param {string} tokenId a tokenId
-             * @param {array} utxos mocked response of chronik.tokenId(tokenId).utxos()
-             */
-            self.setUtxosByTokenId = function (tokenId, utxos) {
-                self.mockedResponses[tokenId].utxos = utxos;
-            };
-
-            // Allow users to set expected chronik script call responses
-            self.setScript = function (type, hash) {
-                // Initialize object that will hold utxos if set
-                self.mockedResponses[type][hash] = {};
-
-                self.mockedMethods[type][hash] = {
-                    history: async function (
-                        pageNumber = 0,
-                        pageSize = CHRONIK_DEFAULT_PAGESIZE,
-                    ) {
-                        if (
-                            self.mockedResponses[type][hash]
-                                .txHistory instanceof Error
-                        ) {
-                            throw self.mockedResponses[type][hash].txHistory;
-                        }
-                        return self.getTxHistory(
-                            pageNumber,
-                            pageSize,
-                            self.mockedResponses[type][hash].txHistory,
-                        );
-                    },
-                    utxos: async function () {
-                        return throwOrReturnValue(
-                            self.mockedResponses[type][hash].utxos,
-                        );
-                    },
-                };
-            };
-
-            // Allow users to set expected chronik address call responses
-            self.setAddress = function (address) {
-                // Initialize object that will hold utxos if set
-                self.mockedResponses[address] = {};
-
-                self.mockedMethods[address] = {
-                    history: async function (
-                        pageNumber = 0,
-                        pageSize = CHRONIK_DEFAULT_PAGESIZE,
-                    ) {
-                        if (
-                            self.mockedResponses[address].txHistory instanceof
-                            Error
-                        ) {
-                            throw self.mockedResponses[address].txHistory;
-                        }
-                        return self.getTxHistory(
-                            pageNumber,
-                            pageSize,
-                            self.mockedResponses[address].txHistory,
-                        );
-                    },
-                    utxos: async function () {
-                        return throwOrReturnValue(
-                            self.mockedResponses[address].utxos,
-                        );
-                    },
-                };
-            };
-
-            // Allow users to set expected chronik tokenId call responses
-            self.setTokenId = function (tokenId) {
-                // Initialize object that will hold utxos if set
-                self.mockedResponses[tokenId] = {};
-
-                self.mockedMethods[tokenId] = {
-                    history: async function (
-                        pageNumber = 0,
-                        pageSize = CHRONIK_DEFAULT_PAGESIZE,
-                    ) {
-                        if (
-                            self.mockedResponses[tokenId].txHistory instanceof
-                            Error
-                        ) {
-                            throw self.mockedResponses[tokenId].txHistory;
-                        }
-                        return self.getTxHistory(
-                            pageNumber,
-                            pageSize,
-                            self.mockedResponses[tokenId].txHistory,
-                        );
-                    },
-                    utxos: async function () {
-                        return throwOrReturnValue(
-                            self.mockedResponses[tokenId].utxos,
-                        );
-                    },
-                };
-            };
-
-            // Allow users to set expected chronik lokadId call responses
-            self.setLokadId = function (lokadId) {
-                // Initialize object that will hold utxos if set
-                self.mockedResponses[lokadId] = {};
-
-                self.mockedMethods[lokadId] = {
-                    history: async function (
-                        pageNumber = 0,
-                        pageSize = CHRONIK_DEFAULT_PAGESIZE,
-                    ) {
-                        if (
-                            self.mockedResponses[lokadId].txHistory instanceof
-                            Error
-                        ) {
-                            throw self.mockedResponses[lokadId].txHistory;
-                        }
-                        return self.getTxHistory(
-                            pageNumber,
-                            pageSize,
-                            self.mockedResponses[lokadId].txHistory,
-                        );
-                    },
-                };
-            };
-        }
-        // Method to get paginated tx history with same variables as chronik
-        getTxHistory(pageNumber = 0, pageSize, txHistory) {
-            // Return chronik shaped responses
-            const startSliceOnePage = pageNumber * pageSize;
-            const endSliceOnePage = startSliceOnePage + pageSize;
-            const thisPage = txHistory.slice(
-                startSliceOnePage,
-                endSliceOnePage,
-            );
-            const response = {};
-
-            response.txs = thisPage;
-            response.numPages = Math.ceil(txHistory.length / pageSize);
-            response.numTxs = txHistory.length;
-            return response;
-        }
-    },
-};
diff --git a/modules/mock-chronik-client/index.test.ts b/modules/mock-chronik-client/index.test.ts
new file mode 100644
--- /dev/null
+++ b/modules/mock-chronik-client/index.test.ts
@@ -0,0 +1,375 @@
+// Copyright (c) 2023 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import * as chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { MockChronikClient, MockAgora } from './index';
+import mocks from './mocks';
+import * as cashaddr from 'ecashaddrjs';
+
+const expect = chai.expect;
+chai.use(chaiAsPromised);
+
+describe('MockAgora', () => {
+    let mockAgora: MockAgora;
+    const agoraError = new Error('some agora error');
+    beforeEach(() => {
+        mockAgora = new MockAgora();
+    });
+    it('We can set and get offeredGroupTokenIds', async () => {
+        const tokenIds = ['a', 'b', 'c'];
+        mockAgora.setOfferedGroupTokenIds(tokenIds);
+        expect(await mockAgora.offeredGroupTokenIds()).to.deep.equal(tokenIds);
+        // And force a thrown error
+        mockAgora.setOfferedGroupTokenIds(agoraError);
+        await expect(mockAgora.offeredGroupTokenIds()).to.be.rejectedWith(
+            agoraError,
+        );
+    });
+    it('We can set and get offeredFungibleTokenIds', async () => {
+        const tokenIds = ['a', 'b', 'c'];
+        mockAgora.setOfferedFungibleTokenIds(tokenIds);
+        expect(await mockAgora.offeredFungibleTokenIds()).to.deep.equal(
+            tokenIds,
+        );
+        // And force a thrown error
+        mockAgora.setOfferedFungibleTokenIds(agoraError);
+        await expect(mockAgora.offeredFungibleTokenIds()).to.be.rejectedWith(
+            agoraError,
+        );
+    });
+    it('We can set and get activeOffersByPubKey', async () => {
+        const pk = 'pk';
+        const mockOffers = [{ test: 'a' }];
+        mockAgora.setActiveOffersByPubKey(pk, mockOffers);
+        expect(await mockAgora.activeOffersByPubKey(pk)).to.deep.equal(
+            mockOffers,
+        );
+        // And force a thrown error
+        mockAgora.setActiveOffersByPubKey(pk, agoraError);
+        await expect(mockAgora.activeOffersByPubKey(pk)).to.be.rejectedWith(
+            agoraError,
+        );
+    });
+    it('We can set and get activeOffersByGroupTokenId', async () => {
+        const groupTokenId = '00'.repeat(32);
+        const mockOffers = [{ test: 'a' }];
+        mockAgora.setActiveOffersByGroupTokenId(groupTokenId, mockOffers);
+        expect(
+            await mockAgora.activeOffersByGroupTokenId(groupTokenId),
+        ).to.deep.equal(mockOffers);
+        // And force a thrown error
+        mockAgora.setActiveOffersByGroupTokenId(groupTokenId, agoraError);
+        await expect(
+            mockAgora.activeOffersByGroupTokenId(groupTokenId),
+        ).to.be.rejectedWith(agoraError);
+    });
+    it('We can set and get activeOffersByTokenId', async () => {
+        const tokenId = '00'.repeat(32);
+        const mockOffers = [{ test: 'a' }];
+        mockAgora.setActiveOffersByTokenId(tokenId, mockOffers);
+        expect(await mockAgora.activeOffersByTokenId(tokenId)).to.deep.equal(
+            mockOffers,
+        );
+        // And force a thrown error
+        mockAgora.setActiveOffersByTokenId(tokenId, agoraError);
+        await expect(
+            mockAgora.activeOffersByTokenId(tokenId),
+        ).to.be.rejectedWith(agoraError);
+    });
+});
+describe('MockChronikClient', () => {
+    let mockChronik: MockChronikClient;
+    const chronikError = new Error('some chronik error');
+    beforeEach(() => {
+        mockChronik = new MockChronikClient();
+    });
+    it('We can set and get a Block by height', async () => {
+        const { tipHeight, block } = mocks;
+        mockChronik.setBlock(tipHeight, block);
+        expect(await mockChronik.block(tipHeight)).to.deep.equal(block);
+        // And force a thrown error
+        mockChronik.setBlock(tipHeight, chronikError);
+        await expect(mockChronik.block(tipHeight)).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get a Block by hash', async () => {
+        const { tipHash, block } = mocks;
+        mockChronik.setBlock(tipHash, block);
+        expect(await mockChronik.block(tipHash)).to.deep.equal(block);
+        // And force a thrown error
+        mockChronik.setBlock(tipHash, chronikError);
+        await expect(mockChronik.block(tipHash)).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get BlockchainInfo', async () => {
+        mockChronik.setBlockchainInfo(mocks.blockchainInfo);
+        expect(await mockChronik.blockchainInfo()).to.deep.equal(
+            mocks.blockchainInfo,
+        );
+        // And force a thrown error
+        mockChronik.setBlockchainInfo(chronikError);
+        await expect(mockChronik.blockchainInfo()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get chronikInfo', async () => {
+        const versionInfo = { version: '1.0.0' };
+        mockChronik.setChronikInfo(versionInfo);
+        expect(await mockChronik.chronikInfo()).to.deep.equal(versionInfo);
+        // And force a thrown error
+        mockChronik.setChronikInfo(chronikError);
+        await expect(mockChronik.chronikInfo()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get a Tx by txid', async () => {
+        const { txid, tx } = mocks;
+        mockChronik.setTx(txid, tx);
+        expect(await mockChronik.tx(txid)).to.deep.equal(tx);
+        // And force a thrown error
+        mockChronik.setTx(txid, chronikError);
+        await expect(mockChronik.tx(txid)).to.be.rejectedWith(chronikError);
+    });
+    it('We can set and get a Token by tokenId', async () => {
+        const { tokenId, token } = mocks;
+        mockChronik.setToken(tokenId, token);
+        expect(await mockChronik.token(tokenId)).to.deep.equal(token);
+        // And force a thrown error
+        mockChronik.setToken(tokenId, chronikError);
+        await expect(mockChronik.token(tokenId)).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can mock broadcasting a rawTx and getting its txid', async () => {
+        const { txid, rawTx } = mocks;
+        mockChronik.setBroadcastTx(rawTx, txid);
+        expect(await mockChronik.broadcastTx(rawTx)).to.deep.equal({ txid });
+
+        // And force a thrown error
+        mockChronik.setBroadcastTx(rawTx, chronikError);
+        await expect(mockChronik.broadcastTx(rawTx)).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can mock broadcasting an array of rawTxs and getting txids', async () => {
+        const { txid, rawTx } = mocks;
+        // We still set them one at a time
+        mockChronik.setBroadcastTx(rawTx, txid);
+        expect(
+            await mockChronik.broadcastTxs([rawTx, rawTx, rawTx]),
+        ).to.deep.equal([{ txid }, { txid }, { txid }]);
+
+        // And force a thrown error
+        mockChronik.setBroadcastTx(rawTx, chronikError);
+        await expect(mockChronik.broadcastTxs([rawTx])).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can test websocket methods', async () => {
+        // Create websocket subscription to listen to confirmations on txid
+        const ws = mockChronik.ws({
+            onMessage: msg => {
+                console.log(`msg`, msg);
+            },
+        });
+
+        // Wait for WS to be connected:
+        await ws.waitForOpen();
+        expect(ws.waitForOpenCalled).to.equal(true);
+
+        // We can test if a websocket was closed by calling wsClose() (aka "manually closed")
+        ws.close();
+        expect(ws.manuallyClosed).to.equal(true);
+
+        // We can subscribe to blocks
+        ws.subscribeToBlocks();
+        expect(ws.subs.blocks).to.equal(true);
+
+        // We can unsubscribe from blocks
+        ws.unsubscribeFromBlocks();
+        expect(ws.subs.blocks).to.equal(false);
+
+        // We can subscribe to a script
+        const scriptType = 'p2pkh';
+        const payload = '00'.repeat(20);
+        ws.subscribeToScript(scriptType, payload);
+        expect(ws.subs.scripts).to.deep.equal([{ scriptType, payload }]);
+
+        // We can unsubscribe from a script
+        ws.unsubscribeFromScript(scriptType, payload);
+        expect(ws.subs.scripts).to.deep.equal([]);
+
+        // We can subscribe to an address
+        const addr = cashaddr.encode('ecash', scriptType, payload);
+        ws.subscribeToAddress(addr);
+        expect(ws.subs.scripts).to.deep.equal([{ scriptType, payload }]);
+
+        // We can unsubscribe from an address
+        ws.unsubscribeFromAddress(addr);
+        expect(ws.subs.scripts).to.deep.equal([]);
+
+        const tokenId = '00'.repeat(32);
+
+        // We can subscribe to a token by tokenId
+        ws.subscribeToTokenId(tokenId);
+        expect(ws.subs.tokens).to.deep.equal([tokenId]);
+
+        // We can unsubscribe from a tokenid
+        ws.unsubscribeFromTokenId(tokenId);
+        expect(ws.subs.tokens).to.deep.equal([]);
+
+        const lokadId = '00'.repeat(2);
+
+        // We can subscribe to a token by lokadId
+        ws.subscribeToLokadId(lokadId);
+        expect(ws.subs.lokadIds).to.deep.equal([lokadId]);
+
+        // We can unsubscribe from a lokadId
+        ws.unsubscribeFromLokadId(lokadId);
+        expect(ws.subs.lokadIds).to.deep.equal([]);
+
+        // We can subscribe to a plugin
+        ws.subscribeToPlugin('name', 'group');
+        expect(ws.subs.plugins).to.deep.equal([
+            { group: 'group', pluginName: 'name' },
+        ]);
+
+        // We can unsubscribe from a plugin
+        ws.unsubscribeFromPlugin('name', 'group');
+        expect(ws.subs.plugins).to.deep.equal([]);
+    });
+    it('We can set and get tx history by script', async () => {
+        const { tx } = mocks;
+        const type = 'p2pkh';
+        const hash = '00'.repeat(20);
+        mockChronik.setTxHistoryByScript(type, hash, [tx]);
+        expect(await mockChronik.script(type, hash).history()).to.deep.equal({
+            txs: [tx],
+            numPages: 1,
+            numTxs: 1,
+        });
+        mockChronik.setTxHistoryByScript(type, hash, chronikError);
+        await expect(
+            mockChronik.script(type, hash).history(),
+        ).to.be.rejectedWith(chronikError);
+    });
+    it('We can set and get utxos by script', async () => {
+        const { utxo } = mocks;
+        const type = 'p2pkh';
+        const hash = '00'.repeat(20);
+        mockChronik.setUtxosByScript(type, hash, [utxo]);
+        expect(await mockChronik.script(type, hash).utxos()).to.deep.equal({
+            outputScript: `76a914${hash}88ac`,
+            utxos: [utxo],
+        });
+        mockChronik.setUtxosByScript(type, hash, chronikError);
+        await expect(mockChronik.script(type, hash).utxos()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get tx history by address', async () => {
+        const { tx } = mocks;
+        const type = 'p2pkh';
+        const hash = '00'.repeat(20);
+        const address = cashaddr.encode('ecash', type, hash);
+        mockChronik.setTxHistoryByAddress(address, [tx]);
+        expect(await mockChronik.address(address).history()).to.deep.equal({
+            txs: [tx],
+            numPages: 1,
+            numTxs: 1,
+        });
+        mockChronik.setTxHistoryByAddress(address, chronikError);
+        await expect(mockChronik.address(address).history()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get utxos by address', async () => {
+        const { utxo, scriptUtxo } = mocks;
+        const type = 'p2pkh';
+        const hash = '00'.repeat(20);
+        const address = cashaddr.encode('ecash', type, hash);
+        mockChronik.setUtxosByAddress(address, [utxo]);
+        expect(await mockChronik.address(address).utxos()).to.deep.equal({
+            outputScript: `76a914${hash}88ac`,
+            utxos: [utxo],
+        });
+        mockChronik.setUtxosByAddress(address, chronikError);
+        await expect(mockChronik.address(address).utxos()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get tx history by tokenId', async () => {
+        const { tx } = mocks;
+        const tokenId = '00'.repeat(32);
+        mockChronik.setTxHistoryByTokenId(tokenId, [tx]);
+        expect(await mockChronik.tokenId(tokenId).history()).to.deep.equal({
+            txs: [tx],
+            numPages: 1,
+            numTxs: 1,
+        });
+        mockChronik.setTxHistoryByTokenId(tokenId, chronikError);
+        await expect(mockChronik.tokenId(tokenId).history()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get utxos by tokenId', async () => {
+        const { utxo } = mocks;
+        const tokenId = '00'.repeat(32);
+        mockChronik.setUtxosByTokenId(tokenId, [utxo]);
+        expect(await mockChronik.tokenId(tokenId).utxos()).to.deep.equal({
+            tokenId,
+            utxos: [utxo],
+        });
+        mockChronik.setUtxosByTokenId(tokenId, chronikError);
+        await expect(mockChronik.tokenId(tokenId).utxos()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    it('We can set and get tx history by lokadId', async () => {
+        // NB we cannot set and get utxos by lokadId, this is not available in chronik-client
+        const { tx } = mocks;
+        const lokadId = '00'.repeat(2);
+        mockChronik.setTxHistoryByLokadId(lokadId, [tx]);
+        expect(await mockChronik.lokadId(lokadId).history()).to.deep.equal({
+            txs: [tx],
+            numPages: 1,
+            numTxs: 1,
+        });
+        mockChronik.setTxHistoryByLokadId(lokadId, chronikError);
+        await expect(mockChronik.lokadId(lokadId).history()).to.be.rejectedWith(
+            chronikError,
+        );
+    });
+    context('Integration tests', () => {
+        it('We can set utxos and then tx history at the same address without overwriting the utxos', async () => {
+            const { utxo, scriptUtxo } = mocks;
+            const type = 'p2pkh';
+            const hash = '00'.repeat(20);
+            const address = cashaddr.encode('ecash', type, hash);
+            mockChronik.setUtxosByAddress(address, [utxo]);
+            expect(await mockChronik.address(address).utxos()).to.deep.equal({
+                outputScript: `76a914${hash}88ac`,
+                utxos: [utxo],
+            });
+
+            const { tx } = mocks;
+            const tokenId = '00'.repeat(32);
+            mockChronik.setTxHistoryByTokenId(tokenId, [tx]);
+            expect(await mockChronik.tokenId(tokenId).history()).to.deep.equal({
+                txs: [tx],
+                numPages: 1,
+                numTxs: 1,
+            });
+
+            // Setting the tx history did not overwrite the utxos
+            expect(await mockChronik.address(address).utxos()).to.deep.equal({
+                outputScript: `76a914${hash}88ac`,
+                utxos: [utxo],
+            });
+        });
+    });
+});
diff --git a/modules/mock-chronik-client/index.ts b/modules/mock-chronik-client/index.ts
new file mode 100644
--- /dev/null
+++ b/modules/mock-chronik-client/index.ts
@@ -0,0 +1,926 @@
+// Copyright (c) 2023 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+import * as cashaddr from 'ecashaddrjs';
+import {
+    Block,
+    TxHistoryPage,
+    Tx,
+    TokenInfo,
+    BlockchainInfo,
+    Utxo,
+    TokenIdUtxos,
+    ScriptUtxo,
+    ScriptUtxos,
+    WsConfig,
+    WsMsgClient,
+    WsSubScriptClient,
+    WsSubPluginClient,
+} from 'chronik-client';
+import * as ws from 'ws';
+
+const CHRONIK_DEFAULT_PAGESIZE = 25;
+
+// Properly this should be AgoraOffer, but the mock will work with anything
+// I do not think we should add ecash-agora as a dep just for this type
+type MockAgoraOffer = Record<string, any>;
+
+/**
+ * MockAgora stores set responses in this object
+ */
+interface MockAgoraMockResponses {
+    /** Supported Agora methods in MockAgora */
+    offeredGroupTokenIds: string[] | Error;
+    offeredFungibleTokenIds: string[] | Error;
+    activeOffersByPubKey: { [key: string]: MockAgoraOffer[] | Error };
+    activeOffersByGroupTokenId: {
+        [key: string]: MockAgoraOffer[] | Error;
+    };
+    activeOffersByTokenId: {
+        [key: string]: MockAgoraOffer[] | Error;
+    };
+}
+
+interface MockAgoraInterface {
+    mockedResponses: MockAgoraMockResponses;
+    /** Methods used to set expected responses */
+    setOfferedGroupTokenIds: (expectedResponse: string[] | Error) => void;
+    setOfferedFungibleTokenIds: (expectedResponse: string[] | Error) => void;
+    setActiveOffersByPubKey: (
+        pubKey: string,
+        expectedResponse: MockAgoraOffer[] | Error,
+    ) => void;
+    setActiveOffersByGroupTokenId: (
+        groupTokenId: string,
+        expectedResponse: MockAgoraOffer[] | Error,
+    ) => void;
+    setActiveOffersByTokenId: (
+        tokenId: string,
+        expectedResponse: MockAgoraOffer[] | Error,
+    ) => void;
+    /** Supported Agora methods */
+    offeredGroupTokenIds: () => void;
+    offeredFungibleTokenIds: () => void;
+    activeOffersByPubKey: (pubKey: string) => void;
+    activeOffersByGroupTokenId: (groupTokenId: string) => void;
+    activeOffersByTokenId: (tokenId: string) => void;
+}
+
+/**
+ * MockAgora
+ * Useful test mock for writing unit tests for functions that use the Agora
+ * class from the ecash-agora library. Drop-in replacement for Agora object
+ * to unit test functions that accept an intialized Agora object as a param.
+ * In this way, can write unit tests without hitting an actual chronik API.
+ *
+ * Mock calls to chronik nodes indexed with the ecash-agora plugin
+ *
+ * See Cashtab and ecash-herald for implementation examples.
+ *
+ * Note: not all Agora methods are mocked, can be extended as needed
+ */
+export class MockAgora implements MockAgoraInterface {
+    mockedResponses: MockAgoraMockResponses;
+
+    // Agora can make specialized chronik-client calls to a chronik-client instance
+    // running the agora plugin
+    // For the purposes of unit testing, we only need to re-create how this object
+    // is initialized and support getting and setting of expected responses
+    constructor() {
+        // API call mock return objects
+        // Can be set with self.setMock
+        this.mockedResponses = {
+            offeredGroupTokenIds: [],
+            offeredFungibleTokenIds: [],
+            activeOffersByPubKey: {},
+            activeOffersByGroupTokenId: {},
+            activeOffersByTokenId: {},
+        };
+    }
+
+    // Allow user to set supported agora query responses
+    setOfferedGroupTokenIds = (expectedResponse: string[] | Error) => {
+        this.mockedResponses.offeredGroupTokenIds = expectedResponse;
+    };
+    setOfferedFungibleTokenIds = (expectedResponse: string[] | Error) => {
+        this.mockedResponses.offeredFungibleTokenIds = expectedResponse;
+    };
+    setActiveOffersByPubKey = (
+        pubKey: string,
+        expectedResponse: MockAgoraOffer[] | Error,
+    ) => {
+        this.mockedResponses.activeOffersByPubKey[pubKey] = expectedResponse;
+    };
+    setActiveOffersByGroupTokenId = (
+        groupTokenId: string,
+        expectedResponse: MockAgoraOffer[] | Error,
+    ) => {
+        this.mockedResponses.activeOffersByGroupTokenId[groupTokenId] =
+            expectedResponse;
+    };
+    setActiveOffersByTokenId = (
+        tokenId: string,
+        expectedResponse: MockAgoraOffer[] | Error,
+    ) => {
+        this.mockedResponses.activeOffersByTokenId[tokenId] = expectedResponse;
+    };
+
+    offeredGroupTokenIds = async () => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.offeredGroupTokenIds,
+        );
+    };
+    offeredFungibleTokenIds = async () => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.offeredFungibleTokenIds,
+        );
+    };
+    activeOffersByPubKey = async (pubKey: string) => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.activeOffersByPubKey[pubKey],
+        );
+    };
+    activeOffersByGroupTokenId = async (groupTokenId: string) => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.activeOffersByGroupTokenId[groupTokenId],
+        );
+    };
+    activeOffersByTokenId = async (tokenId: string) => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.activeOffersByTokenId[tokenId],
+        );
+    };
+
+    // Checks whether the user set this mock response to be an error.
+    // If so, throw it to simulate an API error response.
+    private _throwOrReturnValue<T>(mockResponse: T): T {
+        if (mockResponse instanceof Error) {
+            throw mockResponse;
+        }
+        return mockResponse as T;
+    }
+}
+
+type AddressType = 'p2sh' | 'p2pkh';
+
+/**
+ * Object where we store expected the values we want to get when
+ * we make ChronikClient calls
+ */
+interface MockChronikClientMockedResponses {
+    block: { [key: string | number]: Block | Error };
+    blockTxs: { [key: string | number]: { history: Tx[] | Error } };
+    tx: { [key: string | number]: Tx | Error };
+    token: { [key: string | number]: TokenInfo | Error };
+    broadcastTx: { [key: string | number]: { txid: string } | Error };
+    blockchainInfo: BlockchainInfo | Error | {};
+    chronikInfo: { version: string } | Error;
+    script: {
+        p2pkh: {
+            [key: string | number]: {
+                history: Tx[] | Error;
+                utxos: ScriptUtxo[] | Error;
+            };
+        };
+        p2sh: {
+            [key: string | number]: {
+                history: Tx[] | Error;
+                utxos: ScriptUtxo[] | Error;
+            };
+        };
+    };
+    address: {
+        [key: string | number]: {
+            history: Tx[] | Error;
+            utxos: ScriptUtxo[] | Error;
+        };
+    };
+    tokenId: {
+        [key: string | number]: {
+            history: Tx[] | Error;
+            // Note that getting utxo by tokenId returns a utxo with script as a key
+            utxos: Utxo[] | Error;
+        };
+    };
+    lokadId: {
+        [key: string | number]: {
+            history: Tx[] | Error;
+            utxos: ScriptUtxo[] | Error;
+        };
+    };
+}
+
+/**
+ * Object where we store "deeper" methods
+ * ChronikClient API supports methods that take params
+ * So, when we call chronik.address(address).utxos, we
+ * want the utxos for that address
+ * And we want to also be able to set and get utxos for
+ * another address in the mock
+ */
+interface MockChronikClientMockedMethods {
+    script: {
+        p2pkh: {
+            [key: string]: {
+                history: (
+                    pageNumer?: number,
+                    pageSize?: number,
+                ) => Promise<TxHistoryPage | Error>;
+                utxos: () => Promise<ScriptUtxos | Error>;
+            };
+        };
+        p2sh: {
+            [key: string]: {
+                history: (
+                    pageNumer?: number,
+                    pageSize?: number,
+                ) => Promise<TxHistoryPage | Error>;
+                utxos: () => Promise<ScriptUtxos | Error>;
+            };
+        };
+    };
+    address: {
+        [key: string]: {
+            history: (
+                pageNumer?: number,
+                pageSize?: number,
+            ) => Promise<TxHistoryPage | Error>;
+            utxos: () => Promise<ScriptUtxos | Error>;
+        };
+    };
+    tokenId: {
+        [key: string]: {
+            history: (
+                pageNumer?: number,
+                pageSize?: number,
+            ) => Promise<TxHistoryPage | Error>;
+            utxos: () => Promise<TokenIdUtxos | Error>;
+        };
+    };
+    lokadId: {
+        [key: string]: {
+            history: (
+                pageNumer?: number,
+                pageSize?: number,
+            ) => Promise<TxHistoryPage | Error>;
+        };
+    };
+}
+
+/**
+ * MockChronikClient
+ * Drop-in replacement for ChronikClient in unit tests
+ *
+ * See Cashtab, token-server, ecash-herald for implementation
+ *
+ * Can set expected return values to test ChronikClient functions
+ * without hitting an API
+ */
+export class MockChronikClient {
+    mockedResponses: MockChronikClientMockedResponses;
+    mockedMethods: MockChronikClientMockedMethods;
+
+    constructor() {
+        // API call mock return objects
+        // Can be set with self.setMock
+        this.mockedResponses = {
+            block: {},
+            blockTxs: {},
+            tx: {},
+            token: {},
+            blockchainInfo: {},
+            chronikInfo: { version: 'unset' },
+            address: {},
+            tokenId: {},
+            lokadId: {},
+            script: { p2sh: {}, p2pkh: {} },
+            broadcastTx: {},
+        };
+        this.mockedMethods = {
+            script: { p2pkh: {}, p2sh: {} },
+            address: {},
+            tokenId: {},
+            lokadId: {},
+        };
+    }
+
+    // Public methods from ChronikClient that are mocked by MockChronikClient
+    // Note that there are often peculiar / specific ways to set these mocks
+    // See tests for examples
+    block = async (blockHashOrHeight: string | number) => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.block[blockHashOrHeight],
+        );
+    };
+
+    tx = async (txid: string) => {
+        return this._throwOrReturnValue(this.mockedResponses.tx[txid]);
+    };
+
+    token = async (tokenId: string) => {
+        return this._throwOrReturnValue(this.mockedResponses.token[tokenId]);
+    };
+
+    blockTxs = async (
+        hashOrHeight: number | string,
+        pageNumber = 0,
+        pageSize = CHRONIK_DEFAULT_PAGESIZE,
+    ) => {
+        if (
+            this.mockedResponses.blockTxs[hashOrHeight].history instanceof Error
+        ) {
+            throw this.mockedResponses.blockTxs[hashOrHeight].history;
+        }
+        return this._getTxHistory(
+            pageNumber,
+            pageSize,
+            this.mockedResponses.blockTxs[hashOrHeight].history as Tx[],
+        );
+    };
+
+    broadcastTx = async (txHex: string) => {
+        return this._throwOrReturnValue(
+            this.mockedResponses.broadcastTx[txHex],
+        );
+    };
+    broadcastTxs = async (txsHex: string[]) => {
+        const returns = [];
+        for (const txHex of txsHex) {
+            if (this.mockedResponses.broadcastTx[txHex] instanceof Error) {
+                throw this.mockedResponses.broadcastTx[txHex];
+            } else {
+                returns.push(this.mockedResponses.broadcastTx[txHex]);
+            }
+        }
+        return returns;
+    };
+    blockchainInfo = async () => {
+        return this._throwOrReturnValue(this.mockedResponses.blockchainInfo);
+    };
+
+    // Return assigned script mocks
+    script = (type: AddressType, hash: string) => {
+        return this.mockedMethods.script[type][hash];
+    };
+
+    // Return assigned address mocked methods
+    address = (address: string) => {
+        return this.mockedMethods.address[address];
+    };
+
+    // Return assigned tokenId mocked methods
+    tokenId = (tokenId: string) => {
+        return this.mockedMethods.tokenId[tokenId];
+    };
+
+    // Return assigned lokadId mocked methods
+    lokadId = (lokadId: string) => {
+        return this.mockedMethods.lokadId[lokadId];
+    };
+
+    // Websocket mocks
+    ws = (wsConfig: WsConfig) => {
+        const returnedWs = new MockWsEndpoint(wsConfig);
+        return returnedWs;
+    };
+
+    setBlock = (hashOrHeight: string | number, block: Block | Error) => {
+        this.mockedResponses.block[hashOrHeight] = block;
+    };
+
+    setTx = (txid: string, tx: Tx | Error) => {
+        this.mockedResponses.tx[txid] = tx;
+    };
+
+    setToken = (tokenId: string, token: TokenInfo | Error) => {
+        this.mockedResponses.token[tokenId] = token;
+    };
+
+    setBlockchainInfo = (blockchainInfo: BlockchainInfo | Error) => {
+        this.mockedResponses.blockchainInfo = blockchainInfo;
+    };
+
+    setChronikInfo = (versionInfo: { version: string } | Error) => {
+        this.mockedResponses.chronikInfo = versionInfo;
+    };
+    chronikInfo = async () => {
+        return this._throwOrReturnValue(this.mockedResponses.chronikInfo);
+    };
+
+    setBroadcastTx = (rawTx: string, txidOrError: string | Error) => {
+        this.mockedResponses.broadcastTx[rawTx] =
+            typeof txidOrError === 'string'
+                ? { txid: txidOrError }
+                : txidOrError;
+    };
+
+    setTxHistoryByScript = (
+        type: AddressType,
+        hash: string,
+        history: Tx[] | Error,
+    ) => {
+        this._setScript(type, hash);
+        this.mockedResponses.script[type][hash].history = history;
+    };
+
+    setTxHistoryByAddress = (address: string, history: Tx[] | Error) => {
+        this._setAddress(address);
+        this.mockedResponses.address[address].history = history;
+    };
+
+    setTxHistoryByTokenId = (tokenId: string, history: Tx[] | Error) => {
+        this._setTokenId(tokenId);
+        this.mockedResponses.tokenId[tokenId].history = history;
+    };
+
+    setTxHistoryByLokadId = (lokadId: string, history: Tx[] | Error) => {
+        this._setLokadId(lokadId);
+        this.mockedResponses.lokadId[lokadId].history = history;
+    };
+
+    setTxHistoryByBlock = (
+        hashOrHeight: string | number,
+        history: Tx[] | Error,
+    ) => {
+        // Set all expected tx history as array where it can be accessed by mock method
+        this.mockedResponses.blockTxs[hashOrHeight] = { history };
+    };
+
+    // Set utxos to custom response; must be called after setScript
+    setUtxosByScript = (
+        type: AddressType,
+        hash: string,
+        utxos: ScriptUtxo[] | Error,
+    ) => {
+        this._setScript(type, hash);
+        this.mockedResponses.script[type][hash].utxos = utxos;
+    };
+
+    // Set utxos to custom response; must be called after setAddress
+    setUtxosByAddress = (address: string, utxos: ScriptUtxo[] | Error) => {
+        this._setAddress(address);
+        this.mockedResponses.address[address].utxos = utxos;
+    };
+
+    // Set utxos to custom response; must be called after setTokenId
+    setUtxosByTokenId = (tokenId: string, utxos: Utxo[] | Error) => {
+        this._setTokenId(tokenId);
+        this.mockedResponses.tokenId[tokenId].utxos = utxos;
+    };
+
+    // We need to give mockedChronik a plugin function
+    // This is required for creating a new Agora(mockedChronik)
+    plugin = () => 'dummy plugin';
+    // Dummy values not supported by MockChronikClient
+    private _proxyInterface: unknown = {};
+    proxyInterface = {};
+    // Methods not supported by MockChronikClient
+    blocks = () => {
+        console.info('MockChronikClient does not support blocks');
+    };
+    rawTx = () => {
+        console.info('MockChronikClient does not support rawTx');
+    };
+
+    // Allow users to set expected chronik address call responses
+    private _setAddress(address: string) {
+        // Do not overwrite existing history, but initialize if nothing is there
+        if (typeof this.mockedResponses.address[address] === 'undefined') {
+            this.mockedResponses.address[address] = {
+                history: [],
+                utxos: [],
+            };
+        }
+
+        this.mockedMethods.address[address] = {
+            history: async (
+                pageNumber = 0,
+                pageSize = CHRONIK_DEFAULT_PAGESIZE,
+            ): Promise<TxHistoryPage> => {
+                if (
+                    this.mockedResponses.address[address].history instanceof
+                    Error
+                ) {
+                    throw this.mockedResponses.address[address].history;
+                }
+                return this._getTxHistory(
+                    pageNumber,
+                    pageSize,
+                    this.mockedResponses.address[address].history as Tx[],
+                );
+            },
+            utxos: async (): Promise<ScriptUtxos> => {
+                if (
+                    this.mockedResponses.address[address].utxos instanceof Error
+                ) {
+                    throw this.mockedResponses.address[address].utxos;
+                }
+                return this._getAddressUtxos(
+                    address,
+                    this.mockedResponses.address[address].utxos as ScriptUtxo[],
+                );
+            },
+        };
+    }
+
+    // Allow users to set expected chronik tokenId call responses
+    private _setTokenId(tokenId: string) {
+        if (typeof this.mockedResponses.tokenId[tokenId] === 'undefined') {
+            this.mockedResponses.tokenId[tokenId] = {
+                history: [],
+                utxos: [],
+            };
+        }
+
+        this.mockedMethods.tokenId[tokenId] = {
+            history: async (
+                pageNumber = 0,
+                pageSize = CHRONIK_DEFAULT_PAGESIZE,
+            ): Promise<TxHistoryPage> => {
+                if (
+                    this.mockedResponses.tokenId[tokenId].history instanceof
+                    Error
+                ) {
+                    throw this.mockedResponses.tokenId[tokenId].history;
+                }
+                return this._getTxHistory(
+                    pageNumber,
+                    pageSize,
+                    this.mockedResponses.tokenId[tokenId].history as Tx[],
+                );
+            },
+            utxos: async (): Promise<TokenIdUtxos> => {
+                if (
+                    this.mockedResponses.tokenId[tokenId].utxos instanceof Error
+                ) {
+                    throw this.mockedResponses.tokenId[tokenId].utxos;
+                }
+                return this._getTokenIdUtxos(
+                    tokenId,
+                    this.mockedResponses.tokenId[tokenId].utxos as Utxo[],
+                );
+            },
+        };
+    }
+
+    // Allow users to set expected chronik lokadId call responses
+    private _setLokadId(lokadId: string) {
+        if (typeof this.mockedResponses.lokadId[lokadId] === 'undefined') {
+            this.mockedResponses.lokadId[lokadId] = {
+                history: [],
+                utxos: [],
+            };
+        }
+
+        this.mockedMethods.lokadId[lokadId] = {
+            history: async (
+                pageNumber = 0,
+                pageSize = CHRONIK_DEFAULT_PAGESIZE,
+            ): Promise<TxHistoryPage> => {
+                if (
+                    this.mockedResponses.lokadId[lokadId].history instanceof
+                    Error
+                ) {
+                    throw this.mockedResponses.lokadId[lokadId].history;
+                }
+                return this._getTxHistory(
+                    pageNumber,
+                    pageSize,
+                    this.mockedResponses.lokadId[lokadId].history as Tx[],
+                );
+            },
+        };
+    }
+
+    private _setScript(type: AddressType, hash: string) {
+        if (typeof this.mockedResponses.script[type][hash] === 'undefined') {
+            this.mockedResponses.script[type][hash] = {
+                history: [],
+                utxos: [],
+            };
+        }
+
+        this.mockedMethods.script[type][hash] = {
+            history: async (
+                pageNumber = 0,
+                pageSize = CHRONIK_DEFAULT_PAGESIZE,
+            ): Promise<TxHistoryPage> => {
+                if (
+                    this.mockedResponses.script[type][hash].history instanceof
+                    Error
+                ) {
+                    throw this.mockedResponses.script[type][hash].history;
+                }
+                return this._getTxHistory(
+                    pageNumber,
+                    pageSize,
+                    this.mockedResponses.script[type][hash].history as Tx[],
+                );
+            },
+            utxos: async (): Promise<ScriptUtxos> => {
+                if (
+                    this.mockedResponses.script[type][hash].utxos instanceof
+                    Error
+                ) {
+                    throw this.mockedResponses.script[type][hash].utxos;
+                }
+                return this._getScriptUtxos(
+                    type,
+                    hash,
+                    this.mockedResponses.script[type][hash]
+                        .utxos as ScriptUtxo[],
+                );
+            },
+        };
+    }
+
+    private _getScriptUtxos(
+        type: AddressType,
+        hash: string,
+        utxos: ScriptUtxo[],
+    ): ScriptUtxos {
+        const outputScript = cashaddr.getOutputScriptFromAddress(
+            cashaddr.encode('ecash', type, hash),
+        );
+        return { outputScript, utxos };
+    }
+
+    private _getAddressUtxos(
+        address: string,
+        utxos: ScriptUtxo[],
+    ): ScriptUtxos {
+        const outputScript = cashaddr.getOutputScriptFromAddress(address);
+        return { outputScript, utxos };
+    }
+    private _getTokenIdUtxos(tokenId: string, utxos: Utxo[]): TokenIdUtxos {
+        return { tokenId, utxos };
+    }
+
+    // Method to get paginated tx history with same variables as chronik
+    private _getTxHistory(pageNumber = 0, pageSize: number, history: Tx[]) {
+        // Return chronik shaped responses
+        const startSliceOnePage = pageNumber * pageSize;
+        const endSliceOnePage = startSliceOnePage + pageSize;
+        const thisPage = history.slice(startSliceOnePage, endSliceOnePage);
+        const response: TxHistoryPage = {
+            txs: [],
+            numPages: 0,
+            numTxs: 0,
+        };
+
+        response.txs = thisPage;
+        response.numPages = Math.ceil(history.length / pageSize);
+        response.numTxs = history.length;
+        return response;
+    }
+
+    // Checks whether the user set this mock response to be an error.
+    // If so, throw it to simulate an API error response.
+    private _throwOrReturnValue<T>(mockResponse: T): T {
+        if (mockResponse instanceof Error) {
+            throw mockResponse;
+        }
+        return mockResponse as T;
+    }
+}
+
+interface WsSubscriptions {
+    /** Subscriptions to scripts */
+    scripts: WsSubScriptClient[];
+    /** Subscriptions to tokens by tokenId */
+    tokens: string[];
+    /** Subscriptions to lokadIds */
+    lokadIds: string[];
+    /** Subscriptions to plugins */
+    plugins: WsSubPluginClient[];
+    /** Subscription to blocks */
+    blocks: boolean;
+}
+/**
+ * Mock WsEndpoint for MockChronikClient.
+ * Based on WsEndpoint in chronik-client.
+ * We do not test network functionality
+ * Useful for testing that methods are called as expected,
+ * ws is opened and closed as expected, subscriptions are added
+ * and removed as expected
+ *
+ * See Cashtab and token-server tests for implemented examples
+ */
+export class MockWsEndpoint {
+    /** Fired when a message is sent from the WebSocket. */
+    public onMessage?: (msg: WsMsgClient) => void;
+
+    /** Fired when a connection has been (re)established. */
+    public onConnect?: (e: ws.Event) => void;
+
+    /**
+     * Fired after a connection has been unexpectedly closed, and before a
+     * reconnection attempt is made. Only fired if `autoReconnect` is true.
+     */
+    public onReconnect?: (e: ws.Event) => void;
+
+    /** Fired when an error with the WebSocket occurs. */
+    public onError?: (e: ws.ErrorEvent) => void;
+
+    /**
+     * Fired after a connection has been manually closed, or if `autoReconnect`
+     * is false, if the WebSocket disconnects for any reason.
+     */
+    public onEnd?: (e: ws.Event) => void;
+
+    /** Whether to automatically reconnect on disconnect, default true. */
+    public autoReconnect: boolean;
+
+    public ws: ws.WebSocket | undefined;
+    public connected: Promise<ws.Event> | undefined;
+    public manuallyClosed: boolean;
+    public waitForOpenCalled: boolean;
+    public subs: WsSubscriptions;
+
+    constructor(config: WsConfig) {
+        this.onMessage = config.onMessage;
+        this.onConnect = config.onConnect;
+        this.onReconnect = config.onReconnect;
+        this.onEnd = config.onEnd;
+        this.autoReconnect =
+            config.autoReconnect !== undefined ? config.autoReconnect : true;
+        this.manuallyClosed = false;
+
+        this.subs = {
+            scripts: [],
+            tokens: [],
+            lokadIds: [],
+            plugins: [],
+            blocks: false,
+        };
+        this.waitForOpenCalled = false;
+    }
+
+    /** Wait for the WebSocket to be connected. */
+    public async waitForOpen() {
+        // We just set a flag that tests can use
+        this.waitForOpenCalled = true;
+        await this.connected;
+    }
+
+    /**
+     * Subscribe to block messages
+     */
+    public subscribeToBlocks() {
+        this.subs.blocks = true;
+        this._subUnsubBlocks(false);
+    }
+
+    /**
+     * Unsubscribe from block messages
+     */
+    public unsubscribeFromBlocks() {
+        this.subs.blocks = false;
+        this._subUnsubBlocks(true);
+    }
+
+    /**
+     * Subscribe to the given script type and payload.
+     * For "p2pkh", `scriptPayload` is the 20 byte public key hash.
+     */
+    public subscribeToScript(type: AddressType, payload: string) {
+        // Build sub according to chronik expected type
+        const subscription: WsSubScriptClient = {
+            scriptType: type,
+            payload,
+        };
+
+        this.subs.scripts.push(subscription as WsSubScriptClient);
+    }
+
+    /** Unsubscribe from the given script type and payload. */
+    public unsubscribeFromScript(type: AddressType, payload: string) {
+        // Build sub according to chronik expected type
+        const subscription: WsSubScriptClient = {
+            scriptType: type,
+            payload,
+        };
+
+        // Find the requested unsub script and remove it
+        const unsubIndex = this.subs.scripts.findIndex(
+            sub => sub.scriptType === type && sub.payload === payload,
+        );
+        if (unsubIndex === -1) {
+            // If we cannot find this subscription in this.subs, throw an error
+            // We do not want an app developer thinking they have unsubscribed from something
+            throw new Error(`No existing sub at ${type}, ${payload}`);
+        }
+
+        // Remove the requested subscription from this.subs
+        this.subs.scripts.splice(unsubIndex, 1);
+    }
+
+    /**
+     * Subscribe to an address
+     * Method can be used for p2pkh or p2sh addresses
+     */
+    public subscribeToAddress(address: string) {
+        // Get type and hash
+        const { type, hash } = cashaddr.decode(address, true);
+
+        // Subscribe to script
+        this.subscribeToScript(type as 'p2pkh' | 'p2sh', hash as string);
+    }
+
+    /** Unsubscribe from the given address */
+    public unsubscribeFromAddress(address: string) {
+        // Get type and hash
+        const { type, hash } = cashaddr.decode(address, true);
+
+        // Unsubscribe from script
+        this.unsubscribeFromScript(type as 'p2pkh' | 'p2sh', hash as string);
+    }
+
+    /** Subscribe to a lokadId */
+    public subscribeToLokadId(lokadId: string) {
+        // Update ws.subs to include this lokadId
+        this.subs.lokadIds.push(lokadId);
+    }
+
+    /** Unsubscribe from the given lokadId */
+    public unsubscribeFromLokadId(lokadId: string) {
+        // Find the requested unsub lokadId and remove it
+        const unsubIndex = this.subs.lokadIds.findIndex(
+            thisLokadId => thisLokadId === lokadId,
+        );
+        if (unsubIndex === -1) {
+            // If we cannot find this subscription in this.subs.lokadIds, throw an error
+            // We do not want an app developer thinking they have unsubscribed from something if no action happened
+            throw new Error(`No existing sub at lokadId "${lokadId}"`);
+        }
+
+        // Remove the requested lokadId subscription from this.subs.lokadIds
+        this.subs.lokadIds.splice(unsubIndex, 1);
+    }
+
+    /** Subscribe to a tokenId */
+    public subscribeToTokenId(tokenId: string) {
+        // Update ws.subs to include this tokenId
+        this.subs.tokens.push(tokenId);
+    }
+
+    /** Unsubscribe from the given tokenId */
+    public unsubscribeFromTokenId(tokenId: string) {
+        // Find the requested unsub tokenId and remove it
+        const unsubIndex = this.subs.tokens.findIndex(
+            thisTokenId => thisTokenId === tokenId,
+        );
+        if (unsubIndex === -1) {
+            // If we cannot find this subscription in this.subs.tokens, throw an error
+            // We do not want an app developer thinking they have unsubscribed from something if no action happened
+            throw new Error(`No existing sub at tokenId "${tokenId}"`);
+        }
+
+        // Remove the requested tokenId subscription from this.subs.tokens
+        this.subs.tokens.splice(unsubIndex, 1);
+    }
+
+    /** Subscribe to a plugin */
+    public subscribeToPlugin(pluginName: string, group: string) {
+        // Build sub according to chronik expected type
+        const subscription: WsSubPluginClient = {
+            pluginName,
+            group,
+        };
+
+        // Update ws.subs to include this plugin
+        this.subs.plugins.push(subscription);
+    }
+
+    /** Unsubscribe from the given plugin */
+    public unsubscribeFromPlugin(pluginName: string, group: string) {
+        // Find the requested unsub script and remove it
+        const unsubIndex = this.subs.plugins.findIndex(
+            sub => sub.pluginName === pluginName && sub.group === group,
+        );
+        if (unsubIndex === -1) {
+            // If we cannot find this subscription in this.subs.plugins, throw an error
+            // We do not want an app developer thinking they have unsubscribed from something
+            throw new Error(
+                `No existing sub at pluginName="${pluginName}", group="${group}"`,
+            );
+        }
+
+        // Remove the requested subscription from this.subs.plugins
+        this.subs.plugins.splice(unsubIndex, 1);
+    }
+
+    /**
+     * Close the WebSocket connection and prevent any future reconnection
+     * attempts.
+     */
+    public close() {
+        this.manuallyClosed = true;
+        this.ws?.close();
+    }
+
+    private _subUnsubBlocks(isUnsub: boolean) {
+        // Blocks subscription is empty object
+        this.subs.blocks = !isUnsub;
+    }
+}
diff --git a/modules/mock-chronik-client/mocks/mockChronikResponses.js b/modules/mock-chronik-client/mocks/index.ts
rename from modules/mock-chronik-client/mocks/mockChronikResponses.js
rename to modules/mock-chronik-client/mocks/index.ts
--- a/modules/mock-chronik-client/mocks/mockChronikResponses.js
+++ b/modules/mock-chronik-client/mocks/index.ts
@@ -2,8 +2,226 @@
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 // @generated
-'use strict';
+import {
+    Block,
+    BlockchainInfo,
+    Tx,
+    TokenInfo,
+    ScriptUtxo,
+    Utxo,
+} from 'chronik-client';
 
+interface ChronikMocks {
+    tipHeight: number;
+    tipHash: string;
+    block: Block;
+    blockchainInfo: BlockchainInfo;
+    txid: string;
+    tx: Tx;
+    tokenId: string;
+    token: TokenInfo;
+    rawTx: string;
+    scriptUtxo: ScriptUtxo;
+    utxo: Utxo;
+}
+const DUMMY_SCRIPT_UTXO: ScriptUtxo = {
+    outpoint: { txid: '00'.repeat(32), outIdx: 0 },
+    blockHeight: 800000,
+    isCoinbase: false,
+    value: 546,
+    isFinal: true,
+};
+const DUMMY_UTXO: Utxo = {
+    outpoint: { txid: '00'.repeat(32), outIdx: 0 },
+    script: `76a914${'00'.repeat(20)}88ac`,
+    blockHeight: 800000,
+    isCoinbase: false,
+    value: 546,
+    isFinal: true,
+};
+const chronikMocks: ChronikMocks = {
+    tipHeight: 800000,
+    tipHash: '0000000000000000115e051672e3d4a6c523598594825a1194862937941296fe',
+    block: {
+        blockInfo: {
+            hash: '0000000000000000115e051672e3d4a6c523598594825a1194862937941296fe',
+            prevHash:
+                '0000000000000000023ab587190a05e44cdb76fe1dd7949f6187f1e632d2e123',
+            height: 800000,
+            nBits: 403943060,
+            timestamp: 1688808780,
+            isFinal: true,
+            blockSize: 3094,
+            numTxs: 9,
+            numInputs: 13,
+            numOutputs: 26,
+            sumInputSats: 859760862,
+            sumCoinbaseOutputSats: 625005728,
+            sumNormalOutputSats: 859755134,
+            sumBurnedSats: 0,
+        },
+    },
+    blockchainInfo: {
+        tipHash:
+            '0000000000000000115e051672e3d4a6c523598594825a1194862937941296fe',
+        tipHeight: 800000,
+    },
+    txid: '2c1540131fb7611b72ec3ca9a215ac5c408248327d3c5b5ff987cd2a959ee1d2',
+    tx: {
+        txid: '2c1540131fb7611b72ec3ca9a215ac5c408248327d3c5b5ff987cd2a959ee1d2',
+        version: 2,
+        inputs: [
+            {
+                prevOut: {
+                    txid: '1cb37ece9d9dbc19ef2c6d57766ca3b91bdc3e56d203aa0fc1b41a7268c0c9d2',
+                    outIdx: 2,
+                },
+                inputScript:
+                    '41ebe634094888c4215b8690546b74695a048a9c49c8a5c6efedd940591ec9039e91cf2a6f7836107afbc41bb6e13fb22d99739aeadf1a740f75efea177cfcd86641210353f81d61d41d6e22c73ab449476113dea124afe3972991cd237e654f15950b7c',
+                value: 546,
+                sequenceNo: 4294967295,
+                token: {
+                    tokenId:
+                        'aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1',
+                    tokenType: {
+                        protocol: 'SLP',
+                        type: 'SLP_TOKEN_TYPE_FUNGIBLE',
+                        number: 1,
+                    },
+                    amount: '455320000',
+                    isMintBaton: false,
+                    entryIdx: 0,
+                },
+                outputScript:
+                    '76a914821407ac2993f8684227004f4086082f3f801da788ac',
+            },
+            {
+                prevOut: {
+                    txid: '1cb37ece9d9dbc19ef2c6d57766ca3b91bdc3e56d203aa0fc1b41a7268c0c9d2',
+                    outIdx: 3,
+                },
+                inputScript:
+                    '412e7de41834aaf15d16347a59c954cb886d8ec00584bb566252e16f8e1779ce2e4d6ddd91782bd6ba2f695a28ce4115cd0561d27c1ae16b3d8bf77f8186235b1441210353f81d61d41d6e22c73ab449476113dea124afe3972991cd237e654f15950b7c',
+                value: 94518653,
+                sequenceNo: 4294967295,
+                outputScript:
+                    '76a914821407ac2993f8684227004f4086082f3f801da788ac',
+            },
+        ],
+        outputs: [
+            {
+                value: 0,
+                outputScript:
+                    '6a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000271008000000001b237ab0',
+            },
+            {
+                value: 546,
+                outputScript:
+                    '76a914a805a320360fa685f83605d8e56de6f9d8a7a99988ac',
+                token: {
+                    tokenId:
+                        'aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1',
+                    tokenType: {
+                        protocol: 'SLP',
+                        type: 'SLP_TOKEN_TYPE_FUNGIBLE',
+                        number: 1,
+                    },
+                    amount: '10000',
+                    isMintBaton: false,
+                    entryIdx: 0,
+                },
+            },
+            {
+                value: 546,
+                outputScript:
+                    '76a914821407ac2993f8684227004f4086082f3f801da788ac',
+                token: {
+                    tokenId:
+                        'aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1',
+                    tokenType: {
+                        protocol: 'SLP',
+                        type: 'SLP_TOKEN_TYPE_FUNGIBLE',
+                        number: 1,
+                    },
+                    amount: '455310000',
+                    isMintBaton: false,
+                    entryIdx: 0,
+                },
+                spentBy: {
+                    txid: 'b334ad4ee98718707142a7a951163f1cb94817269b55ac840a7b416ce256eecd',
+                    outIdx: 0,
+                },
+            },
+            {
+                value: 94517640,
+                outputScript:
+                    '76a914821407ac2993f8684227004f4086082f3f801da788ac',
+                spentBy: {
+                    txid: 'b334ad4ee98718707142a7a951163f1cb94817269b55ac840a7b416ce256eecd',
+                    outIdx: 1,
+                },
+            },
+        ],
+        lockTime: 0,
+        timeFirstSeen: 0,
+        size: 467,
+        isCoinbase: false,
+        tokenEntries: [
+            {
+                tokenId:
+                    'aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1',
+                tokenType: {
+                    protocol: 'SLP',
+                    type: 'SLP_TOKEN_TYPE_FUNGIBLE',
+                    number: 1,
+                },
+                txType: 'SEND',
+                isInvalid: false,
+                burnSummary: '',
+                failedColorings: [],
+                actualBurnAmount: '0',
+                intentionalBurn: '0',
+                burnsMintBatons: false,
+            },
+        ],
+        tokenFailedParsings: [],
+        tokenStatus: 'TOKEN_STATUS_NORMAL',
+        isFinal: false,
+        block: {
+            height: 866581,
+            hash: '000000000000000000860f542fac8cbb10614e40ab0f37f90ec4d41b126eb5d9',
+            timestamp: 1728930658,
+        },
+    },
+    tokenId: 'aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1',
+    token: {
+        tokenId:
+            'aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb1',
+        tokenType: {
+            protocol: 'SLP',
+            type: 'SLP_TOKEN_TYPE_FUNGIBLE',
+            number: 1,
+        },
+        timeFirstSeen: 1711776546,
+        genesisInfo: {
+            tokenTicker: 'CACHET',
+            tokenName: 'Cachet',
+            url: 'https://cashtab.com/',
+            decimals: 2,
+            hash: '',
+        },
+        block: {
+            height: 838192,
+            hash: '0000000000000000132232769161d6211f7e6e20cf63b26e5148890aacd26962',
+            timestamp: 1711779364,
+        },
+    },
+    rawTx: '0200000002d2c9c068721ab4c10faa03d2563edc1bb9a36c76576d2cef19bc9d9dce7eb31c020000006441ebe634094888c4215b8690546b74695a048a9c49c8a5c6efedd940591ec9039e91cf2a6f7836107afbc41bb6e13fb22d99739aeadf1a740f75efea177cfcd86641210353f81d61d41d6e22c73ab449476113dea124afe3972991cd237e654f15950b7cffffffffd2c9c068721ab4c10faa03d2563edc1bb9a36c76576d2cef19bc9d9dce7eb31c0300000064412e7de41834aaf15d16347a59c954cb886d8ec00584bb566252e16f8e1779ce2e4d6ddd91782bd6ba2f695a28ce4115cd0561d27c1ae16b3d8bf77f8186235b1441210353f81d61d41d6e22c73ab449476113dea124afe3972991cd237e654f15950b7cffffffff040000000000000000406a04534c500001010453454e4420aed861a31b96934b88c0252ede135cb9700d7649f69191235087a3030e553cb108000000000000271008000000001b237ab022020000000000001976a914a805a320360fa685f83605d8e56de6f9d8a7a99988ac22020000000000001976a914821407ac2993f8684227004f4086082f3f801da788ac8839a205000000001976a914821407ac2993f8684227004f4086082f3f801da788ac00000000',
+    scriptUtxo: DUMMY_SCRIPT_UTXO,
+    utxo: DUMMY_UTXO,
+};
+export default chronikMocks;
+/*
 module.exports = {
     mockBlockInfo: {
         blockInfo: {
@@ -627,3 +845,4 @@
     mockSendXecNoChangeRawTxHex:
         '02000000010c8dabdfc27f4e43e4c99e5f5534f7c3465a9036445674fc05bcd8510437a81f000000006b4830450221008c86fe8d3fee5082c4429eef064a2063fcc1ea9216afd88cba1ff8cfe7b7083602204c1655728b1bbfc5ded78d46db00968a67e2f4c7c8072fe808feff4ccfd29d74412102560c43edaa6a058c9096c90ce5d4a3bbbf70fd18b2030d26614bc32379151de9ffffffff0194110000000000001976a9140b7d35fda03544a08e65464d54cfae4257eb6db788ac00000000',
 };
+*/
diff --git a/modules/mock-chronik-client/package-lock.json b/modules/mock-chronik-client/package-lock.json
--- a/modules/mock-chronik-client/package-lock.json
+++ b/modules/mock-chronik-client/package-lock.json
@@ -1,24 +1,71 @@
 {
     "name": "mock-chronik-client",
-    "version": "1.12.3",
+    "version": "2.0.0",
     "lockfileVersion": 2,
     "requires": true,
     "packages": {
         "": {
             "name": "mock-chronik-client",
-            "version": "1.12.3",
+            "version": "2.0.0",
             "license": "MIT",
             "dependencies": {
-                "ecashaddrjs": "file:../ecashaddrjs"
+                "chronik-client": "file:../chronik-client",
+                "ecashaddrjs": "file:../ecashaddrjs",
+                "ws": "^8.18.0"
             },
             "devDependencies": {
-                "mocha": "^10.2.0",
+                "@types/chai": "^5.0.1",
+                "@types/chai-as-promised": "^8.0.1",
+                "@types/mocha": "^10.0.10",
+                "@types/ws": "^8.5.13",
+                "chai": "^5.1.2",
+                "chai-as-promised": "^8.0.1",
+                "eslint": "8.57",
+                "eslint-plugin-header": "^3.1.1",
+                "mocha": "^10.8.2",
                 "mocha-junit-reporter": "^2.2.1",
-                "nyc": "^15.1.0"
+                "nyc": "^15.1.0",
+                "ts-node": "^10.9.2",
+                "tsx": "^4.19.2",
+                "typescript": "^5.7.2",
+                "typescript-eslint": "^8.18.0"
+            }
+        },
+        "../chronik-client": {
+            "version": "2.0.0",
+            "license": "MIT",
+            "dependencies": {
+                "@types/ws": "^8.2.1",
+                "axios": "^1.6.3",
+                "ecashaddrjs": "file:../ecashaddrjs",
+                "isomorphic-ws": "^4.0.1",
+                "protobufjs": "^6.8.8",
+                "ws": "^8.3.0"
+            },
+            "devDependencies": {
+                "@types/chai": "^4.2.22",
+                "@types/chai-as-promised": "^7.1.4",
+                "@types/mocha": "^9.0.0",
+                "@typescript-eslint/eslint-plugin": "^5.5.0",
+                "@typescript-eslint/parser": "^5.5.0",
+                "chai": "^4.3.4",
+                "chai-as-promised": "^7.1.1",
+                "eslint": "^8.37.0",
+                "eslint-config-prettier": "^8.3.0",
+                "eslint-plugin-header": "^3.1.1",
+                "mocha": "^9.1.3",
+                "mocha-junit-reporter": "^2.2.0",
+                "mocha-suppress-logs": "^0.3.1",
+                "prettier": "^2.5.1",
+                "prettier-plugin-organize-imports": "^2.3.4",
+                "ts-node": "^10.4.0",
+                "ts-proto": "^1.92.1",
+                "typedoc": "^0.22.10",
+                "typescript": "^4.5.2"
             }
         },
         "../ecashaddrjs": {
-            "version": "1.5.8",
+            "version": "1.6.2",
             "license": "MIT",
             "dependencies": {
                 "big-integer": "1.6.36",
@@ -28,7 +75,9 @@
                 "@babel/cli": "^7.21.0",
                 "@babel/core": "^7.21.3",
                 "@babel/preset-env": "^7.20.2",
-                "babel-loader": "^9.1.2",
+                "@types/chai": "^5.0.1",
+                "@types/mocha": "^10.0.7",
+                "@typescript-eslint/parser": "^8.15.0",
                 "buffer": "^6.0.3",
                 "chai": "^4.3.7",
                 "debug": "^4.3.4",
@@ -39,6 +88,8 @@
                 "mocha-suppress-logs": "^0.3.1",
                 "nyc": "^15.1.0",
                 "random-js": "^2.1.0",
+                "ts-loader": "^9.5.1",
+                "ts-node": "^10.9.2",
                 "webpack": "^5.76.2",
                 "webpack-cli": "^5.0.1"
             }
@@ -486,229 +537,1249 @@
                 "node": ">=6.9.0"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config": {
-            "version": "1.1.0",
-            "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
-            "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+        "node_modules/@cspotcode/source-map-support": {
+            "version": "0.8.1",
+            "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+            "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "camelcase": "^5.3.1",
-                "find-up": "^4.1.0",
-                "get-package-type": "^0.1.0",
-                "js-yaml": "^3.13.1",
-                "resolve-from": "^5.0.0"
+                "@jridgewell/trace-mapping": "0.3.9"
             },
             "engines": {
-                "node": ">=8"
+                "node": ">=12"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
-            "version": "1.0.10",
-            "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
-            "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+        "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+            "version": "0.3.9",
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+            "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "sprintf-js": "~1.0.2"
+                "@jridgewell/resolve-uri": "^3.0.3",
+                "@jridgewell/sourcemap-codec": "^1.4.10"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-            "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+        "node_modules/@esbuild/aix-ppc64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+            "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+            "cpu": [
+                "ppc64"
+            ],
             "dev": true,
-            "dependencies": {
-                "locate-path": "^5.0.0",
-                "path-exists": "^4.0.0"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "aix"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
-            "version": "3.14.1",
-            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
-            "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+        "node_modules/@esbuild/android-arm": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+            "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+            "cpu": [
+                "arm"
+            ],
             "dev": true,
-            "dependencies": {
-                "argparse": "^1.0.7",
-                "esprima": "^4.0.0"
-            },
-            "bin": {
-                "js-yaml": "bin/js-yaml.js"
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ],
+            "engines": {
+                "node": ">=18"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-            "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+        "node_modules/@esbuild/android-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+            "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+            "cpu": [
+                "arm64"
+            ],
             "dev": true,
-            "dependencies": {
-                "p-locate": "^4.1.0"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
-            "version": "2.3.0",
-            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-            "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+        "node_modules/@esbuild/android-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+            "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+            "cpu": [
+                "x64"
+            ],
             "dev": true,
-            "dependencies": {
-                "p-try": "^2.0.0"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "android"
+            ],
             "engines": {
-                "node": ">=6"
-            },
-            "funding": {
-                "url": "https://github.com/sponsors/sindresorhus"
+                "node": ">=18"
             }
         },
-        "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
-            "version": "4.1.0",
-            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-            "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+        "node_modules/@esbuild/darwin-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+            "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+            "cpu": [
+                "arm64"
+            ],
             "dev": true,
-            "dependencies": {
-                "p-limit": "^2.2.0"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/@istanbuljs/schema": {
-            "version": "0.1.3",
-            "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
-            "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+        "node_modules/@esbuild/darwin-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+            "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+            "cpu": [
+                "x64"
+            ],
             "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/@jridgewell/gen-mapping": {
-            "version": "0.3.3",
-            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-            "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+        "node_modules/@esbuild/freebsd-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+            "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+            "cpu": [
+                "arm64"
+            ],
             "dev": true,
-            "dependencies": {
-                "@jridgewell/set-array": "^1.0.1",
-                "@jridgewell/sourcemap-codec": "^1.4.10",
-                "@jridgewell/trace-mapping": "^0.3.9"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "freebsd"
+            ],
             "engines": {
-                "node": ">=6.0.0"
+                "node": ">=18"
             }
         },
-        "node_modules/@jridgewell/resolve-uri": {
-            "version": "3.1.1",
-            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
-            "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+        "node_modules/@esbuild/freebsd-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+            "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+            "cpu": [
+                "x64"
+            ],
             "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "freebsd"
+            ],
             "engines": {
-                "node": ">=6.0.0"
+                "node": ">=18"
             }
         },
-        "node_modules/@jridgewell/set-array": {
-            "version": "1.1.2",
-            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-            "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+        "node_modules/@esbuild/linux-arm": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+            "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+            "cpu": [
+                "arm"
+            ],
             "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">=6.0.0"
+                "node": ">=18"
             }
         },
-        "node_modules/@jridgewell/sourcemap-codec": {
-            "version": "1.4.15",
-            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
-            "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
-            "dev": true
+        "node_modules/@esbuild/linux-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+            "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
         },
-        "node_modules/@jridgewell/trace-mapping": {
-            "version": "0.3.19",
-            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
-            "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
+        "node_modules/@esbuild/linux-ia32": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+            "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+            "cpu": [
+                "ia32"
+            ],
             "dev": true,
-            "dependencies": {
-                "@jridgewell/resolve-uri": "^3.1.0",
-                "@jridgewell/sourcemap-codec": "^1.4.14"
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
+            "engines": {
+                "node": ">=18"
             }
         },
-        "node_modules/aggregate-error": {
-            "version": "3.1.0",
-            "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
-            "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+        "node_modules/@esbuild/linux-loong64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+            "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+            "cpu": [
+                "loong64"
+            ],
             "dev": true,
-            "dependencies": {
-                "clean-stack": "^2.0.0",
-                "indent-string": "^4.0.0"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/ansi-colors": {
-            "version": "4.1.1",
-            "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
-            "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+        "node_modules/@esbuild/linux-mips64el": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+            "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+            "cpu": [
+                "mips64el"
+            ],
             "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">=6"
+                "node": ">=18"
             }
         },
-        "node_modules/ansi-regex": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+        "node_modules/@esbuild/linux-ppc64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+            "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+            "cpu": [
+                "ppc64"
+            ],
             "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/ansi-styles": {
-            "version": "4.3.0",
-            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+        "node_modules/@esbuild/linux-riscv64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+            "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+            "cpu": [
+                "riscv64"
+            ],
             "dev": true,
-            "dependencies": {
-                "color-convert": "^2.0.1"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">=8"
-            },
-            "funding": {
-                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+                "node": ">=18"
             }
         },
-        "node_modules/anymatch": {
-            "version": "3.1.3",
-            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
-            "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+        "node_modules/@esbuild/linux-s390x": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+            "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+            "cpu": [
+                "s390x"
+            ],
             "dev": true,
-            "dependencies": {
-                "normalize-path": "^3.0.0",
-                "picomatch": "^2.0.4"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">= 8"
+                "node": ">=18"
             }
         },
-        "node_modules/append-transform": {
-            "version": "2.0.0",
-            "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
-            "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
+        "node_modules/@esbuild/linux-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+            "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+            "cpu": [
+                "x64"
+            ],
             "dev": true,
-            "dependencies": {
-                "default-require-extensions": "^3.0.0"
-            },
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "linux"
+            ],
             "engines": {
-                "node": ">=8"
+                "node": ">=18"
             }
         },
-        "node_modules/archy": {
-            "version": "1.0.0",
-            "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
-            "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
-            "dev": true
+        "node_modules/@esbuild/netbsd-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+            "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "netbsd"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@esbuild/openbsd-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+            "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "openbsd"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@esbuild/openbsd-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+            "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "openbsd"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@esbuild/sunos-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+            "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "sunos"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@esbuild/win32-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+            "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+            "cpu": [
+                "arm64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@esbuild/win32-ia32": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+            "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+            "cpu": [
+                "ia32"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@esbuild/win32-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+            "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+            "cpu": [
+                "x64"
+            ],
+            "dev": true,
+            "license": "MIT",
+            "optional": true,
+            "os": [
+                "win32"
+            ],
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/@eslint-community/eslint-utils": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+            "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "eslint-visitor-keys": "^3.4.3"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+            }
+        },
+        "node_modules/@eslint-community/regexpp": {
+            "version": "4.12.1",
+            "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+            "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+            }
+        },
+        "node_modules/@eslint/eslintrc": {
+            "version": "2.1.4",
+            "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+            "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "ajv": "^6.12.4",
+                "debug": "^4.3.2",
+                "espree": "^9.6.0",
+                "globals": "^13.19.0",
+                "ignore": "^5.2.0",
+                "import-fresh": "^3.2.1",
+                "js-yaml": "^4.1.0",
+                "minimatch": "^3.1.2",
+                "strip-json-comments": "^3.1.1"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/@eslint/eslintrc/node_modules/globals": {
+            "version": "13.24.0",
+            "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+            "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "type-fest": "^0.20.2"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "dev": true,
+            "license": "(MIT OR CC0-1.0)",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@eslint/js": {
+            "version": "8.57.1",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+            "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            }
+        },
+        "node_modules/@humanwhocodes/config-array": {
+            "version": "0.13.0",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+            "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+            "deprecated": "Use @eslint/config-array instead",
+            "dev": true,
+            "license": "Apache-2.0",
+            "dependencies": {
+                "@humanwhocodes/object-schema": "^2.0.3",
+                "debug": "^4.3.1",
+                "minimatch": "^3.0.5"
+            },
+            "engines": {
+                "node": ">=10.10.0"
+            }
+        },
+        "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/@humanwhocodes/module-importer": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+            "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": ">=12.22"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/nzakas"
+            }
+        },
+        "node_modules/@humanwhocodes/object-schema": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+            "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+            "deprecated": "Use @eslint/object-schema instead",
+            "dev": true,
+            "license": "BSD-3-Clause"
+        },
+        "node_modules/@istanbuljs/load-nyc-config": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+            "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+            "dev": true,
+            "dependencies": {
+                "camelcase": "^5.3.1",
+                "find-up": "^4.1.0",
+                "get-package-type": "^0.1.0",
+                "js-yaml": "^3.13.1",
+                "resolve-from": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+            "version": "1.0.10",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+            "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+            "dev": true,
+            "dependencies": {
+                "sprintf-js": "~1.0.2"
+            }
+        },
+        "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+            "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+            "dev": true,
+            "dependencies": {
+                "locate-path": "^5.0.0",
+                "path-exists": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+            "version": "3.14.1",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+            "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+            "dev": true,
+            "dependencies": {
+                "argparse": "^1.0.7",
+                "esprima": "^4.0.0"
+            },
+            "bin": {
+                "js-yaml": "bin/js-yaml.js"
+            }
+        },
+        "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+            "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+            "dev": true,
+            "dependencies": {
+                "p-locate": "^4.1.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+            "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+            "dev": true,
+            "dependencies": {
+                "p-try": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+            "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+            "dev": true,
+            "dependencies": {
+                "p-limit": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@istanbuljs/schema": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+            "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/@jridgewell/gen-mapping": {
+            "version": "0.3.3",
+            "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
+            "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
+            "dev": true,
+            "dependencies": {
+                "@jridgewell/set-array": "^1.0.1",
+                "@jridgewell/sourcemap-codec": "^1.4.10",
+                "@jridgewell/trace-mapping": "^0.3.9"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@jridgewell/resolve-uri": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
+            "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
+            "dev": true,
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@jridgewell/set-array": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+            "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+            "dev": true,
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/@jridgewell/sourcemap-codec": {
+            "version": "1.4.15",
+            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+            "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
+            "dev": true
+        },
+        "node_modules/@jridgewell/trace-mapping": {
+            "version": "0.3.19",
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
+            "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
+            "dev": true,
+            "dependencies": {
+                "@jridgewell/resolve-uri": "^3.1.0",
+                "@jridgewell/sourcemap-codec": "^1.4.14"
+            }
+        },
+        "node_modules/@nodelib/fs.scandir": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+            "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@nodelib/fs.stat": "2.0.5",
+                "run-parallel": "^1.1.9"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/@nodelib/fs.stat": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/@nodelib/fs.walk": {
+            "version": "1.2.8",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+            "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@nodelib/fs.scandir": "2.1.5",
+                "fastq": "^1.6.0"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/@tsconfig/node10": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+            "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/@tsconfig/node12": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+            "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/@tsconfig/node14": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+            "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/@tsconfig/node16": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+            "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/@types/chai": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.1.tgz",
+            "integrity": "sha512-5T8ajsg3M/FOncpLYW7sdOcD6yf4+722sze/tc4KQV0P8Z2rAr3SAuHCIkYmYpt8VbcQlnz8SxlOlPQYefe4cA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/deep-eql": "*"
+            }
+        },
+        "node_modules/@types/chai-as-promised": {
+            "version": "8.0.1",
+            "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.1.tgz",
+            "integrity": "sha512-dAlDhLjJlABwAVYObo9TPWYTRg9NaQM5CXeaeJYcYAkvzUf0JRLIiog88ao2Wqy/20WUnhbbUZcgvngEbJ3YXQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/chai": "*"
+            }
+        },
+        "node_modules/@types/deep-eql": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+            "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/@types/mocha": {
+            "version": "10.0.10",
+            "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
+            "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/@types/node": {
+            "version": "22.10.1",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz",
+            "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "undici-types": "~6.20.0"
+            }
+        },
+        "node_modules/@types/ws": {
+            "version": "8.5.13",
+            "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
+            "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@types/node": "*"
+            }
+        },
+        "node_modules/@typescript-eslint/eslint-plugin": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
+            "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@eslint-community/regexpp": "^4.10.0",
+                "@typescript-eslint/scope-manager": "8.18.0",
+                "@typescript-eslint/type-utils": "8.18.0",
+                "@typescript-eslint/utils": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.3.1",
+                "natural-compare": "^1.4.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+                "eslint": "^8.57.0 || ^9.0.0",
+                "typescript": ">=4.8.4 <5.8.0"
+            }
+        },
+        "node_modules/@typescript-eslint/parser": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz",
+            "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==",
+            "dev": true,
+            "license": "MITClause",
+            "dependencies": {
+                "@typescript-eslint/scope-manager": "8.18.0",
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/typescript-estree": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0",
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.57.0 || ^9.0.0",
+                "typescript": ">=4.8.4 <5.8.0"
+            }
+        },
+        "node_modules/@typescript-eslint/scope-manager": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz",
+            "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/type-utils": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz",
+            "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@typescript-eslint/typescript-estree": "8.18.0",
+                "@typescript-eslint/utils": "8.18.0",
+                "debug": "^4.3.4",
+                "ts-api-utils": "^1.3.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.57.0 || ^9.0.0",
+                "typescript": ">=4.8.4 <5.8.0"
+            }
+        },
+        "node_modules/@typescript-eslint/types": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
+            "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
+            "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0",
+                "debug": "^4.3.4",
+                "fast-glob": "^3.3.2",
+                "is-glob": "^4.0.3",
+                "minimatch": "^9.0.4",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "typescript": ">=4.8.4 <5.8.0"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+            "version": "9.0.5",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+            "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=16 || 14 >=14.17"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
+        "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+            "version": "7.6.3",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+            "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+            "dev": true,
+            "license": "ISC",
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/@typescript-eslint/utils": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz",
+            "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@eslint-community/eslint-utils": "^4.4.0",
+                "@typescript-eslint/scope-manager": "8.18.0",
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/typescript-estree": "8.18.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.57.0 || ^9.0.0",
+                "typescript": ">=4.8.4 <5.8.0"
+            }
+        },
+        "node_modules/@typescript-eslint/visitor-keys": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
+            "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@typescript-eslint/types": "8.18.0",
+                "eslint-visitor-keys": "^4.2.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            }
+        },
+        "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+            "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/@ungap/structured-clone": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
+            "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==",
+            "dev": true,
+            "license": "ISC"
+        },
+        "node_modules/acorn": {
+            "version": "8.14.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+            "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+            "dev": true,
+            "license": "MIT",
+            "bin": {
+                "acorn": "bin/acorn"
+            },
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/acorn-jsx": {
+            "version": "5.3.2",
+            "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+            "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+            "dev": true,
+            "license": "MIT",
+            "peerDependencies": {
+                "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+            }
+        },
+        "node_modules/acorn-walk": {
+            "version": "8.3.4",
+            "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+            "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "acorn": "^8.11.0"
+            },
+            "engines": {
+                "node": ">=0.4.0"
+            }
+        },
+        "node_modules/aggregate-error": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+            "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+            "dev": true,
+            "dependencies": {
+                "clean-stack": "^2.0.0",
+                "indent-string": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            },
+            "funding": {
+                "type": "github",
+                "url": "https://github.com/sponsors/epoberezkin"
+            }
+        },
+        "node_modules/ansi-colors": {
+            "version": "4.1.3",
+            "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+            "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/ansi-regex": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/ansi-styles": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+            "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+            "dev": true,
+            "dependencies": {
+                "color-convert": "^2.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+            }
+        },
+        "node_modules/anymatch": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+            "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+            "dev": true,
+            "dependencies": {
+                "normalize-path": "^3.0.0",
+                "picomatch": "^2.0.4"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/append-transform": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
+            "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
+            "dev": true,
+            "dependencies": {
+                "default-require-extensions": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/archy": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+            "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
+            "dev": true
+        },
+        "node_modules/arg": {
+            "version": "4.1.3",
+            "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+            "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+            "dev": true,
+            "license": "MIT"
         },
         "node_modules/argparse": {
             "version": "2.0.1",
@@ -716,6 +1787,16 @@
             "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
             "dev": true
         },
+        "node_modules/assertion-error": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+            "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=12"
+            }
+        },
         "node_modules/balanced-match": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -806,6 +1887,16 @@
                 "node": ">=8"
             }
         },
+        "node_modules/callsites": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+            "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/camelcase": {
             "version": "5.3.1",
             "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -835,6 +1926,36 @@
                 }
             ]
         },
+        "node_modules/chai": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+            "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "assertion-error": "^2.0.1",
+                "check-error": "^2.1.1",
+                "deep-eql": "^5.0.1",
+                "loupe": "^3.1.0",
+                "pathval": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/chai-as-promised": {
+            "version": "8.0.1",
+            "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.1.tgz",
+            "integrity": "sha512-OIEJtOL8xxJSH8JJWbIoRjybbzR52iFuDHuF8eb+nTPD6tgXLjRqsgnUGqQfFODxYvq5QdirT0pN9dZ0+Gz6rA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "check-error": "^2.0.0"
+            },
+            "peerDependencies": {
+                "chai": ">= 2.1.2 < 6"
+            }
+        },
         "node_modules/chalk": {
             "version": "4.1.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -872,6 +1993,16 @@
                 "node": "*"
             }
         },
+        "node_modules/check-error": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+            "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 16"
+            }
+        },
         "node_modules/chokidar": {
             "version": "3.5.3",
             "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -899,6 +2030,10 @@
                 "fsevents": "~2.3.2"
             }
         },
+        "node_modules/chronik-client": {
+            "resolved": "../chronik-client",
+            "link": true
+        },
         "node_modules/clean-stack": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -955,6 +2090,13 @@
             "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
             "dev": true
         },
+        "node_modules/create-require": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+            "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/cross-spawn": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -979,12 +2121,13 @@
             }
         },
         "node_modules/debug": {
-            "version": "4.3.4",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "version": "4.4.0",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+            "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
-                "ms": "2.1.2"
+                "ms": "^2.1.3"
             },
             "engines": {
                 "node": ">=6.0"
@@ -995,12 +2138,6 @@
                 }
             }
         },
-        "node_modules/debug/node_modules/ms": {
-            "version": "2.1.2",
-            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-            "dev": true
-        },
         "node_modules/decamelize": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@@ -1010,6 +2147,23 @@
                 "node": ">=0.10.0"
             }
         },
+        "node_modules/deep-eql": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+            "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/deep-is": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+            "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/default-require-extensions": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz",
@@ -1026,14 +2180,28 @@
             }
         },
         "node_modules/diff": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
-            "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+            "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
             "dev": true,
+            "license": "BSD-3-Clause",
             "engines": {
                 "node": ">=0.3.1"
             }
         },
+        "node_modules/doctrine": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+            "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "dependencies": {
+                "esutils": "^2.0.2"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
         "node_modules/ecashaddrjs": {
             "resolved": "../ecashaddrjs",
             "link": true
@@ -1056,6 +2224,46 @@
             "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
             "dev": true
         },
+        "node_modules/esbuild": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+            "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+            "dev": true,
+            "hasInstallScript": true,
+            "license": "MIT",
+            "bin": {
+                "esbuild": "bin/esbuild"
+            },
+            "engines": {
+                "node": ">=18"
+            },
+            "optionalDependencies": {
+                "@esbuild/aix-ppc64": "0.23.1",
+                "@esbuild/android-arm": "0.23.1",
+                "@esbuild/android-arm64": "0.23.1",
+                "@esbuild/android-x64": "0.23.1",
+                "@esbuild/darwin-arm64": "0.23.1",
+                "@esbuild/darwin-x64": "0.23.1",
+                "@esbuild/freebsd-arm64": "0.23.1",
+                "@esbuild/freebsd-x64": "0.23.1",
+                "@esbuild/linux-arm": "0.23.1",
+                "@esbuild/linux-arm64": "0.23.1",
+                "@esbuild/linux-ia32": "0.23.1",
+                "@esbuild/linux-loong64": "0.23.1",
+                "@esbuild/linux-mips64el": "0.23.1",
+                "@esbuild/linux-ppc64": "0.23.1",
+                "@esbuild/linux-riscv64": "0.23.1",
+                "@esbuild/linux-s390x": "0.23.1",
+                "@esbuild/linux-x64": "0.23.1",
+                "@esbuild/netbsd-x64": "0.23.1",
+                "@esbuild/openbsd-arm64": "0.23.1",
+                "@esbuild/openbsd-x64": "0.23.1",
+                "@esbuild/sunos-x64": "0.23.1",
+                "@esbuild/win32-arm64": "0.23.1",
+                "@esbuild/win32-ia32": "0.23.1",
+                "@esbuild/win32-x64": "0.23.1"
+            }
+        },
         "node_modules/escalade": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -1077,6 +2285,187 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/eslint": {
+            "version": "8.57.1",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+            "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+            "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@eslint-community/eslint-utils": "^4.2.0",
+                "@eslint-community/regexpp": "^4.6.1",
+                "@eslint/eslintrc": "^2.1.4",
+                "@eslint/js": "8.57.1",
+                "@humanwhocodes/config-array": "^0.13.0",
+                "@humanwhocodes/module-importer": "^1.0.1",
+                "@nodelib/fs.walk": "^1.2.8",
+                "@ungap/structured-clone": "^1.2.0",
+                "ajv": "^6.12.4",
+                "chalk": "^4.0.0",
+                "cross-spawn": "^7.0.2",
+                "debug": "^4.3.2",
+                "doctrine": "^3.0.0",
+                "escape-string-regexp": "^4.0.0",
+                "eslint-scope": "^7.2.2",
+                "eslint-visitor-keys": "^3.4.3",
+                "espree": "^9.6.1",
+                "esquery": "^1.4.2",
+                "esutils": "^2.0.2",
+                "fast-deep-equal": "^3.1.3",
+                "file-entry-cache": "^6.0.1",
+                "find-up": "^5.0.0",
+                "glob-parent": "^6.0.2",
+                "globals": "^13.19.0",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.2.0",
+                "imurmurhash": "^0.1.4",
+                "is-glob": "^4.0.0",
+                "is-path-inside": "^3.0.3",
+                "js-yaml": "^4.1.0",
+                "json-stable-stringify-without-jsonify": "^1.0.1",
+                "levn": "^0.4.1",
+                "lodash.merge": "^4.6.2",
+                "minimatch": "^3.1.2",
+                "natural-compare": "^1.4.0",
+                "optionator": "^0.9.3",
+                "strip-ansi": "^6.0.1",
+                "text-table": "^0.2.0"
+            },
+            "bin": {
+                "eslint": "bin/eslint.js"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/eslint-plugin-header": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz",
+            "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==",
+            "dev": true,
+            "license": "MIT",
+            "peerDependencies": {
+                "eslint": ">=7.7.0"
+            }
+        },
+        "node_modules/eslint-scope": {
+            "version": "7.2.2",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+            "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+            "dev": true,
+            "license": "BSD-2-Clause",
+            "dependencies": {
+                "esrecurse": "^4.3.0",
+                "estraverse": "^5.2.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/eslint-visitor-keys": {
+            "version": "3.4.3",
+            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
+        "node_modules/eslint/node_modules/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/eslint/node_modules/glob-parent": {
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+            "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "is-glob": "^4.0.3"
+            },
+            "engines": {
+                "node": ">=10.13.0"
+            }
+        },
+        "node_modules/eslint/node_modules/globals": {
+            "version": "13.24.0",
+            "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+            "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "type-fest": "^0.20.2"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/eslint/node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/eslint/node_modules/type-fest": {
+            "version": "0.20.2",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+            "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+            "dev": true,
+            "license": "(MIT OR CC0-1.0)",
+            "engines": {
+                "node": ">=10"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/espree": {
+            "version": "9.6.1",
+            "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+            "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+            "dev": true,
+            "license": "BSD-2-Clause",
+            "dependencies": {
+                "acorn": "^8.9.0",
+                "acorn-jsx": "^5.3.2",
+                "eslint-visitor-keys": "^3.4.1"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            },
+            "funding": {
+                "url": "https://opencollective.com/eslint"
+            }
+        },
         "node_modules/esprima": {
             "version": "4.0.1",
             "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@@ -1090,6 +2479,113 @@
                 "node": ">=4"
             }
         },
+        "node_modules/esquery": {
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+            "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+            "dev": true,
+            "license": "BSD-3-Clause",
+            "dependencies": {
+                "estraverse": "^5.1.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/esrecurse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+            "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+            "dev": true,
+            "license": "BSD-2-Clause",
+            "dependencies": {
+                "estraverse": "^5.2.0"
+            },
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/estraverse": {
+            "version": "5.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+            "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+            "dev": true,
+            "license": "BSD-2-Clause",
+            "engines": {
+                "node": ">=4.0"
+            }
+        },
+        "node_modules/esutils": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+            "dev": true,
+            "license": "BSD-2-Clause",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/fast-deep-equal": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/fast-glob": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+            "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@nodelib/fs.stat": "^2.0.2",
+                "@nodelib/fs.walk": "^1.2.3",
+                "glob-parent": "^5.1.2",
+                "merge2": "^1.3.0",
+                "micromatch": "^4.0.4"
+            },
+            "engines": {
+                "node": ">=8.6.0"
+            }
+        },
+        "node_modules/fast-json-stable-stringify": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+            "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/fast-levenshtein": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/fastq": {
+            "version": "1.17.1",
+            "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+            "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "reusify": "^1.0.4"
+            }
+        },
+        "node_modules/file-entry-cache": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+            "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "flat-cache": "^3.0.4"
+            },
+            "engines": {
+                "node": "^10.12.0 || >=12.0.0"
+            }
+        },
         "node_modules/fill-range": {
             "version": "7.1.1",
             "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1145,6 +2641,28 @@
                 "flat": "cli.js"
             }
         },
+        "node_modules/flat-cache": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+            "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "flatted": "^3.2.9",
+                "keyv": "^4.5.3",
+                "rimraf": "^3.0.2"
+            },
+            "engines": {
+                "node": "^10.12.0 || >=12.0.0"
+            }
+        },
+        "node_modules/flatted": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
+            "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+            "dev": true,
+            "license": "ISC"
+        },
         "node_modules/foreground-child": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
@@ -1225,6 +2743,19 @@
                 "node": ">=8.0.0"
             }
         },
+        "node_modules/get-tsconfig": {
+            "version": "4.8.1",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
+            "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "resolve-pkg-maps": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+            }
+        },
         "node_modules/glob": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -1294,6 +2825,13 @@
             "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
             "dev": true
         },
+        "node_modules/graphemer": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+            "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/has-flag": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -1334,6 +2872,43 @@
             "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
             "dev": true
         },
+        "node_modules/ignore": {
+            "version": "5.3.2",
+            "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+            "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 4"
+            }
+        },
+        "node_modules/import-fresh": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+            "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "parent-module": "^1.0.0",
+                "resolve-from": "^4.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/import-fresh/node_modules/resolve-from": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+            "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=4"
+            }
+        },
         "node_modules/imurmurhash": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -1426,6 +3001,16 @@
                 "node": ">=0.12.0"
             }
         },
+        "node_modules/is-path-inside": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+            "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=8"
+            }
+        },
         "node_modules/is-plain-obj": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
@@ -1664,6 +3249,27 @@
                 "node": ">=4"
             }
         },
+        "node_modules/json-buffer": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+            "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true,
+            "license": "MIT"
+        },
+        "node_modules/json-stable-stringify-without-jsonify": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+            "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/json5": {
             "version": "2.2.3",
             "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -1676,6 +3282,30 @@
                 "node": ">=6"
             }
         },
+        "node_modules/keyv": {
+            "version": "4.5.4",
+            "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+            "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "json-buffer": "3.0.1"
+            }
+        },
+        "node_modules/levn": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+            "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "prelude-ls": "^1.2.1",
+                "type-check": "~0.4.0"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
         "node_modules/locate-path": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -1697,6 +3327,13 @@
             "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
             "dev": true
         },
+        "node_modules/lodash.merge": {
+            "version": "4.6.2",
+            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+            "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/log-symbols": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -1713,6 +3350,13 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/loupe": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+            "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/lru-cache": {
             "version": "5.1.1",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -1737,6 +3381,13 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/make-error": {
+            "version": "1.3.6",
+            "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+            "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+            "dev": true,
+            "license": "ISC"
+        },
         "node_modules/md5": {
             "version": "2.3.0",
             "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -1748,11 +3399,36 @@
                 "is-buffer": "~1.1.6"
             }
         },
+        "node_modules/merge2": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/micromatch": {
+            "version": "4.0.8",
+            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+            "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "braces": "^3.0.3",
+                "picomatch": "^2.3.1"
+            },
+            "engines": {
+                "node": ">=8.6"
+            }
+        },
         "node_modules/minimatch": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
-            "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+            "version": "5.1.6",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+            "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
             "dev": true,
+            "license": "ISC",
             "dependencies": {
                 "brace-expansion": "^2.0.1"
             },
@@ -1776,32 +3452,32 @@
             }
         },
         "node_modules/mocha": {
-            "version": "10.2.0",
-            "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
-            "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
-            "dev": true,
-            "dependencies": {
-                "ansi-colors": "4.1.1",
-                "browser-stdout": "1.3.1",
-                "chokidar": "3.5.3",
-                "debug": "4.3.4",
-                "diff": "5.0.0",
-                "escape-string-regexp": "4.0.0",
-                "find-up": "5.0.0",
-                "glob": "7.2.0",
-                "he": "1.2.0",
-                "js-yaml": "4.1.0",
-                "log-symbols": "4.1.0",
-                "minimatch": "5.0.1",
-                "ms": "2.1.3",
-                "nanoid": "3.3.3",
-                "serialize-javascript": "6.0.0",
-                "strip-json-comments": "3.1.1",
-                "supports-color": "8.1.1",
-                "workerpool": "6.2.1",
-                "yargs": "16.2.0",
-                "yargs-parser": "20.2.4",
-                "yargs-unparser": "2.0.0"
+            "version": "10.8.2",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
+            "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "ansi-colors": "^4.1.3",
+                "browser-stdout": "^1.3.1",
+                "chokidar": "^3.5.3",
+                "debug": "^4.3.5",
+                "diff": "^5.2.0",
+                "escape-string-regexp": "^4.0.0",
+                "find-up": "^5.0.0",
+                "glob": "^8.1.0",
+                "he": "^1.2.0",
+                "js-yaml": "^4.1.0",
+                "log-symbols": "^4.1.0",
+                "minimatch": "^5.1.6",
+                "ms": "^2.1.3",
+                "serialize-javascript": "^6.0.2",
+                "strip-json-comments": "^3.1.1",
+                "supports-color": "^8.1.1",
+                "workerpool": "^6.5.1",
+                "yargs": "^16.2.0",
+                "yargs-parser": "^20.2.9",
+                "yargs-unparser": "^2.0.0"
             },
             "bin": {
                 "_mocha": "bin/_mocha",
@@ -1809,10 +3485,6 @@
             },
             "engines": {
                 "node": ">= 14.0.0"
-            },
-            "funding": {
-                "type": "opencollective",
-                "url": "https://opencollective.com/mochajs"
             }
         },
         "node_modules/mocha-junit-reporter": {
@@ -1831,23 +3503,39 @@
                 "mocha": ">=2.2.5"
             }
         },
+        "node_modules/mocha/node_modules/glob": {
+            "version": "8.1.0",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+            "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+            "deprecated": "Glob versions prior to v9 are no longer supported",
+            "dev": true,
+            "license": "ISC",
+            "dependencies": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^5.0.1",
+                "once": "^1.3.0"
+            },
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/isaacs"
+            }
+        },
         "node_modules/ms": {
             "version": "2.1.3",
             "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
             "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
             "dev": true
         },
-        "node_modules/nanoid": {
-            "version": "3.3.3",
-            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
-            "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+        "node_modules/natural-compare": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+            "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
             "dev": true,
-            "bin": {
-                "nanoid": "bin/nanoid.cjs"
-            },
-            "engines": {
-                "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-            }
+            "license": "MIT"
         },
         "node_modules/node-preload": {
             "version": "0.2.1",
@@ -2044,6 +3732,24 @@
                 "wrappy": "1"
             }
         },
+        "node_modules/optionator": {
+            "version": "0.9.4",
+            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+            "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "deep-is": "^0.1.3",
+                "fast-levenshtein": "^2.0.6",
+                "levn": "^0.4.1",
+                "prelude-ls": "^1.2.1",
+                "type-check": "^0.4.0",
+                "word-wrap": "^1.2.5"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
         "node_modules/p-limit": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -2110,6 +3816,19 @@
                 "node": ">=8"
             }
         },
+        "node_modules/parent-module": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+            "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "callsites": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/path-exists": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -2137,6 +3856,16 @@
                 "node": ">=8"
             }
         },
+        "node_modules/pathval": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+            "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 14.16"
+            }
+        },
         "node_modules/picocolors": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -2219,6 +3948,16 @@
                 "node": ">=8"
             }
         },
+        "node_modules/prelude-ls": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+            "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
         "node_modules/process-on-spawn": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz",
@@ -2231,11 +3970,43 @@
                 "node": ">=8"
             }
         },
+        "node_modules/punycode": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+            "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/queue-microtask": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ],
+            "license": "MIT"
+        },
         "node_modules/randombytes": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
             "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
             "dev": true,
+            "license": "MIT",
             "dependencies": {
                 "safe-buffer": "^5.1.0"
             }
@@ -2288,6 +4059,27 @@
                 "node": ">=8"
             }
         },
+        "node_modules/resolve-pkg-maps": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+            "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+            "dev": true,
+            "license": "MIT",
+            "funding": {
+                "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+            }
+        },
+        "node_modules/reusify": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "iojs": ">=1.0.0",
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/rimraf": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -2303,6 +4095,30 @@
                 "url": "https://github.com/sponsors/isaacs"
             }
         },
+        "node_modules/run-parallel": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+            "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+            "dev": true,
+            "funding": [
+                {
+                    "type": "github",
+                    "url": "https://github.com/sponsors/feross"
+                },
+                {
+                    "type": "patreon",
+                    "url": "https://www.patreon.com/feross"
+                },
+                {
+                    "type": "consulting",
+                    "url": "https://feross.org/support"
+                }
+            ],
+            "license": "MIT",
+            "dependencies": {
+                "queue-microtask": "^1.2.2"
+            }
+        },
         "node_modules/safe-buffer": {
             "version": "5.2.1",
             "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -2321,7 +4137,8 @@
                     "type": "consulting",
                     "url": "https://feross.org/support"
                 }
-            ]
+            ],
+            "license": "MIT"
         },
         "node_modules/semver": {
             "version": "6.3.1",
@@ -2333,10 +4150,11 @@
             }
         },
         "node_modules/serialize-javascript": {
-            "version": "6.0.0",
-            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
-            "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+            "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
             "dev": true,
+            "license": "BSD-3-Clause",
             "dependencies": {
                 "randombytes": "^2.1.0"
             }
@@ -2504,6 +4322,13 @@
                 "node": "*"
             }
         },
+        "node_modules/text-table": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/to-fast-properties": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -2526,6 +4351,106 @@
                 "node": ">=8.0"
             }
         },
+        "node_modules/ts-api-utils": {
+            "version": "1.4.3",
+            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+            "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=16"
+            },
+            "peerDependencies": {
+                "typescript": ">=4.2.0"
+            }
+        },
+        "node_modules/ts-node": {
+            "version": "10.9.2",
+            "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+            "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@cspotcode/source-map-support": "^0.8.0",
+                "@tsconfig/node10": "^1.0.7",
+                "@tsconfig/node12": "^1.0.7",
+                "@tsconfig/node14": "^1.0.0",
+                "@tsconfig/node16": "^1.0.2",
+                "acorn": "^8.4.1",
+                "acorn-walk": "^8.1.1",
+                "arg": "^4.1.0",
+                "create-require": "^1.1.0",
+                "diff": "^4.0.1",
+                "make-error": "^1.1.1",
+                "v8-compile-cache-lib": "^3.0.1",
+                "yn": "3.1.1"
+            },
+            "bin": {
+                "ts-node": "dist/bin.js",
+                "ts-node-cwd": "dist/bin-cwd.js",
+                "ts-node-esm": "dist/bin-esm.js",
+                "ts-node-script": "dist/bin-script.js",
+                "ts-node-transpile-only": "dist/bin-transpile.js",
+                "ts-script": "dist/bin-script-deprecated.js"
+            },
+            "peerDependencies": {
+                "@swc/core": ">=1.2.50",
+                "@swc/wasm": ">=1.2.50",
+                "@types/node": "*",
+                "typescript": ">=2.7"
+            },
+            "peerDependenciesMeta": {
+                "@swc/core": {
+                    "optional": true
+                },
+                "@swc/wasm": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/ts-node/node_modules/diff": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+            "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+            "dev": true,
+            "license": "BSD-3-Clause",
+            "engines": {
+                "node": ">=0.3.1"
+            }
+        },
+        "node_modules/tsx": {
+            "version": "4.19.2",
+            "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
+            "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "esbuild": "~0.23.0",
+                "get-tsconfig": "^4.7.5"
+            },
+            "bin": {
+                "tsx": "dist/cli.mjs"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            },
+            "optionalDependencies": {
+                "fsevents": "~2.3.3"
+            }
+        },
+        "node_modules/type-check": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+            "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "prelude-ls": "^1.2.1"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
         "node_modules/type-fest": {
             "version": "0.8.1",
             "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
@@ -2541,9 +4466,53 @@
             "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
             "dev": true,
             "dependencies": {
-                "is-typedarray": "^1.0.0"
+                "is-typedarray": "^1.0.0"
+            }
+        },
+        "node_modules/typescript": {
+            "version": "5.7.2",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+            "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
+            "dev": true,
+            "license": "Apache-2.0",
+            "bin": {
+                "tsc": "bin/tsc",
+                "tsserver": "bin/tsserver"
+            },
+            "engines": {
+                "node": ">=14.17"
+            }
+        },
+        "node_modules/typescript-eslint": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.0.tgz",
+            "integrity": "sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==",
+            "dev": true,
+            "license": "MIT",
+            "dependencies": {
+                "@typescript-eslint/eslint-plugin": "8.18.0",
+                "@typescript-eslint/parser": "8.18.0",
+                "@typescript-eslint/utils": "8.18.0"
+            },
+            "engines": {
+                "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/typescript-eslint"
+            },
+            "peerDependencies": {
+                "eslint": "^8.57.0 || ^9.0.0",
+                "typescript": ">=4.8.4 <5.8.0"
             }
         },
+        "node_modules/undici-types": {
+            "version": "6.20.0",
+            "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+            "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/update-browserslist-db": {
             "version": "1.0.11",
             "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
@@ -2574,6 +4543,16 @@
                 "browserslist": ">= 4.21.0"
             }
         },
+        "node_modules/uri-js": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+            "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+            "dev": true,
+            "license": "BSD-2-Clause",
+            "dependencies": {
+                "punycode": "^2.1.0"
+            }
+        },
         "node_modules/uuid": {
             "version": "8.3.2",
             "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -2583,6 +4562,13 @@
                 "uuid": "dist/bin/uuid"
             }
         },
+        "node_modules/v8-compile-cache-lib": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+            "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+            "dev": true,
+            "license": "MIT"
+        },
         "node_modules/which": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -2604,11 +4590,22 @@
             "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
             "dev": true
         },
+        "node_modules/word-wrap": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+            "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/workerpool": {
-            "version": "6.2.1",
-            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
-            "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
-            "dev": true
+            "version": "6.5.1",
+            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+            "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
+            "dev": true,
+            "license": "Apache-2.0"
         },
         "node_modules/wrap-ansi": {
             "version": "7.0.0",
@@ -2645,6 +4642,27 @@
                 "typedarray-to-buffer": "^3.1.5"
             }
         },
+        "node_modules/ws": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+            "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+            "license": "MIT",
+            "engines": {
+                "node": ">=10.0.0"
+            },
+            "peerDependencies": {
+                "bufferutil": "^4.0.1",
+                "utf-8-validate": ">=5.0.2"
+            },
+            "peerDependenciesMeta": {
+                "bufferutil": {
+                    "optional": true
+                },
+                "utf-8-validate": {
+                    "optional": true
+                }
+            }
+        },
         "node_modules/xml": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
@@ -2685,10 +4703,11 @@
             }
         },
         "node_modules/yargs-parser": {
-            "version": "20.2.4",
-            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
-            "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+            "version": "20.2.9",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+            "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
             "dev": true,
+            "license": "ISC",
             "engines": {
                 "node": ">=10"
             }
@@ -2732,6 +4751,16 @@
                 "url": "https://github.com/sponsors/sindresorhus"
             }
         },
+        "node_modules/yn": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+            "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+            "dev": true,
+            "license": "MIT",
+            "engines": {
+                "node": ">=6"
+            }
+        },
         "node_modules/yocto-queue": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -2955,137 +4984,444 @@
             "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
             "dev": true
         },
-        "@babel/helper-validator-option": {
-            "version": "7.22.5",
-            "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
-            "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
-            "dev": true
+        "@babel/helper-validator-option": {
+            "version": "7.22.5",
+            "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
+            "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
+            "dev": true
+        },
+        "@babel/helpers": {
+            "version": "7.22.11",
+            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz",
+            "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==",
+            "dev": true,
+            "requires": {
+                "@babel/template": "^7.22.5",
+                "@babel/traverse": "^7.22.11",
+                "@babel/types": "^7.22.11"
+            }
+        },
+        "@babel/highlight": {
+            "version": "7.22.13",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+            "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
+            "dev": true,
+            "requires": {
+                "@babel/helper-validator-identifier": "^7.22.5",
+                "chalk": "^2.4.2",
+                "js-tokens": "^4.0.0"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "3.2.1",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+                    "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+                    "dev": true,
+                    "requires": {
+                        "color-convert": "^1.9.0"
+                    }
+                },
+                "chalk": {
+                    "version": "2.4.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+                    "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-styles": "^3.2.1",
+                        "escape-string-regexp": "^1.0.5",
+                        "supports-color": "^5.3.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "1.9.3",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+                    "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+                    "dev": true,
+                    "requires": {
+                        "color-name": "1.1.3"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.3",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+                    "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+                    "dev": true
+                },
+                "escape-string-regexp": {
+                    "version": "1.0.5",
+                    "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+                    "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+                    "dev": true
+                },
+                "has-flag": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+                    "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+                    "dev": true
+                },
+                "supports-color": {
+                    "version": "5.5.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+                    "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^3.0.0"
+                    }
+                }
+            }
+        },
+        "@babel/parser": {
+            "version": "7.23.0",
+            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
+            "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
+            "dev": true
+        },
+        "@babel/template": {
+            "version": "7.22.15",
+            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
+            "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+            "dev": true,
+            "requires": {
+                "@babel/code-frame": "^7.22.13",
+                "@babel/parser": "^7.22.15",
+                "@babel/types": "^7.22.15"
+            }
+        },
+        "@babel/traverse": {
+            "version": "7.23.2",
+            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
+            "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
+            "dev": true,
+            "requires": {
+                "@babel/code-frame": "^7.22.13",
+                "@babel/generator": "^7.23.0",
+                "@babel/helper-environment-visitor": "^7.22.20",
+                "@babel/helper-function-name": "^7.23.0",
+                "@babel/helper-hoist-variables": "^7.22.5",
+                "@babel/helper-split-export-declaration": "^7.22.6",
+                "@babel/parser": "^7.23.0",
+                "@babel/types": "^7.23.0",
+                "debug": "^4.1.0",
+                "globals": "^11.1.0"
+            }
+        },
+        "@babel/types": {
+            "version": "7.23.0",
+            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
+            "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
+            "dev": true,
+            "requires": {
+                "@babel/helper-string-parser": "^7.22.5",
+                "@babel/helper-validator-identifier": "^7.22.20",
+                "to-fast-properties": "^2.0.0"
+            }
+        },
+        "@cspotcode/source-map-support": {
+            "version": "0.8.1",
+            "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+            "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+            "dev": true,
+            "requires": {
+                "@jridgewell/trace-mapping": "0.3.9"
+            },
+            "dependencies": {
+                "@jridgewell/trace-mapping": {
+                    "version": "0.3.9",
+                    "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+                    "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+                    "dev": true,
+                    "requires": {
+                        "@jridgewell/resolve-uri": "^3.0.3",
+                        "@jridgewell/sourcemap-codec": "^1.4.10"
+                    }
+                }
+            }
+        },
+        "@esbuild/aix-ppc64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+            "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/android-arm": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+            "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/android-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+            "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/android-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+            "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/darwin-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+            "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/darwin-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+            "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/freebsd-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+            "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/freebsd-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+            "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-arm": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+            "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+            "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-ia32": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+            "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-loong64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+            "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-mips64el": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+            "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-ppc64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+            "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-riscv64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+            "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-s390x": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+            "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/linux-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+            "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/netbsd-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+            "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/openbsd-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+            "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/openbsd-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+            "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/sunos-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+            "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/win32-arm64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+            "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/win32-ia32": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+            "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+            "dev": true,
+            "optional": true
+        },
+        "@esbuild/win32-x64": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+            "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+            "dev": true,
+            "optional": true
         },
-        "@babel/helpers": {
-            "version": "7.22.11",
-            "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz",
-            "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==",
+        "@eslint-community/eslint-utils": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+            "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
             "dev": true,
             "requires": {
-                "@babel/template": "^7.22.5",
-                "@babel/traverse": "^7.22.11",
-                "@babel/types": "^7.22.11"
+                "eslint-visitor-keys": "^3.4.3"
             }
         },
-        "@babel/highlight": {
-            "version": "7.22.13",
-            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
-            "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
+        "@eslint-community/regexpp": {
+            "version": "4.12.1",
+            "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+            "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+            "dev": true
+        },
+        "@eslint/eslintrc": {
+            "version": "2.1.4",
+            "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+            "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
             "dev": true,
             "requires": {
-                "@babel/helper-validator-identifier": "^7.22.5",
-                "chalk": "^2.4.2",
-                "js-tokens": "^4.0.0"
+                "ajv": "^6.12.4",
+                "debug": "^4.3.2",
+                "espree": "^9.6.0",
+                "globals": "^13.19.0",
+                "ignore": "^5.2.0",
+                "import-fresh": "^3.2.1",
+                "js-yaml": "^4.1.0",
+                "minimatch": "^3.1.2",
+                "strip-json-comments": "^3.1.1"
             },
             "dependencies": {
-                "ansi-styles": {
-                    "version": "3.2.1",
-                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-                    "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+                "brace-expansion": {
+                    "version": "1.1.11",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
                     "dev": true,
                     "requires": {
-                        "color-convert": "^1.9.0"
+                        "balanced-match": "^1.0.0",
+                        "concat-map": "0.0.1"
                     }
                 },
-                "chalk": {
-                    "version": "2.4.2",
-                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-                    "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+                "globals": {
+                    "version": "13.24.0",
+                    "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+                    "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
                     "dev": true,
                     "requires": {
-                        "ansi-styles": "^3.2.1",
-                        "escape-string-regexp": "^1.0.5",
-                        "supports-color": "^5.3.0"
+                        "type-fest": "^0.20.2"
                     }
                 },
-                "color-convert": {
-                    "version": "1.9.3",
-                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-                    "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+                "minimatch": {
+                    "version": "3.1.2",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+                    "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
                     "dev": true,
                     "requires": {
-                        "color-name": "1.1.3"
+                        "brace-expansion": "^1.1.7"
                     }
                 },
-                "color-name": {
-                    "version": "1.1.3",
-                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-                    "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
-                    "dev": true
-                },
-                "escape-string-regexp": {
-                    "version": "1.0.5",
-                    "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-                    "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
-                    "dev": true
-                },
-                "has-flag": {
-                    "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-                    "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+                "type-fest": {
+                    "version": "0.20.2",
+                    "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+                    "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
                     "dev": true
-                },
-                "supports-color": {
-                    "version": "5.5.0",
-                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-                    "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-                    "dev": true,
-                    "requires": {
-                        "has-flag": "^3.0.0"
-                    }
                 }
             }
         },
-        "@babel/parser": {
-            "version": "7.23.0",
-            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
-            "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
+        "@eslint/js": {
+            "version": "8.57.1",
+            "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+            "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
             "dev": true
         },
-        "@babel/template": {
-            "version": "7.22.15",
-            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
-            "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
+        "@humanwhocodes/config-array": {
+            "version": "0.13.0",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+            "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
             "dev": true,
             "requires": {
-                "@babel/code-frame": "^7.22.13",
-                "@babel/parser": "^7.22.15",
-                "@babel/types": "^7.22.15"
+                "@humanwhocodes/object-schema": "^2.0.3",
+                "debug": "^4.3.1",
+                "minimatch": "^3.0.5"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "1.1.11",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                    "dev": true,
+                    "requires": {
+                        "balanced-match": "^1.0.0",
+                        "concat-map": "0.0.1"
+                    }
+                },
+                "minimatch": {
+                    "version": "3.1.2",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+                    "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+                    "dev": true,
+                    "requires": {
+                        "brace-expansion": "^1.1.7"
+                    }
+                }
             }
         },
-        "@babel/traverse": {
-            "version": "7.23.2",
-            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
-            "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
-            "dev": true,
-            "requires": {
-                "@babel/code-frame": "^7.22.13",
-                "@babel/generator": "^7.23.0",
-                "@babel/helper-environment-visitor": "^7.22.20",
-                "@babel/helper-function-name": "^7.23.0",
-                "@babel/helper-hoist-variables": "^7.22.5",
-                "@babel/helper-split-export-declaration": "^7.22.6",
-                "@babel/parser": "^7.23.0",
-                "@babel/types": "^7.23.0",
-                "debug": "^4.1.0",
-                "globals": "^11.1.0"
-            }
+        "@humanwhocodes/module-importer": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+            "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+            "dev": true
         },
-        "@babel/types": {
-            "version": "7.23.0",
-            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
-            "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
-            "dev": true,
-            "requires": {
-                "@babel/helper-string-parser": "^7.22.5",
-                "@babel/helper-validator-identifier": "^7.22.20",
-                "to-fast-properties": "^2.0.0"
-            }
+        "@humanwhocodes/object-schema": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+            "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+            "dev": true
         },
         "@istanbuljs/load-nyc-config": {
             "version": "1.1.0",
@@ -3203,6 +5539,253 @@
                 "@jridgewell/sourcemap-codec": "^1.4.14"
             }
         },
+        "@nodelib/fs.scandir": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+            "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+            "dev": true,
+            "requires": {
+                "@nodelib/fs.stat": "2.0.5",
+                "run-parallel": "^1.1.9"
+            }
+        },
+        "@nodelib/fs.stat": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+            "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+            "dev": true
+        },
+        "@nodelib/fs.walk": {
+            "version": "1.2.8",
+            "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+            "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+            "dev": true,
+            "requires": {
+                "@nodelib/fs.scandir": "2.1.5",
+                "fastq": "^1.6.0"
+            }
+        },
+        "@tsconfig/node10": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+            "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+            "dev": true
+        },
+        "@tsconfig/node12": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+            "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+            "dev": true
+        },
+        "@tsconfig/node14": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+            "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+            "dev": true
+        },
+        "@tsconfig/node16": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+            "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+            "dev": true
+        },
+        "@types/chai": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.1.tgz",
+            "integrity": "sha512-5T8ajsg3M/FOncpLYW7sdOcD6yf4+722sze/tc4KQV0P8Z2rAr3SAuHCIkYmYpt8VbcQlnz8SxlOlPQYefe4cA==",
+            "dev": true,
+            "requires": {
+                "@types/deep-eql": "*"
+            }
+        },
+        "@types/chai-as-promised": {
+            "version": "8.0.1",
+            "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.1.tgz",
+            "integrity": "sha512-dAlDhLjJlABwAVYObo9TPWYTRg9NaQM5CXeaeJYcYAkvzUf0JRLIiog88ao2Wqy/20WUnhbbUZcgvngEbJ3YXQ==",
+            "dev": true,
+            "requires": {
+                "@types/chai": "*"
+            }
+        },
+        "@types/deep-eql": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+            "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+            "dev": true
+        },
+        "@types/mocha": {
+            "version": "10.0.10",
+            "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
+            "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
+            "dev": true
+        },
+        "@types/node": {
+            "version": "22.10.1",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz",
+            "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==",
+            "dev": true,
+            "requires": {
+                "undici-types": "~6.20.0"
+            }
+        },
+        "@types/ws": {
+            "version": "8.5.13",
+            "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
+            "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
+            "dev": true,
+            "requires": {
+                "@types/node": "*"
+            }
+        },
+        "@typescript-eslint/eslint-plugin": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
+            "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/regexpp": "^4.10.0",
+                "@typescript-eslint/scope-manager": "8.18.0",
+                "@typescript-eslint/type-utils": "8.18.0",
+                "@typescript-eslint/utils": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.3.1",
+                "natural-compare": "^1.4.0",
+                "ts-api-utils": "^1.3.0"
+            }
+        },
+        "@typescript-eslint/parser": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz",
+            "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/scope-manager": "8.18.0",
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/typescript-estree": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0",
+                "debug": "^4.3.4"
+            }
+        },
+        "@typescript-eslint/scope-manager": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz",
+            "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0"
+            }
+        },
+        "@typescript-eslint/type-utils": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz",
+            "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/typescript-estree": "8.18.0",
+                "@typescript-eslint/utils": "8.18.0",
+                "debug": "^4.3.4",
+                "ts-api-utils": "^1.3.0"
+            }
+        },
+        "@typescript-eslint/types": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
+            "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
+            "dev": true
+        },
+        "@typescript-eslint/typescript-estree": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
+            "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/visitor-keys": "8.18.0",
+                "debug": "^4.3.4",
+                "fast-glob": "^3.3.2",
+                "is-glob": "^4.0.3",
+                "minimatch": "^9.0.4",
+                "semver": "^7.6.0",
+                "ts-api-utils": "^1.3.0"
+            },
+            "dependencies": {
+                "minimatch": {
+                    "version": "9.0.5",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+                    "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+                    "dev": true,
+                    "requires": {
+                        "brace-expansion": "^2.0.1"
+                    }
+                },
+                "semver": {
+                    "version": "7.6.3",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+                    "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+                    "dev": true
+                }
+            }
+        },
+        "@typescript-eslint/utils": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz",
+            "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/eslint-utils": "^4.4.0",
+                "@typescript-eslint/scope-manager": "8.18.0",
+                "@typescript-eslint/types": "8.18.0",
+                "@typescript-eslint/typescript-estree": "8.18.0"
+            }
+        },
+        "@typescript-eslint/visitor-keys": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
+            "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/types": "8.18.0",
+                "eslint-visitor-keys": "^4.2.0"
+            },
+            "dependencies": {
+                "eslint-visitor-keys": {
+                    "version": "4.2.0",
+                    "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+                    "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+                    "dev": true
+                }
+            }
+        },
+        "@ungap/structured-clone": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
+            "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==",
+            "dev": true
+        },
+        "acorn": {
+            "version": "8.14.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+            "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+            "dev": true
+        },
+        "acorn-jsx": {
+            "version": "5.3.2",
+            "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+            "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+            "dev": true,
+            "requires": {}
+        },
+        "acorn-walk": {
+            "version": "8.3.4",
+            "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+            "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+            "dev": true,
+            "requires": {
+                "acorn": "^8.11.0"
+            }
+        },
         "aggregate-error": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -3213,10 +5796,22 @@
                 "indent-string": "^4.0.0"
             }
         },
+        "ajv": {
+            "version": "6.12.6",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+            "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+            "dev": true,
+            "requires": {
+                "fast-deep-equal": "^3.1.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            }
+        },
         "ansi-colors": {
-            "version": "4.1.1",
-            "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
-            "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+            "version": "4.1.3",
+            "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+            "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
             "dev": true
         },
         "ansi-regex": {
@@ -3259,12 +5854,24 @@
             "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
             "dev": true
         },
+        "arg": {
+            "version": "4.1.3",
+            "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+            "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+            "dev": true
+        },
         "argparse": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
             "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
             "dev": true
         },
+        "assertion-error": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+            "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+            "dev": true
+        },
         "balanced-match": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3325,6 +5932,12 @@
                 "write-file-atomic": "^3.0.0"
             }
         },
+        "callsites": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+            "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+            "dev": true
+        },
         "camelcase": {
             "version": "5.3.1",
             "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
@@ -3337,6 +5950,28 @@
             "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==",
             "dev": true
         },
+        "chai": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+            "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+            "dev": true,
+            "requires": {
+                "assertion-error": "^2.0.1",
+                "check-error": "^2.1.1",
+                "deep-eql": "^5.0.1",
+                "loupe": "^3.1.0",
+                "pathval": "^2.0.0"
+            }
+        },
+        "chai-as-promised": {
+            "version": "8.0.1",
+            "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.1.tgz",
+            "integrity": "sha512-OIEJtOL8xxJSH8JJWbIoRjybbzR52iFuDHuF8eb+nTPD6tgXLjRqsgnUGqQfFODxYvq5QdirT0pN9dZ0+Gz6rA==",
+            "dev": true,
+            "requires": {
+                "check-error": "^2.0.0"
+            }
+        },
         "chalk": {
             "version": "4.1.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -3364,6 +5999,12 @@
             "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
             "dev": true
         },
+        "check-error": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+            "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+            "dev": true
+        },
         "chokidar": {
             "version": "3.5.3",
             "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -3380,6 +6021,36 @@
                 "readdirp": "~3.6.0"
             }
         },
+        "chronik-client": {
+            "version": "file:../chronik-client",
+            "requires": {
+                "@types/chai": "^4.2.22",
+                "@types/chai-as-promised": "^7.1.4",
+                "@types/mocha": "^9.0.0",
+                "@types/ws": "^8.2.1",
+                "@typescript-eslint/eslint-plugin": "^5.5.0",
+                "@typescript-eslint/parser": "^5.5.0",
+                "axios": "^1.6.3",
+                "chai": "^4.3.4",
+                "chai-as-promised": "^7.1.1",
+                "ecashaddrjs": "file:../ecashaddrjs",
+                "eslint": "^8.37.0",
+                "eslint-config-prettier": "^8.3.0",
+                "eslint-plugin-header": "^3.1.1",
+                "isomorphic-ws": "^4.0.1",
+                "mocha": "^9.1.3",
+                "mocha-junit-reporter": "^2.2.0",
+                "mocha-suppress-logs": "^0.3.1",
+                "prettier": "^2.5.1",
+                "prettier-plugin-organize-imports": "^2.3.4",
+                "protobufjs": "^6.8.8",
+                "ts-node": "^10.4.0",
+                "ts-proto": "^1.92.1",
+                "typedoc": "^0.22.10",
+                "typescript": "^4.5.2",
+                "ws": "^8.3.0"
+            }
+        },
         "clean-stack": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -3430,6 +6101,12 @@
             "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
             "dev": true
         },
+        "create-require": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+            "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+            "dev": true
+        },
         "cross-spawn": {
             "version": "7.0.3",
             "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3448,20 +6125,12 @@
             "dev": true
         },
         "debug": {
-            "version": "4.3.4",
-            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "version": "4.4.0",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+            "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
             "dev": true,
             "requires": {
-                "ms": "2.1.2"
-            },
-            "dependencies": {
-                "ms": {
-                    "version": "2.1.2",
-                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-                    "dev": true
-                }
+                "ms": "^2.1.3"
             }
         },
         "decamelize": {
@@ -3470,6 +6139,18 @@
             "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
             "dev": true
         },
+        "deep-eql": {
+            "version": "5.0.2",
+            "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+            "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+            "dev": true
+        },
+        "deep-is": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+            "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+            "dev": true
+        },
         "default-require-extensions": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz",
@@ -3480,18 +6161,29 @@
             }
         },
         "diff": {
-            "version": "5.0.0",
-            "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
-            "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+            "version": "5.2.0",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
+            "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
             "dev": true
         },
+        "doctrine": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+            "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+            "dev": true,
+            "requires": {
+                "esutils": "^2.0.2"
+            }
+        },
         "ecashaddrjs": {
             "version": "file:../ecashaddrjs",
             "requires": {
                 "@babel/cli": "^7.21.0",
                 "@babel/core": "^7.21.3",
                 "@babel/preset-env": "^7.20.2",
-                "babel-loader": "^9.1.2",
+                "@types/chai": "^5.0.1",
+                "@types/mocha": "^10.0.7",
+                "@typescript-eslint/parser": "^8.15.0",
                 "big-integer": "1.6.36",
                 "bs58check": "^3.0.1",
                 "buffer": "^6.0.3",
@@ -3504,6 +6196,8 @@
                 "mocha-suppress-logs": "^0.3.1",
                 "nyc": "^15.1.0",
                 "random-js": "^2.1.0",
+                "ts-loader": "^9.5.1",
+                "ts-node": "^10.9.2",
                 "webpack": "^5.76.2",
                 "webpack-cli": "^5.0.1"
             }
@@ -3526,6 +6220,38 @@
             "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
             "dev": true
         },
+        "esbuild": {
+            "version": "0.23.1",
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+            "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+            "dev": true,
+            "requires": {
+                "@esbuild/aix-ppc64": "0.23.1",
+                "@esbuild/android-arm": "0.23.1",
+                "@esbuild/android-arm64": "0.23.1",
+                "@esbuild/android-x64": "0.23.1",
+                "@esbuild/darwin-arm64": "0.23.1",
+                "@esbuild/darwin-x64": "0.23.1",
+                "@esbuild/freebsd-arm64": "0.23.1",
+                "@esbuild/freebsd-x64": "0.23.1",
+                "@esbuild/linux-arm": "0.23.1",
+                "@esbuild/linux-arm64": "0.23.1",
+                "@esbuild/linux-ia32": "0.23.1",
+                "@esbuild/linux-loong64": "0.23.1",
+                "@esbuild/linux-mips64el": "0.23.1",
+                "@esbuild/linux-ppc64": "0.23.1",
+                "@esbuild/linux-riscv64": "0.23.1",
+                "@esbuild/linux-s390x": "0.23.1",
+                "@esbuild/linux-x64": "0.23.1",
+                "@esbuild/netbsd-x64": "0.23.1",
+                "@esbuild/openbsd-arm64": "0.23.1",
+                "@esbuild/openbsd-x64": "0.23.1",
+                "@esbuild/sunos-x64": "0.23.1",
+                "@esbuild/win32-arm64": "0.23.1",
+                "@esbuild/win32-ia32": "0.23.1",
+                "@esbuild/win32-x64": "0.23.1"
+            }
+        },
         "escalade": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -3538,12 +6264,216 @@
             "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
             "dev": true
         },
+        "eslint": {
+            "version": "8.57.1",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+            "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+            "dev": true,
+            "requires": {
+                "@eslint-community/eslint-utils": "^4.2.0",
+                "@eslint-community/regexpp": "^4.6.1",
+                "@eslint/eslintrc": "^2.1.4",
+                "@eslint/js": "8.57.1",
+                "@humanwhocodes/config-array": "^0.13.0",
+                "@humanwhocodes/module-importer": "^1.0.1",
+                "@nodelib/fs.walk": "^1.2.8",
+                "@ungap/structured-clone": "^1.2.0",
+                "ajv": "^6.12.4",
+                "chalk": "^4.0.0",
+                "cross-spawn": "^7.0.2",
+                "debug": "^4.3.2",
+                "doctrine": "^3.0.0",
+                "escape-string-regexp": "^4.0.0",
+                "eslint-scope": "^7.2.2",
+                "eslint-visitor-keys": "^3.4.3",
+                "espree": "^9.6.1",
+                "esquery": "^1.4.2",
+                "esutils": "^2.0.2",
+                "fast-deep-equal": "^3.1.3",
+                "file-entry-cache": "^6.0.1",
+                "find-up": "^5.0.0",
+                "glob-parent": "^6.0.2",
+                "globals": "^13.19.0",
+                "graphemer": "^1.4.0",
+                "ignore": "^5.2.0",
+                "imurmurhash": "^0.1.4",
+                "is-glob": "^4.0.0",
+                "is-path-inside": "^3.0.3",
+                "js-yaml": "^4.1.0",
+                "json-stable-stringify-without-jsonify": "^1.0.1",
+                "levn": "^0.4.1",
+                "lodash.merge": "^4.6.2",
+                "minimatch": "^3.1.2",
+                "natural-compare": "^1.4.0",
+                "optionator": "^0.9.3",
+                "strip-ansi": "^6.0.1",
+                "text-table": "^0.2.0"
+            },
+            "dependencies": {
+                "brace-expansion": {
+                    "version": "1.1.11",
+                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                    "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+                    "dev": true,
+                    "requires": {
+                        "balanced-match": "^1.0.0",
+                        "concat-map": "0.0.1"
+                    }
+                },
+                "glob-parent": {
+                    "version": "6.0.2",
+                    "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+                    "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+                    "dev": true,
+                    "requires": {
+                        "is-glob": "^4.0.3"
+                    }
+                },
+                "globals": {
+                    "version": "13.24.0",
+                    "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+                    "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+                    "dev": true,
+                    "requires": {
+                        "type-fest": "^0.20.2"
+                    }
+                },
+                "minimatch": {
+                    "version": "3.1.2",
+                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+                    "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+                    "dev": true,
+                    "requires": {
+                        "brace-expansion": "^1.1.7"
+                    }
+                },
+                "type-fest": {
+                    "version": "0.20.2",
+                    "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+                    "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+                    "dev": true
+                }
+            }
+        },
+        "eslint-plugin-header": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz",
+            "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==",
+            "dev": true,
+            "requires": {}
+        },
+        "eslint-scope": {
+            "version": "7.2.2",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+            "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+            "dev": true,
+            "requires": {
+                "esrecurse": "^4.3.0",
+                "estraverse": "^5.2.0"
+            }
+        },
+        "eslint-visitor-keys": {
+            "version": "3.4.3",
+            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+            "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+            "dev": true
+        },
+        "espree": {
+            "version": "9.6.1",
+            "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+            "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+            "dev": true,
+            "requires": {
+                "acorn": "^8.9.0",
+                "acorn-jsx": "^5.3.2",
+                "eslint-visitor-keys": "^3.4.1"
+            }
+        },
         "esprima": {
             "version": "4.0.1",
             "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
             "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
             "dev": true
         },
+        "esquery": {
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+            "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+            "dev": true,
+            "requires": {
+                "estraverse": "^5.1.0"
+            }
+        },
+        "esrecurse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+            "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+            "dev": true,
+            "requires": {
+                "estraverse": "^5.2.0"
+            }
+        },
+        "estraverse": {
+            "version": "5.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+            "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+            "dev": true
+        },
+        "esutils": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+            "dev": true
+        },
+        "fast-deep-equal": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+            "dev": true
+        },
+        "fast-glob": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+            "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+            "dev": true,
+            "requires": {
+                "@nodelib/fs.stat": "^2.0.2",
+                "@nodelib/fs.walk": "^1.2.3",
+                "glob-parent": "^5.1.2",
+                "merge2": "^1.3.0",
+                "micromatch": "^4.0.4"
+            }
+        },
+        "fast-json-stable-stringify": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+            "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+            "dev": true
+        },
+        "fast-levenshtein": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+            "dev": true
+        },
+        "fastq": {
+            "version": "1.17.1",
+            "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+            "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+            "dev": true,
+            "requires": {
+                "reusify": "^1.0.4"
+            }
+        },
+        "file-entry-cache": {
+            "version": "6.0.1",
+            "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+            "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+            "dev": true,
+            "requires": {
+                "flat-cache": "^3.0.4"
+            }
+        },
         "fill-range": {
             "version": "7.1.1",
             "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3580,6 +6510,23 @@
             "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
             "dev": true
         },
+        "flat-cache": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+            "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+            "dev": true,
+            "requires": {
+                "flatted": "^3.2.9",
+                "keyv": "^4.5.3",
+                "rimraf": "^3.0.2"
+            }
+        },
+        "flatted": {
+            "version": "3.3.2",
+            "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
+            "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+            "dev": true
+        },
         "foreground-child": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
@@ -3627,6 +6574,15 @@
             "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
             "dev": true
         },
+        "get-tsconfig": {
+            "version": "4.8.1",
+            "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
+            "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
+            "dev": true,
+            "requires": {
+                "resolve-pkg-maps": "^1.0.0"
+            }
+        },
         "glob": {
             "version": "7.2.0",
             "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
@@ -3683,6 +6639,12 @@
             "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
             "dev": true
         },
+        "graphemer": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+            "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+            "dev": true
+        },
         "has-flag": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3711,6 +6673,30 @@
             "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
             "dev": true
         },
+        "ignore": {
+            "version": "5.3.2",
+            "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+            "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+            "dev": true
+        },
+        "import-fresh": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+            "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+            "dev": true,
+            "requires": {
+                "parent-module": "^1.0.0",
+                "resolve-from": "^4.0.0"
+            },
+            "dependencies": {
+                "resolve-from": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+                    "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+                    "dev": true
+                }
+            }
+        },
         "imurmurhash": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -3781,6 +6767,12 @@
             "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
             "dev": true
         },
+        "is-path-inside": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+            "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+            "dev": true
+        },
         "is-plain-obj": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
@@ -3955,12 +6947,49 @@
             "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
             "dev": true
         },
+        "json-buffer": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+            "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+            "dev": true
+        },
+        "json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+            "dev": true
+        },
+        "json-stable-stringify-without-jsonify": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+            "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+            "dev": true
+        },
         "json5": {
             "version": "2.2.3",
             "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
             "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
             "dev": true
         },
+        "keyv": {
+            "version": "4.5.4",
+            "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+            "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+            "dev": true,
+            "requires": {
+                "json-buffer": "3.0.1"
+            }
+        },
+        "levn": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+            "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+            "dev": true,
+            "requires": {
+                "prelude-ls": "^1.2.1",
+                "type-check": "~0.4.0"
+            }
+        },
         "locate-path": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -3976,6 +7005,12 @@
             "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
             "dev": true
         },
+        "lodash.merge": {
+            "version": "4.6.2",
+            "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+            "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+            "dev": true
+        },
         "log-symbols": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -3986,6 +7021,12 @@
                 "is-unicode-supported": "^0.1.0"
             }
         },
+        "loupe": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+            "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+            "dev": true
+        },
         "lru-cache": {
             "version": "5.1.1",
             "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4004,6 +7045,12 @@
                 "semver": "^6.0.0"
             }
         },
+        "make-error": {
+            "version": "1.3.6",
+            "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+            "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+            "dev": true
+        },
         "md5": {
             "version": "2.3.0",
             "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -4015,10 +7062,26 @@
                 "is-buffer": "~1.1.6"
             }
         },
+        "merge2": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+            "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+            "dev": true
+        },
+        "micromatch": {
+            "version": "4.0.8",
+            "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+            "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+            "dev": true,
+            "requires": {
+                "braces": "^3.0.3",
+                "picomatch": "^2.3.1"
+            }
+        },
         "minimatch": {
-            "version": "5.0.1",
-            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
-            "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+            "version": "5.1.6",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+            "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
             "dev": true,
             "requires": {
                 "brace-expansion": "^2.0.1"
@@ -4031,32 +7094,46 @@
             "dev": true
         },
         "mocha": {
-            "version": "10.2.0",
-            "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
-            "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
-            "dev": true,
-            "requires": {
-                "ansi-colors": "4.1.1",
-                "browser-stdout": "1.3.1",
-                "chokidar": "3.5.3",
-                "debug": "4.3.4",
-                "diff": "5.0.0",
-                "escape-string-regexp": "4.0.0",
-                "find-up": "5.0.0",
-                "glob": "7.2.0",
-                "he": "1.2.0",
-                "js-yaml": "4.1.0",
-                "log-symbols": "4.1.0",
-                "minimatch": "5.0.1",
-                "ms": "2.1.3",
-                "nanoid": "3.3.3",
-                "serialize-javascript": "6.0.0",
-                "strip-json-comments": "3.1.1",
-                "supports-color": "8.1.1",
-                "workerpool": "6.2.1",
-                "yargs": "16.2.0",
-                "yargs-parser": "20.2.4",
-                "yargs-unparser": "2.0.0"
+            "version": "10.8.2",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
+            "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
+            "dev": true,
+            "requires": {
+                "ansi-colors": "^4.1.3",
+                "browser-stdout": "^1.3.1",
+                "chokidar": "^3.5.3",
+                "debug": "^4.3.5",
+                "diff": "^5.2.0",
+                "escape-string-regexp": "^4.0.0",
+                "find-up": "^5.0.0",
+                "glob": "^8.1.0",
+                "he": "^1.2.0",
+                "js-yaml": "^4.1.0",
+                "log-symbols": "^4.1.0",
+                "minimatch": "^5.1.6",
+                "ms": "^2.1.3",
+                "serialize-javascript": "^6.0.2",
+                "strip-json-comments": "^3.1.1",
+                "supports-color": "^8.1.1",
+                "workerpool": "^6.5.1",
+                "yargs": "^16.2.0",
+                "yargs-parser": "^20.2.9",
+                "yargs-unparser": "^2.0.0"
+            },
+            "dependencies": {
+                "glob": {
+                    "version": "8.1.0",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+                    "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+                    "dev": true,
+                    "requires": {
+                        "fs.realpath": "^1.0.0",
+                        "inflight": "^1.0.4",
+                        "inherits": "2",
+                        "minimatch": "^5.0.1",
+                        "once": "^1.3.0"
+                    }
+                }
             }
         },
         "mocha-junit-reporter": {
@@ -4078,10 +7155,10 @@
             "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
             "dev": true
         },
-        "nanoid": {
-            "version": "3.3.3",
-            "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
-            "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+        "natural-compare": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+            "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
             "dev": true
         },
         "node-preload": {
@@ -4245,6 +7322,20 @@
                 "wrappy": "1"
             }
         },
+        "optionator": {
+            "version": "0.9.4",
+            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+            "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+            "dev": true,
+            "requires": {
+                "deep-is": "^0.1.3",
+                "fast-levenshtein": "^2.0.6",
+                "levn": "^0.4.1",
+                "prelude-ls": "^1.2.1",
+                "type-check": "^0.4.0",
+                "word-wrap": "^1.2.5"
+            }
+        },
         "p-limit": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -4290,6 +7381,15 @@
                 "release-zalgo": "^1.0.0"
             }
         },
+        "parent-module": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+            "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+            "dev": true,
+            "requires": {
+                "callsites": "^3.0.0"
+            }
+        },
         "path-exists": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4308,6 +7408,12 @@
             "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
             "dev": true
         },
+        "pathval": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+            "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+            "dev": true
+        },
         "picocolors": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -4368,6 +7474,12 @@
                 }
             }
         },
+        "prelude-ls": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+            "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+            "dev": true
+        },
         "process-on-spawn": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz",
@@ -4377,6 +7489,18 @@
                 "fromentries": "^1.2.0"
             }
         },
+        "punycode": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+            "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+            "dev": true
+        },
+        "queue-microtask": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+            "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+            "dev": true
+        },
         "randombytes": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -4422,6 +7546,18 @@
             "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
             "dev": true
         },
+        "resolve-pkg-maps": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+            "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+            "dev": true
+        },
+        "reusify": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+            "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+            "dev": true
+        },
         "rimraf": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -4431,6 +7567,15 @@
                 "glob": "^7.1.3"
             }
         },
+        "run-parallel": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+            "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+            "dev": true,
+            "requires": {
+                "queue-microtask": "^1.2.2"
+            }
+        },
         "safe-buffer": {
             "version": "5.2.1",
             "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -4444,9 +7589,9 @@
             "dev": true
         },
         "serialize-javascript": {
-            "version": "6.0.0",
-            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
-            "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+            "version": "6.0.2",
+            "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+            "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
             "dev": true,
             "requires": {
                 "randombytes": "^2.1.0"
@@ -4578,6 +7723,12 @@
                 }
             }
         },
+        "text-table": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+            "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+            "dev": true
+        },
         "to-fast-properties": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -4593,6 +7744,62 @@
                 "is-number": "^7.0.0"
             }
         },
+        "ts-api-utils": {
+            "version": "1.4.3",
+            "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+            "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+            "dev": true,
+            "requires": {}
+        },
+        "ts-node": {
+            "version": "10.9.2",
+            "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+            "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+            "dev": true,
+            "requires": {
+                "@cspotcode/source-map-support": "^0.8.0",
+                "@tsconfig/node10": "^1.0.7",
+                "@tsconfig/node12": "^1.0.7",
+                "@tsconfig/node14": "^1.0.0",
+                "@tsconfig/node16": "^1.0.2",
+                "acorn": "^8.4.1",
+                "acorn-walk": "^8.1.1",
+                "arg": "^4.1.0",
+                "create-require": "^1.1.0",
+                "diff": "^4.0.1",
+                "make-error": "^1.1.1",
+                "v8-compile-cache-lib": "^3.0.1",
+                "yn": "3.1.1"
+            },
+            "dependencies": {
+                "diff": {
+                    "version": "4.0.2",
+                    "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+                    "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+                    "dev": true
+                }
+            }
+        },
+        "tsx": {
+            "version": "4.19.2",
+            "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
+            "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
+            "dev": true,
+            "requires": {
+                "esbuild": "~0.23.0",
+                "fsevents": "~2.3.3",
+                "get-tsconfig": "^4.7.5"
+            }
+        },
+        "type-check": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+            "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+            "dev": true,
+            "requires": {
+                "prelude-ls": "^1.2.1"
+            }
+        },
         "type-fest": {
             "version": "0.8.1",
             "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
@@ -4608,6 +7815,29 @@
                 "is-typedarray": "^1.0.0"
             }
         },
+        "typescript": {
+            "version": "5.7.2",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+            "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
+            "dev": true
+        },
+        "typescript-eslint": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.0.tgz",
+            "integrity": "sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==",
+            "dev": true,
+            "requires": {
+                "@typescript-eslint/eslint-plugin": "8.18.0",
+                "@typescript-eslint/parser": "8.18.0",
+                "@typescript-eslint/utils": "8.18.0"
+            }
+        },
+        "undici-types": {
+            "version": "6.20.0",
+            "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+            "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+            "dev": true
+        },
         "update-browserslist-db": {
             "version": "1.0.11",
             "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
@@ -4618,12 +7848,27 @@
                 "picocolors": "^1.0.0"
             }
         },
+        "uri-js": {
+            "version": "4.4.1",
+            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+            "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+            "dev": true,
+            "requires": {
+                "punycode": "^2.1.0"
+            }
+        },
         "uuid": {
             "version": "8.3.2",
             "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
             "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
             "dev": true
         },
+        "v8-compile-cache-lib": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+            "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+            "dev": true
+        },
         "which": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4639,10 +7884,16 @@
             "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
             "dev": true
         },
+        "word-wrap": {
+            "version": "1.2.5",
+            "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+            "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+            "dev": true
+        },
         "workerpool": {
-            "version": "6.2.1",
-            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
-            "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+            "version": "6.5.1",
+            "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
+            "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
             "dev": true
         },
         "wrap-ansi": {
@@ -4674,6 +7925,12 @@
                 "typedarray-to-buffer": "^3.1.5"
             }
         },
+        "ws": {
+            "version": "8.18.0",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+            "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+            "requires": {}
+        },
         "xml": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
@@ -4708,9 +7965,9 @@
             }
         },
         "yargs-parser": {
-            "version": "20.2.4",
-            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
-            "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+            "version": "20.2.9",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+            "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
             "dev": true
         },
         "yargs-unparser": {
@@ -4739,6 +7996,12 @@
                 }
             }
         },
+        "yn": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+            "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+            "dev": true
+        },
         "yocto-queue": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/modules/mock-chronik-client/package.json b/modules/mock-chronik-client/package.json
--- a/modules/mock-chronik-client/package.json
+++ b/modules/mock-chronik-client/package.json
@@ -1,18 +1,33 @@
 {
     "dependencies": {
-        "ecashaddrjs": "file:../ecashaddrjs"
+        "chronik-client": "file:../chronik-client",
+        "ecashaddrjs": "file:../ecashaddrjs",
+        "ws": "^8.18.0"
     },
     "name": "mock-chronik-client",
     "description": "Testing utility to mock the Chronik indexer client and support unit tests that need to mock chronik related API calls.",
-    "version": "1.12.3",
-    "main": "index.js",
+    "version": "2.0.0",
+    "main": "./dist/index.js",
     "devDependencies": {
-        "mocha": "^10.2.0",
+        "@types/chai": "^5.0.1",
+        "@types/chai-as-promised": "^8.0.1",
+        "@types/mocha": "^10.0.10",
+        "@types/ws": "^8.5.13",
+        "chai": "^5.1.2",
+        "chai-as-promised": "^8.0.1",
+        "eslint": "8.57",
+        "eslint-plugin-header": "^3.1.1",
+        "mocha": "^10.8.2",
         "mocha-junit-reporter": "^2.2.1",
-        "nyc": "^15.1.0"
+        "nyc": "^15.1.0",
+        "ts-node": "^10.9.2",
+        "tsx": "^4.19.2",
+        "typescript": "^5.7.2",
+        "typescript-eslint": "^8.18.0"
     },
     "scripts": {
-        "test": "mocha",
+        "test": "mocha --import=tsx index.test.ts",
+        "build": "tsc",
         "coverage": "nyc mocha",
         "junit": "mocha test --reporter mocha-junit-reporter"
     },
diff --git a/modules/mock-chronik-client/test/index.test.js b/modules/mock-chronik-client/test/index.test.js
deleted file mode 100644
--- a/modules/mock-chronik-client/test/index.test.js
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright (c) 2023 The Bitcoin developers
-// Distributed under the MIT software license, see the accompanying
-// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-'use strict';
-const assert = require('assert');
-const { MockChronikClient, MockAgora } = require('../index');
-const {
-    mockBlockInfo,
-    mockTxInfo,
-    mockTokenInfo,
-    mockTxHistory,
-    mockP2pkhUtxos,
-    mockBlockchainInfo,
-    mockRawTxHex,
-} = require('../mocks/mockChronikResponses');
-const cashaddr = require('ecashaddrjs');
-const P2PKH_ADDRESS = 'ecash:qzth8qvakhr6y8zcefdrvx30zrdmt2z2gvp7zc5vj8';
-
-it('Mock the block() API response', async function () {
-    // Initialize chronik mock with block info
-    const blockHash = mockBlockInfo.blockInfo.hash;
-    const mockedChronik = new MockChronikClient();
-    mockedChronik.setMock('block', {
-        input: blockHash,
-        output: mockBlockInfo,
-    });
-
-    // Execute the API call
-    const result = await mockedChronik.block(blockHash);
-    assert.deepEqual(result, mockBlockInfo);
-});
-
-it('Mock the tx() API response', async function () {
-    // Initialize chronik mock with tx info
-    const txid = mockTxInfo.txid;
-    const mockedChronik = new MockChronikClient();
-    mockedChronik.setMock('tx', {
-        input: txid,
-        output: mockTxInfo,
-    });
-
-    // Execute the API call
-    const result = await mockedChronik.tx(txid);
-    assert.deepEqual(result, mockTxInfo);
-});
-
-it('Mock the token() API response', async function () {
-    // Initialize chronik mock with token info
-    const tokenId = mockTokenInfo.slpTxData.slpMeta.tokenId;
-    const mockedChronik = new MockChronikClient();
-    mockedChronik.setMock('token', {
-        input: tokenId,
-        output: mockTokenInfo,
-    });
-
-    // Execute the API call
-    const result = await mockedChronik.token(tokenId);
-    assert.deepEqual(result, mockTokenInfo);
-});
-
-it('Mock the blockchainInfo() API response', async function () {
-    // Initialize chronik mock with blockchain info
-    const mockedChronik = new MockChronikClient();
-    mockedChronik.setMock('blockchainInfo', {
-        output: mockBlockchainInfo,
-    });
-
-    // Execute the API call
-    const result = await mockedChronik.blockchainInfo();
-    assert.deepEqual(result, mockBlockchainInfo);
-});
-
-it('Mock the broadcastTx() API response', async function () {
-    // Initialize chronik mock with tx broadcast info
-    const mockedChronik = new MockChronikClient();
-    const txid =
-        '0075130c9ecb342b5162bb1a8a870e69c935ea0c9b2353a967cda404401acf19';
-    mockedChronik.setMock('broadcastTx', {
-        input: mockRawTxHex,
-        output: { txid: txid },
-    });
-
-    // Execute the API call
-    const result = await mockedChronik.broadcastTx(mockRawTxHex);
-    assert.deepEqual(result, { txid: txid });
-});
-
-it('Mock the script().utxos() API response', async function () {
-    // Initialize chronik mock with a utxo set
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-    mockedChronik.setScript(type, hash);
-    mockedChronik.setUtxos(type, hash, mockP2pkhUtxos);
-
-    // Execute the API call
-    const result = await mockedChronik.script(type, hash).utxos();
-    assert.deepEqual(result, mockP2pkhUtxos);
-});
-
-it('We get the same script().utxos() API response using address().utxos()', async function () {
-    // Initialize chronik mock with a utxo set
-    const mockedChronik = new MockChronikClient();
-    mockedChronik.setAddress(P2PKH_ADDRESS);
-    mockedChronik.setUtxosByAddress(P2PKH_ADDRESS, mockP2pkhUtxos);
-
-    // Execute the API call
-    const result = await mockedChronik.address(P2PKH_ADDRESS).utxos();
-    assert.deepEqual(result, mockP2pkhUtxos);
-});
-
-it('We can get a tokenId().utxos() API response using tokenId().utxos()', async function () {
-    // Initialize chronik mock with a utxo set
-    const mockedChronik = new MockChronikClient();
-
-    const tokenId =
-        '50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e';
-    mockedChronik.setTokenId(tokenId);
-    mockedChronik.setUtxosByTokenId(tokenId, mockP2pkhUtxos);
-
-    // Execute the API call
-    const result = await mockedChronik.tokenId(tokenId).utxos();
-    assert.deepEqual(result, mockP2pkhUtxos);
-});
-
-it('Mock the script().history() API response', async function () {
-    // Initialize chronik mock with history info
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-    mockedChronik.setScript(type, hash);
-    mockedChronik.setTxHistory(type, hash, mockTxHistory.txs);
-
-    // Execute the API call
-    const result = await mockedChronik.script(type, hash).history(0, 2);
-    assert.deepEqual(result, mockTxHistory);
-
-    // We can also call this without page size
-    assert.deepEqual(
-        await mockedChronik.script(type, hash).history(),
-        mockTxHistory,
-    );
-});
-
-it('We get the same script().history() API response using address().history()', async function () {
-    // Initialize chronik mock with history info
-    const mockedChronik = new MockChronikClient();
-    mockedChronik.setAddress(P2PKH_ADDRESS);
-    mockedChronik.setTxHistoryByAddress(P2PKH_ADDRESS, mockTxHistory.txs);
-
-    // Execute the API call
-    const result = await mockedChronik.address(P2PKH_ADDRESS).history(0, 2);
-    assert.deepEqual(result, mockTxHistory);
-
-    // We can also call this without page size
-    assert.deepEqual(
-        await mockedChronik.address(P2PKH_ADDRESS).history(),
-        mockTxHistory,
-    );
-});
-
-it('We can also set and get tx history by tokenId', async function () {
-    // Initialize chronik mock with history info
-    const mockedChronik = new MockChronikClient();
-    const tokenId =
-        '50d8292c6255cda7afc6c8566fed3cf42a2794e9619740fe8f4c95431271410e';
-    mockedChronik.setTokenId(tokenId);
-    mockedChronik.setTxHistoryByTokenId(tokenId, mockTxHistory.txs);
-
-    // Execute the API call
-    const result = await mockedChronik.tokenId(tokenId).history(0, 2);
-    assert.deepEqual(result, mockTxHistory);
-
-    // We can also call this without page size
-    assert.deepEqual(
-        await mockedChronik.tokenId(tokenId).history(),
-        mockTxHistory,
-    );
-});
-
-it('We can also set and get tx history by lokadId', async function () {
-    // Initialize chronik mock with history info
-    const mockedChronik = new MockChronikClient();
-    const lokadId = '63686174';
-    mockedChronik.setLokadId(lokadId);
-    mockedChronik.setTxHistoryByLokadId(lokadId, mockTxHistory.txs);
-
-    // Execute the API call
-    const result = await mockedChronik.lokadId(lokadId).history(0, 2);
-    assert.deepEqual(result, mockTxHistory);
-
-    // We can also call this without page size
-    assert.deepEqual(
-        await mockedChronik.lokadId(lokadId).history(),
-        mockTxHistory,
-    );
-});
-
-it('We can get blockTxs by height', async function () {
-    // Initialize chronik mock with history info
-    const mockedChronik = new MockChronikClient();
-    const height = 800000;
-    mockedChronik.setTxHistoryByBlock(height, mockTxHistory.txs);
-
-    // Execute the API call
-    const result = await mockedChronik.blockTxs(height, 0, 2);
-    assert.deepEqual(result, mockTxHistory);
-
-    // We can also call this without page size
-    assert.deepEqual(await mockedChronik.blockTxs(height), mockTxHistory);
-});
-
-it('We can get blockTxs by hash', async function () {
-    // Initialize chronik mock with history info
-    const mockedChronik = new MockChronikClient();
-    const hash =
-        '0000000000000000115e051672e3d4a6c523598594825a1194862937941296fe';
-    mockedChronik.setTxHistoryByBlock(hash, mockTxHistory.txs);
-
-    // Execute the API call
-    const result = await mockedChronik.blockTxs(hash, 0, 2);
-    assert.deepEqual(result, mockTxHistory);
-
-    // We can also call this without page size
-    assert.deepEqual(await mockedChronik.blockTxs(hash), mockTxHistory);
-});
-
-it('We can sub and unsub to scripts with the websocket', async function () {
-    // Initialize chronik mock with script info
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-
-    // Create websocket subscription to listen to confirmations on txid
-    const ws = mockedChronik.ws({
-        onMessage: msg => {
-            console.log(`msg`, msg);
-        },
-    });
-
-    // Wait for WS to be connected:
-    await ws.waitForOpen();
-
-    // Subscribe to scripts
-    ws.subscribe(type, hash);
-
-    // The sub is in ws.subs
-    assert.deepEqual(ws.subs.scripts, [
-        { scriptType: type, scriptPayload: hash },
-    ]);
-
-    // We can unsubscribe from the script
-    ws.unsubscribe(type, hash);
-
-    // The sub is no longer there
-    assert.deepEqual(ws.subs.scripts, []);
-});
-
-it('We can mock a chronik websocket connection', async function () {
-    // Initialize chronik mock with script info
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-
-    // Create websocket subscription to listen to confirmations on txid
-    const ws = mockedChronik.ws({
-        onMessage: msg => {
-            console.log(`msg`, msg);
-        },
-    });
-
-    // Wait for WS to be connected:
-    await ws.waitForOpen();
-
-    // Subscribe to scripts
-    ws.subscribe(type, hash);
-    // We can test that ws.subscribe was called
-    assert.strictEqual(mockedChronik.wsSubscribeCalled, true);
-
-    // The sub is in ws.subs
-    assert.deepEqual(ws.subs.scripts, [
-        { scriptType: type, scriptPayload: hash },
-    ]);
-
-    // We can unsubscribe from the script
-    ws.unsubscribe(type, hash);
-
-    // The sub is no longer there
-    assert.deepEqual(ws.subs.scripts, []);
-
-    // We can test that waitForOpen() has been called
-    await ws.waitForOpen();
-    assert.equal(mockedChronik.wsWaitForOpenCalled, true);
-
-    // We can test if a websocket was closed by calling wsClose() (aka "manually closed")
-    mockedChronik.wsClose();
-    assert.equal(mockedChronik.manuallyClosed, true);
-
-    // We can subscribe to blocks (in-node chronik-client)
-    ws.subscribeToBlocks();
-    assert.equal(ws.subs.blocks, true);
-
-    // We can unsubscribe from blocks (in-node chronik-client)
-    ws.unsubscribeFromBlocks();
-    assert.equal(ws.subs.blocks, false);
-});
-
-it('We can subscribe to and unsubscribe from addresses with the ws object', async function () {
-    // Initialize chronik mock with script info
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-
-    // Create websocket subscription to listen to confirmations on txid
-    const ws = mockedChronik.ws({
-        onMessage: msg => {
-            console.log(msg);
-        },
-    });
-
-    // Wait for WS to be connected:
-    await ws.waitForOpen();
-
-    // Subscribe to address
-    ws.subscribeToAddress(P2PKH_ADDRESS);
-
-    // Verify websocket subscription is as expected
-    assert.deepEqual(ws.subs.scripts, [{ scriptType: type, payload: hash }]);
-
-    // Unsubscribe from address
-    ws.unsubscribeFromAddress(P2PKH_ADDRESS);
-
-    // Verify websocket subscription is as expected
-    assert.deepEqual(ws.subs.scripts, []);
-
-    // We expect an error if we unsubscribe from an address and there is no existing subscription
-    assert.throws(
-        () => ws.unsubscribeFromAddress(P2PKH_ADDRESS),
-        new Error(`No existing sub at ${type}, ${hash}`),
-    );
-});
-
-it('We can subscribe to and unsubscribe from scripts with the ws object', async function () {
-    // Initialize chronik mock with script info
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-
-    // Create websocket subscription to listen to confirmations on txid
-    const ws = mockedChronik.ws({
-        onMessage: msg => {
-            console.log(msg);
-        },
-    });
-
-    // Wait for WS to be connected:
-    await ws.waitForOpen();
-
-    // Subscribe to address
-    ws.subscribeToScript(type, hash);
-
-    // Verify websocket subscription is as expected
-    assert.deepEqual(ws.subs.scripts, [{ scriptType: type, payload: hash }]);
-
-    // Unsubscribe from address
-    ws.unsubscribeFromScript(type, hash);
-
-    // Verify websocket subscription is as expected
-    assert.deepEqual(ws.subs.scripts, []);
-
-    // We expect an error if we unsubscribe from an address and there is no existing subscription
-    assert.throws(
-        () => ws.unsubscribeFromAddress(P2PKH_ADDRESS),
-        new Error(`No existing sub at ${type}, ${hash}`),
-    );
-});
-
-it('Mock an error returned from the block() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const blockHash = 'some block hash';
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setMock('block', {
-        input: blockHash,
-        output: expectedError,
-    });
-
-    // Execute the API call
-    await assert.rejects(
-        async () => mockedChronik.block(blockHash),
-        expectedError,
-    );
-});
-
-it('Mock an error returned from the tx() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const txid = 'some txid';
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setMock('tx', {
-        input: txid,
-        output: expectedError,
-    });
-
-    // Execute the API call
-    await assert.rejects(mockedChronik.tx(txid), expectedError);
-});
-
-it('Mock an error returned from the token() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const tokenId = 'some token id';
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setMock('token', {
-        input: tokenId,
-        output: expectedError,
-    });
-
-    // Execute the API call
-    await assert.rejects(mockedChronik.token(tokenId), expectedError);
-});
-
-it('Mock an error returned from the blockchainInfo() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setMock('blockchainInfo', {
-        output: expectedError,
-    });
-
-    // Execute the API call
-    await assert.rejects(mockedChronik.blockchainInfo(), expectedError);
-});
-
-it('Mock an error returned from the broadcastTx() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const rawTxHex = 'some raw tx hex';
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setMock('broadcastTx', {
-        input: rawTxHex,
-        output: expectedError,
-    });
-
-    // Execute the API call
-    await assert.rejects(mockedChronik.broadcastTx(rawTxHex), expectedError);
-});
-
-it('Mock an error returned from the script().utxos() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setScript(type, hash);
-    mockedChronik.setUtxos(type, hash, expectedError);
-
-    // Execute the API call
-    await assert.rejects(
-        mockedChronik.script(type, hash).utxos(),
-        expectedError,
-    );
-});
-
-it('Mock an error returned from the address().utxos() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setAddress(P2PKH_ADDRESS);
-    mockedChronik.setUtxosByAddress(P2PKH_ADDRESS, expectedError);
-
-    await assert.rejects(
-        mockedChronik.address(P2PKH_ADDRESS).utxos(),
-        expectedError,
-    );
-});
-
-it('Mock an error returned from the script().history() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const { type, hash } = cashaddr.decode(P2PKH_ADDRESS, true);
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setScript(type, hash);
-    mockedChronik.setTxHistory(type, hash, expectedError);
-
-    // Execute the API call
-    await assert.rejects(
-        mockedChronik.script(type, hash).history(0, 2),
-        expectedError,
-    );
-});
-
-it('Mock an error returned from the address().history() API', async function () {
-    const mockedChronik = new MockChronikClient();
-    const expectedError = new Error('Bad response from Chronik');
-    mockedChronik.setAddress(P2PKH_ADDRESS);
-    mockedChronik.setTxHistoryByAddress(P2PKH_ADDRESS, expectedError);
-
-    // Execute the API call
-    await assert.rejects(
-        async () => mockedChronik.address(P2PKH_ADDRESS).history(0, 2),
-        expectedError,
-    );
-});
-
-it('We can set and return expected values for supported Agora query methods', async function () {
-    // Initialize agora mock
-    const mockedAgora = new MockAgora();
-
-    // offeredGroupTokenIds
-    const mockOfferedTokenIds = [
-        '0000000000000000000000000000000000000000000000000000000000000000',
-        '1111111111111111111111111111111111111111111111111111111111111111',
-    ];
-    mockedAgora.setOfferedGroupTokenIds(mockOfferedTokenIds);
-    assert.deepEqual(
-        await mockedAgora.offeredGroupTokenIds(),
-        mockOfferedTokenIds,
-    );
-
-    // offeredFungibleTokenIds
-    const mockOfferedFungibleTokenIds = [
-        '0000000000000000000000000000000000000000000000000000000000000000',
-        '1111111111111111111111111111111111111111111111111111111111111111',
-    ];
-    mockedAgora.setOfferedFungibleTokenIds(mockOfferedFungibleTokenIds);
-    assert.deepEqual(
-        await mockedAgora.offeredFungibleTokenIds(),
-        mockOfferedTokenIds,
-    );
-
-    // activeOffersByPubKey
-    const mockPubKey = '01020304';
-    const mockActiveOffersByPubKey = [{ test: 'test' }, { test: 'test' }];
-    mockedAgora.setActiveOffersByPubKey(mockPubKey, mockActiveOffersByPubKey);
-    assert.deepEqual(
-        await mockedAgora.activeOffersByPubKey(mockPubKey),
-        mockActiveOffersByPubKey,
-    );
-
-    // activeOffersByGroupTokenId
-    const mockGroupTokenId =
-        '3333333333333333333333333333333333333333333333333333333333333333';
-    const mockActiveOffersByGroupTokenId = [
-        { test: 'test2' },
-        { test: 'test2' },
-    ];
-    mockedAgora.setActiveOffersByGroupTokenId(
-        mockGroupTokenId,
-        mockActiveOffersByGroupTokenId,
-    );
-    assert.deepEqual(
-        await mockedAgora.activeOffersByGroupTokenId(mockGroupTokenId),
-        mockActiveOffersByGroupTokenId,
-    );
-
-    // activeOffersByTokenId
-    const mockTokenId =
-        '3333333333333333333333333333333333333333333333333333333333333333';
-    const mockActiveOffersByTokenId = [{ test: 'test2' }, { test: 'test2' }];
-    mockedAgora.setActiveOffersByTokenId(
-        mockTokenId,
-        mockActiveOffersByTokenId,
-    );
-    assert.deepEqual(
-        await mockedAgora.activeOffersByTokenId(mockTokenId),
-        mockActiveOffersByTokenId,
-    );
-
-    // We can also set and throw errors
-    const expectedError = new Error('some agora error');
-    mockedAgora.setOfferedGroupTokenIds(expectedError);
-
-    await assert.rejects(
-        async () => mockedAgora.offeredGroupTokenIds(),
-        expectedError,
-    );
-});
diff --git a/apps/token-server/tsconfig.json b/modules/mock-chronik-client/tsconfig.json
copy from apps/token-server/tsconfig.json
copy to modules/mock-chronik-client/tsconfig.json
--- a/apps/token-server/tsconfig.json
+++ b/modules/mock-chronik-client/tsconfig.json
@@ -1,12 +1,14 @@
 {
     "compilerOptions": {
-        "target": "es2020",
+        "target": "es2016",
         "module": "commonjs",
+        "declaration": true,
+        "declarationMap": true,
+        "sourceMap": true,
         "outDir": "./dist",
         "esModuleInterop": true,
         "forceConsistentCasingInFileNames": true,
         "strict": true,
-        "alwaysStrict": true,
         "skipLibCheck": true
-    },
+    }
 }