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