@stellar/typescript-wallet-sdk 1.0.0-alpha.1 → 1.0.0-beta.0

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