@mainnet-cash/postgresql-storage 2.1.0-alpha.5

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.
Files changed (44) hide show
  1. package/README.md +3 -0
  2. package/dist/module/SqlProvider.d.ts +30 -0
  3. package/dist/module/SqlProvider.js +159 -0
  4. package/dist/module/SqlProvider.js.map +1 -0
  5. package/dist/module/index.d.ts +2 -0
  6. package/dist/module/index.js +3 -0
  7. package/dist/module/index.js.map +1 -0
  8. package/dist/module/util.d.ts +7 -0
  9. package/dist/module/util.js +24 -0
  10. package/dist/module/util.js.map +1 -0
  11. package/dist/module/webhook/Webhook.d.ts +35 -0
  12. package/dist/module/webhook/Webhook.js +77 -0
  13. package/dist/module/webhook/Webhook.js.map +1 -0
  14. package/dist/module/webhook/WebhookBch.d.ts +13 -0
  15. package/dist/module/webhook/WebhookBch.js +141 -0
  16. package/dist/module/webhook/WebhookBch.js.map +1 -0
  17. package/dist/module/webhook/WebhookWorker.d.ts +22 -0
  18. package/dist/module/webhook/WebhookWorker.js +94 -0
  19. package/dist/module/webhook/WebhookWorker.js.map +1 -0
  20. package/dist/module/webhook/index.d.ts +4 -0
  21. package/dist/module/webhook/index.js +5 -0
  22. package/dist/module/webhook/index.js.map +1 -0
  23. package/dist/module/webhook/interface.d.ts +7 -0
  24. package/dist/module/webhook/interface.js +2 -0
  25. package/dist/module/webhook/interface.js.map +1 -0
  26. package/dist/tsconfig.browser.tsbuildinfo +1 -0
  27. package/package.json +34 -0
  28. package/src/SqlProvider.test.ts +264 -0
  29. package/src/SqlProvider.ts +233 -0
  30. package/src/Wallet.test.ts +571 -0
  31. package/src/createWallet.test.ts +158 -0
  32. package/src/index.test.ts +67 -0
  33. package/src/index.ts +2 -0
  34. package/src/util.ts +30 -0
  35. package/src/webhook/Webhook.test.ts +9 -0
  36. package/src/webhook/Webhook.ts +99 -0
  37. package/src/webhook/WebhookBch.test.ts +323 -0
  38. package/src/webhook/WebhookBch.ts +198 -0
  39. package/src/webhook/WebhookWorker.test.ts +94 -0
  40. package/src/webhook/WebhookWorker.ts +119 -0
  41. package/src/webhook/index.ts +4 -0
  42. package/src/webhook/interface.ts +7 -0
  43. package/tsconfig.browser.json +6 -0
  44. package/tsconfig.json +28 -0
@@ -0,0 +1,158 @@
1
+ import { BaseWallet, createWallet, walletFromId } from "mainnet-js";
2
+ import { WalletRequestI } from "mainnet-js";
3
+ import { initProviders, disconnectProviders } from "mainnet-js";
4
+ import { delay } from "mainnet-js";
5
+ import { WalletTypeEnum } from "mainnet-js";
6
+ import { default as SqlProvider } from "./SqlProvider.js";
7
+
8
+ BaseWallet.StorageProvider = SqlProvider;
9
+
10
+ beforeAll(async () => {
11
+ await initProviders();
12
+ });
13
+ afterAll(async () => {
14
+ await disconnectProviders();
15
+ });
16
+
17
+ describe(`Named Wallets`, () => {
18
+ test("Retrieve a named regtest wallet", async () => {
19
+ const req = {
20
+ network: "regtest",
21
+ name: "test",
22
+ };
23
+ let w = await createWallet(req);
24
+ expect(w.cashaddr).toMatch(/bchreg:/);
25
+ expect(w.name).toBe("test");
26
+ expect(w.toString()).toBe("named:regtest:test");
27
+
28
+ // recover it from the id
29
+ let w2 = await walletFromId(w.toString());
30
+ expect(w2.getInfo()).toMatchObject(w.getInfo());
31
+ expect(w2.name).toBe(w.name);
32
+
33
+ let w3 = await createWallet(req);
34
+ expect(w3.getInfo()).toMatchObject(w.getInfo());
35
+ expect(w3.name).toBe(w.name);
36
+ });
37
+
38
+ test("Retrieve a named regtest wif wallet", async () => {
39
+ const req = {
40
+ network: "regtest",
41
+ name: "test.wif",
42
+ type: WalletTypeEnum.Wif,
43
+ };
44
+ let w = await createWallet(req);
45
+ expect(w.cashaddr).toMatch(/bchreg:/);
46
+ expect(w.name).toBe("test.wif");
47
+ expect(w.toString()).toBe("named:regtest:test.wif");
48
+
49
+ // recover it from id
50
+ let w2 = await walletFromId(w.toString());
51
+ expect(w2.getInfo()).toMatchObject(w.getInfo());
52
+ expect(w2.name).toBe(w.name);
53
+ });
54
+
55
+ test("Get create an unnamed regtest seed wallet", async () => {
56
+ const req = { network: "regtest" } as WalletRequestI;
57
+ let w = await createWallet(req);
58
+ expect(w.cashaddr).toMatch(/bchreg:/);
59
+ expect(w.walletType).toBe("seed");
60
+ });
61
+
62
+ test("Watch wallet from Id", async () => {
63
+ const w = await walletFromId(
64
+ "watch:testnet:qppr9h7whx9pzucgqukhtlj8lvgvjlgr3g9ggtkq22"
65
+ );
66
+ expect(w.getInfo()).toStrictEqual({
67
+ cashaddr: "bchtest:qppr9h7whx9pzucgqukhtlj8lvgvjlgr3g9ggtkq22",
68
+ tokenaddr: "bchtest:zppr9h7whx9pzucgqukhtlj8lvgvjlgr3gzzm4cx4e",
69
+ derivationPath: undefined,
70
+ isTestnet: true,
71
+ name: "",
72
+ network: "testnet",
73
+ parentDerivationPath: undefined,
74
+ parentXPubKey: undefined,
75
+ privateKey: undefined,
76
+ privateKeyWif: undefined,
77
+ publicKey: undefined,
78
+ publicKeyHash: "4232dfceb98a117308072d75fe47fb10c97d038a",
79
+ seed: undefined,
80
+ walletDbEntry:
81
+ "watch:testnet:bchtest:qppr9h7whx9pzucgqukhtlj8lvgvjlgr3g9ggtkq22",
82
+ walletId:
83
+ "watch:testnet:bchtest:qppr9h7whx9pzucgqukhtlj8lvgvjlgr3g9ggtkq22",
84
+ });
85
+ });
86
+
87
+ test("Get create a regtest wif wallet", async () => {
88
+ const req = {
89
+ network: "regtest",
90
+ type: "wif",
91
+ name: "wif2",
92
+ } as WalletRequestI;
93
+ let w = await createWallet(req);
94
+ expect(w.cashaddr).toMatch(/bchreg:/);
95
+ expect(w.walletType).toBe("wif");
96
+
97
+ // recover it from the id
98
+ let w2 = await walletFromId(w.toString());
99
+ expect(w2.getInfo()).toMatchObject(w.getInfo());
100
+ expect(w2.name).toBe(w.name);
101
+
102
+ // recover it from the database
103
+ const req2 = {
104
+ network: "regtest",
105
+ type: "wif",
106
+ name: "wif2",
107
+ } as WalletRequestI;
108
+ let w3 = await createWallet(req2);
109
+ expect(w3.getInfo()).toMatchObject(w.getInfo());
110
+ expect(w3.name).toBe(w.name);
111
+ });
112
+
113
+ test("Get create a named regtest seed wallet", async () => {
114
+ const req = {
115
+ name: "Bob's Regtest Wallet",
116
+ type: "seed",
117
+ network: "regtest",
118
+ } as WalletRequestI;
119
+ let w = await createWallet(req);
120
+ expect(w.cashaddr).toMatch(/bchreg:/);
121
+ expect(w.name).toBe("Bob's Regtest Wallet");
122
+ });
123
+
124
+ test("Get create a unnamed mainnet wallet", async () => {
125
+ const req = {
126
+ type: "seed",
127
+ network: "mainnet",
128
+ } as WalletRequestI;
129
+ let w = await createWallet(req);
130
+ expect(w.cashaddr).toMatch(/bitcoincash:q/);
131
+ expect(w.walletType).toBe("seed");
132
+ });
133
+
134
+ test("Get create a mainnet wif wallet", async () => {
135
+ const req = {
136
+ type: "wif",
137
+ network: "mainnet",
138
+ } as WalletRequestI;
139
+ let w = await createWallet(req);
140
+ expect(w.cashaddr).toMatch(/bitcoincash:q/);
141
+ expect(w.walletType).toBe("wif");
142
+ });
143
+
144
+ test("Retrieve a mainnet wif wallet", async () => {
145
+ const req = {
146
+ name: "Bob's Testnet Wallet, Again",
147
+ type: "wif",
148
+ network: "testnet",
149
+ } as WalletRequestI;
150
+ let w = await createWallet(req);
151
+ expect(w.cashaddr).toMatch(/bchtest:q/);
152
+ expect(w.walletType).toBe("wif");
153
+ delay(1000);
154
+ let w2 = await walletFromId(w.toString());
155
+ expect(w2.cashaddr).toBe(w.cashaddr);
156
+ expect(w2.name).toBe(w.name);
157
+ });
158
+ });
@@ -0,0 +1,67 @@
1
+ import { RegTestWallet, TestNetWallet, Wallet, BaseWallet } from "mainnet-js";
2
+ import { default as SqlProvider } from "./SqlProvider";
3
+
4
+ BaseWallet.StorageProvider = SqlProvider;
5
+
6
+ /**
7
+ * @jest-environment jsdom
8
+ */
9
+ test("Store and retrieve a Regtest wallet", async () => {
10
+ let w1 = await RegTestWallet.named("Basic Regtest");
11
+ expect(w1.name).toBe("Basic Regtest");
12
+ expect(w1.network).toBe("regtest");
13
+ expect(w1.walletType).toBe("seed");
14
+ expect(w1.toString()).toBe("named:regtest:Basic Regtest");
15
+ expect(w1.toDbString()).toMatch(/seed:regtest:(\w+\s){11}\w+/);
16
+ let w1Again = await RegTestWallet.named("Basic Regtest");
17
+
18
+ expect(w1.name).toBe(w1Again.name);
19
+ expect(w1.network).toBe(w1Again.network);
20
+ expect(w1.cashaddr).toBe(w1Again.cashaddr);
21
+ expect(w1.privateKeyWif).toBe(w1Again.privateKeyWif);
22
+ expect(w1.toString()).toBe(w1Again.toString());
23
+ });
24
+
25
+ test("Store and retrieve a TestNet wallet", async () => {
26
+ let w1 = await TestNetWallet.named("Basic Testnet Wallet", "db-test");
27
+ expect(w1.name).toBe("Basic Testnet Wallet");
28
+ expect(w1.network).toBe("testnet");
29
+ expect(w1.walletType).toBe("seed");
30
+ expect(w1.toDbString()).toMatch(/seed:testnet:(\w+\s){11}\w+/);
31
+ expect(w1.toString()).toBe("named:testnet:Basic Testnet Wallet");
32
+ let w1Again = await TestNetWallet.named("Basic Testnet Wallet", "db-test");
33
+
34
+ expect(w1.name).toBe(w1Again.name);
35
+ expect(w1.network).toBe(w1Again.network);
36
+ expect(w1.cashaddr).toBe(w1Again.cashaddr);
37
+ expect(w1.privateKeyWif).toBe(w1Again.privateKeyWif);
38
+ expect(w1.toString()).toBe(w1Again.toString());
39
+ });
40
+
41
+ test("Store and retrieve a seed wallet", async () => {
42
+ let w1 = await Wallet.named("Seed Wallet", "db-test");
43
+ expect(w1.name).toBe("Seed Wallet");
44
+ expect(w1.network).toBe("mainnet");
45
+ expect(w1.walletType).toBe("seed");
46
+ expect(w1.toDbString()).toMatch(/seed:mainnet:(\w+\s){11}\w+/);
47
+ expect(w1.toString()).toBe("named:mainnet:Seed Wallet");
48
+ let w1Again = await Wallet.named("Seed Wallet", "db-test");
49
+
50
+ expect(w1.name).toBe(w1Again.name);
51
+ expect(w1.network).toBe(w1Again.network);
52
+ expect(w1.cashaddr).toBe(w1Again.cashaddr);
53
+ expect(w1.privateKeyWif).toBe(w1Again.privateKeyWif);
54
+ expect(w1.toString()).toBe(w1Again.toString());
55
+ });
56
+
57
+ test("Expect Error passing mainnet wallet to error", async () => {
58
+ expect.assertions(1);
59
+ try {
60
+ process.env.ALLOW_MAINNET_USER_WALLETS = "false";
61
+ await Wallet.named("Seed Wallet", "db-test");
62
+ } catch (e: any) {
63
+ expect(e.message).toBe(
64
+ 'Refusing to save wallet in an open public database, remove ALLOW_MAINNET_USER_WALLETS="false", if this service is secure and private'
65
+ );
66
+ }
67
+ });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default as SqlProvider } from "./SqlProvider.js";
2
+ export * from "./webhook/index.js";
package/src/util.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface sslConfigI {
2
+ rejectUnauthorized: boolean;
3
+ ca?: string;
4
+ key?: string;
5
+ cert?: string;
6
+ }
7
+
8
+ export function getSslConfig(): sslConfigI | undefined {
9
+ const ca = process.env.DATABASE_SSL_CA
10
+ ? Buffer.from(process.env.DATABASE_SSL_CA, "base64").toString("ascii")
11
+ : undefined;
12
+ const key = process.env.DATABASE_SSL_KEY
13
+ ? Buffer.from(process.env.DATABASE_SSL_KEY, "base64").toString("ascii")
14
+ : undefined;
15
+ const cert = process.env.DATABASE_SSL_CERT
16
+ ? Buffer.from(process.env.DATABASE_SSL_CERT, "base64").toString("ascii")
17
+ : undefined;
18
+ let ssl: sslConfigI = {
19
+ rejectUnauthorized:
20
+ process.env.DATABASE_SSL_REJECT_UNAUTHORIZED == "false" ? false : true,
21
+ ca: ca,
22
+ key: key,
23
+ cert: cert,
24
+ };
25
+ if (ssl.ca || ssl.cert || ssl.key) {
26
+ return ssl;
27
+ } else {
28
+ return;
29
+ }
30
+ }
@@ -0,0 +1,9 @@
1
+ import { Webhook } from "./Webhook";
2
+
3
+ describe("Webhook worker tests", () => {
4
+ test("Test creating hook", async () => {
5
+ const hook = new Webhook({ cashaddr: "asdf" });
6
+ expect(hook.cashaddr).toBe("asdf");
7
+ expect(hook.type).toBe(undefined);
8
+ });
9
+ });
@@ -0,0 +1,99 @@
1
+ import SqlProvider from "../SqlProvider.js";
2
+ import { TxI } from "mainnet-js";
3
+
4
+ import axios from "axios";
5
+
6
+ export enum WebhookType {
7
+ transactionIn = "transaction:in",
8
+ transactionOut = "transaction:out",
9
+ transactionInOut = "transaction:in,out",
10
+ balance = "balance",
11
+ }
12
+
13
+ export enum WebhookRecurrence {
14
+ once = "once",
15
+ recurrent = "recurrent",
16
+ }
17
+
18
+ export class Webhook {
19
+ id?: number;
20
+ cashaddr!: string;
21
+ type!: string;
22
+ recurrence!: string;
23
+ url!: string;
24
+ status!: string;
25
+ last_height!: number;
26
+ tx_seen!: TxI[];
27
+ expires_at!: Date;
28
+
29
+ db!: SqlProvider;
30
+
31
+ constructor(hook: Webhook | Object) {
32
+ Object.assign(this, hook);
33
+ }
34
+
35
+ // abstract, empty implementation
36
+ async start(): Promise<void> {}
37
+
38
+ // abstract, empty implementation
39
+ async stop(): Promise<void> {}
40
+
41
+ async destroy(): Promise<void> {
42
+ if (this.id) {
43
+ await this.db.deleteWebhook(this.id);
44
+ }
45
+ }
46
+
47
+ async post(data: any): Promise<boolean> {
48
+ try {
49
+ await axios.post(this.url, data);
50
+ // console.debug("Posted webhook", this.url, data);
51
+ return true;
52
+ } catch (e: any) {
53
+ if (e.message && e.message.status === 200) {
54
+ return true;
55
+ }
56
+
57
+ // console.debug("Failed to post webhook", this.url, e);
58
+ return false;
59
+ }
60
+ }
61
+
62
+ //#region debug
63
+ public static debug = class {
64
+ static setupAxiosMocks() {
65
+ axios.interceptors.request.use((config) => {
66
+ const url = config.url!;
67
+ if (!url.startsWith("http://example.com")) {
68
+ return config;
69
+ }
70
+
71
+ let response;
72
+ if (url === "http://example.com/fail") {
73
+ response = { status: 503 };
74
+ } else {
75
+ response = { status: 200 };
76
+ }
77
+
78
+ if (url in this.responses) {
79
+ this.responses[url].push(response);
80
+ } else {
81
+ this.responses[url] = [response];
82
+ }
83
+
84
+ // cancel actual http request
85
+ return {
86
+ ...config,
87
+ cancelToken: new axios.CancelToken((cancel) => cancel(response)),
88
+ };
89
+ });
90
+ }
91
+
92
+ static reset() {
93
+ this.responses = {};
94
+ }
95
+
96
+ static responses: any = {};
97
+ };
98
+ //#endregion
99
+ }
@@ -0,0 +1,323 @@
1
+ import WebhookWorker from "../webhook/WebhookWorker";
2
+ import { RegTestWallet } from "mainnet-js";
3
+ import { mine } from "mainnet-js";
4
+ import { Webhook, WebhookRecurrence, WebhookType } from "./Webhook";
5
+
6
+ let worker: WebhookWorker;
7
+ let alice;
8
+ let aliceWif;
9
+
10
+ /**
11
+ * @jest-environment jsdom
12
+ */
13
+ describe("Webhook worker tests", () => {
14
+ beforeAll(async () => {
15
+ try {
16
+ if (process.env.PRIVATE_WIF) {
17
+ alice = process.env.ADDRESS!;
18
+ aliceWif = `wif:regtest:${process.env.PRIVATE_WIF!}`;
19
+ } else {
20
+ console.error("regtest env vars not set");
21
+ }
22
+
23
+ Webhook.debug.setupAxiosMocks();
24
+ worker = await WebhookWorker.instance();
25
+ } catch (e: any) {
26
+ throw e;
27
+ }
28
+ });
29
+
30
+ beforeEach(async () => {
31
+ worker.deleteAllWebhooks();
32
+ });
33
+
34
+ afterEach(async () => {
35
+ Webhook.debug.reset();
36
+ });
37
+
38
+ afterAll(async () => {
39
+ await worker.destroy();
40
+ await worker.db.close();
41
+ });
42
+
43
+ test("Test non-recurrent hook to be deleted after successful call", async () => {
44
+ try {
45
+ const aliceWallet = await RegTestWallet.fromId(aliceWif);
46
+ const bobWallet = await RegTestWallet.newRandom();
47
+ await worker.registerWebhook({
48
+ cashaddr: bobWallet.cashaddr!,
49
+ url: "http://example.com/success",
50
+ type: WebhookType.transactionIn,
51
+ recurrence: WebhookRecurrence.once,
52
+ });
53
+
54
+ await Promise.all([
55
+ aliceWallet.send([
56
+ {
57
+ cashaddr: bobWallet.cashaddr!,
58
+ value: 1000,
59
+ unit: "satoshis",
60
+ },
61
+ ]),
62
+ bobWallet.waitForTransaction(),
63
+ ]);
64
+
65
+ // return funds
66
+ // let sendResponse2 = await bobWallet.sendMax(aliceWallet.cashaddr!);
67
+
68
+ await new Promise((resolve) =>
69
+ setTimeout(async () => {
70
+ expect(
71
+ Webhook.debug.responses["http://example.com/success"].length
72
+ ).toBe(1);
73
+ expect(worker.activeHooks.size).toBe(0);
74
+
75
+ resolve(true);
76
+ }, 3000)
77
+ );
78
+ } catch (e: any) {
79
+ console.log(e, e.stack, e.message);
80
+ throw e;
81
+ }
82
+ });
83
+
84
+ test("Test non-recurrent hook to be not deleted after failed call", async () => {
85
+ try {
86
+ const aliceWallet = await RegTestWallet.fromId(aliceWif);
87
+ const bobWallet = await RegTestWallet.newRandom();
88
+ await worker.registerWebhook({
89
+ cashaddr: bobWallet.cashaddr!,
90
+ url: "http://example.com/fail",
91
+ type: WebhookType.transactionIn,
92
+ recurrence: WebhookRecurrence.once,
93
+ });
94
+
95
+ await Promise.all([
96
+ aliceWallet.send([
97
+ {
98
+ cashaddr: bobWallet.cashaddr!,
99
+ value: 1000,
100
+ unit: "satoshis",
101
+ },
102
+ ]),
103
+ bobWallet.waitForTransaction(),
104
+ ]);
105
+
106
+ await new Promise((resolve) =>
107
+ setTimeout(async () => {
108
+ expect(
109
+ Webhook.debug.responses["http://example.com/fail"].length
110
+ ).toBe(1);
111
+ expect(worker.activeHooks.size).toBe(1);
112
+
113
+ // return funds
114
+ // let sendResponse2 = await bobWallet.sendMax(aliceWallet.cashaddr!);
115
+ resolve(true);
116
+ }, 3000)
117
+ );
118
+ } catch (e: any) {
119
+ console.log(e, e.stack, e.message);
120
+ throw e;
121
+ }
122
+ });
123
+
124
+ test("Test recurrent hook for incoming transaction", async () => {
125
+ try {
126
+ const aliceWallet = await RegTestWallet.fromId(aliceWif);
127
+ const bobWallet = await RegTestWallet.newRandom();
128
+ await worker.registerWebhook({
129
+ cashaddr: bobWallet.cashaddr!,
130
+ url: "http://example.com/bob",
131
+ type: WebhookType.transactionIn,
132
+ recurrence: WebhookRecurrence.recurrent,
133
+ });
134
+
135
+ await Promise.all([
136
+ aliceWallet.send([
137
+ {
138
+ cashaddr: bobWallet.cashaddr!,
139
+ value: 1000,
140
+ unit: "satoshis",
141
+ },
142
+ ]),
143
+ bobWallet.waitForTransaction(),
144
+ ]);
145
+
146
+ // return funds
147
+ // let sendResponse2 = await bobWallet.sendMax(aliceWallet.cashaddr!);
148
+
149
+ await new Promise((resolve) =>
150
+ setTimeout(async () => {
151
+ expect(Webhook.debug.responses["http://example.com/bob"].length).toBe(
152
+ 1
153
+ );
154
+ expect(worker.activeHooks.size).toBe(1);
155
+
156
+ resolve(true);
157
+ }, 3000)
158
+ );
159
+ } catch (e: any) {
160
+ console.log(e, e.stack, e.message);
161
+ throw e;
162
+ }
163
+ });
164
+
165
+ test("Test recurrent hook for outgoing transactions", async () => {
166
+ try {
167
+ const aliceWallet = await RegTestWallet.fromId(aliceWif);
168
+ const bobWallet = await RegTestWallet.newRandom();
169
+ await worker.registerWebhook({
170
+ cashaddr: bobWallet.cashaddr!,
171
+ url: "http://example.com/bob",
172
+ type: WebhookType.transactionOut,
173
+ recurrence: WebhookRecurrence.recurrent,
174
+ });
175
+
176
+ await Promise.all([
177
+ aliceWallet.send([
178
+ {
179
+ cashaddr: bobWallet.cashaddr!,
180
+ value: 1000,
181
+ unit: "satoshis",
182
+ },
183
+ ]),
184
+ bobWallet.waitForTransaction(),
185
+ ]);
186
+
187
+ // return funds
188
+ await Promise.all([
189
+ bobWallet.sendMax(aliceWallet.cashaddr!),
190
+ aliceWallet.waitForTransaction(),
191
+ ]);
192
+
193
+ await new Promise((resolve) =>
194
+ setTimeout(async () => {
195
+ expect(Webhook.debug.responses["http://example.com/bob"].length).toBe(
196
+ 1
197
+ );
198
+ expect(worker.activeHooks.size).toBe(1);
199
+
200
+ resolve(true);
201
+ }, 3000)
202
+ );
203
+ } catch (e: any) {
204
+ console.log(e, e.stack, e.message);
205
+ throw e;
206
+ }
207
+ });
208
+
209
+ test("Test should pickup transactions happened while offline", async () => {
210
+ try {
211
+ const aliceWallet = await RegTestWallet.fromId(aliceWif);
212
+ const bobWallet = await RegTestWallet.newRandom();
213
+ const minerWallet = await RegTestWallet.newRandom();
214
+ const hookId = await worker.registerWebhook({
215
+ cashaddr: bobWallet.cashaddr!,
216
+ url: "http://example.com/bob",
217
+ type: WebhookType.transactionIn,
218
+ recurrence: WebhookRecurrence.recurrent,
219
+ });
220
+
221
+ // initial transaction
222
+ await aliceWallet.send([
223
+ {
224
+ cashaddr: bobWallet.cashaddr!,
225
+ value: 1000,
226
+ unit: "satoshis",
227
+ },
228
+ ]);
229
+
230
+ // wait worker to process, updating the status of the hook
231
+ await new Promise((resolve) => setTimeout(resolve, 5000));
232
+
233
+ let hook = await worker.getWebhook(hookId);
234
+ expect(hook!.status).not.toBe("");
235
+ expect(hook!.tx_seen).not.toBe([]);
236
+ hook!.tx_seen[0];
237
+ expect(Webhook.debug.responses["http://example.com/bob"].length).toBe(1);
238
+
239
+ // shutdown
240
+ await worker.destroy();
241
+ expect(worker.activeHooks.size).toBe(0);
242
+
243
+ // also mine a block while offline)
244
+ await mine({ cashaddr: minerWallet.cashaddr!, blocks: 1 });
245
+
246
+ // make two more transactions "offline"
247
+ await aliceWallet.send([
248
+ {
249
+ cashaddr: bobWallet.cashaddr!,
250
+ value: 1000,
251
+ unit: "satoshis",
252
+ },
253
+ ]);
254
+ await aliceWallet.send([
255
+ {
256
+ cashaddr: bobWallet.cashaddr!,
257
+ value: 2000,
258
+ unit: "satoshis",
259
+ },
260
+ ]);
261
+ await mine({ cashaddr: minerWallet.cashaddr!, blocks: 1 });
262
+ await new Promise((resolve) => setTimeout(resolve, 5000));
263
+
264
+ // wait worker to process the transactions occured while offline
265
+ await worker.init();
266
+
267
+ await new Promise((resolve) =>
268
+ setTimeout(async () => {
269
+ expect(worker.activeHooks.size).toBe(1);
270
+ expect(Webhook.debug.responses["http://example.com/bob"].length).toBe(
271
+ 3
272
+ );
273
+
274
+ resolve(true);
275
+ }, 10000)
276
+ );
277
+ } catch (e: any) {
278
+ console.log(e, e.stack, e.message);
279
+ throw e;
280
+ }
281
+ });
282
+
283
+ test("Test non-recurrent watch balance hook", async () => {
284
+ try {
285
+ const aliceWallet = await RegTestWallet.fromId(aliceWif);
286
+ const bobWallet = await RegTestWallet.newRandom();
287
+ await worker.registerWebhook({
288
+ cashaddr: bobWallet.cashaddr!,
289
+ url: "http://example.com/watchBalance",
290
+ type: WebhookType.balance,
291
+ recurrence: WebhookRecurrence.once,
292
+ });
293
+
294
+ await Promise.all([
295
+ aliceWallet.send([
296
+ {
297
+ cashaddr: bobWallet.cashaddr!,
298
+ value: 1000,
299
+ unit: "satoshis",
300
+ },
301
+ ]),
302
+ bobWallet.waitForTransaction(),
303
+ ]);
304
+
305
+ // return funds
306
+ // let sendResponse2 = await bobWallet.sendMax(aliceWallet.cashaddr!);
307
+
308
+ await new Promise((resolve) =>
309
+ setTimeout(async () => {
310
+ expect(
311
+ Webhook.debug.responses["http://example.com/watchBalance"].length
312
+ ).toBe(1);
313
+ expect(worker.activeHooks.size).toBe(0);
314
+
315
+ resolve(true);
316
+ }, 3000)
317
+ );
318
+ } catch (e: any) {
319
+ console.log(e, e.stack, e.message);
320
+ throw e;
321
+ }
322
+ });
323
+ });