@stellar/typescript-wallet-sdk 1.0.0-alpha.2 → 1.1.0-alpha.1

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 (34) hide show
  1. package/lib/bundle.js +1541 -80
  2. package/lib/bundle.js.map +1 -1
  3. package/lib/walletSdk/Watcher/Types.d.ts +65 -0
  4. package/lib/walletSdk/Watcher/index.d.ts +54 -0
  5. package/lib/walletSdk/anchor/Types.d.ts +76 -0
  6. package/lib/walletSdk/anchor/index.d.ts +78 -7
  7. package/lib/walletSdk/auth/WalletSigner.d.ts +3 -0
  8. package/lib/walletSdk/auth/index.d.ts +8 -5
  9. package/lib/walletSdk/exception/index.d.ts +9 -0
  10. package/lib/walletSdk/horizon/Account.d.ts +20 -0
  11. package/lib/walletSdk/horizon/AccountService.d.ts +8 -0
  12. package/lib/walletSdk/horizon/Stellar.d.ts +5 -1
  13. package/lib/walletSdk/index.d.ts +12 -13
  14. package/lib/walletSdk/interactive/index.d.ts +16 -5
  15. package/lib/walletSdk/util/camelToSnakeCase.d.ts +2 -0
  16. package/lib/walletSdk/util/sleep.d.ts +1 -0
  17. package/package.json +12 -6
  18. package/src/walletSdk/Anchor/Types.ts +79 -0
  19. package/src/walletSdk/Anchor/index.ts +224 -24
  20. package/src/walletSdk/Auth/WalletSigner.ts +18 -3
  21. package/src/walletSdk/Auth/index.ts +41 -15
  22. package/src/walletSdk/Watcher/Types.ts +81 -0
  23. package/src/walletSdk/Watcher/index.ts +355 -0
  24. package/src/walletSdk/exception/index.ts +23 -2
  25. package/src/walletSdk/horizon/Account.ts +52 -0
  26. package/src/walletSdk/horizon/AccountService.ts +19 -0
  27. package/src/walletSdk/horizon/Stellar.ts +10 -2
  28. package/src/walletSdk/index.ts +41 -28
  29. package/src/walletSdk/interactive/index.ts +32 -43
  30. package/src/walletSdk/util/camelToSnakeCase.ts +13 -0
  31. package/test/account.test.ts +36 -0
  32. package/test/fixtures/TransactionsResponse.ts +230 -0
  33. package/test/wallet.test.ts +1722 -0
  34. package/test/index.test.ts +0 -73
@@ -0,0 +1,1722 @@
1
+ import StellarSdk, { Keypair } from "stellar-sdk";
2
+ import http from "http";
3
+ import sinon from "sinon";
4
+
5
+ import sdk from "../src";
6
+ import { AssetNotSupportedError, ServerRequestFailedError } from "../src/walletSdk/exception";
7
+ import {
8
+ TransactionStatus,
9
+ WatcherResponse,
10
+ } from "../src/walletSdk/Watcher/Types";
11
+ import { Watcher } from "../src/walletSdk/Watcher";
12
+
13
+ import { TransactionsResponse } from "../test/fixtures/TransactionsResponse";
14
+
15
+ const originalSetTimeout = global.setTimeout;
16
+ function sleep(time: number) {
17
+ return new Promise((resolve) => {
18
+ originalSetTimeout(resolve, time);
19
+ });
20
+ }
21
+
22
+ const { walletSdk } = sdk;
23
+ describe("Wallet", () => {
24
+ it("should init", () => {
25
+ walletSdk.Wallet.TestNet();
26
+ walletSdk.Wallet.MainNet();
27
+ });
28
+ it("should be able return a client", async () => {
29
+ let appConfig = new walletSdk.ApplicationConfiguration();
30
+ let wal = new walletSdk.Wallet(
31
+ walletSdk.StellarConfiguration.TestNet(),
32
+ appConfig
33
+ );
34
+
35
+ let client = wal.getClient();
36
+
37
+ // custom client
38
+ client = wal.getClient({
39
+ httpAgent: new http.Agent({ keepAlive: false }),
40
+ });
41
+ });
42
+ });
43
+
44
+ describe("SEP-24 flow", () => {
45
+ it("should init a wallet with network and domain", () => {
46
+ const Wal = walletSdk.Wallet.TestNet();
47
+ Wal.anchor("anchor-domain");
48
+ });
49
+ });
50
+
51
+ let anchor;
52
+ let accountKp;
53
+ let authToken;
54
+ describe("Anchor", () => {
55
+ beforeEach(() => {
56
+ const Wal = walletSdk.Wallet.TestNet();
57
+ anchor = Wal.anchor("testanchor.stellar.org");
58
+ accountKp = Keypair.fromSecret(
59
+ "SDXC3OHSJZEQIXKEWFDNEZEQ7SW5DWBPW7RKUWI36ILY3QZZ6VER7TXV"
60
+ );
61
+ });
62
+ it("should give TOML info", async () => {
63
+ const resp = await anchor.getInfo();
64
+
65
+ expect(resp.webAuthEndpoint).toBe("https://testanchor.stellar.org/auth");
66
+ expect(resp.currencies.length).toBe(2);
67
+ });
68
+ it("should be able to authenticate", async () => {
69
+ const auth = await anchor.auth();
70
+
71
+ authToken = await auth.authenticate(accountKp);
72
+ expect(authToken).toBeTruthy();
73
+ expect(typeof authToken).toBe("string");
74
+ });
75
+
76
+ it("should be able to authenticate with client domain", async () => {
77
+ const auth = await anchor.auth();
78
+ let signedByClient = false;
79
+ let signedByDomain = false;
80
+
81
+ const walletSigner = {
82
+ signWithClientAccount: (txn, account) => {
83
+ txn.sign(account);
84
+ signedByClient = true;
85
+ return txn;
86
+ },
87
+ signWithDomainAccount: (transactionXDR, networkPassPhrase, account) => {
88
+ // dummy secret key for signing
89
+ const clientDomainKp = Keypair.fromSecret(
90
+ "SC7PKBRGRI5X4XP4QICBZ2NL67VUJJVKFKXDTGSPI3SQYZGC4NZWONIH"
91
+ );
92
+ const transaction = StellarSdk.TransactionBuilder.fromXDR(
93
+ transactionXDR,
94
+ networkPassPhrase
95
+ );
96
+ transaction.sign(clientDomainKp);
97
+ signedByDomain = true;
98
+ return transaction;
99
+ },
100
+ };
101
+
102
+ // because using dummy sk and not real demo wallet sk, lets just check that signing is called
103
+ const challengeResponse = await auth.challenge(
104
+ accountKp,
105
+ "",
106
+ "demo-wallet-server.stellar.org"
107
+ );
108
+ const txn = auth.sign(accountKp, challengeResponse, walletSigner);
109
+ expect(txn).toBeTruthy();
110
+ expect(signedByClient).toBe(true);
111
+ expect(signedByDomain).toBe(true);
112
+ });
113
+ it("should get anchor services info", async () => {
114
+ const serviceInfo = await anchor.getServicesInfo();
115
+
116
+ expect(serviceInfo.deposit).toBeTruthy();
117
+ expect(serviceInfo.withdraw).toBeTruthy();
118
+ });
119
+
120
+ it("should give interactive deposit url", async () => {
121
+ const assetCode = "SRT";
122
+ const resp = await anchor.interactive().deposit({
123
+ accountAddress: accountKp.publicKey(),
124
+ assetCode,
125
+ authToken,
126
+ lang: "en-US",
127
+ extraFields: {
128
+ wallet_name: "Test Wallet",
129
+ wallet_url: "https://stellar.org/",
130
+ },
131
+ });
132
+
133
+ expect(resp.url).toBeTruthy();
134
+ expect(resp.id).toBeTruthy();
135
+ });
136
+
137
+ it("should give interactive withdraw url", async () => {
138
+ const assetCode = "SRT";
139
+ const resp = await anchor.interactive().withdraw({
140
+ accountAddress: accountKp.publicKey(),
141
+ assetCode,
142
+ authToken,
143
+ lang: "en-US",
144
+ extraFields: {
145
+ wallet_name: "Test Wallet",
146
+ wallet_url: "https://stellar.org/",
147
+ },
148
+ });
149
+
150
+ expect(resp.url).toBeTruthy();
151
+ expect(resp.id).toBeTruthy();
152
+ });
153
+
154
+ it("should fetch new transaction by id", async () => {
155
+ const assetCode = "SRT";
156
+
157
+ // creates new 'incomplete' deposit transaction
158
+ const { id: transactionId } = await anchor.interactive().deposit({
159
+ accountAddress: accountKp.publicKey(),
160
+ assetCode,
161
+ authToken,
162
+ });
163
+
164
+ // fetches transaction that has just been created
165
+ const transaction = await anchor.getTransactionBy({
166
+ authToken,
167
+ id: transactionId,
168
+ });
169
+
170
+ const { id, kind, status, amount_in, amount_out } = transaction;
171
+
172
+ expect(transaction).toBeTruthy();
173
+ expect(id === transactionId).toBeTruthy;
174
+ expect(kind === "deposit").toBeTruthy;
175
+ expect(status === "incomplete").toBeTruthy;
176
+ // we expect fresh 'incomplete' transactions to not have amounts set yet
177
+ expect(amount_in).toBeFalsy;
178
+ expect(amount_out).toBeFalsy;
179
+ });
180
+
181
+ it("should error fetching non-existing transaction by id", async () => {
182
+ await expect(async () => {
183
+ const nonExistingTransactionId = "da8575e9-edc6-4f99-98cf-2b302f203cc7";
184
+ await anchor.getTransactionBy({
185
+ authToken,
186
+ id: nonExistingTransactionId,
187
+ });
188
+ }).rejects.toThrowError(ServerRequestFailedError);
189
+ });
190
+
191
+ it("should fetch 5 existing transactions by token code", async () => {
192
+ const transactions = await anchor.getTransactionsForAsset({
193
+ authToken,
194
+ assetCode: "SRT",
195
+ limit: 5,
196
+ lang: "en-US",
197
+ });
198
+
199
+ transactions.forEach(({ status }) => {
200
+ expect(status).toBeTruthy();
201
+ expect(typeof status).toBe("string");
202
+ });
203
+
204
+ expect(transactions.length === 5).toBeTruthy();
205
+ });
206
+
207
+ it("should fetch 3 existing deposit transactions by token code", async () => {
208
+ const transactions = await anchor.getTransactionsForAsset({
209
+ authToken,
210
+ assetCode: "SRT",
211
+ limit: 3,
212
+ kind: "deposit",
213
+ lang: "en-US",
214
+ });
215
+
216
+ transactions.forEach(({ kind }) => {
217
+ expect(kind === "deposit").toBeTruthy();
218
+ });
219
+
220
+ expect(transactions.length === 3).toBeTruthy();
221
+ });
222
+
223
+ it("should fetch 2 existing withdrawal transactions by token code", async () => {
224
+ const transactions = await anchor.getTransactionsForAsset({
225
+ authToken,
226
+ assetCode: "SRT",
227
+ limit: 2,
228
+ kind: "withdrawal",
229
+ lang: "en-US",
230
+ });
231
+
232
+ transactions.forEach(({ kind }) => {
233
+ expect(kind === "withdrawal").toBeTruthy();
234
+ });
235
+
236
+ expect(transactions.length === 2).toBeTruthy();
237
+ });
238
+
239
+ it("should error fetching transactions with invalid pading id", async () => {
240
+ await expect(async () => {
241
+ await anchor.getTransactionsForAsset({
242
+ authToken,
243
+ assetCode: "SRT",
244
+ lang: "en-US",
245
+ pagingId: "randomPagingId",
246
+ });
247
+ }).rejects.toThrowError(ServerRequestFailedError);
248
+ });
249
+
250
+ it("should fetch only finished transactions", async () => {
251
+ // mock TOML response to include the "SRT" currency
252
+ jest
253
+ .spyOn(anchor, "getInfo")
254
+ .mockResolvedValue({
255
+ networkPassphrase: 'Test SDF Network ; September 2015',
256
+ transferServerSep24: 'https://testanchor.stellar.org/sep24',
257
+ webAuthEndpoint: 'https://testanchor.stellar.org/auth',
258
+ accounts: [
259
+ 'GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE',
260
+ 'GDRND2IUXVMHZ4XTB2RZ4AJ3AOLON3WTAOC23XEASB56NHDFW3ED57TW'
261
+ ],
262
+ documentation: {
263
+ orgName: 'Stellar Development Foundation',
264
+ orgUrl: 'https://stellar.org',
265
+ orgGithub: 'stellar',
266
+ },
267
+ principals: [],
268
+ currencies: [{ code: "SRT" }, { code: "USDC" }],
269
+ validators: []
270
+ });
271
+
272
+ // mock transactions response so we have a few completed/refunded
273
+ jest
274
+ .spyOn(anchor, "getTransactionsForAsset")
275
+ .mockResolvedValue(TransactionsResponse);
276
+
277
+ const transactions = await anchor.getHistory({
278
+ authToken,
279
+ assetCode: "SRT",
280
+ });
281
+
282
+ expect(transactions.length).toBeGreaterThan(0);
283
+
284
+ transactions.forEach(({ status }) => {
285
+ expect([
286
+ TransactionStatus.completed,
287
+ TransactionStatus.refunded
288
+ ].includes(status)).toBeTruthy();
289
+ });
290
+ });
291
+
292
+ it("should error fetching history for unsupported asset", async () => {
293
+ await expect(async () => {
294
+ await anchor.getHistory({
295
+ authToken,
296
+ assetCode: "ABC",
297
+ });
298
+ }).rejects.toThrowError(AssetNotSupportedError);
299
+ });
300
+
301
+ describe("watchAllTransactions", () => {
302
+ let clock: sinon.SinonFakeTimers;
303
+ let watcher: Watcher;
304
+
305
+ beforeEach(async () => {
306
+ clock = sinon.useFakeTimers(0);
307
+ watcher = anchor.watcher();
308
+ });
309
+
310
+ afterEach(() => {
311
+ clock.restore();
312
+ });
313
+
314
+ test("Return only pending and incomplete", async () => {
315
+ const onMessage = sinon.spy(() => {
316
+ expect(onMessage.callCount).toBeLessThan(6);
317
+ });
318
+
319
+ const onError = sinon.spy((e) => {
320
+ expect(e).toBeUndefined();
321
+ });
322
+
323
+ // mock default transactions response
324
+ jest
325
+ .spyOn(anchor, "getTransactionsForAsset")
326
+ .mockResolvedValue(TransactionsResponse);
327
+
328
+ // start watching
329
+ const { stop } = watcher.watchAllTransactions({
330
+ authToken,
331
+ assetCode: "SRT",
332
+ lang: "en-US",
333
+ onMessage,
334
+ onError,
335
+ timeout: 1,
336
+ });
337
+
338
+ // nothing should run at first (async api call in progress)
339
+ expect(onMessage.callCount).toBe(0);
340
+ expect(onError.callCount).toBe(0);
341
+
342
+ await sleep(1);
343
+
344
+ // 5 pending & incomplete
345
+ expect(onMessage.callCount).toBe(5);
346
+ expect(onError.callCount).toBe(0);
347
+
348
+ // stops watching
349
+ stop();
350
+ });
351
+
352
+ test("Return pending and incomplete + watchlist", async () => {
353
+ const onMessage = sinon.spy(() => {
354
+ expect(onMessage.callCount).toBeLessThan(9);
355
+ });
356
+
357
+ const onError = sinon.spy((e) => {
358
+ expect(e).toBeUndefined();
359
+ });
360
+
361
+ // mock default transactions response
362
+ jest
363
+ .spyOn(anchor, "getTransactionsForAsset")
364
+ .mockResolvedValue(TransactionsResponse);
365
+
366
+ // start watching
367
+ const { stop } = watcher.watchAllTransactions({
368
+ authToken,
369
+ assetCode: "SRT",
370
+ lang: "en-US",
371
+ onMessage,
372
+ onError,
373
+ // transactions from watchlist should ALWAYS be returned regardless of their statuses
374
+ watchlist: [
375
+ "hytcf7c4-927d-4b7a-8a1f-d7188ebddu8i", // expired
376
+ "def5d166-5a5e-4d5c-ba5d-271c32cd8abc", // refunded
377
+ "uyt1576b-ac28-4c02-a521-ddbfd9ae7oiu", // completed
378
+ ],
379
+ timeout: 1,
380
+ });
381
+
382
+ // nothing should run at first (async api call in progress)
383
+ expect(onMessage.callCount).toBe(0);
384
+ expect(onError.callCount).toBe(0);
385
+
386
+ await sleep(1);
387
+
388
+ // 5 pending & incomplete + 3 from watchlist
389
+ expect(onMessage.callCount).toBe(8);
390
+ expect(onError.callCount).toBe(0);
391
+
392
+ // stops watching
393
+ stop();
394
+ });
395
+
396
+ test("Watch three transaction updates", async () => {
397
+ const onMessage = sinon.spy(() => {
398
+ expect(onMessage.callCount).toBeLessThan(9);
399
+ });
400
+
401
+ const onError = sinon.spy((e) => {
402
+ expect(e).toBeUndefined();
403
+ });
404
+
405
+ // mock default transactions response
406
+ jest
407
+ .spyOn(anchor, "getTransactionsForAsset")
408
+ .mockResolvedValue(TransactionsResponse);
409
+
410
+ // start watching
411
+ const { stop } = watcher.watchAllTransactions({
412
+ authToken,
413
+ assetCode: "SRT",
414
+ lang: "en-US",
415
+ onMessage,
416
+ onError,
417
+ timeout: 1,
418
+ });
419
+
420
+ // nothing should run at first (async api call in progress)
421
+ expect(onMessage.callCount).toBe(0);
422
+ expect(onError.callCount).toBe(0);
423
+
424
+ await sleep(1);
425
+
426
+ // 5 pending & incomplete
427
+ expect(onMessage.callCount).toBe(5);
428
+ expect(onError.callCount).toBe(0);
429
+
430
+ // change one transaction to "completed"
431
+ const [firstTxA, ...restA] = TransactionsResponse;
432
+ let updatedTransactions = [
433
+ {
434
+ ...firstTxA,
435
+ status: TransactionStatus.completed,
436
+ },
437
+ ...restA,
438
+ ];
439
+
440
+ // update mock with new transaction status
441
+ jest
442
+ .spyOn(anchor, "getTransactionsForAsset")
443
+ .mockResolvedValue(updatedTransactions);
444
+
445
+ clock.next();
446
+ await sleep(1);
447
+
448
+ // 5 pending & incomplete + 1 recently completed
449
+ expect(onMessage.callCount).toBe(6);
450
+ expect(onError.callCount).toBe(0);
451
+
452
+ await sleep(1);
453
+
454
+ // 5 pending & incomplete + 1 recently completed
455
+ expect(onMessage.callCount).toBe(6);
456
+ expect(onError.callCount).toBe(0);
457
+
458
+ // change another transaction to "refunded"
459
+ const [firstTxB, secondTxB, ...restB] = updatedTransactions;
460
+ updatedTransactions = [
461
+ firstTxB,
462
+ {
463
+ ...secondTxB,
464
+ status: TransactionStatus.refunded,
465
+ },
466
+ ...restB,
467
+ ];
468
+
469
+ // update mock with new transaction status
470
+ jest
471
+ .spyOn(anchor, "getTransactionsForAsset")
472
+ .mockResolvedValue(updatedTransactions);
473
+
474
+ clock.next();
475
+ await sleep(1);
476
+
477
+ // 5 pending & incomplete + 1 recently completed + 1 recently refunded
478
+ expect(onMessage.callCount).toBe(7);
479
+ expect(onError.callCount).toBe(0);
480
+
481
+ // change another transaction to "pending_user_transfer_start"
482
+ const [firstTxC, secondTxC, thirdTxC, ...restC] = updatedTransactions;
483
+ updatedTransactions = [
484
+ firstTxC,
485
+ secondTxC,
486
+ {
487
+ ...thirdTxC,
488
+ status: TransactionStatus.pending_user_transfer_start,
489
+ },
490
+ ...restC,
491
+ ];
492
+
493
+ // update mock with new transaction status
494
+ jest
495
+ .spyOn(anchor, "getTransactionsForAsset")
496
+ .mockResolvedValue(updatedTransactions);
497
+
498
+ clock.next();
499
+ await sleep(1);
500
+
501
+ // 5 pending & incomplete + 1 recently completed + 1 recently refunded + 1 recently pending
502
+ expect(onMessage.callCount).toBe(8);
503
+ expect(onError.callCount).toBe(0);
504
+
505
+ // stops watching
506
+ stop();
507
+ });
508
+
509
+ test("Stops watching after 2 transaction updates", async () => {
510
+ const onMessage = sinon.spy(() => {
511
+ expect(onMessage.callCount).toBeLessThan(8);
512
+ });
513
+
514
+ const onError = sinon.spy((e) => {
515
+ expect(e).toBeUndefined();
516
+ });
517
+
518
+ // mock default transactions response
519
+ jest
520
+ .spyOn(anchor, "getTransactionsForAsset")
521
+ .mockResolvedValue(TransactionsResponse);
522
+
523
+ // start watching
524
+ const { stop } = watcher.watchAllTransactions({
525
+ authToken,
526
+ assetCode: "SRT",
527
+ lang: "en-US",
528
+ onMessage,
529
+ onError,
530
+ timeout: 1,
531
+ });
532
+
533
+ // nothing should run at first (async api call in progress)
534
+ expect(onMessage.callCount).toBe(0);
535
+ expect(onError.callCount).toBe(0);
536
+
537
+ await sleep(1);
538
+
539
+ // 5 pending & incomplete
540
+ expect(onMessage.callCount).toBe(5);
541
+ expect(onError.callCount).toBe(0);
542
+
543
+ // change one transaction to "completed"
544
+ const [firstTxA, ...restA] = TransactionsResponse;
545
+ let updatedTransactions = [
546
+ {
547
+ ...firstTxA,
548
+ status: TransactionStatus.completed,
549
+ },
550
+ ...restA,
551
+ ];
552
+
553
+ // update mock with new transaction status
554
+ jest
555
+ .spyOn(anchor, "getTransactionsForAsset")
556
+ .mockResolvedValue(updatedTransactions);
557
+
558
+ clock.next();
559
+ await sleep(1);
560
+
561
+ // 5 pending & incomplete + 1 recently completed
562
+ expect(onMessage.callCount).toBe(6);
563
+ expect(onError.callCount).toBe(0);
564
+
565
+ await sleep(1);
566
+
567
+ // 5 pending & incomplete + 1 recently completed
568
+ expect(onMessage.callCount).toBe(6);
569
+ expect(onError.callCount).toBe(0);
570
+
571
+ // change another transaction to "refunded"
572
+ const [firstTxB, secondTxB, ...restB] = updatedTransactions;
573
+ updatedTransactions = [
574
+ firstTxB,
575
+ {
576
+ ...secondTxB,
577
+ status: TransactionStatus.refunded,
578
+ },
579
+ ...restB,
580
+ ];
581
+
582
+ // update mock with new transaction status
583
+ jest
584
+ .spyOn(anchor, "getTransactionsForAsset")
585
+ .mockResolvedValue(updatedTransactions);
586
+
587
+ clock.next();
588
+ await sleep(1);
589
+
590
+ // 5 pending & incomplete + 1 recently completed + 1 recently refunded
591
+ expect(onMessage.callCount).toBe(7);
592
+ expect(onError.callCount).toBe(0);
593
+
594
+ // stops watching before next transaction update
595
+ stop();
596
+
597
+ // change another transaction to "pending_user_transfer_start"
598
+ const [firstTxC, secondTxC, thirdTxC, ...restC] = updatedTransactions;
599
+ updatedTransactions = [
600
+ firstTxC,
601
+ secondTxC,
602
+ {
603
+ ...thirdTxC,
604
+ status: TransactionStatus.pending_user_transfer_start,
605
+ },
606
+ ...restC,
607
+ ];
608
+
609
+ // update mock with new transaction status
610
+ jest
611
+ .spyOn(anchor, "getTransactionsForAsset")
612
+ .mockResolvedValue(updatedTransactions);
613
+
614
+ clock.next();
615
+ await sleep(1);
616
+
617
+ // nothing should change or happen after watcher has stopped
618
+ expect(onMessage.callCount).toBe(7);
619
+ expect(onError.callCount).toBe(0);
620
+ });
621
+
622
+ test("Immediate completed|refunded|expired should get messages", async () => {
623
+ const onMessage = sinon.spy(() => {
624
+ expect(onMessage.callCount).toBeLessThan(4);
625
+ });
626
+
627
+ const onError = sinon.spy((e) => {
628
+ expect(e).toBeUndefined();
629
+ });
630
+
631
+ // mock an empty transactions array
632
+ jest.spyOn(anchor, "getTransactionsForAsset").mockResolvedValue([]);
633
+
634
+ // start watching
635
+ const { stop } = watcher.watchAllTransactions({
636
+ authToken,
637
+ assetCode: "SRT",
638
+ lang: "en-US",
639
+ onMessage,
640
+ onError,
641
+ timeout: 1,
642
+ });
643
+
644
+ // nothing should run at first (async api call in progress)
645
+ expect(onMessage.callCount).toBe(0);
646
+ expect(onError.callCount).toBe(0);
647
+
648
+ await sleep(1);
649
+
650
+ // still nothing
651
+ expect(onMessage.callCount).toBe(0);
652
+ expect(onError.callCount).toBe(0);
653
+
654
+ const completedTransaction = {
655
+ id: "uyt1576b-ac28-4c02-a521-ddbfd9ae7oiu",
656
+ kind: "deposit",
657
+ status: "completed",
658
+ status_eta: null,
659
+ amount_in: "150.45",
660
+ amount_out: "149.45",
661
+ amount_fee: "1.00",
662
+ started_at: "2023-05-22T12:11:27.227597Z",
663
+ completed_at: "2023-05-22T18:11:27.227597Z",
664
+ stellar_transaction_id:
665
+ "pokib2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9cbngt",
666
+ external_transaction_id: null,
667
+ more_info_url:
668
+ "https://testanchor.stellar.org/sep24/transaction/more_info?id=uyt1576b-ac28-4c02-a521-ddbfd9ae7oiu",
669
+ refunded: false,
670
+ message: "deposit completed!",
671
+ claimable_balance_id: null,
672
+ to: "GCZSYKPDWAKPGR7GYFBOIQB3TH352X7ELZL27WSJET5PDVFORDMGYTB5",
673
+ from: null,
674
+ deposit_memo_type: "hash",
675
+ deposit_memo: null,
676
+ };
677
+
678
+ // add a new success message
679
+ jest
680
+ .spyOn(anchor, "getTransactionsForAsset")
681
+ .mockResolvedValue([completedTransaction]);
682
+
683
+ clock.next();
684
+ await sleep(1);
685
+
686
+ // should have a success
687
+ expect(onMessage.callCount).toBe(1);
688
+ expect(onError.callCount).toBe(0);
689
+
690
+ // getting the same thing again should change nothing
691
+ jest
692
+ .spyOn(anchor, "getTransactionsForAsset")
693
+ .mockResolvedValue([completedTransaction]);
694
+
695
+ clock.next();
696
+ await sleep(1);
697
+
698
+ // 1 immediate completed
699
+ expect(onMessage.callCount).toBe(1);
700
+ expect(onError.callCount).toBe(0);
701
+
702
+ const refundedTransaction = {
703
+ id: "def5d166-5a5e-4d5c-ba5d-271c32cd8abc",
704
+ kind: "withdrawal",
705
+ status: "refunded",
706
+ status_eta: null,
707
+ amount_in: "95.35",
708
+ amount_in_asset:
709
+ "stellar:SRT:GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B",
710
+ amount_out: "144.48",
711
+ amount_out_asset: "iso4217:USD",
712
+ amount_fee: "1.00",
713
+ amount_fee_asset:
714
+ "stellar:SRT:GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B",
715
+ started_at: "2023-05-25T13:12:35.128156Z",
716
+ completed_at: "2023-05-26T15:12:35.128156Z",
717
+ stellar_transaction_id:
718
+ "abu0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9cdega",
719
+ external_transaction_id: null,
720
+ more_info_url:
721
+ "https://testanchor.stellar.org/sep24/transaction/more_info?id=def5d166-5a5e-4d5c-ba5d-271c32cd8abc",
722
+ refunded: true,
723
+ refunds: {
724
+ amount_refunded: "95.35",
725
+ amount_fee: "5",
726
+ payments: [
727
+ {
728
+ id: "1937103",
729
+ id_type: "external",
730
+ amount: "40.0",
731
+ fee: "5",
732
+ },
733
+ {
734
+ id:
735
+ "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020",
736
+ id_type: "stellar",
737
+ amount: "55.35",
738
+ fee: "0",
739
+ },
740
+ ],
741
+ },
742
+ message: null,
743
+ to: null,
744
+ from: "GCZSYKPDWAKPGR7GYFBOIQB3TH352X7ELZL27WSJET5PDVFORDMGYTB5",
745
+ withdraw_memo_type: "hash",
746
+ withdraw_memo: "AAAAAAAAAAAAAAAAAAAAANsV0WZaXk1cul0nHDLNjPA=",
747
+ withdraw_anchor_account:
748
+ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE",
749
+ };
750
+
751
+ // add a new success message
752
+ jest
753
+ .spyOn(anchor, "getTransactionsForAsset")
754
+ .mockResolvedValue([completedTransaction, refundedTransaction]);
755
+
756
+ clock.next();
757
+ await sleep(1);
758
+
759
+ // 1 immediate completed + 1 immediate refunded
760
+ expect(onMessage.callCount).toBe(2);
761
+ expect(onError.callCount).toBe(0);
762
+
763
+ // getting the same thing again should change nothing
764
+ jest
765
+ .spyOn(anchor, "getTransactionsForAsset")
766
+ .mockResolvedValue([completedTransaction, refundedTransaction]);
767
+
768
+ clock.next();
769
+ await sleep(1);
770
+
771
+ // no updates expected
772
+ expect(onMessage.callCount).toBe(2);
773
+ expect(onError.callCount).toBe(0);
774
+
775
+ const expiredTransaction = {
776
+ id: "hytcf7c4-927d-4b7a-8a1f-d7188ebddu8i",
777
+ kind: "withdrawal",
778
+ status: "expired",
779
+ status_eta: null,
780
+ amount_in: null,
781
+ amount_out: null,
782
+ amount_fee: null,
783
+ started_at: "2023-05-25T18:56:29.615274Z",
784
+ completed_at: null,
785
+ stellar_transaction_id: null,
786
+ external_transaction_id: null,
787
+ more_info_url:
788
+ "https://testanchor.stellar.org/sep24/transaction/more_info?id=hytcf7c4-927d-4b7a-8a1f-d7188ebddu8i",
789
+ refunded: false,
790
+ message: "transaction has expired",
791
+ to: null,
792
+ from: "GCZSYKPDWAKPGR7GYFBOIQB3TH352X7ELZL27WSJET5PDVFORDMGYTB5",
793
+ withdraw_memo_type: "hash",
794
+ withdraw_memo: null,
795
+ withdraw_anchor_account: null,
796
+ };
797
+
798
+ // add a new success message
799
+ jest
800
+ .spyOn(anchor, "getTransactionsForAsset")
801
+ .mockResolvedValue([
802
+ completedTransaction,
803
+ refundedTransaction,
804
+ expiredTransaction,
805
+ ]);
806
+
807
+ clock.next();
808
+ await sleep(1);
809
+
810
+ // 1 immediate completed + 1 immediate refunded + 1 immediate expired
811
+ expect(onMessage.callCount).toBe(3);
812
+ expect(onError.callCount).toBe(0);
813
+
814
+ // getting the same thing again should change nothing
815
+ jest
816
+ .spyOn(anchor, "getTransactionsForAsset")
817
+ .mockResolvedValue([
818
+ completedTransaction,
819
+ refundedTransaction,
820
+ expiredTransaction,
821
+ ]);
822
+
823
+ clock.next();
824
+ await sleep(1);
825
+
826
+ // no updates expected
827
+ expect(onMessage.callCount).toBe(3);
828
+ expect(onError.callCount).toBe(0);
829
+
830
+ // stops watching
831
+ stop();
832
+ });
833
+ });
834
+
835
+ describe("watchOneTransaction", () => {
836
+ let clock: sinon.SinonFakeTimers;
837
+ let watcher: Watcher;
838
+
839
+ const makeTransaction = (eta: number, txStatus: TransactionStatus) => ({
840
+ kind: "deposit",
841
+ id: "TEST",
842
+ status: txStatus,
843
+ status_eta: eta,
844
+ });
845
+
846
+ beforeEach(async () => {
847
+ clock = sinon.useFakeTimers(0);
848
+ watcher = anchor.watcher();
849
+ });
850
+
851
+ afterEach(() => {
852
+ clock.restore();
853
+ });
854
+
855
+ test("One completed / refunded / expired successes", async () => {
856
+ const onMessage = sinon.spy(() => {
857
+ expect(onMessage.callCount).toBe(0);
858
+ });
859
+
860
+ const onSuccess = sinon.spy(() => {
861
+ expect(onSuccess.callCount).toBeLessThanOrEqual(3);
862
+ });
863
+
864
+ const onError = sinon.spy((e) => {
865
+ expect(e).toBeUndefined();
866
+ });
867
+
868
+ const successfulTransaction = makeTransaction(
869
+ 0,
870
+ TransactionStatus.completed
871
+ );
872
+
873
+ // queue up a success
874
+ jest
875
+ .spyOn(anchor, "getTransactionBy")
876
+ .mockResolvedValue(successfulTransaction);
877
+
878
+ // start watching
879
+ const { stop: stop1 } = watcher.watchOneTransaction({
880
+ authToken,
881
+ assetCode: "SRT",
882
+ id: successfulTransaction.id,
883
+ onMessage,
884
+ onSuccess,
885
+ onError,
886
+ timeout: 1,
887
+ lang: "en-US",
888
+ });
889
+
890
+ // nothing should run at first
891
+ expect(onMessage.callCount).toBe(0);
892
+ expect(onSuccess.callCount).toBe(0);
893
+ expect(onError.callCount).toBe(0);
894
+
895
+ // wait a second, then onSuccess should call back
896
+ await sleep(1);
897
+
898
+ expect(onMessage.callCount).toBe(0);
899
+ expect(onSuccess.callCount).toBe(1);
900
+ expect(onError.callCount).toBe(0);
901
+
902
+ // stops watching transaction
903
+ stop1();
904
+
905
+ const refundedTransaction = makeTransaction(
906
+ 0,
907
+ TransactionStatus.refunded
908
+ );
909
+
910
+ // queue up a success
911
+ jest
912
+ .spyOn(anchor, "getTransactionBy")
913
+ .mockResolvedValue(refundedTransaction);
914
+
915
+ // start watching
916
+ const { stop: stop2 } = watcher.watchOneTransaction({
917
+ authToken,
918
+ assetCode: "SRT",
919
+ id: refundedTransaction.id,
920
+ onMessage,
921
+ onSuccess,
922
+ onError,
923
+ timeout: 1,
924
+ lang: "en-US",
925
+ });
926
+
927
+ // nothing should run at first
928
+ expect(onMessage.callCount).toBe(0);
929
+ expect(onSuccess.callCount).toBe(1);
930
+ expect(onError.callCount).toBe(0);
931
+
932
+ // wait a second, then onSuccess should call back
933
+ await sleep(1);
934
+
935
+ expect(onMessage.callCount).toBe(0);
936
+ expect(onSuccess.callCount).toBe(2);
937
+ expect(onError.callCount).toBe(0);
938
+
939
+ // stops watching transaction
940
+ stop2();
941
+
942
+ const expiredTransaction = makeTransaction(0, TransactionStatus.expired);
943
+
944
+ // queue up a success
945
+ jest
946
+ .spyOn(anchor, "getTransactionBy")
947
+ .mockResolvedValue(expiredTransaction);
948
+
949
+ // start watching
950
+ const { stop: stop3 } = watcher.watchOneTransaction({
951
+ authToken,
952
+ assetCode: "SRT",
953
+ id: expiredTransaction.id,
954
+ onMessage,
955
+ onSuccess,
956
+ onError,
957
+ timeout: 1,
958
+ lang: "en-US",
959
+ });
960
+
961
+ // nothing should run at first
962
+ expect(onMessage.callCount).toBe(0);
963
+ expect(onSuccess.callCount).toBe(2);
964
+ expect(onError.callCount).toBe(0);
965
+
966
+ // wait a second, then onSuccess should call back
967
+ await sleep(1);
968
+
969
+ expect(onMessage.callCount).toBe(0);
970
+ expect(onSuccess.callCount).toBe(3);
971
+ expect(onError.callCount).toBe(0);
972
+
973
+ // stops watching transaction
974
+ stop3();
975
+ });
976
+
977
+ test("One incomplete / pending_user_transfer_start messages", async () => {
978
+ const onMessage = sinon.spy(() => {
979
+ expect(onMessage.callCount).toBeLessThanOrEqual(2);
980
+ });
981
+
982
+ const onSuccess = sinon.spy(() => {
983
+ expect(onSuccess.callCount).toBe(0);
984
+ });
985
+
986
+ const onError = sinon.spy((e) => {
987
+ expect(e).toBeUndefined();
988
+ });
989
+
990
+ const incompleteTransaction = makeTransaction(
991
+ 0,
992
+ TransactionStatus.incomplete
993
+ );
994
+
995
+ // queue up an incomplete transaction response
996
+ jest
997
+ .spyOn(anchor, "getTransactionBy")
998
+ .mockResolvedValue(incompleteTransaction);
999
+
1000
+ // start watching
1001
+ watcher.watchOneTransaction({
1002
+ authToken,
1003
+ assetCode: "SRT",
1004
+ id: incompleteTransaction.id,
1005
+ onMessage,
1006
+ onSuccess,
1007
+ onError,
1008
+ timeout: 1,
1009
+ lang: "en-US",
1010
+ });
1011
+
1012
+ // nothing should run at first
1013
+ expect(onMessage.callCount).toBe(0);
1014
+ expect(onSuccess.callCount).toBe(0);
1015
+ expect(onError.callCount).toBe(0);
1016
+
1017
+ // wait a second, then onMessage should call back
1018
+ await sleep(1);
1019
+
1020
+ expect(onMessage.callCount).toBe(1);
1021
+ expect(onSuccess.callCount).toBe(0);
1022
+ expect(onError.callCount).toBe(0);
1023
+
1024
+ const pendingTransaction = makeTransaction(
1025
+ 0,
1026
+ TransactionStatus.pending_user_transfer_start
1027
+ );
1028
+
1029
+ // queue up a pending transaction response
1030
+ jest
1031
+ .spyOn(anchor, "getTransactionBy")
1032
+ .mockResolvedValue(pendingTransaction);
1033
+
1034
+ // start watching
1035
+ watcher.watchOneTransaction({
1036
+ authToken,
1037
+ assetCode: "SRT",
1038
+ id: pendingTransaction.id,
1039
+ onMessage,
1040
+ onSuccess,
1041
+ onError,
1042
+ timeout: 1,
1043
+ lang: "en-US",
1044
+ });
1045
+
1046
+ // nothing should run at first
1047
+ expect(onMessage.callCount).toBe(1);
1048
+ expect(onSuccess.callCount).toBe(0);
1049
+ expect(onError.callCount).toBe(0);
1050
+
1051
+ // wait a second, then onMessage should call back
1052
+ await sleep(1);
1053
+
1054
+ expect(onMessage.callCount).toBe(2);
1055
+ expect(onSuccess.callCount).toBe(0);
1056
+ expect(onError.callCount).toBe(0);
1057
+ });
1058
+
1059
+ test("One error / no_market errors", async () => {
1060
+ const onMessage = sinon.spy(() => {
1061
+ expect(onMessage.callCount).toBe(0);
1062
+ });
1063
+
1064
+ const onSuccess = sinon.spy(() => {
1065
+ expect(onSuccess.callCount).toBe(0);
1066
+ });
1067
+
1068
+ const onError = sinon.spy((e) => {
1069
+ expect(e).toBeTruthy();
1070
+ expect(onError.callCount).toBeLessThanOrEqual(2);
1071
+ });
1072
+
1073
+ const errorTransaction = makeTransaction(0, TransactionStatus.error);
1074
+
1075
+ // queue up an error transaction response
1076
+ jest
1077
+ .spyOn(anchor, "getTransactionBy")
1078
+ .mockResolvedValue(errorTransaction);
1079
+
1080
+ // start watching
1081
+ watcher.watchOneTransaction({
1082
+ authToken,
1083
+ assetCode: "SRT",
1084
+ id: errorTransaction.id,
1085
+ onMessage,
1086
+ onSuccess,
1087
+ onError,
1088
+ timeout: 1,
1089
+ lang: "en-US",
1090
+ });
1091
+
1092
+ // nothing should run at first
1093
+ expect(onMessage.callCount).toBe(0);
1094
+ expect(onSuccess.callCount).toBe(0);
1095
+ expect(onError.callCount).toBe(0);
1096
+
1097
+ // wait a second, then onMessage should call back
1098
+ await sleep(1);
1099
+
1100
+ expect(onMessage.callCount).toBe(0);
1101
+ expect(onSuccess.callCount).toBe(0);
1102
+ expect(onError.callCount).toBe(1);
1103
+
1104
+ const noMarketTransaction = makeTransaction(
1105
+ 0,
1106
+ TransactionStatus.no_market
1107
+ );
1108
+
1109
+ // queue up a "no market" transaction response
1110
+ jest
1111
+ .spyOn(anchor, "getTransactionBy")
1112
+ .mockResolvedValue(noMarketTransaction);
1113
+
1114
+ // start watching
1115
+ watcher.watchOneTransaction({
1116
+ authToken,
1117
+ assetCode: "SRT",
1118
+ id: noMarketTransaction.id,
1119
+ onMessage,
1120
+ onSuccess,
1121
+ onError,
1122
+ timeout: 1,
1123
+ lang: "en-US",
1124
+ });
1125
+
1126
+ // nothing should run at first
1127
+ expect(onMessage.callCount).toBe(0);
1128
+ expect(onSuccess.callCount).toBe(0);
1129
+ expect(onError.callCount).toBe(1);
1130
+
1131
+ // wait a second, then onMessage should call back
1132
+ await sleep(1);
1133
+
1134
+ expect(onMessage.callCount).toBe(0);
1135
+ expect(onSuccess.callCount).toBe(0);
1136
+ expect(onError.callCount).toBe(2);
1137
+ });
1138
+
1139
+ test("Several pending transactions, one completed, no more after that", async () => {
1140
+ const onMessage = sinon.spy(() => {
1141
+ expect(onMessage.callCount).toBeLessThanOrEqual(8);
1142
+ });
1143
+
1144
+ const onSuccess = sinon.spy(() => {
1145
+ expect(onSuccess.callCount).toBeLessThanOrEqual(1);
1146
+ });
1147
+
1148
+ const onError = sinon.spy((e) => {
1149
+ expect(e).toBeUndefined();
1150
+ });
1151
+
1152
+ // queue up several pending status updates
1153
+ jest
1154
+ .spyOn(anchor, "getTransactionBy")
1155
+ .mockResolvedValueOnce(makeTransaction(0, TransactionStatus.incomplete))
1156
+ .mockResolvedValueOnce(
1157
+ makeTransaction(1, TransactionStatus.pending_user)
1158
+ )
1159
+ .mockResolvedValueOnce(
1160
+ makeTransaction(2, TransactionStatus.pending_anchor)
1161
+ )
1162
+ .mockResolvedValueOnce(
1163
+ makeTransaction(3, TransactionStatus.pending_external)
1164
+ )
1165
+ .mockResolvedValueOnce(
1166
+ makeTransaction(4, TransactionStatus.pending_stellar)
1167
+ )
1168
+ .mockResolvedValueOnce(
1169
+ makeTransaction(5, TransactionStatus.pending_trust)
1170
+ )
1171
+ .mockResolvedValueOnce(
1172
+ makeTransaction(6, TransactionStatus.pending_user_transfer_start)
1173
+ )
1174
+ .mockResolvedValueOnce(
1175
+ makeTransaction(7, TransactionStatus.pending_user_transfer_complete)
1176
+ )
1177
+ .mockResolvedValueOnce(makeTransaction(8, TransactionStatus.completed))
1178
+ .mockResolvedValueOnce(
1179
+ makeTransaction(9, TransactionStatus.pending_anchor)
1180
+ )
1181
+ .mockResolvedValueOnce(
1182
+ makeTransaction(10, TransactionStatus.pending_external)
1183
+ );
1184
+
1185
+ // start watching
1186
+ watcher.watchOneTransaction({
1187
+ authToken,
1188
+ assetCode: "SRT",
1189
+ id: makeTransaction(0, TransactionStatus.incomplete).id,
1190
+ onMessage,
1191
+ onSuccess,
1192
+ onError,
1193
+ timeout: 1,
1194
+ lang: "en-US",
1195
+ });
1196
+
1197
+ // nothing should run at first
1198
+ expect(onMessage.callCount).toBe(0);
1199
+ expect(onSuccess.callCount).toBe(0);
1200
+ expect(onError.callCount).toBe(0);
1201
+
1202
+ // loop through all pending statuses updates
1203
+ await sleep(1);
1204
+
1205
+ clock.next();
1206
+ await sleep(1);
1207
+
1208
+ clock.next();
1209
+ await sleep(1);
1210
+
1211
+ clock.next();
1212
+ await sleep(1);
1213
+
1214
+ clock.next();
1215
+ await sleep(1);
1216
+
1217
+ clock.next();
1218
+ await sleep(1);
1219
+
1220
+ clock.next();
1221
+ await sleep(1);
1222
+
1223
+ clock.next();
1224
+ await sleep(1);
1225
+
1226
+ // 1 incomplete + 7 pending transactions
1227
+ expect(onMessage.callCount).toBe(8);
1228
+ expect(onSuccess.callCount).toBe(0);
1229
+ expect(onError.callCount).toBe(0);
1230
+
1231
+ clock.next();
1232
+ await sleep(1);
1233
+
1234
+ // the next time a success should happen
1235
+ expect(onMessage.callCount).toBe(8);
1236
+ expect(onSuccess.callCount).toBe(1);
1237
+ expect(onError.callCount).toBe(0);
1238
+
1239
+ clock.next();
1240
+ await sleep(1);
1241
+
1242
+ clock.next();
1243
+ await sleep(1);
1244
+
1245
+ // after success, nothing should change or run again
1246
+ expect(onMessage.callCount).toBe(8);
1247
+ expect(onSuccess.callCount).toBe(1);
1248
+ expect(onError.callCount).toBe(0);
1249
+ });
1250
+
1251
+ test("Stops watching after 3 transaction updates", async () => {
1252
+ const onMessage = sinon.spy(() => {
1253
+ expect(onMessage.callCount).toBeLessThanOrEqual(3);
1254
+ });
1255
+
1256
+ const onSuccess = sinon.spy(() => {
1257
+ expect(onSuccess.callCount).toBeLessThanOrEqual(0);
1258
+ });
1259
+
1260
+ const onError = sinon.spy((e) => {
1261
+ expect(e).toBeUndefined();
1262
+ });
1263
+
1264
+ // queue up several pending status updates
1265
+ jest
1266
+ .spyOn(anchor, "getTransactionBy")
1267
+ .mockResolvedValueOnce(makeTransaction(0, TransactionStatus.incomplete))
1268
+ .mockResolvedValueOnce(
1269
+ makeTransaction(1, TransactionStatus.pending_user)
1270
+ )
1271
+ .mockResolvedValueOnce(
1272
+ makeTransaction(2, TransactionStatus.pending_anchor)
1273
+ )
1274
+ .mockResolvedValueOnce(
1275
+ makeTransaction(3, TransactionStatus.pending_external)
1276
+ )
1277
+ .mockResolvedValueOnce(
1278
+ makeTransaction(4, TransactionStatus.pending_stellar)
1279
+ )
1280
+ .mockResolvedValueOnce(
1281
+ makeTransaction(5, TransactionStatus.pending_trust)
1282
+ );
1283
+
1284
+ // start watching
1285
+ const { stop } = watcher.watchOneTransaction({
1286
+ authToken,
1287
+ assetCode: "SRT",
1288
+ id: makeTransaction(0, TransactionStatus.incomplete).id,
1289
+ onMessage,
1290
+ onSuccess,
1291
+ onError,
1292
+ timeout: 1,
1293
+ lang: "en-US",
1294
+ });
1295
+
1296
+ // nothing should run at first
1297
+ expect(onMessage.callCount).toBe(0);
1298
+ expect(onSuccess.callCount).toBe(0);
1299
+ expect(onError.callCount).toBe(0);
1300
+
1301
+ // loop through all pending statuses updates
1302
+ await sleep(1);
1303
+
1304
+ clock.next();
1305
+ await sleep(1);
1306
+
1307
+ clock.next();
1308
+ await sleep(1);
1309
+
1310
+ // 1 incomplete + 2 pending transactions
1311
+ expect(onMessage.callCount).toBe(3);
1312
+ expect(onSuccess.callCount).toBe(0);
1313
+ expect(onError.callCount).toBe(0);
1314
+
1315
+ // stops watching after the third update
1316
+ stop();
1317
+
1318
+ clock.next();
1319
+ await sleep(1);
1320
+
1321
+ clock.next();
1322
+ await sleep(1);
1323
+
1324
+ clock.next();
1325
+ await sleep(1);
1326
+
1327
+ // after stopping, nothing should change or run again
1328
+ expect(onMessage.callCount).toBe(3);
1329
+ expect(onSuccess.callCount).toBe(0);
1330
+ expect(onError.callCount).toBe(0);
1331
+ });
1332
+
1333
+ test("One pending, one completed, no more after that", async () => {
1334
+ const onMessage = sinon.spy(() => {
1335
+ expect(onMessage.callCount).toBeLessThanOrEqual(1);
1336
+ });
1337
+
1338
+ const onSuccess = sinon.spy(() => {
1339
+ expect(onSuccess.callCount).toBeLessThanOrEqual(1);
1340
+ });
1341
+
1342
+ const onError = sinon.spy((e) => {
1343
+ expect(e).toBeUndefined();
1344
+ });
1345
+
1346
+ // queue up transactions
1347
+ jest
1348
+ .spyOn(anchor, "getTransactionBy")
1349
+ .mockResolvedValueOnce(
1350
+ makeTransaction(0, TransactionStatus.pending_user_transfer_complete)
1351
+ )
1352
+ .mockResolvedValueOnce(makeTransaction(1, TransactionStatus.completed))
1353
+ .mockResolvedValueOnce(
1354
+ makeTransaction(2, TransactionStatus.pending_anchor)
1355
+ )
1356
+ .mockResolvedValueOnce(
1357
+ makeTransaction(3, TransactionStatus.pending_external)
1358
+ );
1359
+
1360
+ // start watching
1361
+ watcher.watchOneTransaction({
1362
+ authToken,
1363
+ assetCode: "SRT",
1364
+ id: makeTransaction(0, TransactionStatus.pending_user_transfer_complete)
1365
+ .id,
1366
+ onMessage,
1367
+ onSuccess,
1368
+ onError,
1369
+ timeout: 1,
1370
+ lang: "en-US",
1371
+ });
1372
+
1373
+ // nothing should run at first
1374
+ expect(onMessage.callCount).toBe(0);
1375
+ expect(onSuccess.callCount).toBe(0);
1376
+ expect(onError.callCount).toBe(0);
1377
+
1378
+ await sleep(1);
1379
+
1380
+ // wait a second, then the pending should resolve
1381
+ expect(onMessage.callCount).toBe(1);
1382
+ expect(onSuccess.callCount).toBe(0);
1383
+ expect(onError.callCount).toBe(0);
1384
+
1385
+ clock.next();
1386
+ await sleep(1);
1387
+
1388
+ // the second time, a success should happen
1389
+ expect(onMessage.callCount).toBe(1);
1390
+ expect(onSuccess.callCount).toBe(1);
1391
+ expect(onError.callCount).toBe(0);
1392
+
1393
+ clock.next();
1394
+ await sleep(1);
1395
+
1396
+ clock.next();
1397
+ await sleep(1);
1398
+
1399
+ // after success, nothing should change or run again
1400
+ expect(onMessage.callCount).toBe(1);
1401
+ expect(onSuccess.callCount).toBe(1);
1402
+ expect(onError.callCount).toBe(0);
1403
+ });
1404
+
1405
+ test("One pending, one refunded, no more after that", async () => {
1406
+ const onMessage = sinon.spy(() => {
1407
+ expect(onMessage.callCount).toBeLessThanOrEqual(1);
1408
+ });
1409
+
1410
+ const onSuccess = sinon.spy(() => {
1411
+ expect(onSuccess.callCount).toBeLessThanOrEqual(1);
1412
+ });
1413
+
1414
+ const onError = sinon.spy((e) => {
1415
+ expect(e).toBeUndefined();
1416
+ });
1417
+
1418
+ // queue up transactions
1419
+ jest
1420
+ .spyOn(anchor, "getTransactionBy")
1421
+ .mockResolvedValueOnce(
1422
+ makeTransaction(0, TransactionStatus.pending_user_transfer_complete)
1423
+ )
1424
+ .mockResolvedValueOnce(makeTransaction(1, TransactionStatus.refunded))
1425
+ .mockResolvedValueOnce(
1426
+ makeTransaction(2, TransactionStatus.pending_anchor)
1427
+ )
1428
+ .mockResolvedValueOnce(
1429
+ makeTransaction(3, TransactionStatus.pending_external)
1430
+ );
1431
+
1432
+ // start watching
1433
+ watcher.watchOneTransaction({
1434
+ authToken,
1435
+ assetCode: "SRT",
1436
+ id: makeTransaction(0, TransactionStatus.pending_user_transfer_complete)
1437
+ .id,
1438
+ onMessage,
1439
+ onSuccess,
1440
+ onError,
1441
+ timeout: 1,
1442
+ lang: "en-US",
1443
+ });
1444
+
1445
+ // nothing should run at first
1446
+ expect(onMessage.callCount).toBe(0);
1447
+ expect(onSuccess.callCount).toBe(0);
1448
+ expect(onError.callCount).toBe(0);
1449
+
1450
+ await sleep(1);
1451
+
1452
+ // wait a second, then the pending should resolve
1453
+ expect(onMessage.callCount).toBe(1);
1454
+ expect(onSuccess.callCount).toBe(0);
1455
+ expect(onError.callCount).toBe(0);
1456
+
1457
+ clock.next();
1458
+ await sleep(1);
1459
+
1460
+ // the second time, a success should happen
1461
+ expect(onMessage.callCount).toBe(1);
1462
+ expect(onSuccess.callCount).toBe(1);
1463
+ expect(onError.callCount).toBe(0);
1464
+
1465
+ clock.next();
1466
+ await sleep(1);
1467
+
1468
+ clock.next();
1469
+ await sleep(1);
1470
+
1471
+ // after success, nothing should change or run again
1472
+ expect(onMessage.callCount).toBe(1);
1473
+ expect(onSuccess.callCount).toBe(1);
1474
+ expect(onError.callCount).toBe(0);
1475
+ });
1476
+
1477
+ test("One pending, one error, no more after that", async () => {
1478
+ const onMessage = sinon.spy(() => {
1479
+ expect(onMessage.callCount).toBeLessThanOrEqual(1);
1480
+ });
1481
+
1482
+ const onSuccess = sinon.spy(() => {
1483
+ expect(onSuccess.callCount).toBe(0);
1484
+ });
1485
+
1486
+ const onError = sinon.spy((e) => {
1487
+ expect(e).toBeTruthy();
1488
+ expect(onError.callCount).toBeLessThanOrEqual(1);
1489
+ });
1490
+
1491
+ // queue up transactions
1492
+ jest
1493
+ .spyOn(anchor, "getTransactionBy")
1494
+ .mockResolvedValueOnce(
1495
+ makeTransaction(0, TransactionStatus.pending_user_transfer_start)
1496
+ )
1497
+ .mockResolvedValueOnce(makeTransaction(1, TransactionStatus.error))
1498
+ .mockResolvedValueOnce(
1499
+ makeTransaction(2, TransactionStatus.pending_anchor)
1500
+ )
1501
+ .mockResolvedValueOnce(
1502
+ makeTransaction(3, TransactionStatus.pending_external)
1503
+ );
1504
+
1505
+ // start watching
1506
+ watcher.watchOneTransaction({
1507
+ authToken,
1508
+ assetCode: "SRT",
1509
+ id: makeTransaction(0, TransactionStatus.pending_user_transfer_start)
1510
+ .id,
1511
+ onMessage,
1512
+ onSuccess,
1513
+ onError,
1514
+ timeout: 1,
1515
+ lang: "en-US",
1516
+ });
1517
+
1518
+ // nothing should run at first
1519
+ expect(onMessage.callCount).toBe(0);
1520
+ expect(onSuccess.callCount).toBe(0);
1521
+ expect(onError.callCount).toBe(0);
1522
+
1523
+ await sleep(1);
1524
+
1525
+ // wait a second, then the pending should resolve
1526
+ expect(onMessage.callCount).toBe(1);
1527
+ expect(onSuccess.callCount).toBe(0);
1528
+ expect(onError.callCount).toBe(0);
1529
+
1530
+ clock.next();
1531
+ await sleep(1);
1532
+
1533
+ // the second time, a error should happen
1534
+ expect(onMessage.callCount).toBe(1);
1535
+ expect(onSuccess.callCount).toBe(0);
1536
+ expect(onError.callCount).toBe(1);
1537
+
1538
+ clock.next();
1539
+ await sleep(1);
1540
+
1541
+ clock.next();
1542
+ await sleep(1);
1543
+
1544
+ // after error, nothing should change or run again
1545
+ expect(onMessage.callCount).toBe(1);
1546
+ expect(onSuccess.callCount).toBe(0);
1547
+ expect(onError.callCount).toBe(1);
1548
+ });
1549
+
1550
+ test("One pending, one no_market, no more after that", async () => {
1551
+ const onMessage = sinon.spy(() => {
1552
+ expect(onMessage.callCount).toBeLessThanOrEqual(1);
1553
+ });
1554
+
1555
+ const onSuccess = sinon.spy(() => {
1556
+ expect(onSuccess.callCount).toBe(0);
1557
+ });
1558
+
1559
+ const onError = sinon.spy((e) => {
1560
+ expect(e).toBeTruthy();
1561
+ expect(onError.callCount).toBeLessThanOrEqual(1);
1562
+ });
1563
+
1564
+ // queue up transactions
1565
+ jest
1566
+ .spyOn(anchor, "getTransactionBy")
1567
+ .mockResolvedValueOnce(
1568
+ makeTransaction(0, TransactionStatus.pending_user_transfer_start)
1569
+ )
1570
+ .mockResolvedValueOnce(makeTransaction(1, TransactionStatus.no_market))
1571
+ .mockResolvedValueOnce(
1572
+ makeTransaction(2, TransactionStatus.pending_anchor)
1573
+ )
1574
+ .mockResolvedValueOnce(
1575
+ makeTransaction(3, TransactionStatus.pending_external)
1576
+ );
1577
+
1578
+ // start watching
1579
+ watcher.watchOneTransaction({
1580
+ authToken,
1581
+ assetCode: "SRT",
1582
+ id: makeTransaction(0, TransactionStatus.pending_user_transfer_start)
1583
+ .id,
1584
+ onMessage,
1585
+ onSuccess,
1586
+ onError,
1587
+ timeout: 1,
1588
+ lang: "en-US",
1589
+ });
1590
+
1591
+ // nothing should run at first
1592
+ expect(onMessage.callCount).toBe(0);
1593
+ expect(onSuccess.callCount).toBe(0);
1594
+ expect(onError.callCount).toBe(0);
1595
+
1596
+ await sleep(1);
1597
+
1598
+ // wait a second, then the pending should resolve
1599
+ expect(onMessage.callCount).toBe(1);
1600
+ expect(onSuccess.callCount).toBe(0);
1601
+ expect(onError.callCount).toBe(0);
1602
+
1603
+ clock.next();
1604
+ await sleep(1);
1605
+
1606
+ // the second time, a error should happen
1607
+ expect(onMessage.callCount).toBe(1);
1608
+ expect(onSuccess.callCount).toBe(0);
1609
+ expect(onError.callCount).toBe(1);
1610
+
1611
+ clock.next();
1612
+ await sleep(1);
1613
+
1614
+ clock.next();
1615
+ await sleep(1);
1616
+
1617
+ // after error, nothing should change or run again
1618
+ expect(onMessage.callCount).toBe(1);
1619
+ expect(onSuccess.callCount).toBe(0);
1620
+ expect(onError.callCount).toBe(1);
1621
+ });
1622
+
1623
+ test("Two pending, one error, no more after that", async () => {
1624
+ const onMessage = sinon.spy(() => {
1625
+ expect(onMessage.callCount).toBeLessThanOrEqual(2);
1626
+ });
1627
+
1628
+ const onSuccess = sinon.spy(() => {
1629
+ expect(onSuccess.callCount).toBe(0);
1630
+ });
1631
+
1632
+ const onError = sinon.spy((e) => {
1633
+ expect(e).toBeTruthy();
1634
+ expect(onError.callCount).toBeLessThanOrEqual(1);
1635
+ });
1636
+
1637
+ // queue up transactions
1638
+ jest
1639
+ .spyOn(anchor, "getTransactionBy")
1640
+ .mockResolvedValueOnce(
1641
+ makeTransaction(0, TransactionStatus.pending_user_transfer_start)
1642
+ )
1643
+ .mockResolvedValueOnce(
1644
+ makeTransaction(1, TransactionStatus.pending_user_transfer_complete)
1645
+ )
1646
+ .mockResolvedValueOnce(makeTransaction(2, TransactionStatus.error))
1647
+ .mockResolvedValueOnce(
1648
+ makeTransaction(3, TransactionStatus.pending_anchor)
1649
+ )
1650
+ .mockResolvedValueOnce(
1651
+ makeTransaction(4, TransactionStatus.pending_external)
1652
+ );
1653
+
1654
+ // start watching
1655
+ watcher.watchOneTransaction({
1656
+ authToken,
1657
+ assetCode: "SRT",
1658
+ id: makeTransaction(0, TransactionStatus.pending_user_transfer_start)
1659
+ .id,
1660
+ onMessage,
1661
+ onSuccess,
1662
+ onError,
1663
+ timeout: 1,
1664
+ lang: "en-US",
1665
+ });
1666
+
1667
+ // nothing should run at first
1668
+ expect(onMessage.callCount).toBe(0);
1669
+ expect(onSuccess.callCount).toBe(0);
1670
+ expect(onError.callCount).toBe(0);
1671
+
1672
+ await sleep(1);
1673
+
1674
+ // wait a second, then the pending should resolve
1675
+ expect(onMessage.callCount).toBe(1);
1676
+ expect(onSuccess.callCount).toBe(0);
1677
+ expect(onError.callCount).toBe(0);
1678
+
1679
+ clock.next();
1680
+ await sleep(1);
1681
+
1682
+ // the next time, another pending
1683
+ expect(onMessage.callCount).toBe(2);
1684
+ expect(onSuccess.callCount).toBe(0);
1685
+ expect(onError.callCount).toBe(0);
1686
+
1687
+ clock.next();
1688
+ await sleep(1);
1689
+
1690
+ // the next time, an error
1691
+ expect(onMessage.callCount).toBe(2);
1692
+ expect(onSuccess.callCount).toBe(0);
1693
+ expect(onError.callCount).toBe(1);
1694
+
1695
+ clock.next();
1696
+ await sleep(1);
1697
+
1698
+ clock.next();
1699
+ await sleep(1);
1700
+
1701
+ // after error, nothing should change or run again
1702
+ expect(onMessage.callCount).toBe(2);
1703
+ expect(onSuccess.callCount).toBe(0);
1704
+ expect(onError.callCount).toBe(1);
1705
+ });
1706
+ });
1707
+ });
1708
+
1709
+ describe("Http client", () => {
1710
+ it("should work with http", async () => {
1711
+ const accountKp = Keypair.fromSecret(
1712
+ "SDXC3OHSJZEQIXKEWFDNEZEQ7SW5DWBPW7RKUWI36ILY3QZZ6VER7TXV"
1713
+ );
1714
+ const wal = walletSdk.Wallet.TestNet();
1715
+ const client = wal.getClient();
1716
+
1717
+ const resp = await client.get(
1718
+ `http://testanchor.stellar.org/auth?account=${accountKp.publicKey()}`
1719
+ );
1720
+ expect(resp.data.transaction).toBeTruthy();
1721
+ });
1722
+ });