@rango-dev/queue-manager-rango-preset 0.0.0-experimental-936229e8-20251208

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/CHANGELOG.md +353 -0
  2. package/dist/actions/checkStatus.d.ts +13 -0
  3. package/dist/actions/checkStatus.d.ts.map +1 -0
  4. package/dist/actions/common/checkEnvironmentBeforeExecuteTransaction.d.ts +8 -0
  5. package/dist/actions/common/checkEnvironmentBeforeExecuteTransaction.d.ts.map +1 -0
  6. package/dist/actions/common/produceNextStateForTransaction.d.ts +17 -0
  7. package/dist/actions/common/produceNextStateForTransaction.d.ts.map +1 -0
  8. package/dist/actions/common/utils.d.ts +5 -0
  9. package/dist/actions/common/utils.d.ts.map +1 -0
  10. package/dist/actions/createTransaction.d.ts +12 -0
  11. package/dist/actions/createTransaction.d.ts.map +1 -0
  12. package/dist/actions/executeTransaction/executeTransaction.d.ts +14 -0
  13. package/dist/actions/executeTransaction/executeTransaction.d.ts.map +1 -0
  14. package/dist/actions/executeTransaction/index.d.ts +2 -0
  15. package/dist/actions/executeTransaction/index.d.ts.map +1 -0
  16. package/dist/actions/scheduleNextStep.d.ts +14 -0
  17. package/dist/actions/scheduleNextStep.d.ts.map +1 -0
  18. package/dist/actions/start.d.ts +4 -0
  19. package/dist/actions/start.d.ts.map +1 -0
  20. package/dist/configs.d.ts +14 -0
  21. package/dist/configs.d.ts.map +1 -0
  22. package/dist/constants.d.ts +7 -0
  23. package/dist/constants.d.ts.map +1 -0
  24. package/dist/helpers.d.ts +255 -0
  25. package/dist/helpers.d.ts.map +1 -0
  26. package/dist/hooks.d.ts +19 -0
  27. package/dist/hooks.d.ts.map +1 -0
  28. package/dist/index.d.ts +11 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +2 -0
  31. package/dist/index.js.map +7 -0
  32. package/dist/migration.d.ts +15 -0
  33. package/dist/migration.d.ts.map +1 -0
  34. package/dist/numbers.d.ts +3 -0
  35. package/dist/numbers.d.ts.map +1 -0
  36. package/dist/queue-manager-rango-preset.build.json +1 -0
  37. package/dist/queueDef.d.ts +10 -0
  38. package/dist/queueDef.d.ts.map +1 -0
  39. package/dist/services/eventEmitter.d.ts +10 -0
  40. package/dist/services/eventEmitter.d.ts.map +1 -0
  41. package/dist/services/httpService.d.ts +3 -0
  42. package/dist/services/httpService.d.ts.map +1 -0
  43. package/dist/services/index.d.ts +2 -0
  44. package/dist/services/index.d.ts.map +1 -0
  45. package/dist/shared-errors.d.ts +25 -0
  46. package/dist/shared-errors.d.ts.map +1 -0
  47. package/dist/shared.d.ts +81 -0
  48. package/dist/shared.d.ts.map +1 -0
  49. package/dist/types.d.ts +172 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/package.json +45 -0
  52. package/readme.md +2 -0
  53. package/src/actions/checkStatus.ts +564 -0
  54. package/src/actions/common/checkEnvironmentBeforeExecuteTransaction.ts +157 -0
  55. package/src/actions/common/produceNextStateForTransaction.ts +167 -0
  56. package/src/actions/common/utils.ts +25 -0
  57. package/src/actions/createTransaction.ts +117 -0
  58. package/src/actions/executeTransaction/executeTransaction.ts +107 -0
  59. package/src/actions/executeTransaction/index.ts +1 -0
  60. package/src/actions/scheduleNextStep.ts +104 -0
  61. package/src/actions/start.ts +16 -0
  62. package/src/configs.ts +38 -0
  63. package/src/constants.ts +18 -0
  64. package/src/helpers.ts +1598 -0
  65. package/src/hooks.ts +94 -0
  66. package/src/index.ts +68 -0
  67. package/src/migration.ts +124 -0
  68. package/src/numbers.ts +68 -0
  69. package/src/queueDef.ts +39 -0
  70. package/src/services/eventEmitter.ts +290 -0
  71. package/src/services/httpService.ts +10 -0
  72. package/src/services/index.ts +1 -0
  73. package/src/shared-errors.ts +165 -0
  74. package/src/shared.ts +473 -0
  75. package/src/types.ts +305 -0
package/src/helpers.ts ADDED
@@ -0,0 +1,1598 @@
1
+ /* eslint-disable destructuring/in-params */
2
+
3
+ /* eslint-disable @typescript-eslint/no-floating-promises */
4
+ import type { NotifierParams } from './services/eventEmitter';
5
+ import type { SwapStatus, TargetNamespace, Wallet } from './shared';
6
+ import type {
7
+ ArrayElement,
8
+ LastConnectedWallet,
9
+ Step,
10
+ SwapQueueContext,
11
+ SwapQueueDef,
12
+ SwapStorage,
13
+ UseQueueManagerParams,
14
+ } from './types';
15
+ import type {
16
+ ExecuterActions,
17
+ Manager,
18
+ QueueInfo,
19
+ QueueName,
20
+ QueueType,
21
+ SetStorage,
22
+ } from '@rango-dev/queue-manager-core';
23
+ import type { Provider } from '@rango-dev/wallets-core';
24
+ import type {
25
+ Meta,
26
+ Network,
27
+ Providers,
28
+ WalletState,
29
+ WalletType,
30
+ } from '@rango-dev/wallets-shared';
31
+ import type {
32
+ CreateTransactionResponse,
33
+ EvmBlockchainMeta,
34
+ Transaction,
35
+ } from 'rango-sdk';
36
+ import type {
37
+ APIErrorCode,
38
+ PendingSwap,
39
+ PendingSwapStep,
40
+ SignerErrorCode,
41
+ StepStatus,
42
+ } from 'rango-types';
43
+
44
+ import { warn } from '@rango-dev/logging-core';
45
+ import { Status } from '@rango-dev/queue-manager-core';
46
+ import { legacyReadAccountAddress as readAccountAddress } from '@rango-dev/wallets-core/legacy';
47
+ import {
48
+ getBlockChainNameFromId,
49
+ getEvmProvider,
50
+ } from '@rango-dev/wallets-shared';
51
+ import BigNumber from 'bignumber.js';
52
+ import {
53
+ PendingSwapNetworkStatus,
54
+ SignerError,
55
+ TransactionType,
56
+ } from 'rango-types';
57
+
58
+ import {
59
+ DEFAULT_ERROR_CODE,
60
+ ERROR_MESSAGE_WAIT_FOR_CHANGE_NETWORK,
61
+ ERROR_MESSAGE_WAIT_FOR_WALLET,
62
+ ERROR_MESSAGE_WAIT_FOR_WALLET_DESCRIPTION,
63
+ } from './constants';
64
+ import { httpService } from './services';
65
+ import { notifier } from './services/eventEmitter';
66
+ import {
67
+ getCurrentNamespaceOf,
68
+ getCurrentNamespaceOfOrNull,
69
+ getRelatedWallet,
70
+ getRelatedWalletOrNull,
71
+ getScannerUrl,
72
+ MessageSeverity,
73
+ } from './shared';
74
+ import {
75
+ mapAppErrorCodesToAPIErrorCode,
76
+ prettifyErrorMessage,
77
+ PrettyError,
78
+ } from './shared-errors';
79
+ import {
80
+ BlockReason,
81
+ StepEventType,
82
+ StepExecutionBlockedEventStatus,
83
+ StepExecutionEventStatus,
84
+ SwapActionTypes,
85
+ } from './types';
86
+
87
+ type WhenTaskBlocked = Parameters<NonNullable<SwapQueueDef['whenTaskBlocked']>>;
88
+ type WhenTaskBlockedEvent = WhenTaskBlocked[0];
89
+ type WhenTaskBlockedMeta = WhenTaskBlocked[1];
90
+
91
+ let swapClaimedBy: { id: string } | null = null;
92
+
93
+ /**
94
+ *
95
+ * We simply use module-level variable to keep track of which queue has claimed the execution of parallel runnings.
96
+ *
97
+ */
98
+ export function claimQueue() {
99
+ return {
100
+ claimedBy: () => swapClaimedBy?.id,
101
+ setClaimer: (queue_id: string) => {
102
+ swapClaimedBy = {
103
+ id: queue_id,
104
+ };
105
+ },
106
+ reset: () => {
107
+ swapClaimedBy = null;
108
+ },
109
+ };
110
+ }
111
+
112
+ /**
113
+ *
114
+ * We use module-level variable to keep track of
115
+ * map of transactions hash to the TransactionResponse and ...
116
+ *
117
+ */
118
+ type TransactionData = {
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ response?: any; // e.g. TransactionResponse in case of EVM transactions
121
+ receiptReceived?: boolean; // e.g. is TransactionReceipt ready in case of EVM transactions
122
+ };
123
+ const swapTransactionToDataMap: { [id: string]: TransactionData } = {};
124
+ export function inMemoryTransactionsData() {
125
+ return {
126
+ getTransactionDataByHash: (hash: string) =>
127
+ swapTransactionToDataMap[hash] || {},
128
+ setTransactionDataByHash: (hash: string, data: TransactionData) => {
129
+ const r = swapTransactionToDataMap[hash];
130
+ if (!r) {
131
+ swapTransactionToDataMap[hash] = {};
132
+ }
133
+ swapTransactionToDataMap[hash].response =
134
+ data.response || swapTransactionToDataMap[hash].response;
135
+ swapTransactionToDataMap[hash].receiptReceived =
136
+ data.receiptReceived ||
137
+ swapTransactionToDataMap[hash].receiptReceived ||
138
+ false;
139
+ },
140
+ };
141
+ }
142
+
143
+ /**
144
+ *
145
+ * Returns `steps`, if it's a `running` swap.
146
+ * Each `PendingSwap` has a `steps` inside it, `steps` shows how many tasks should be created and run to finish the swap.
147
+ *
148
+ */
149
+ export const getCurrentStep = (swap: PendingSwap): PendingSwapStep | null => {
150
+ return (
151
+ swap.steps.find(
152
+ (step) => step.status !== 'failed' && step.status !== 'success'
153
+ ) || null
154
+ );
155
+ };
156
+
157
+ /**
158
+ *
159
+ * Returns current step transaction
160
+ *
161
+ */
162
+ export const getCurrentStepTx = (
163
+ currentStep: PendingSwapStep
164
+ ): Transaction | null => {
165
+ const {
166
+ evmTransaction,
167
+ evmApprovalTransaction,
168
+ cosmosTransaction,
169
+ solanaTransaction,
170
+ transferTransaction,
171
+ starknetApprovalTransaction,
172
+ starknetTransaction,
173
+ tronApprovalTransaction,
174
+ tronTransaction,
175
+ tonTransaction,
176
+ suiTransaction,
177
+ } = currentStep;
178
+ return (
179
+ evmTransaction ||
180
+ evmApprovalTransaction ||
181
+ cosmosTransaction ||
182
+ solanaTransaction ||
183
+ transferTransaction ||
184
+ starknetApprovalTransaction ||
185
+ starknetTransaction ||
186
+ tronApprovalTransaction ||
187
+ tronTransaction ||
188
+ tonTransaction ||
189
+ suiTransaction
190
+ );
191
+ };
192
+
193
+ /**
194
+ *
195
+ * Set current step transaction
196
+ *
197
+ */
198
+ export const setCurrentStepTx = (
199
+ currentStep: PendingSwapStep,
200
+ transaction: Transaction
201
+ ): PendingSwapStep => {
202
+ currentStep.transferTransaction = null;
203
+ currentStep.cosmosTransaction = null;
204
+ currentStep.evmTransaction = null;
205
+ currentStep.solanaTransaction = null;
206
+ currentStep.evmApprovalTransaction = null;
207
+ currentStep.starknetApprovalTransaction = null;
208
+ currentStep.starknetTransaction = null;
209
+ currentStep.tronApprovalTransaction = null;
210
+ currentStep.tronTransaction = null;
211
+ currentStep.tonTransaction = null;
212
+ currentStep.suiTransaction = null;
213
+
214
+ const txType = transaction.type;
215
+ switch (txType) {
216
+ case TransactionType.EVM:
217
+ if (transaction.isApprovalTx) {
218
+ currentStep.evmApprovalTransaction = transaction;
219
+ } else {
220
+ currentStep.evmTransaction = transaction;
221
+ }
222
+ break;
223
+ case TransactionType.TRON:
224
+ if (transaction.isApprovalTx) {
225
+ currentStep.tronApprovalTransaction = transaction;
226
+ } else {
227
+ currentStep.tronTransaction = transaction;
228
+ }
229
+ break;
230
+ case TransactionType.STARKNET:
231
+ if (transaction.isApprovalTx) {
232
+ currentStep.starknetApprovalTransaction = transaction;
233
+ } else {
234
+ currentStep.starknetTransaction = transaction;
235
+ }
236
+ break;
237
+ case TransactionType.COSMOS:
238
+ currentStep.cosmosTransaction = transaction;
239
+ break;
240
+ case TransactionType.SOLANA:
241
+ currentStep.solanaTransaction = transaction;
242
+ break;
243
+ case TransactionType.TRANSFER:
244
+ currentStep.transferTransaction = transaction;
245
+ break;
246
+ case TransactionType.TON:
247
+ currentStep.tonTransaction = transaction;
248
+ break;
249
+ case TransactionType.SUI:
250
+ currentStep.suiTransaction = transaction;
251
+ break;
252
+ case TransactionType.XRPL:
253
+ currentStep.xrplTransaction = transaction;
254
+ break;
255
+ default:
256
+ ((x: never) => {
257
+ throw new Error(`${x} was unhandled!`);
258
+ })(txType);
259
+ }
260
+ return currentStep;
261
+ };
262
+
263
+ /**
264
+ *
265
+ * Returns current step transaction type
266
+ *
267
+ */
268
+ export const getCurrentStepTxType = (
269
+ currentStep: PendingSwapStep
270
+ ): TransactionType | undefined => {
271
+ return getCurrentStepTx(currentStep)?.type;
272
+ };
273
+
274
+ /**
275
+ *
276
+ * Returns a boolean indicating that current step is an approval tx or not.
277
+ *
278
+ */
279
+ export const isApprovalCurrentStepTx = (
280
+ currentStep: PendingSwapStep
281
+ ): boolean => {
282
+ const {
283
+ evmApprovalTransaction,
284
+ starknetApprovalTransaction,
285
+ tronApprovalTransaction,
286
+ } = currentStep;
287
+ return !!(
288
+ evmApprovalTransaction ||
289
+ starknetApprovalTransaction ||
290
+ tronApprovalTransaction
291
+ );
292
+ };
293
+
294
+ /**
295
+ * When we are doing a swap, there are some common fields that will be updated together.
296
+ * This function helps us to update a swap status and also it will update some more fields like `extraMessageSeverity` based on the input.
297
+ */
298
+ export function updateSwapStatus({
299
+ getStorage,
300
+ setStorage,
301
+ nextStatus,
302
+ nextStepStatus,
303
+ message,
304
+ details,
305
+ errorCode = null,
306
+ hasAlreadyProceededToSign,
307
+ trace = null,
308
+ }: {
309
+ getStorage: ExecuterActions<
310
+ SwapStorage,
311
+ SwapActionTypes,
312
+ SwapQueueContext
313
+ >['getStorage'];
314
+ setStorage: ExecuterActions<
315
+ SwapStorage,
316
+ SwapActionTypes,
317
+ SwapQueueContext
318
+ >['setStorage'];
319
+ nextStatus?: SwapStatus;
320
+ nextStepStatus?: StepStatus;
321
+ message?: string;
322
+ details?: string | null | undefined;
323
+ errorCode?: APIErrorCode | SignerErrorCode | null;
324
+ hasAlreadyProceededToSign?: boolean;
325
+ trace?: Error | null | undefined;
326
+ }): {
327
+ swap: PendingSwap;
328
+ step: PendingSwapStep | null;
329
+ failureType?: APIErrorCode;
330
+ } {
331
+ const swap = getStorage().swapDetails;
332
+ const currentStep = getCurrentStep(swap);
333
+ const updatedResult: {
334
+ swap: PendingSwap;
335
+ step: PendingSwapStep | null;
336
+ failureType?: APIErrorCode;
337
+ } = {
338
+ swap,
339
+ step: currentStep,
340
+ };
341
+ if (!!nextStepStatus && !!currentStep) {
342
+ currentStep.status = nextStepStatus;
343
+ }
344
+
345
+ if (nextStatus) {
346
+ swap.status = nextStatus;
347
+ }
348
+ swap.hasAlreadyProceededToSign = hasAlreadyProceededToSign;
349
+ if (!!nextStatus && ['failed', 'success'].includes(nextStatus)) {
350
+ swap.finishTime = new Date().getTime().toString();
351
+ }
352
+
353
+ if (!!message || !!details) {
354
+ swap.extraMessage = message || '';
355
+ swap.extraMessageDetail = details || '';
356
+ }
357
+
358
+ if (!!nextStepStatus && ['failed'].includes(nextStepStatus)) {
359
+ //if user cancel the swap, we should pass relevant reason to the server.
360
+ const errorReason =
361
+ details && details.includes('Warning')
362
+ ? 'Swap canceled by user.'
363
+ : details;
364
+ const walletType = getRelatedWalletOrNull(swap, currentStep)?.walletType;
365
+ swap.extraMessageSeverity = MessageSeverity.error;
366
+
367
+ const failureType = mapAppErrorCodesToAPIErrorCode(errorCode);
368
+ updatedResult.failureType = failureType;
369
+
370
+ // If trace of error was available, we will send it to the api (except user rejection)
371
+ const errorReasonForAPI =
372
+ errorCode !== 'REJECTED_BY_USER' &&
373
+ trace?.message &&
374
+ typeof trace.message === 'string'
375
+ ? trace.message
376
+ : errorReason || '';
377
+
378
+ httpService()
379
+ .reportFailure({
380
+ requestId: swap.requestId,
381
+ step: currentStep?.id || 1,
382
+ eventType: failureType,
383
+ reason: errorReasonForAPI,
384
+ tags: walletType
385
+ ? {
386
+ wallet: walletType,
387
+ }
388
+ : undefined,
389
+ })
390
+ .then()
391
+ .catch();
392
+ } else if (!!nextStepStatus && ['running'].includes(nextStepStatus)) {
393
+ swap.extraMessageSeverity = MessageSeverity.info;
394
+ } else if (
395
+ !!nextStepStatus &&
396
+ ['success', 'approved'].includes(nextStepStatus)
397
+ ) {
398
+ swap.extraMessageSeverity = MessageSeverity.success;
399
+ } else if (
400
+ nextStepStatus &&
401
+ ['waitingForApproval'].includes(nextStepStatus)
402
+ ) {
403
+ swap.extraMessageSeverity = MessageSeverity.warning;
404
+ }
405
+
406
+ if (nextStepStatus === 'running' && currentStep) {
407
+ currentStep.startTransactionTime = new Date().getTime();
408
+ }
409
+
410
+ setStorage({
411
+ ...getStorage(),
412
+ swapDetails: swap,
413
+ });
414
+
415
+ return updatedResult;
416
+ }
417
+
418
+ /**
419
+ *
420
+ * Set current step transaction hash, update pending swap status, and notify user if needed
421
+ *
422
+ */
423
+ export function setStepTransactionIds(
424
+ { getStorage, setStorage }: ExecuterActions<SwapStorage, SwapActionTypes>,
425
+ txId: string | null,
426
+ explorerUrl?: { url?: string; description?: string }
427
+ ): void {
428
+ const swap = getStorage().swapDetails;
429
+ swap.hasAlreadyProceededToSign = null;
430
+
431
+ const currentStep = getCurrentStep(swap)!;
432
+ currentStep.executedTransactionId = txId;
433
+ currentStep.executedTransactionTime = new Date().getTime().toString();
434
+ if (explorerUrl?.url) {
435
+ currentStep.explorerUrl = [
436
+ ...(currentStep.explorerUrl || []),
437
+ {
438
+ url: explorerUrl.url,
439
+ description: explorerUrl.description || null,
440
+ },
441
+ ];
442
+ }
443
+
444
+ const isApproval = isApprovalCurrentStepTx(currentStep);
445
+
446
+ if (isApproval) {
447
+ swap.extraMessage = 'Checking approve transaction status ...';
448
+ } else {
449
+ swap.extraMessage = 'Checking transaction status ...';
450
+ }
451
+
452
+ swap.extraMessageDetail = '';
453
+ swap.extraMessageSeverity = MessageSeverity.info;
454
+
455
+ setStorage({
456
+ ...getStorage(),
457
+ swapDetails: swap,
458
+ });
459
+
460
+ notifier({
461
+ event: {
462
+ type: StepEventType.TX_EXECUTION,
463
+ status: StepExecutionEventStatus.TX_SENT,
464
+ },
465
+ swap: swap,
466
+ step: currentStep,
467
+ });
468
+
469
+ notifier({
470
+ event: { type: StepEventType.CHECK_STATUS },
471
+ swap: swap,
472
+ step: currentStep,
473
+ });
474
+ }
475
+
476
+ /**
477
+ * If a swap needs a wallet to be connected,
478
+ * By calling this function some related fields will be updated to show a correct message and state for notfiying the user.
479
+ */
480
+ export function markRunningSwapAsWaitingForConnectingWallet(
481
+ {
482
+ getStorage,
483
+ setStorage,
484
+ }: Pick<ExecuterActions, 'getStorage' | 'setStorage'>,
485
+ reason: string,
486
+ reasonDetail: string
487
+ ): void {
488
+ const swap = getStorage().swapDetails as SwapStorage['swapDetails'];
489
+ const currentStep = getCurrentStep(swap);
490
+ if (!currentStep) {
491
+ return;
492
+ }
493
+ const currentTime = new Date();
494
+ swap.lastNotificationTime = currentTime.getTime().toString();
495
+
496
+ const isAlreadyMarked =
497
+ currentStep.networkStatus ===
498
+ PendingSwapNetworkStatus.WaitingForConnectingWallet &&
499
+ swap.networkStatusExtraMessage === reason &&
500
+ swap.networkStatusExtraMessageDetail === reasonDetail;
501
+
502
+ if (isAlreadyMarked) {
503
+ return;
504
+ }
505
+
506
+ currentStep.networkStatus =
507
+ PendingSwapNetworkStatus.WaitingForConnectingWallet;
508
+ swap.networkStatusExtraMessage = reason;
509
+ swap.networkStatusExtraMessageDetail = reasonDetail;
510
+
511
+ setStorage({
512
+ ...getStorage(),
513
+ swapDetails: swap,
514
+ });
515
+ }
516
+
517
+ /**
518
+ * If a swap needs a certain network to proceed,
519
+ * By calling this function some related fields will be updated to show a correct message and state for notfiying the user.
520
+ */
521
+ export function markRunningSwapAsSwitchingNetwork({
522
+ getStorage,
523
+ setStorage,
524
+ }: Pick<ExecuterActions, 'getStorage' | 'setStorage'>):
525
+ | {
526
+ swap: PendingSwap;
527
+ step: PendingSwapStep;
528
+ }
529
+ | undefined {
530
+ const swap = getStorage().swapDetails as SwapStorage['swapDetails'];
531
+
532
+ const currentStep = getCurrentStep(swap);
533
+ if (!currentStep) {
534
+ return;
535
+ }
536
+
537
+ // Generate message
538
+ const { type } = getRequiredWallet(swap);
539
+ const fromNamespace = getCurrentNamespaceOf(swap, currentStep);
540
+ const reason = `Change ${type} wallet network to ${fromNamespace.network}`;
541
+ const reasonDetail = `We’re switching the connected network to ${fromNamespace.network}. Please check your wallet.`;
542
+
543
+ const currentTime = new Date();
544
+ swap.lastNotificationTime = currentTime.getTime().toString();
545
+
546
+ currentStep.networkStatus = PendingSwapNetworkStatus.WaitingForNetworkChange;
547
+ swap.networkStatusExtraMessage = reason;
548
+ swap.networkStatusExtraMessageDetail = reasonDetail;
549
+
550
+ setStorage({
551
+ ...getStorage(),
552
+ swapDetails: swap,
553
+ });
554
+
555
+ return {
556
+ swap,
557
+ step: currentStep,
558
+ };
559
+ }
560
+
561
+ /**
562
+ * We are marking the queue as it depends on other queues to be run (on Parallel mode)
563
+ * By calling this function some related fields will be updated to show a correct message and state for notfiying the user.
564
+ */
565
+ export function markRunningSwapAsDependsOnOtherQueues({
566
+ getStorage,
567
+ setStorage,
568
+ }: Pick<ExecuterActions, 'getStorage' | 'setStorage'>):
569
+ | {
570
+ swap: PendingSwap;
571
+ step: PendingSwapStep;
572
+ }
573
+ | undefined {
574
+ const swap = getStorage().swapDetails as SwapStorage['swapDetails'];
575
+ const currentStep = getCurrentStep(swap);
576
+ if (!currentStep) {
577
+ return;
578
+ }
579
+
580
+ swap.networkStatusExtraMessage = '';
581
+ swap.networkStatusExtraMessageDetail = '';
582
+ currentStep.networkStatus = PendingSwapNetworkStatus.WaitingForQueue;
583
+
584
+ notifier({
585
+ event: {
586
+ type: StepEventType.TX_EXECUTION_BLOCKED,
587
+ status: StepExecutionBlockedEventStatus.WAITING_FOR_QUEUE,
588
+ },
589
+ swap,
590
+ step: currentStep,
591
+ });
592
+
593
+ setStorage({
594
+ ...getStorage(),
595
+ swapDetails: swap,
596
+ });
597
+
598
+ return {
599
+ swap,
600
+ step: currentStep,
601
+ };
602
+ }
603
+
604
+ export async function delay(ms: number): Promise<unknown> {
605
+ return new Promise((res) => setTimeout(res, ms));
606
+ }
607
+
608
+ /**
609
+ *
610
+ * To execute a swap, we are keeping the user prefrences on what wallet they are going to use for a sepecific blockchain
611
+ * By passing the swap and the network we are looking for, it returns the wallet name that user selected.
612
+ *
613
+ */
614
+ export const getSwapWalletType = (
615
+ swap: PendingSwap,
616
+ network: Network
617
+ ): WalletType => {
618
+ return swap.wallets[network]?.walletType;
619
+ };
620
+
621
+ /**
622
+ *
623
+ * We are keeping the connected wallet in a specific structure (`Wallet`),
624
+ * By using this function we normally want to check a specific wallet is connected and exists or not.
625
+ *
626
+ */
627
+ export function isWalletNull(wallet: Wallet | null): boolean {
628
+ return (
629
+ wallet === null ||
630
+ wallet?.blockchains === null ||
631
+ wallet?.blockchains.length === 0
632
+ );
633
+ }
634
+
635
+ /**
636
+ * In a `PendingSwap`, each step needs a wallet to proceed,
637
+ * By using this function we can access what wallet exactly we need to run current step.
638
+ */
639
+ export function getRequiredWallet(swap: PendingSwap): {
640
+ type: WalletType | null;
641
+ namespace: TargetNamespace | null;
642
+ address: string | null;
643
+ } {
644
+ const step = getCurrentStep(swap)!;
645
+ const currentNamespace = getCurrentNamespaceOfOrNull(swap, step);
646
+ if (!currentNamespace) {
647
+ return {
648
+ type: null,
649
+ namespace: null,
650
+ address: null,
651
+ };
652
+ }
653
+
654
+ const walletType = getSwapWalletType(swap, currentNamespace.network);
655
+ const sourceWallet = swap.wallets[currentNamespace.network];
656
+
657
+ return {
658
+ type: walletType || null,
659
+ namespace: currentNamespace,
660
+ address: sourceWallet ? sourceWallet.address : null,
661
+ };
662
+ }
663
+
664
+ /**
665
+ * On EVM compatible wallets, There is one instance with different chains (like Polygon)
666
+ * To get the chain from instance we will use this function.
667
+ */
668
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
669
+ async function getChainId(provider: any): Promise<string | number | null> {
670
+ try {
671
+ const chainId: number | string | null =
672
+ (await provider.request({ method: 'eth_chainId' })) || provider?.chainId;
673
+ return chainId;
674
+ } catch {
675
+ return provider?.chainId;
676
+ }
677
+ }
678
+
679
+ export async function getProviderChainId(
680
+ providers: Providers,
681
+ hubProvider: (type: WalletType) => Provider,
682
+ walletType: string
683
+ ) {
684
+ const legacyProvider = getEvmProvider(providers, walletType);
685
+ if (legacyProvider) {
686
+ const chainId = await getChainId(legacyProvider);
687
+ return chainId;
688
+ }
689
+
690
+ // Legacy provider was not found, try to get the chain id from hub provider.
691
+ const provider = hubProvider(walletType);
692
+ const evmNamespace = provider.get('evm');
693
+ if (!evmNamespace) {
694
+ throw new Error('EVM namespace not found for wallet type: ' + walletType);
695
+ }
696
+ const chainId = evmNamespace.getChainId();
697
+ return chainId;
698
+ }
699
+
700
+ /**
701
+ * For running a swap safely, we need to make sure about the state of wallet
702
+ * which means the netowrk/chain of wallet should be exactly on what a transaction needs.
703
+ */
704
+ export async function isNetworkMatchedForTransaction(
705
+ swap: PendingSwap,
706
+ step: PendingSwapStep,
707
+ wallet: Wallet | null,
708
+ meta: Meta,
709
+ providers: Providers,
710
+ hubProvider: (type: WalletType) => Provider
711
+ ): Promise<boolean> {
712
+ if (isWalletNull(wallet)) {
713
+ return false;
714
+ }
715
+ const fromNamespace = getCurrentNamespaceOfOrNull(swap, step);
716
+ if (!fromNamespace) {
717
+ return false;
718
+ }
719
+
720
+ if (
721
+ meta.evmBasedChains.find(
722
+ (evmBlochain) => evmBlochain.name === fromNamespace.network
723
+ )
724
+ ) {
725
+ try {
726
+ const sourceWallet = swap.wallets[fromNamespace.network];
727
+ if (sourceWallet) {
728
+ const chainId = await getProviderChainId(
729
+ providers,
730
+ hubProvider,
731
+ sourceWallet.walletType
732
+ );
733
+ if (chainId) {
734
+ const blockChain = getBlockChainNameFromId(
735
+ chainId,
736
+ Object.entries(meta.blockchains).map(
737
+ ([, blockchainMeta]) => blockchainMeta
738
+ )
739
+ );
740
+ if (
741
+ blockChain &&
742
+ blockChain.toLowerCase() === fromNamespace.network.toLowerCase()
743
+ ) {
744
+ return true;
745
+ }
746
+ if (
747
+ blockChain &&
748
+ blockChain.toLowerCase() !== fromNamespace.network.toLowerCase()
749
+ ) {
750
+ return false;
751
+ }
752
+ }
753
+ }
754
+ } catch (e) {
755
+ console.log(e);
756
+ }
757
+ return false;
758
+ }
759
+ return true;
760
+ }
761
+
762
+ export const isTxAlreadyCreated = (
763
+ swap: PendingSwap,
764
+ step: PendingSwapStep
765
+ ): boolean => {
766
+ const result =
767
+ swap.wallets[step.evmTransaction?.blockChain || ''] ||
768
+ swap.wallets[step.evmApprovalTransaction?.blockChain || ''] ||
769
+ swap.wallets[step.tronTransaction?.blockChain || ''] ||
770
+ swap.wallets[step.tronApprovalTransaction?.blockChain || ''] ||
771
+ swap.wallets[step.starknetTransaction?.blockChain || ''] ||
772
+ swap.wallets[step.starknetApprovalTransaction?.blockChain || ''] ||
773
+ swap.wallets[step.cosmosTransaction?.blockChain || ''] ||
774
+ swap.wallets[step.solanaTransaction?.blockChain || ''] ||
775
+ swap.wallets[step.tonTransaction?.blockChain || ''] ||
776
+ swap.wallets[step.suiTransaction?.blockChain || ''] ||
777
+ step.transferTransaction?.fromWalletAddress ||
778
+ null;
779
+
780
+ return result !== null;
781
+ };
782
+
783
+ export function resetNetworkStatus(
784
+ actions: ExecuterActions<SwapStorage, SwapActionTypes, SwapQueueContext>
785
+ ): void {
786
+ const { getStorage, setStorage } = actions;
787
+ const swap = getStorage().swapDetails;
788
+ const currentStep = getCurrentStep(swap);
789
+
790
+ if (currentStep?.networkStatus) {
791
+ currentStep.networkStatus = null;
792
+ setStorage({ ...getStorage(), swapDetails: swap });
793
+ }
794
+ }
795
+
796
+ export function updateNetworkStatus(
797
+ actions: Pick<
798
+ ExecuterActions<SwapStorage, SwapActionTypes, SwapQueueContext>,
799
+ 'getStorage' | 'setStorage'
800
+ >,
801
+ data: {
802
+ message: string;
803
+ details: string;
804
+ status: PendingSwapNetworkStatus | null;
805
+ } = {
806
+ message: '',
807
+ details: '',
808
+ status: null,
809
+ }
810
+ ): void {
811
+ const { message, details, status } = data;
812
+ const { getStorage, setStorage } = actions;
813
+ const swap = getStorage().swapDetails;
814
+ const currentStep = getCurrentStep(swap);
815
+
816
+ if (currentStep?.networkStatus) {
817
+ swap.networkStatusExtraMessage = message;
818
+ swap.networkStatusExtraMessageDetail = details;
819
+ currentStep.networkStatus = status;
820
+ setStorage({ ...getStorage(), swapDetails: swap });
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Event handler for blocked tasks.
826
+ * If a transcation execution is manually blocked (like for parallel or waiting for wallet),
827
+ * This function will be called by queue manager using `queue definition`.
828
+ *
829
+ * It checks if the required wallet is connected, unblock the queue to be run.
830
+ */
831
+ export function onBlockForConnectWallet(
832
+ event: WhenTaskBlockedEvent,
833
+ meta: WhenTaskBlockedMeta
834
+ ): void {
835
+ const { context, queue } = meta;
836
+ const swap = queue.getStorage().swapDetails as SwapStorage['swapDetails'];
837
+
838
+ const { ok, reason } = isRequiredWalletConnected(swap, context.state);
839
+
840
+ if (!ok) {
841
+ const currentStep = getCurrentStep(swap)!;
842
+ const { type: walletType, address } = getRequiredWallet(swap);
843
+ notifier({
844
+ event: {
845
+ type: StepEventType.TX_EXECUTION_BLOCKED,
846
+ ...(reason === 'account_miss_match'
847
+ ? {
848
+ status:
849
+ StepExecutionBlockedEventStatus.WAITING_FOR_CHANGE_WALLET_ACCOUNT,
850
+ requiredAccount: address ?? undefined,
851
+ }
852
+ : {
853
+ status:
854
+ StepExecutionBlockedEventStatus.WAITING_FOR_WALLET_CONNECT,
855
+ requiredWallet: walletType ?? undefined,
856
+ requiredAccount: address ?? undefined,
857
+ }),
858
+ },
859
+ swap: swap,
860
+ step: currentStep,
861
+ });
862
+
863
+ markRunningSwapAsWaitingForConnectingWallet(
864
+ {
865
+ getStorage: queue.getStorage.bind(queue),
866
+ setStorage: queue.setStorage.bind(queue),
867
+ },
868
+ ERROR_MESSAGE_WAIT_FOR_WALLET,
869
+ event.reason.description
870
+ );
871
+
872
+ return;
873
+ }
874
+
875
+ queue.unblock();
876
+ }
877
+
878
+ /**
879
+ * Event handler for blocked tasks.
880
+ * If a transcation execution is manually blocked (like for parallel or waiting for walle),
881
+ * This function will be called by queue manager using `queue definition`.
882
+ *
883
+ * It checks if the required network is connected, unblock the queue to be run.
884
+ * Note: it automatically try to switch the network if its `provider` supports.
885
+ */
886
+ export function onBlockForChangeNetwork(
887
+ _event: WhenTaskBlockedEvent,
888
+ meta: WhenTaskBlockedMeta
889
+ ): void {
890
+ const { context, queue } = meta;
891
+ const swap = queue.getStorage().swapDetails as SwapStorage['swapDetails'];
892
+ const currentStep = getCurrentStep(swap);
893
+
894
+ if (!currentStep || swap.status !== 'running') {
895
+ return;
896
+ }
897
+
898
+ const result = markRunningSwapAsSwitchingNetwork({
899
+ getStorage: queue.getStorage.bind(queue),
900
+ setStorage: queue.setStorage.bind(queue),
901
+ });
902
+
903
+ const requiredNetwork = getCurrentNamespaceOfOrNull(
904
+ swap,
905
+ currentStep
906
+ )?.network;
907
+
908
+ const requiredWallet = getRequiredWallet(swap).type;
909
+
910
+ const currentNetwork = requiredWallet
911
+ ? context.state(requiredWallet).network
912
+ : undefined;
913
+
914
+ if (result) {
915
+ notifier({
916
+ event: {
917
+ type: StepEventType.TX_EXECUTION_BLOCKED,
918
+ status: StepExecutionBlockedEventStatus.WAITING_FOR_NETWORK_CHANGE,
919
+ requiredNetwork: requiredNetwork ?? undefined,
920
+ currentNetwork: currentNetwork ?? undefined,
921
+ },
922
+ swap: result.swap,
923
+ step: result.step,
924
+ });
925
+ }
926
+
927
+ // Try to auto switch
928
+ const { type, namespace } = getRequiredWallet(swap);
929
+ if (!!type && !!namespace?.network) {
930
+ if (context.canSwitchNetworkTo(type, namespace.network, namespace)) {
931
+ const result = context.switchNetwork(type, namespace);
932
+ if (result) {
933
+ result
934
+ .then(() => {
935
+ queue.unblock();
936
+ })
937
+ .catch((error) => {
938
+ // Update network to mark it as network change failed.
939
+ updateNetworkStatus(
940
+ {
941
+ getStorage: queue.getStorage.bind(queue) as () => SwapStorage,
942
+ setStorage: queue.setStorage.bind(
943
+ queue
944
+ ) as SetStorage<SwapStorage>,
945
+ },
946
+ {
947
+ message: error.message,
948
+ details: error.message,
949
+ status: PendingSwapNetworkStatus.NetworkChangeFailed,
950
+ }
951
+ );
952
+ });
953
+ }
954
+ }
955
+ }
956
+ }
957
+
958
+ /**
959
+ * Event handler for blocked tasks. (Parallel mode)
960
+ * If a transcation execution flow is manually blocked (like for parallel or waiting for walle),
961
+ * This function will be called by queue manager using `queue definition`.
962
+ *
963
+ * It checks the blocked tasks, if there is no active `claimed` queue, try to give it to the best candidate.
964
+ */
965
+ export function onDependsOnOtherQueues(
966
+ _event: WhenTaskBlockedEvent,
967
+ meta: WhenTaskBlockedMeta
968
+ ): void {
969
+ const { getBlockedTasks, forceExecute, queue, manager, context } = meta;
970
+ const { setClaimer, claimedBy, reset } = claimQueue();
971
+
972
+ // We only needs those blocked tasks that have DEPENDS_ON_OTHER_QUEUES reason.
973
+ const blockedTasks = getBlockedTasks().filter(
974
+ (task) => task.reason.reason === BlockReason.DEPENDS_ON_OTHER_QUEUES
975
+ );
976
+
977
+ if (blockedTasks.length === 0) {
978
+ return;
979
+ }
980
+
981
+ const claimerId = claimedBy();
982
+ const isClaimedByAnyQueue = !!claimerId;
983
+
984
+ if (claimerId === queue.id) {
985
+ return;
986
+ }
987
+
988
+ // Check if any queue `claimed` before, if yes, we don't should do anything.
989
+ if (isClaimedByAnyQueue) {
990
+ // We need to keep the latest swap messages
991
+ markRunningSwapAsDependsOnOtherQueues({
992
+ getStorage: queue.getStorage.bind(queue),
993
+ setStorage: queue.setStorage.bind(queue),
994
+ });
995
+ return;
996
+ }
997
+
998
+ // Prioritize current queue to be run first.
999
+
1000
+ let task = blockedTasks.find((task) => {
1001
+ return task.queue_id === meta.queue_id;
1002
+ });
1003
+
1004
+ // If current task isn't available anymore, fallback to first blocked task.
1005
+ if (!task) {
1006
+ const firstBlockedTask = blockedTasks[0];
1007
+ task = firstBlockedTask;
1008
+ }
1009
+
1010
+ setClaimer(task.queue_id);
1011
+ const claimedStorage = task.storage.get() as SwapStorage;
1012
+ const { type, namespace, address } = getRequiredWallet(
1013
+ claimedStorage.swapDetails
1014
+ );
1015
+
1016
+ // Run
1017
+ forceExecute(task.queue_id, {
1018
+ claimedBy: claimedBy(),
1019
+ resetClaimedBy: () => {
1020
+ reset();
1021
+ // TODO: Use key generator
1022
+ if (type) {
1023
+ retryOn(
1024
+ {
1025
+ walletType: type,
1026
+ network: namespace?.network,
1027
+ accounts: address ? [address] : [],
1028
+ },
1029
+ manager,
1030
+ context.canSwitchNetworkTo
1031
+ );
1032
+ }
1033
+ },
1034
+ });
1035
+ }
1036
+
1037
+ export function isRequiredWalletConnected(
1038
+ swap: PendingSwap,
1039
+ getState: (type: WalletType) => WalletState
1040
+ ): { ok: boolean; reason: 'not_connected' | 'account_miss_match' } {
1041
+ const { type, address } = getRequiredWallet(swap);
1042
+ if (!type || !address) {
1043
+ return { ok: false, reason: 'not_connected' };
1044
+ }
1045
+ const walletState = getState(type);
1046
+ const { accounts, connected } = walletState;
1047
+ const connectedAccounts = accounts || [];
1048
+ if (!connected) {
1049
+ return { ok: false, reason: 'not_connected' };
1050
+ }
1051
+
1052
+ const matched = connectedAccounts.some((account) => {
1053
+ const { address: accountAddress } = readAccountAddress(account);
1054
+ return address.toLocaleLowerCase() === accountAddress.toLocaleLowerCase();
1055
+ });
1056
+ return { ok: matched, reason: 'account_miss_match' };
1057
+ }
1058
+
1059
+ export function handleSuccessfulSign(
1060
+ actions: ExecuterActions<SwapStorage, SwapActionTypes, SwapQueueContext>,
1061
+ data: { isApproval: boolean }
1062
+ ) {
1063
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1064
+ return ({ hash, response }: { hash: string; response?: any }) => {
1065
+ const { setTransactionDataByHash } = inMemoryTransactionsData();
1066
+ const { getStorage, next, schedule, context } = actions;
1067
+ const { meta } = context;
1068
+ const swap = getStorage().swapDetails;
1069
+
1070
+ const currentStep = getCurrentStep(swap)!;
1071
+
1072
+ const currentStepNamespace = getCurrentNamespaceOf(swap, currentStep);
1073
+ const explorerUrl = getScannerUrl(
1074
+ hash,
1075
+ currentStepNamespace.network,
1076
+ meta.blockchains
1077
+ );
1078
+ setStepTransactionIds(
1079
+ actions,
1080
+ hash,
1081
+ explorerUrl && (!response || (response && !response.hashRequiringUpdate))
1082
+ ? {
1083
+ url: explorerUrl,
1084
+ description: data.isApproval ? 'Approve' : 'Swap',
1085
+ }
1086
+ : undefined
1087
+ );
1088
+ // response used for evm transactions to get receipt and track replaced
1089
+ if (response) {
1090
+ setTransactionDataByHash(hash, { response });
1091
+ }
1092
+ schedule(SwapActionTypes.CHECK_TRANSACTION_STATUS);
1093
+ next();
1094
+ };
1095
+ }
1096
+
1097
+ export function handleRejectedSign(
1098
+ actions: ExecuterActions<SwapStorage, SwapActionTypes, SwapQueueContext>
1099
+ ) {
1100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1101
+ return (error: any) => {
1102
+ const { getStorage, setStorage, failed } = actions;
1103
+ const swap = getStorage().swapDetails;
1104
+
1105
+ const currentStep = getCurrentStep(swap)!;
1106
+
1107
+ const sourceWallet = getRelatedWallet(swap, currentStep);
1108
+ if (swap.status === 'failed') {
1109
+ return;
1110
+ }
1111
+
1112
+ const { extraMessage, extraMessageDetail, extraMessageErrorCode } =
1113
+ prettifyErrorMessage(error);
1114
+
1115
+ warn(error, {
1116
+ tags: {
1117
+ requestId: swap.requestId,
1118
+ rpc: true,
1119
+ swapper: currentStep?.swapperId || '',
1120
+ walletType: sourceWallet?.walletType || '',
1121
+ },
1122
+ context: SignerError.isSignerError(error) ? error.getErrorContext() : {},
1123
+ });
1124
+
1125
+ const updateResult = updateSwapStatus({
1126
+ getStorage,
1127
+ setStorage,
1128
+ nextStatus: 'failed',
1129
+ nextStepStatus: 'failed',
1130
+ message: extraMessage,
1131
+ details: extraMessageDetail,
1132
+ errorCode: extraMessageErrorCode,
1133
+ trace: error?.cause,
1134
+ });
1135
+
1136
+ notifier({
1137
+ event: {
1138
+ type: StepEventType.FAILED,
1139
+ reason: extraMessage,
1140
+ reasonCode: updateResult.failureType ?? DEFAULT_ERROR_CODE,
1141
+ inputAmount: getLastFinishedStepInput(swap),
1142
+ inputAmountUsd: getLastFinishedStepInputUsd(swap),
1143
+ },
1144
+ ...updateResult,
1145
+ });
1146
+ failed();
1147
+ };
1148
+ }
1149
+
1150
+ export function checkWaitingForConnectWalletChange(params: {
1151
+ lastConnectedWallet: LastConnectedWallet;
1152
+ manager?: Manager;
1153
+ evmChains: EvmBlockchainMeta[];
1154
+ }): void {
1155
+ const { lastConnectedWallet, evmChains, manager } = params;
1156
+ const { walletType: wallet, network } = lastConnectedWallet;
1157
+ // We only need change network for EVM chains.
1158
+ if (!evmChains.some((chain) => chain.name == network)) {
1159
+ return;
1160
+ }
1161
+
1162
+ manager?.getAll().forEach((q) => {
1163
+ const queueStorage = q.list.getStorage() as SwapStorage | undefined;
1164
+ const swap = queueStorage?.swapDetails;
1165
+ if (swap && swap.status === 'running') {
1166
+ const currentStep = getCurrentStep(swap);
1167
+ if (currentStep) {
1168
+ const currentStepRequiredWallet =
1169
+ queueStorage?.swapDetails.wallets[currentStep.fromBlockchain]
1170
+ ?.walletType;
1171
+ const hasWaitingForConnect = Object.keys(q.list.state.tasks).some(
1172
+ (taskId) => {
1173
+ const task = q.list.state.tasks[taskId];
1174
+ return (
1175
+ task.status === Status.BLOCKED &&
1176
+ // TODO double check later
1177
+ [BlockReason.WAIT_FOR_CONNECT_WALLET].includes(
1178
+ task.blockedFor?.reason
1179
+ )
1180
+ );
1181
+ }
1182
+ );
1183
+
1184
+ const requiredNetwork = getCurrentNamespaceOfOrNull(
1185
+ swap,
1186
+ currentStep
1187
+ )?.network;
1188
+
1189
+ // We only need change network for EVM chains.
1190
+ if (!evmChains.some((chain) => chain.name == requiredNetwork)) {
1191
+ return;
1192
+ }
1193
+
1194
+ if (
1195
+ currentStepRequiredWallet === wallet &&
1196
+ hasWaitingForConnect &&
1197
+ requiredNetwork != network
1198
+ ) {
1199
+ const queueInstance = q.list;
1200
+ const { type } = getRequiredWallet(swap);
1201
+ const description = ERROR_MESSAGE_WAIT_FOR_CHANGE_NETWORK(type);
1202
+
1203
+ q.list.block({
1204
+ reason: {
1205
+ reason: BlockReason.WAIT_FOR_NETWORK_CHANGE,
1206
+ description,
1207
+ },
1208
+ silent: true,
1209
+ });
1210
+
1211
+ const result = markRunningSwapAsSwitchingNetwork({
1212
+ getStorage: queueInstance.getStorage.bind(queueInstance),
1213
+ setStorage: queueInstance.setStorage.bind(queueInstance),
1214
+ });
1215
+
1216
+ if (result) {
1217
+ notifier({
1218
+ event: {
1219
+ type: StepEventType.TX_EXECUTION_BLOCKED,
1220
+ status:
1221
+ StepExecutionBlockedEventStatus.WAITING_FOR_NETWORK_CHANGE,
1222
+ currentNetwork: network,
1223
+ requiredNetwork: requiredNetwork ?? undefined,
1224
+ },
1225
+ swap: result.swap,
1226
+ step: result.step,
1227
+ });
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ });
1233
+ }
1234
+
1235
+ export function checkWaitingForNetworkChange(manager?: Manager): void {
1236
+ manager?.getAll().forEach((q) => {
1237
+ const hasWaitingForNetwork = Object.keys(q.list.state.tasks).some(
1238
+ (taskId) => {
1239
+ const task = q.list.state.tasks[taskId];
1240
+ return (
1241
+ task.status === Status.BLOCKED &&
1242
+ [
1243
+ BlockReason.WAIT_FOR_NETWORK_CHANGE,
1244
+ BlockReason.DEPENDS_ON_OTHER_QUEUES,
1245
+ ].includes(task.blockedFor?.reason)
1246
+ );
1247
+ }
1248
+ );
1249
+
1250
+ if (hasWaitingForNetwork) {
1251
+ const swap = q.list.getStorage()
1252
+ ?.swapDetails as SwapStorage['swapDetails'];
1253
+ if (swap.status === 'running') {
1254
+ const { type } = getRequiredWallet(swap);
1255
+ const description = ERROR_MESSAGE_WAIT_FOR_WALLET_DESCRIPTION(type);
1256
+
1257
+ // Change the block reason to waiting for connecting wallet
1258
+ q.list.block({
1259
+ reason: {
1260
+ reason: BlockReason.WAIT_FOR_CONNECT_WALLET,
1261
+ description,
1262
+ },
1263
+ });
1264
+ }
1265
+ }
1266
+ });
1267
+ }
1268
+
1269
+ /**
1270
+ * Get list of all running swaps
1271
+ *
1272
+ * @param manager
1273
+ * @returns list of pending swaps
1274
+ */
1275
+ export function getRunningSwaps(manager: Manager): PendingSwap[] {
1276
+ const queues = manager?.getAll() || new Map<QueueName, QueueInfo>();
1277
+ const result: PendingSwap[] = [];
1278
+ queues.forEach((q) => {
1279
+ // retry only on affected queues
1280
+ const queueStorage = q.list.getStorage() as SwapStorage | undefined;
1281
+ const swap = queueStorage?.swapDetails;
1282
+ if (!swap || swap.status !== 'running') {
1283
+ return;
1284
+ }
1285
+ result.push(swap);
1286
+ });
1287
+ return result;
1288
+ }
1289
+
1290
+ /**
1291
+ *
1292
+ * Trying to reset notifications for pending swaps to correct message on page load.
1293
+ * We could remove this after supporting auto connect for wallets.
1294
+ *
1295
+ * @param swaps
1296
+ * @param notifier
1297
+ * @returns
1298
+ */
1299
+ export function resetRunningSwapNotifsOnPageLoad(runningSwaps: PendingSwap[]) {
1300
+ runningSwaps.forEach((swap) => {
1301
+ const currentStep = getCurrentStep(swap);
1302
+ const eventType = StepEventType.TX_EXECUTION_BLOCKED;
1303
+ let eventSubtype:
1304
+ | StepExecutionBlockedEventStatus.WAITING_FOR_QUEUE
1305
+ | StepExecutionBlockedEventStatus.WAITING_FOR_WALLET_CONNECT
1306
+ | undefined;
1307
+ if (
1308
+ currentStep?.networkStatus === PendingSwapNetworkStatus.WaitingForQueue
1309
+ ) {
1310
+ eventSubtype = StepExecutionBlockedEventStatus.WAITING_FOR_QUEUE;
1311
+ } else if (swap?.status === 'running') {
1312
+ eventSubtype = StepExecutionBlockedEventStatus.WAITING_FOR_WALLET_CONNECT;
1313
+ }
1314
+ if (!!eventType && !!notifier) {
1315
+ notifier({
1316
+ event: {
1317
+ type: eventType,
1318
+ status:
1319
+ eventSubtype ?? StepExecutionBlockedEventStatus.WAITING_FOR_QUEUE,
1320
+ },
1321
+ swap: swap,
1322
+ step: currentStep,
1323
+ });
1324
+ }
1325
+ });
1326
+ }
1327
+
1328
+ /**
1329
+ *
1330
+ * Try to run blocked tasks by wallet and network name.
1331
+ * Goes through queues and extract blocked queues with matched wallet.
1332
+ * If found any blocked tasks with same wallet and network, runs them.
1333
+ * If not, runs only blocked tasks with matched wallet.
1334
+ *
1335
+ * @param wallet_network a string includes `wallet` type and `network` type.
1336
+ * @param manager
1337
+ * @returns
1338
+ */
1339
+ export function retryOn(
1340
+ lastConnectedWallet: LastConnectedWallet,
1341
+ manager?: Manager,
1342
+ canSwitchNetworkTo?: UseQueueManagerParams['canSwitchNetworkTo'],
1343
+ options = { fallbackToOnlyWallet: true }
1344
+ ): void {
1345
+ const { walletType: wallet, network } = lastConnectedWallet;
1346
+ if (!wallet) {
1347
+ return;
1348
+ }
1349
+
1350
+ const walletAndNetworkMatched: QueueType[] = [];
1351
+ const onlyWalletMatched: QueueType[] = [];
1352
+
1353
+ manager?.getAll().forEach((q) => {
1354
+ // retry only on affected queues
1355
+ if (q.status === Status.BLOCKED) {
1356
+ const queueStorage = q.list.getStorage() as SwapStorage | undefined;
1357
+ const swap = queueStorage?.swapDetails;
1358
+
1359
+ if (swap && swap.status === 'running') {
1360
+ const currentStep = getCurrentStep(swap);
1361
+ if (currentStep) {
1362
+ if (
1363
+ network &&
1364
+ getCurrentNamespaceOfOrNull(swap, currentStep)?.network ==
1365
+ network &&
1366
+ queueStorage?.swapDetails.wallets[network]?.walletType === wallet
1367
+ ) {
1368
+ walletAndNetworkMatched.push(q.list);
1369
+ } else if (
1370
+ queueStorage?.swapDetails.wallets[currentStep.fromBlockchain]
1371
+ ?.walletType === wallet
1372
+ ) {
1373
+ onlyWalletMatched.push(q.list);
1374
+ }
1375
+ }
1376
+ }
1377
+ }
1378
+ });
1379
+
1380
+ let finalQueueToBeRun: QueueType | undefined = undefined;
1381
+ if (walletAndNetworkMatched.length > 0) {
1382
+ finalQueueToBeRun = walletAndNetworkMatched[0];
1383
+
1384
+ if (walletAndNetworkMatched.length > 1) {
1385
+ for (let i = 1; i < walletAndNetworkMatched.length; i++) {
1386
+ const currentQueue = walletAndNetworkMatched[i];
1387
+
1388
+ markRunningSwapAsDependsOnOtherQueues({
1389
+ getStorage: currentQueue.getStorage.bind(currentQueue),
1390
+ setStorage: currentQueue.setStorage.bind(currentQueue),
1391
+ });
1392
+ }
1393
+ }
1394
+ } else if (onlyWalletMatched.length > 0 && options.fallbackToOnlyWallet) {
1395
+ finalQueueToBeRun = onlyWalletMatched[0];
1396
+ }
1397
+
1398
+ if (finalQueueToBeRun) {
1399
+ const finalQueueStorage = finalQueueToBeRun.getStorage() as SwapStorage;
1400
+ const currentSwap = getCurrentStep(finalQueueStorage?.swapDetails);
1401
+
1402
+ const currentNamespace = currentSwap
1403
+ ? getCurrentNamespaceOfOrNull(finalQueueStorage.swapDetails, currentSwap)
1404
+ : null;
1405
+ if (
1406
+ !network ||
1407
+ !currentNamespace ||
1408
+ !canSwitchNetworkTo?.(wallet, network, currentNamespace)
1409
+ ) {
1410
+ finalQueueToBeRun.unblock();
1411
+ } else {
1412
+ finalQueueToBeRun.checkBlock();
1413
+ }
1414
+ }
1415
+ }
1416
+
1417
+ /*
1418
+ *For avoiding conflict by making too many requests to wallet, we need to make sure
1419
+ *We only run one request at a time (In parallel mode).
1420
+ */
1421
+ export function isNeedBlockQueueForParallel(step: PendingSwapStep): boolean {
1422
+ return !!step.evmTransaction || !!step.evmApprovalTransaction;
1423
+ }
1424
+
1425
+ /*
1426
+ *Create transaction endpoint doesn't return error code on http status code,
1427
+ *For backward compatibilty with server and sdk, we use this wrapper to reject the promise.
1428
+ */
1429
+ export async function throwOnOK(
1430
+ rawResponse: Promise<CreateTransactionResponse>
1431
+ ): Promise<CreateTransactionResponse> {
1432
+ const responseBody = await rawResponse;
1433
+ if (!responseBody.ok || !responseBody.transaction) {
1434
+ throw PrettyError.CreateTransaction(
1435
+ responseBody.error || 'bad response from create tx endpoint'
1436
+ );
1437
+ }
1438
+ return responseBody;
1439
+ }
1440
+
1441
+ export function cancelSwap(
1442
+ swap: QueueInfo,
1443
+ manager?: Manager
1444
+ ): {
1445
+ swap: PendingSwap;
1446
+ step: PendingSwapStep | null;
1447
+ } {
1448
+ const { reset } = claimQueue();
1449
+ swap.actions.cancel();
1450
+
1451
+ const updateResult = updateSwapStatus({
1452
+ getStorage: swap.actions.getStorage,
1453
+ setStorage: swap.actions.setStorage,
1454
+ message: 'Swap canceled by user.',
1455
+ details:
1456
+ "Warning: If you've already signed and sent a transaction, it won't be affected, but next swap steps will not be executed.",
1457
+ nextStatus: 'failed',
1458
+ nextStepStatus: 'failed',
1459
+ errorCode: 'USER_CANCEL',
1460
+ });
1461
+
1462
+ notifier({
1463
+ event: {
1464
+ type: StepEventType.FAILED,
1465
+ reasonCode: 'USER_CANCEL',
1466
+ reason: updateResult.swap.extraMessage ?? undefined,
1467
+ inputAmount: getLastFinishedStepInput(updateResult.swap),
1468
+ inputAmountUsd: getLastFinishedStepInputUsd(updateResult.swap),
1469
+ },
1470
+
1471
+ swap: updateResult.swap,
1472
+ step: updateResult.step,
1473
+ });
1474
+
1475
+ reset();
1476
+ if (manager) {
1477
+ manager?.retry();
1478
+ }
1479
+
1480
+ return updateResult;
1481
+ }
1482
+
1483
+ export function getLastSuccessfulStep<T extends { status: StepStatus }[]>(
1484
+ steps: T
1485
+ ): ArrayElement<T> | undefined {
1486
+ return steps
1487
+ .slice()
1488
+ .reverse()
1489
+ .find((step) => step.status === 'success') as ArrayElement<T> | undefined;
1490
+ }
1491
+
1492
+ export function getFailedStep<T extends { status: StepStatus }[]>(
1493
+ steps: T
1494
+ ): ArrayElement<T> | undefined {
1495
+ return steps
1496
+ .slice()
1497
+ .reverse()
1498
+ .find((step) => step.status === 'failed') as ArrayElement<T> | undefined;
1499
+ }
1500
+
1501
+ export function isApprovalTX(step: Step): boolean {
1502
+ const { transaction } = step;
1503
+ const approvalTx =
1504
+ (transaction?.type === TransactionType.EVM && transaction.isApprovalTx) ||
1505
+ (transaction?.type === TransactionType.STARKNET &&
1506
+ transaction.isApprovalTx) ||
1507
+ (transaction?.type === TransactionType.TRON && transaction.isApprovalTx);
1508
+
1509
+ return approvalTx;
1510
+ }
1511
+
1512
+ export function getTokenAmountInUsd(
1513
+ amount: string | number,
1514
+ usdPrice: string | number
1515
+ ): string {
1516
+ const usdValue = new BigNumber(amount).multipliedBy(usdPrice);
1517
+ if (isNaN(usdValue.toNumber())) {
1518
+ return '';
1519
+ }
1520
+ return usdValue.toString();
1521
+ }
1522
+
1523
+ export function getSwapInputUsd(swap: PendingSwap): string {
1524
+ return getTokenAmountInUsd(
1525
+ swap.inputAmount,
1526
+ swap.steps[0].fromUsdPrice ?? ''
1527
+ );
1528
+ }
1529
+
1530
+ export function getSwapOutputUsd(swap: PendingSwap): string {
1531
+ const lastStep = swap.steps[swap.steps.length - 1];
1532
+
1533
+ return getTokenAmountInUsd(
1534
+ lastStep.outputAmount ?? '',
1535
+ lastStep.toUsdPrice ?? ''
1536
+ );
1537
+ }
1538
+
1539
+ export function getLastFinishedStep(
1540
+ swap: PendingSwap
1541
+ ): { step: PendingSwapStep; index: number } | undefined {
1542
+ const FINISHED_STATUS: PendingSwap['steps'][number]['status'][] = [
1543
+ 'success',
1544
+ 'failed',
1545
+ ];
1546
+
1547
+ const lastFinishedStepIndex = swap.steps.findLastIndex((step) =>
1548
+ FINISHED_STATUS.includes(step.status)
1549
+ );
1550
+
1551
+ return lastFinishedStepIndex < 0
1552
+ ? undefined
1553
+ : { step: swap.steps[lastFinishedStepIndex], index: lastFinishedStepIndex };
1554
+ }
1555
+
1556
+ export function getLastFinishedStepInput(swap: PendingSwap): string {
1557
+ const lastFinishedStep = getLastFinishedStep(swap);
1558
+
1559
+ if (!lastFinishedStep) {
1560
+ return '';
1561
+ }
1562
+
1563
+ return lastFinishedStep.index === 0
1564
+ ? swap.inputAmount
1565
+ : swap.steps[lastFinishedStep.index - 1].outputAmount ?? '';
1566
+ }
1567
+
1568
+ export function getLastFinishedStepInputUsd(swap: PendingSwap): string {
1569
+ const lastSuccessfulStep = getLastFinishedStep(swap);
1570
+
1571
+ return getTokenAmountInUsd(
1572
+ getLastFinishedStepInput(swap),
1573
+ lastSuccessfulStep?.step?.fromUsdPrice ?? ''
1574
+ );
1575
+ }
1576
+
1577
+ export function getLastSuccessfulStepOutputUsd(swap: PendingSwap): string {
1578
+ const lastSuccessfulStep = getLastSuccessfulStep(swap.steps);
1579
+
1580
+ return getTokenAmountInUsd(
1581
+ lastSuccessfulStep?.outputAmount ?? '',
1582
+ lastSuccessfulStep?.toUsdPrice ?? ''
1583
+ );
1584
+ }
1585
+
1586
+ export function createStepFailedEvent(
1587
+ swap: PendingSwap,
1588
+ message: string,
1589
+ reasonCode?: APIErrorCode
1590
+ ): NotifierParams['event'] & { type: StepEventType.FAILED } {
1591
+ return {
1592
+ type: StepEventType.FAILED,
1593
+ reason: message,
1594
+ reasonCode: reasonCode ?? DEFAULT_ERROR_CODE,
1595
+ inputAmount: getLastFinishedStepInput(swap),
1596
+ inputAmountUsd: getLastFinishedStepInputUsd(swap),
1597
+ };
1598
+ }