@rango-dev/queue-manager-rango-preset 0.1.10-next.101

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