mainnet-js 3.1.7 → 4.0.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.html +1 -1
- package/dist/{mainnet-3.1.7.js → mainnet-4.0.0-next.2.js} +66 -166
- package/dist/module/cache/walletCache.d.ts +16 -6
- package/dist/module/cache/walletCache.d.ts.map +1 -1
- package/dist/module/cache/walletCache.js +92 -34
- package/dist/module/cache/walletCache.js.map +1 -1
- package/dist/module/mine/mine.d.ts.map +1 -1
- package/dist/module/mine/mine.js +14 -19
- package/dist/module/mine/mine.js.map +1 -1
- package/dist/module/network/Connection.d.ts +1 -12
- package/dist/module/network/Connection.d.ts.map +1 -1
- package/dist/module/network/Connection.js +12 -33
- package/dist/module/network/Connection.js.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.d.ts +4 -7
- package/dist/module/network/ElectrumNetworkProvider.d.ts.map +1 -1
- package/dist/module/network/ElectrumNetworkProvider.js +43 -70
- package/dist/module/network/ElectrumNetworkProvider.js.map +1 -1
- package/dist/module/network/configuration.d.ts +2 -4
- package/dist/module/network/configuration.d.ts.map +1 -1
- package/dist/module/network/configuration.js +25 -50
- package/dist/module/network/configuration.js.map +1 -1
- package/dist/module/network/constant.d.ts +7 -7
- package/dist/module/network/constant.d.ts.map +1 -1
- package/dist/module/network/constant.js +20 -24
- package/dist/module/network/constant.js.map +1 -1
- package/dist/module/network/default.d.ts +3 -2
- package/dist/module/network/default.d.ts.map +1 -1
- package/dist/module/network/default.js +19 -51
- package/dist/module/network/default.js.map +1 -1
- package/dist/module/network/index.d.ts +2 -2
- package/dist/module/network/index.d.ts.map +1 -1
- package/dist/module/network/index.js +2 -2
- package/dist/module/network/index.js.map +1 -1
- package/dist/module/network/interface.d.ts +0 -6
- package/dist/module/network/interface.d.ts.map +1 -1
- package/dist/module/wallet/Base.d.ts.map +1 -1
- package/dist/module/wallet/Base.js +33 -12
- package/dist/module/wallet/Base.js.map +1 -1
- package/dist/module/wallet/HDWallet.d.ts.map +1 -1
- package/dist/module/wallet/HDWallet.js +16 -35
- package/dist/module/wallet/HDWallet.js.map +1 -1
- package/dist/module/wallet/Watch.d.ts +22 -1
- package/dist/module/wallet/Watch.d.ts.map +1 -1
- package/dist/module/wallet/Watch.js +137 -6
- package/dist/module/wallet/Watch.js.map +1 -1
- package/dist/module/wallet/Wif.d.ts +1 -1
- package/dist/module/wallet/Wif.d.ts.map +1 -1
- package/dist/module/wallet/Wif.js +0 -4
- package/dist/module/wallet/Wif.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/src/cache/walletCache.ts +122 -57
- package/src/mine/mine.ts +18 -22
- package/src/network/Connection.test.ts +8 -7
- package/src/network/Connection.ts +16 -42
- package/src/network/ElectrumNetworkProvider.ts +54 -88
- package/src/network/Rpc.test.ts +6 -5
- package/src/network/configuration.test.ts +23 -25
- package/src/network/configuration.ts +29 -55
- package/src/network/constant.ts +20 -25
- package/src/network/default.ts +25 -87
- package/src/network/electrum.test.ts +47 -11
- package/src/network/index.ts +7 -2
- package/src/network/interface.ts +0 -7
- package/src/network/subscription.test.ts +268 -0
- package/src/wallet/Base.ts +39 -17
- package/src/wallet/Cashtokens.test.headless.js +6 -0
- package/src/wallet/Cashtokens.test.ts +13 -0
- package/src/wallet/HDWallet.test.ts +4 -4
- package/src/wallet/HDWallet.ts +19 -35
- package/src/wallet/WalletCache.test.ts +6 -2
- package/src/wallet/Watch.ts +199 -7
- package/src/wallet/Wif.test.ts +6 -0
- package/src/wallet/Wif.ts +2 -9
- package/tsconfig.browser.json +10 -1
- package/tsconfig.json +12 -1
- package/webpack.config.cjs +1 -0
- package/dist/module/network/util.d.ts +0 -3
- package/dist/module/network/util.d.ts.map +0 -1
- package/dist/module/network/util.js +0 -27
- package/dist/module/network/util.js.map +0 -1
- package/src/network/util.test.ts +0 -24
- package/src/network/util.ts +0 -30
package/src/cache/walletCache.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface WalletCache {
|
|
|
58
58
|
// Full interface for wallet cache management
|
|
59
59
|
export interface WalletCacheI extends WalletCache {
|
|
60
60
|
init(): Promise<void>;
|
|
61
|
-
persist(): Promise<void>;
|
|
61
|
+
persist(immediate?: boolean): Promise<void>;
|
|
62
62
|
get(address: string): WalletCacheEntry | undefined;
|
|
63
63
|
getByIndex(addressIndex: number, change: boolean): WalletCacheEntry;
|
|
64
64
|
setStatusAndUtxos(
|
|
@@ -70,51 +70,114 @@ export interface WalletCacheI extends WalletCache {
|
|
|
70
70
|
): void;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
function getStorage(): CacheProvider | undefined {
|
|
74
|
+
if (Config.UseMemoryCache) return new MemoryCache();
|
|
75
|
+
if (Config.UseLocalStorageCache) return new WebStorageCache();
|
|
76
|
+
if (Config.UseIndexedDBCache) return new IndexedDbCache("WalletCache");
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Cache for single-address wallets (WatchWallet, Wif)
|
|
81
|
+
export class SingleAddressWalletCache implements WalletCacheI {
|
|
82
|
+
private entry: WalletCacheEntry;
|
|
74
83
|
private _storage: CacheProvider | undefined;
|
|
75
|
-
private walletCache: Record<string, WalletCacheEntry> = {};
|
|
76
|
-
private indexCache: Record<
|
|
77
|
-
string,
|
|
78
|
-
{
|
|
79
|
-
index: number;
|
|
80
|
-
change: boolean;
|
|
81
|
-
}
|
|
82
|
-
> = {};
|
|
83
84
|
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
constructor(
|
|
87
|
+
public walletId: string,
|
|
88
|
+
address: string,
|
|
89
|
+
tokenAddress: string,
|
|
90
|
+
public writeTimeout: number = 2000
|
|
91
|
+
) {
|
|
92
|
+
this.entry = {
|
|
93
|
+
address,
|
|
94
|
+
tokenAddress,
|
|
95
|
+
privateKey: undefined,
|
|
96
|
+
publicKey: new Uint8Array(),
|
|
97
|
+
publicKeyHash: new Uint8Array(),
|
|
98
|
+
index: 0,
|
|
99
|
+
change: false,
|
|
100
|
+
status: null,
|
|
101
|
+
utxos: [],
|
|
102
|
+
rawHistory: [],
|
|
103
|
+
lastConfirmedHeight: 0,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
public async init() {
|
|
108
|
+
this._storage = getStorage();
|
|
109
|
+
await this._storage?.init();
|
|
110
|
+
const data = await this._storage?.getItem(`walletCache-${this.walletId}`);
|
|
111
|
+
if (data) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = parse(data);
|
|
114
|
+
// Restore persisted fields, keep address identity from constructor
|
|
115
|
+
const addr = this.entry.address;
|
|
116
|
+
const tokenAddr = this.entry.tokenAddress;
|
|
117
|
+
Object.assign(this.entry, parsed, {
|
|
118
|
+
address: addr,
|
|
119
|
+
tokenAddress: tokenAddr,
|
|
120
|
+
});
|
|
121
|
+
} catch (_e) {
|
|
122
|
+
// ignore
|
|
123
|
+
}
|
|
98
124
|
}
|
|
125
|
+
}
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this._storage
|
|
105
|
-
|
|
127
|
+
public async persist(immediate = false) {
|
|
128
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
129
|
+
if (immediate) {
|
|
130
|
+
this.debounceTimer = undefined;
|
|
131
|
+
await this._storage?.setItem(
|
|
132
|
+
`walletCache-${this.walletId}`,
|
|
133
|
+
stringify(this.entry)
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
this.debounceTimer = setTimeout(() => {
|
|
137
|
+
this.debounceTimer = undefined;
|
|
138
|
+
this._storage?.setItem(
|
|
139
|
+
`walletCache-${this.walletId}`,
|
|
140
|
+
stringify(this.entry)
|
|
141
|
+
);
|
|
142
|
+
}, this.writeTimeout);
|
|
106
143
|
}
|
|
144
|
+
}
|
|
107
145
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
146
|
+
public get(address: string) {
|
|
147
|
+
return address === this.entry.address ? this.entry : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public getByIndex(_index: number, _change: boolean) {
|
|
151
|
+
return this.entry;
|
|
152
|
+
}
|
|
115
153
|
|
|
116
|
-
|
|
154
|
+
public setStatusAndUtxos(
|
|
155
|
+
address: string,
|
|
156
|
+
status: string | null,
|
|
157
|
+
utxos: Utxo[],
|
|
158
|
+
rawHistory: TxI[],
|
|
159
|
+
lastConfirmedHeight: number
|
|
160
|
+
) {
|
|
161
|
+
if (address !== this.entry.address) return;
|
|
162
|
+
this.entry.status = status;
|
|
163
|
+
this.entry.utxos = utxos;
|
|
164
|
+
this.entry.rawHistory = rawHistory;
|
|
165
|
+
this.entry.lastConfirmedHeight = lastConfirmedHeight;
|
|
166
|
+
this.persist();
|
|
117
167
|
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export class HDWalletCache implements WalletCacheI {
|
|
171
|
+
private _storage: CacheProvider | undefined;
|
|
172
|
+
private walletCache: Record<string, WalletCacheEntry> = {};
|
|
173
|
+
private indexCache: Record<
|
|
174
|
+
string,
|
|
175
|
+
{
|
|
176
|
+
index: number;
|
|
177
|
+
change: boolean;
|
|
178
|
+
}
|
|
179
|
+
> = {};
|
|
180
|
+
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
118
181
|
|
|
119
182
|
constructor(
|
|
120
183
|
public walletId: string,
|
|
@@ -128,8 +191,9 @@ export class PersistentWalletCache implements WalletCacheI {
|
|
|
128
191
|
}
|
|
129
192
|
|
|
130
193
|
public async init() {
|
|
131
|
-
|
|
132
|
-
|
|
194
|
+
this._storage = getStorage();
|
|
195
|
+
await this._storage?.init();
|
|
196
|
+
const data = await this._storage?.getItem(`walletCache-${this.walletId}`);
|
|
133
197
|
if (data) {
|
|
134
198
|
try {
|
|
135
199
|
const parsed = parse(data);
|
|
@@ -141,24 +205,25 @@ export class PersistentWalletCache implements WalletCacheI {
|
|
|
141
205
|
}
|
|
142
206
|
}
|
|
143
207
|
|
|
144
|
-
|
|
208
|
+
public async persist(immediate = false) {
|
|
145
209
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
146
|
-
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
210
|
+
const write = () =>
|
|
211
|
+
this._storage?.setItem(
|
|
212
|
+
`walletCache-${this.walletId}`,
|
|
213
|
+
stringify({
|
|
214
|
+
walletCache: this.walletCache,
|
|
215
|
+
indexCache: this.indexCache,
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
if (immediate) {
|
|
219
|
+
this.debounceTimer = undefined;
|
|
220
|
+
await write();
|
|
221
|
+
} else {
|
|
222
|
+
this.debounceTimer = setTimeout(() => {
|
|
223
|
+
this.debounceTimer = undefined;
|
|
224
|
+
write();
|
|
225
|
+
}, this.writeTimeout);
|
|
226
|
+
}
|
|
162
227
|
}
|
|
163
228
|
|
|
164
229
|
public getByIndex(addressIndex: number, change: boolean) {
|
|
@@ -212,7 +277,7 @@ export class PersistentWalletCache implements WalletCacheI {
|
|
|
212
277
|
change,
|
|
213
278
|
};
|
|
214
279
|
|
|
215
|
-
this.
|
|
280
|
+
this.persist();
|
|
216
281
|
}
|
|
217
282
|
|
|
218
283
|
return this.walletCache[id];
|
|
@@ -249,6 +314,6 @@ export class PersistentWalletCache implements WalletCacheI {
|
|
|
249
314
|
this.walletCache[key].rawHistory = rawHistory;
|
|
250
315
|
this.walletCache[key].lastConfirmedHeight = lastConfirmedHeight;
|
|
251
316
|
|
|
252
|
-
this.
|
|
317
|
+
this.persist();
|
|
253
318
|
}
|
|
254
319
|
}
|
package/src/mine/mine.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { binToBase64, utf8ToBin } from "@bitauth/libauth";
|
|
2
|
+
import { http } from "@rpckit/http";
|
|
3
|
+
import { fallback } from "@rpckit/fallback";
|
|
4
|
+
import type { Transport } from "@rpckit/core";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Mine blocks to a regtest address
|
|
@@ -16,27 +19,20 @@ export const mine = async ({
|
|
|
16
19
|
cashaddr: string;
|
|
17
20
|
blocks: number;
|
|
18
21
|
}): Promise<any> => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"Content-Type": "application/json",
|
|
23
|
-
Authorization:
|
|
24
|
-
"Basic " +
|
|
25
|
-
binToBase64(
|
|
26
|
-
utf8ToBin(`${process.env.RPC_USER}:${process.env.RPC_PASS}`)
|
|
27
|
-
),
|
|
28
|
-
},
|
|
29
|
-
body: JSON.stringify({
|
|
30
|
-
jsonrpc: "2.0",
|
|
31
|
-
id: "0",
|
|
32
|
-
method: "generatetoaddress",
|
|
33
|
-
params: {
|
|
34
|
-
nblocks: blocks,
|
|
35
|
-
address: cashaddr,
|
|
36
|
-
},
|
|
37
|
-
}),
|
|
38
|
-
});
|
|
39
|
-
const json = await response.json();
|
|
22
|
+
const auth =
|
|
23
|
+
"Basic " +
|
|
24
|
+
binToBase64(utf8ToBin(`${process.env.RPC_USER}:${process.env.RPC_PASS}`));
|
|
40
25
|
|
|
41
|
-
|
|
26
|
+
const transports = ["127.0.0.1", "host.docker.internal"].map((host) =>
|
|
27
|
+
http(`http://${host}:${process.env.RPC_PORT}/`, {
|
|
28
|
+
headers: { Authorization: auth },
|
|
29
|
+
})
|
|
30
|
+
) as [Transport, Transport];
|
|
31
|
+
|
|
32
|
+
const transport = fallback(transports);
|
|
33
|
+
try {
|
|
34
|
+
return await transport.request("generatetoaddress", blocks, cashaddr);
|
|
35
|
+
} finally {
|
|
36
|
+
await transport.close();
|
|
37
|
+
}
|
|
42
38
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { initProviders, disconnectProviders
|
|
1
|
+
import { initProviders, disconnectProviders } from "./Connection";
|
|
2
|
+
import { createProvider } from "./default";
|
|
2
3
|
import { RegTestWallet, TestNetWallet, Wallet } from "../wallet/Wif";
|
|
3
4
|
|
|
4
5
|
beforeAll(async () => {
|
|
@@ -41,11 +42,11 @@ test.skip("Should lower overhead in creating wallets", async () => {
|
|
|
41
42
|
expect(height).toBeGreaterThan(114);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
test("Should create a
|
|
45
|
-
let
|
|
46
|
-
await
|
|
47
|
-
expect(
|
|
48
|
-
let blockheight = await
|
|
45
|
+
test("Should create a provider with custom servers", async () => {
|
|
46
|
+
let provider = await createProvider("mainnet", "wss://fulcrum.pat.mn:50004");
|
|
47
|
+
await provider.connect();
|
|
48
|
+
expect(provider == globalThis.BCH).toBeFalsy();
|
|
49
|
+
let blockheight = await provider.getBlockHeight();
|
|
49
50
|
expect(blockheight).toBeGreaterThan(10000);
|
|
50
|
-
|
|
51
|
+
await provider.disconnect();
|
|
51
52
|
});
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import { default as NetworkProvider } from "./NetworkProvider.js";
|
|
2
1
|
import {
|
|
3
|
-
|
|
2
|
+
createProvider,
|
|
4
3
|
setGlobalProvider,
|
|
5
4
|
getGlobalProvider,
|
|
6
5
|
removeGlobalProvider,
|
|
7
6
|
} from "./default.js";
|
|
8
7
|
import { Network } from "../interface.js";
|
|
9
8
|
import { networkTickerMap } from "./constant.js";
|
|
10
|
-
import { prefixFromNetworkMap } from "../enum.js";
|
|
11
|
-
import { CashAddressNetworkPrefix } from "@bitauth/libauth";
|
|
12
9
|
|
|
13
10
|
export async function initProvider(network: Network) {
|
|
14
11
|
if (!getGlobalProvider(network)) {
|
|
15
|
-
const
|
|
16
|
-
|
|
12
|
+
const provider = await createProvider(network);
|
|
13
|
+
await provider.connect();
|
|
17
14
|
setGlobalProvider(network, provider);
|
|
18
15
|
return provider;
|
|
19
16
|
}
|
|
@@ -22,10 +19,18 @@ export async function initProvider(network: Network) {
|
|
|
22
19
|
|
|
23
20
|
export async function initProviders(networks?: Network[]) {
|
|
24
21
|
networks = networks ? networks : (Object.keys(networkTickerMap) as Network[]);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const results = await Promise.allSettled(
|
|
23
|
+
networks.map((n) => initProvider(n))
|
|
24
|
+
);
|
|
25
|
+
for (let i = 0; i < results.length; i++) {
|
|
26
|
+
if (results[i].status === "rejected") {
|
|
27
|
+
const { reason } = results[i] as PromiseRejectedResult;
|
|
28
|
+
const message = reason instanceof Error ? reason.message : reason;
|
|
29
|
+
console.warn(
|
|
30
|
+
`Warning, couldn't establish a connection for ${networks[i]}: ${message}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
async function disconnectProvider(network: Network) {
|
|
@@ -33,41 +38,10 @@ async function disconnectProvider(network: Network) {
|
|
|
33
38
|
if (provider) {
|
|
34
39
|
await provider.disconnect();
|
|
35
40
|
removeGlobalProvider(network);
|
|
36
|
-
return;
|
|
37
|
-
} else {
|
|
38
|
-
// console.warn(
|
|
39
|
-
// `Ignoring attempt to disconnect non-existent ${network} provider`
|
|
40
|
-
// );
|
|
41
|
-
return true;
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
export async function disconnectProviders(networks?: Network[]) {
|
|
46
45
|
networks = networks ? networks : (Object.keys(networkTickerMap) as Network[]);
|
|
47
|
-
|
|
48
|
-
await Promise.all(disconnectPromises);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export class Connection {
|
|
52
|
-
public network: Network;
|
|
53
|
-
public servers?: string[];
|
|
54
|
-
public networkPrefix: CashAddressNetworkPrefix;
|
|
55
|
-
public networkProvider: NetworkProvider;
|
|
56
|
-
|
|
57
|
-
constructor(network?: Network, servers?: string[] | string) {
|
|
58
|
-
this.network = network ? network : "mainnet";
|
|
59
|
-
this.networkPrefix = prefixFromNetworkMap[this.network];
|
|
60
|
-
this.networkProvider = getNetworkProvider(this.network, servers, true);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
public async ready() {
|
|
64
|
-
await this.networkProvider.connect();
|
|
65
|
-
await this.networkProvider.ready();
|
|
66
|
-
return this;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public async disconnect() {
|
|
70
|
-
await this.networkProvider.disconnect();
|
|
71
|
-
return this;
|
|
72
|
-
}
|
|
46
|
+
await Promise.all(networks.map((n) => disconnectProvider(n)));
|
|
73
47
|
}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
RequestResponse,
|
|
4
|
-
ElectrumClientEvents,
|
|
5
|
-
RPCNotification,
|
|
6
|
-
ConnectionStatus,
|
|
7
|
-
} from "@electrum-cash/network";
|
|
1
|
+
import type { Transport, Unsubscribe } from "@rpckit/core";
|
|
2
|
+
import type { ElectrumCashSchema } from "@rpckit/core/electrum-cash";
|
|
8
3
|
import { default as NetworkProvider } from "./NetworkProvider.js";
|
|
9
4
|
import {
|
|
10
5
|
HexHeaderI,
|
|
@@ -34,9 +29,8 @@ import { MemoryCache } from "../cache/MemoryCache.js";
|
|
|
34
29
|
type CachedRawTransaction = ElectrumRawTransaction & { fetchHeight: number };
|
|
35
30
|
|
|
36
31
|
export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
37
|
-
public
|
|
32
|
+
public transport: Transport<ElectrumCashSchema>;
|
|
38
33
|
public subscriptions: number = 0;
|
|
39
|
-
private subscriptionMap: Record<string, number> = {};
|
|
40
34
|
private currentHeight: number = 0;
|
|
41
35
|
private headerCancelFn?: CancelFn;
|
|
42
36
|
|
|
@@ -74,14 +68,13 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
constructor(
|
|
77
|
-
|
|
78
|
-
public network: Network = Network.MAINNET
|
|
79
|
-
private manualConnectionManagement?: boolean
|
|
71
|
+
transport: Transport<ElectrumCashSchema>,
|
|
72
|
+
public network: Network = Network.MAINNET
|
|
80
73
|
) {
|
|
81
|
-
if (
|
|
82
|
-
this.
|
|
74
|
+
if (transport) {
|
|
75
|
+
this.transport = transport;
|
|
83
76
|
} else {
|
|
84
|
-
throw new Error(`A
|
|
77
|
+
throw new Error(`A transport is required.`);
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
80
|
|
|
@@ -162,6 +155,7 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
162
155
|
}
|
|
163
156
|
|
|
164
157
|
if (misses.length > 0) {
|
|
158
|
+
// rpckit automatically batches concurrent requests via BatchScheduler
|
|
165
159
|
const fetched = await Promise.all(
|
|
166
160
|
misses.map(async (hash) => {
|
|
167
161
|
const tx = await this.performRequest<string>(
|
|
@@ -215,6 +209,7 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
215
209
|
}
|
|
216
210
|
|
|
217
211
|
if (misses.length > 0) {
|
|
212
|
+
// rpckit automatically batches concurrent requests via BatchScheduler
|
|
218
213
|
const fetched = await Promise.all(
|
|
219
214
|
misses.map(async (height) => {
|
|
220
215
|
const result = await this.performRequest<HexHeaderI>(
|
|
@@ -342,7 +337,7 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
342
337
|
} catch (error: any) {
|
|
343
338
|
if (
|
|
344
339
|
(error.message as string).indexOf(
|
|
345
|
-
"No such mempool or blockchain transaction
|
|
340
|
+
"No such mempool or blockchain transaction"
|
|
346
341
|
) > -1
|
|
347
342
|
)
|
|
348
343
|
throw Error(
|
|
@@ -490,8 +485,8 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
490
485
|
public async waitForBlock(height?: number): Promise<HexHeaderI> {
|
|
491
486
|
return new Promise(async (resolve) => {
|
|
492
487
|
let cancelWatch: CancelFn;
|
|
493
|
-
if (this.
|
|
494
|
-
height = this.
|
|
488
|
+
if (this.currentHeight && !height) {
|
|
489
|
+
height = this.currentHeight + 1;
|
|
495
490
|
}
|
|
496
491
|
|
|
497
492
|
cancelWatch = await this.watchBlocks(async (header) => {
|
|
@@ -549,63 +544,38 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
549
544
|
): Promise<T> {
|
|
550
545
|
await this.ready();
|
|
551
546
|
|
|
552
|
-
const
|
|
553
|
-
setTimeout(function () {
|
|
554
|
-
reject("electrum-cash request timed out, retrying");
|
|
555
|
-
}, 30000);
|
|
556
|
-
}).catch(function (e) {
|
|
557
|
-
throw e;
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
const request = this.electrum.request(name, ...parameters);
|
|
547
|
+
const TIMEOUT_MSG = "electrum-cash request timed out, retrying";
|
|
561
548
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
})
|
|
568
|
-
.catch(async () => {
|
|
569
|
-
return await Promise.race([request, requestTimeout])
|
|
570
|
-
.then((value) => {
|
|
571
|
-
if (value instanceof Error) throw value;
|
|
572
|
-
let result = value as RequestResponse;
|
|
573
|
-
return result as T;
|
|
574
|
-
})
|
|
575
|
-
.catch(function (e) {
|
|
576
|
-
throw e;
|
|
577
|
-
});
|
|
549
|
+
const makeTimeout = () =>
|
|
550
|
+
new Promise<never>(function (_resolve, reject) {
|
|
551
|
+
setTimeout(function () {
|
|
552
|
+
reject(TIMEOUT_MSG);
|
|
553
|
+
}, 30000);
|
|
578
554
|
});
|
|
579
|
-
}
|
|
580
555
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
this.subscriptionMap[key]++;
|
|
588
|
-
} else {
|
|
589
|
-
this.subscriptionMap[key] = 1;
|
|
590
|
-
}
|
|
556
|
+
const ensureError = (e: unknown): Error => {
|
|
557
|
+
if (e instanceof Error) return e;
|
|
558
|
+
if (typeof e === "object" && e !== null && "message" in e)
|
|
559
|
+
return Object.assign(new Error((e as any).message), e);
|
|
560
|
+
return new Error(typeof e === "string" ? e : String(e));
|
|
561
|
+
};
|
|
591
562
|
|
|
592
|
-
|
|
593
|
-
}
|
|
563
|
+
const request = this.transport.request(name as any, ...(parameters as any));
|
|
594
564
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
565
|
+
try {
|
|
566
|
+
const value = await Promise.race([request, makeTimeout()]);
|
|
567
|
+
if (value instanceof Error) throw value;
|
|
568
|
+
return value as T;
|
|
569
|
+
} catch (e: unknown) {
|
|
570
|
+
const error = ensureError(e);
|
|
571
|
+
// Only retry on timeout, not on server errors
|
|
572
|
+
if (error.message !== TIMEOUT_MSG) throw error;
|
|
573
|
+
try {
|
|
574
|
+
const value = await Promise.race([request, makeTimeout()]);
|
|
575
|
+
if (value instanceof Error) throw value;
|
|
576
|
+
return value as T;
|
|
577
|
+
} catch (e2: unknown) {
|
|
578
|
+
throw ensureError(e2);
|
|
609
579
|
}
|
|
610
580
|
}
|
|
611
581
|
}
|
|
@@ -617,19 +587,21 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
617
587
|
): Promise<CancelFn> {
|
|
618
588
|
await this.ready();
|
|
619
589
|
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
590
|
+
const subscribeFn = this.transport.subscribe.bind(this.transport) as (
|
|
591
|
+
method: string,
|
|
592
|
+
...args: unknown[]
|
|
593
|
+
) => Promise<Unsubscribe>;
|
|
594
|
+
const unsubscribe: Unsubscribe = await subscribeFn(
|
|
595
|
+
methodName,
|
|
596
|
+
...parameters,
|
|
597
|
+
(data: unknown) => {
|
|
598
|
+
callback(data);
|
|
623
599
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
this.electrum.on("notification", handler);
|
|
627
|
-
await this.trackSubscription(methodName, ...parameters);
|
|
600
|
+
);
|
|
628
601
|
this.subscriptions++;
|
|
629
602
|
|
|
630
603
|
return async () => {
|
|
631
|
-
|
|
632
|
-
await this.untrackSubscription(methodName, ...parameters);
|
|
604
|
+
await unsubscribe();
|
|
633
605
|
this.subscriptions--;
|
|
634
606
|
};
|
|
635
607
|
}
|
|
@@ -640,18 +612,12 @@ export default class ElectrumNetworkProvider implements NetworkProvider {
|
|
|
640
612
|
|
|
641
613
|
async connect(): Promise<void> {
|
|
642
614
|
await this.cache?.init();
|
|
643
|
-
|
|
644
|
-
await this.electrum.connect();
|
|
645
|
-
}
|
|
615
|
+
await this.transport.connect();
|
|
646
616
|
}
|
|
647
617
|
|
|
648
618
|
async disconnect(): Promise<boolean> {
|
|
649
|
-
if (this.subscriptions > 0) {
|
|
650
|
-
// console.warn(
|
|
651
|
-
// `Trying to disconnect a network provider with ${this.subscriptions} active subscriptions. This is in most cases a bad idea.`
|
|
652
|
-
// );
|
|
653
|
-
}
|
|
654
619
|
await this.headerCancelFn?.();
|
|
655
|
-
|
|
620
|
+
await this.transport.close();
|
|
621
|
+
return true;
|
|
656
622
|
}
|
|
657
623
|
}
|
package/src/network/Rpc.test.ts
CHANGED
|
@@ -70,17 +70,18 @@ describe("Rpc tests", () => {
|
|
|
70
70
|
|
|
71
71
|
test("Watch wallet balance", async () => {
|
|
72
72
|
const aliceWallet = await RegTestWallet.fromId(aliceWif);
|
|
73
|
+
const bobWallet = await RegTestWallet.newRandom();
|
|
73
74
|
|
|
74
75
|
let result = false;
|
|
75
|
-
aliceWallet.watchBalance((balance) => {
|
|
76
|
+
const cancel = await aliceWallet.watchBalance((balance) => {
|
|
76
77
|
expect(balance).toBeGreaterThan(0);
|
|
77
78
|
result = true;
|
|
78
|
-
// stop watching
|
|
79
|
-
return true;
|
|
80
79
|
});
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
// Trigger a real status change on Alice's address
|
|
81
|
+
await aliceWallet.send([{ cashaddr: bobWallet.cashaddr!, value: 1000n }]);
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
83
83
|
expect(result).toBe(true);
|
|
84
|
+
await cancel();
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
test("Wait for block timeout", async () => {
|