@keplr-wallet/background 0.12.313 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +1 -0
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/build/keyring-cosmos/service.d.ts +10 -0
- package/build/keyring-cosmos/service.js +100 -0
- package/build/keyring-cosmos/service.js.map +1 -1
- package/build/keyring-ethereum/service.d.ts +5 -0
- package/build/keyring-ethereum/service.js +66 -0
- package/build/keyring-ethereum/service.js.map +1 -1
- package/build/recent-send-history/api.d.ts +31 -0
- package/build/recent-send-history/api.js +97 -0
- package/build/recent-send-history/api.js.map +1 -0
- package/build/recent-send-history/handler.js +36 -0
- package/build/recent-send-history/handler.js.map +1 -1
- package/build/recent-send-history/init.js +5 -0
- package/build/recent-send-history/init.js.map +1 -1
- package/build/recent-send-history/messages.d.ts +76 -1
- package/build/recent-send-history/messages.js +121 -1
- package/build/recent-send-history/messages.js.map +1 -1
- package/build/recent-send-history/service.d.ts +262 -9
- package/build/recent-send-history/service.js +2103 -812
- package/build/recent-send-history/service.js.map +1 -1
- package/build/recent-send-history/types.d.ts +214 -22
- package/build/recent-send-history/types.js +21 -0
- package/build/recent-send-history/types.js.map +1 -1
- package/build/tx/service.d.ts +2 -0
- package/build/tx/service.js +35 -0
- package/build/tx/service.js.map +1 -1
- package/build/tx-ethereum/service.d.ts +2 -0
- package/build/tx-ethereum/service.js +42 -0
- package/build/tx-ethereum/service.js.map +1 -1
- package/build/tx-executor/constants.d.ts +1 -0
- package/build/tx-executor/constants.js +5 -0
- package/build/tx-executor/constants.js.map +1 -0
- package/build/tx-executor/handler.d.ts +3 -0
- package/build/tx-executor/handler.js +45 -0
- package/build/tx-executor/handler.js.map +1 -0
- package/build/tx-executor/index.d.ts +3 -0
- package/build/tx-executor/index.js +20 -0
- package/build/tx-executor/index.js.map +1 -0
- package/build/tx-executor/init.d.ts +3 -0
- package/build/tx-executor/init.js +14 -0
- package/build/tx-executor/init.js.map +1 -0
- package/build/tx-executor/internal.d.ts +4 -0
- package/build/tx-executor/internal.js +24 -0
- package/build/tx-executor/internal.js.map +1 -0
- package/build/tx-executor/messages.d.ts +53 -0
- package/build/tx-executor/messages.js +116 -0
- package/build/tx-executor/messages.js.map +1 -0
- package/build/tx-executor/service.d.ts +67 -0
- package/build/tx-executor/service.js +715 -0
- package/build/tx-executor/service.js.map +1 -0
- package/build/tx-executor/types.d.ts +105 -0
- package/build/tx-executor/types.js +33 -0
- package/build/tx-executor/types.js.map +1 -0
- package/build/tx-executor/utils/cosmos.d.ts +59 -0
- package/build/tx-executor/utils/cosmos.js +526 -0
- package/build/tx-executor/utils/cosmos.js.map +1 -0
- package/build/tx-executor/utils/evm.d.ts +4 -0
- package/build/tx-executor/utils/evm.js +236 -0
- package/build/tx-executor/utils/evm.js.map +1 -0
- package/package.json +13 -13
- package/src/index.ts +24 -1
- package/src/keyring-cosmos/service.ts +151 -0
- package/src/keyring-ethereum/service.ts +103 -6
- package/src/recent-send-history/api.ts +119 -0
- package/src/recent-send-history/handler.ts +84 -0
- package/src/recent-send-history/init.ts +10 -0
- package/src/recent-send-history/messages.ts +163 -1
- package/src/recent-send-history/service.ts +3042 -1153
- package/src/recent-send-history/types.ts +268 -31
- package/src/tx/service.ts +41 -0
- package/src/tx-ethereum/service.ts +57 -0
- package/src/tx-executor/constants.ts +1 -0
- package/src/tx-executor/handler.ts +71 -0
- package/src/tx-executor/index.ts +3 -0
- package/src/tx-executor/init.ts +20 -0
- package/src/tx-executor/internal.ts +9 -0
- package/src/tx-executor/messages.ts +157 -0
- package/src/tx-executor/service.ts +1025 -0
- package/src/tx-executor/types.ts +161 -0
- package/src/tx-executor/utils/cosmos.ts +771 -0
- package/src/tx-executor/utils/evm.ts +310 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChainsService } from "../chains";
|
|
2
|
+
import { AnalyticsService } from "../analytics";
|
|
2
3
|
import {
|
|
3
4
|
Bech32Address,
|
|
4
5
|
ChainIdHelper,
|
|
@@ -17,10 +18,15 @@ import {
|
|
|
17
18
|
import { KVStore, retry } from "@keplr-wallet/common";
|
|
18
19
|
import {
|
|
19
20
|
IBCHistory,
|
|
21
|
+
IBCSwapMinimalTrackingData,
|
|
22
|
+
IbcHop,
|
|
20
23
|
RecentSendHistory,
|
|
21
24
|
SkipHistory,
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
SwapProvider,
|
|
26
|
+
SwapV2History,
|
|
27
|
+
SwapV2RouteStepStatus,
|
|
28
|
+
SwapV2TxStatus,
|
|
29
|
+
SwapV2TxStatusResponse,
|
|
24
30
|
} from "./types";
|
|
25
31
|
import { Buffer } from "buffer/";
|
|
26
32
|
import {
|
|
@@ -30,8 +36,18 @@ import {
|
|
|
30
36
|
EthTxStatus,
|
|
31
37
|
} from "@keplr-wallet/types";
|
|
32
38
|
import { CoinPretty } from "@keplr-wallet/unit";
|
|
33
|
-
import { simpleFetch } from "@keplr-wallet/simple-fetch";
|
|
34
39
|
import { id } from "@ethersproject/hash";
|
|
40
|
+
import { EventBusPublisher } from "@keplr-wallet/common";
|
|
41
|
+
import { TxExecutionEvent } from "../tx-executor/types";
|
|
42
|
+
import {
|
|
43
|
+
requestSkipTxTrack,
|
|
44
|
+
requestSwapV2TxStatus,
|
|
45
|
+
requestEthTxReceipt,
|
|
46
|
+
requestSkipTxStatus,
|
|
47
|
+
requestEthTxTrace,
|
|
48
|
+
} from "./api";
|
|
49
|
+
|
|
50
|
+
export const UNKNOWN_TX_STATUS_TIMEOUT_MS = 5 * 60 * 1000; // 5분
|
|
35
51
|
|
|
36
52
|
const SWAP_API_ENDPOINT = process.env["KEPLR_API_ENDPOINT"] ?? "";
|
|
37
53
|
|
|
@@ -53,16 +69,38 @@ export class RecentSendHistoryService {
|
|
|
53
69
|
@observable
|
|
54
70
|
protected readonly recentSkipHistoryMap: Map<string, SkipHistory> = new Map();
|
|
55
71
|
|
|
72
|
+
@observable
|
|
73
|
+
protected recentSwapV2HistorySeq: number = 0;
|
|
74
|
+
// Key: id (sequence, it should be increased by 1 for each)
|
|
75
|
+
@observable
|
|
76
|
+
protected readonly recentSwapV2HistoryMap: Map<string, SwapV2History> =
|
|
77
|
+
new Map();
|
|
78
|
+
|
|
56
79
|
constructor(
|
|
57
80
|
protected readonly kvStore: KVStore,
|
|
58
81
|
protected readonly chainsService: ChainsService,
|
|
59
82
|
protected readonly txService: BackgroundTxService,
|
|
60
|
-
protected readonly
|
|
83
|
+
protected readonly analyticsService: AnalyticsService,
|
|
84
|
+
protected readonly notification: Notification,
|
|
85
|
+
protected readonly publisher: EventBusPublisher<TxExecutionEvent>
|
|
61
86
|
) {
|
|
62
87
|
makeObservable(this);
|
|
63
88
|
}
|
|
64
89
|
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Init – load & persist histories
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
65
94
|
async init(): Promise<void> {
|
|
95
|
+
await this.initRecentSendHistory();
|
|
96
|
+
await this.initRecentIBCHistory();
|
|
97
|
+
await this.initRecentSkipHistory();
|
|
98
|
+
await this.initRecentSwapV2History();
|
|
99
|
+
|
|
100
|
+
this.chainsService.addChainRemovedHandler(this.onChainRemoved);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
protected async initRecentSendHistory(): Promise<void> {
|
|
66
104
|
const recentSendHistoryMapSaved = await this.kvStore.get<
|
|
67
105
|
Record<string, RecentSendHistory[]>
|
|
68
106
|
>("recentSendHistoryMap");
|
|
@@ -81,7 +119,9 @@ export class RecentSendHistoryService {
|
|
|
81
119
|
obj
|
|
82
120
|
);
|
|
83
121
|
});
|
|
122
|
+
}
|
|
84
123
|
|
|
124
|
+
protected async initRecentIBCHistory(): Promise<void> {
|
|
85
125
|
// 밑의 storage의 key들이 ibc transfer를 포함하는데
|
|
86
126
|
// 이 이유는 이전에 transfer history만 지원되었을때
|
|
87
127
|
// key를 그렇게 정했었기 때문이다
|
|
@@ -127,9 +167,18 @@ export class RecentSendHistoryService {
|
|
|
127
167
|
});
|
|
128
168
|
|
|
129
169
|
for (const history of this.getRecentIBCHistories()) {
|
|
130
|
-
this.trackIBCPacketForwardingRecursive(
|
|
170
|
+
this.trackIBCPacketForwardingRecursive((onFulfill, onClose, onError) => {
|
|
171
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
172
|
+
history.id,
|
|
173
|
+
onFulfill,
|
|
174
|
+
onClose,
|
|
175
|
+
onError
|
|
176
|
+
);
|
|
177
|
+
});
|
|
131
178
|
}
|
|
179
|
+
}
|
|
132
180
|
|
|
181
|
+
protected async initRecentSkipHistory(): Promise<void> {
|
|
133
182
|
// Load skip history sequence from the storage
|
|
134
183
|
const recentSkipHistorySeqSaved = await this.kvStore.get<number>(
|
|
135
184
|
"recentSkipHistorySeq"
|
|
@@ -178,10 +227,63 @@ export class RecentSendHistoryService {
|
|
|
178
227
|
for (const history of this.getRecentSkipHistories()) {
|
|
179
228
|
this.trackSkipSwapRecursive(history.id);
|
|
180
229
|
}
|
|
230
|
+
}
|
|
181
231
|
|
|
182
|
-
|
|
232
|
+
protected async initRecentSwapV2History(): Promise<void> {
|
|
233
|
+
const recentSwapV2HistorySeqSaved = await this.kvStore.get<number>(
|
|
234
|
+
"recentSwapV2HistorySeq"
|
|
235
|
+
);
|
|
236
|
+
if (recentSwapV2HistorySeqSaved) {
|
|
237
|
+
runInAction(() => {
|
|
238
|
+
this.recentSwapV2HistorySeq = recentSwapV2HistorySeqSaved;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Save the swap v2 history sequence to the storage when the swap v2 history sequence is changed
|
|
243
|
+
autorun(() => {
|
|
244
|
+
const js = toJS(this.recentSwapV2HistorySeq);
|
|
245
|
+
this.kvStore.set<number>("recentSwapV2HistorySeq", js);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Load swap v2 history from the storage
|
|
249
|
+
const recentSwapV2HistoryMapSaved = await this.kvStore.get<
|
|
250
|
+
Record<string, SwapV2History>
|
|
251
|
+
>("recentSwapV2HistoryMap");
|
|
252
|
+
if (recentSwapV2HistoryMapSaved) {
|
|
253
|
+
runInAction(() => {
|
|
254
|
+
let entries = Object.entries(recentSwapV2HistoryMapSaved);
|
|
255
|
+
entries = entries.sort(([, a], [, b]) => {
|
|
256
|
+
return parseInt(a.id) - parseInt(b.id);
|
|
257
|
+
});
|
|
258
|
+
for (const [key, value] of entries) {
|
|
259
|
+
this.recentSwapV2HistoryMap.set(key, value);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Save the swap v2 history to the storage when the swap v2 history is changed
|
|
265
|
+
autorun(() => {
|
|
266
|
+
const js = toJS(this.recentSwapV2HistoryMap);
|
|
267
|
+
const obj = Object.fromEntries(js);
|
|
268
|
+
this.kvStore.set<Record<string, SwapV2History>>(
|
|
269
|
+
"recentSwapV2HistoryMap",
|
|
270
|
+
obj
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
for (const history of this.getRecentSwapV2Histories()) {
|
|
275
|
+
this.trackSwapV2Recursive(history.id);
|
|
276
|
+
|
|
277
|
+
if (history.additionalTrackingData && !history.additionalTrackDone) {
|
|
278
|
+
this.trackSwapV2AdditionalRecursive(history.id);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
183
281
|
}
|
|
184
282
|
|
|
283
|
+
// ============================================================================
|
|
284
|
+
// Send tx and record
|
|
285
|
+
// ============================================================================
|
|
286
|
+
|
|
185
287
|
async sendTxAndRecord(
|
|
186
288
|
type: string,
|
|
187
289
|
sourceChainId: string,
|
|
@@ -244,23 +346,10 @@ export class RecentSendHistoryService {
|
|
|
244
346
|
if (shouldLegacyTrack) {
|
|
245
347
|
// no wait
|
|
246
348
|
setTimeout(() => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
...(() => {
|
|
252
|
-
const res: { authorization?: string } = {};
|
|
253
|
-
if (process.env["SKIP_API_KEY"]) {
|
|
254
|
-
res.authorization = process.env["SKIP_API_KEY"];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return res;
|
|
258
|
-
})(),
|
|
259
|
-
},
|
|
260
|
-
body: JSON.stringify({
|
|
261
|
-
tx_hash: Buffer.from(tx.hash).toString("hex"),
|
|
262
|
-
chain_id: sourceChainId,
|
|
263
|
-
}),
|
|
349
|
+
requestSkipTxTrack({
|
|
350
|
+
endpoint: SWAP_API_ENDPOINT,
|
|
351
|
+
chainId: sourceChainId,
|
|
352
|
+
txHash: Buffer.from(tx.hash).toString("hex"),
|
|
264
353
|
})
|
|
265
354
|
.then((result) => {
|
|
266
355
|
console.log(
|
|
@@ -290,12 +379,46 @@ export class RecentSendHistoryService {
|
|
|
290
379
|
txHash
|
|
291
380
|
);
|
|
292
381
|
|
|
293
|
-
this.trackIBCPacketForwardingRecursive(
|
|
382
|
+
this.trackIBCPacketForwardingRecursive((onFulfill, onClose, onError) => {
|
|
383
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
384
|
+
id,
|
|
385
|
+
onFulfill,
|
|
386
|
+
onClose,
|
|
387
|
+
onError
|
|
388
|
+
);
|
|
389
|
+
});
|
|
294
390
|
}
|
|
295
391
|
|
|
296
392
|
return txHash;
|
|
297
393
|
}
|
|
298
394
|
|
|
395
|
+
getRecentSendHistories(chainId: string, type: string): RecentSendHistory[] {
|
|
396
|
+
const key = `${ChainIdHelper.parse(chainId).identifier}/${type}`;
|
|
397
|
+
return (this.recentSendHistoryMap.get(key) ?? []).slice(0, 20);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@action
|
|
401
|
+
addRecentSendHistory(
|
|
402
|
+
chainId: string,
|
|
403
|
+
type: string,
|
|
404
|
+
history: Omit<RecentSendHistory, "timestamp">
|
|
405
|
+
) {
|
|
406
|
+
const key = `${ChainIdHelper.parse(chainId).identifier}/${type}`;
|
|
407
|
+
|
|
408
|
+
let histories = this.recentSendHistoryMap.get(key) ?? [];
|
|
409
|
+
histories.unshift({
|
|
410
|
+
timestamp: Date.now(),
|
|
411
|
+
...history,
|
|
412
|
+
});
|
|
413
|
+
histories = histories.slice(0, 20);
|
|
414
|
+
|
|
415
|
+
this.recentSendHistoryMap.set(key, histories);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// Send tx and record IBC swap/transfer
|
|
420
|
+
// ============================================================================
|
|
421
|
+
|
|
299
422
|
async sendTxAndRecordIBCSwap(
|
|
300
423
|
swapType: "amount-in" | "amount-out",
|
|
301
424
|
sourceChainId: string,
|
|
@@ -343,23 +466,10 @@ export class RecentSendHistoryService {
|
|
|
343
466
|
if (shouldLegacyTrack) {
|
|
344
467
|
setTimeout(() => {
|
|
345
468
|
// no wait
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
...(() => {
|
|
351
|
-
const res: { authorization?: string } = {};
|
|
352
|
-
if (process.env["SKIP_API_KEY"]) {
|
|
353
|
-
res.authorization = process.env["SKIP_API_KEY"];
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return res;
|
|
357
|
-
})(),
|
|
358
|
-
},
|
|
359
|
-
body: JSON.stringify({
|
|
360
|
-
tx_hash: Buffer.from(tx.hash).toString("hex"),
|
|
361
|
-
chain_id: sourceChainId,
|
|
362
|
-
}),
|
|
469
|
+
requestSkipTxTrack({
|
|
470
|
+
endpoint: SWAP_API_ENDPOINT,
|
|
471
|
+
chainId: sourceChainId,
|
|
472
|
+
txHash: Buffer.from(tx.hash).toString("hex"),
|
|
363
473
|
})
|
|
364
474
|
.then((result) => {
|
|
365
475
|
console.log(
|
|
@@ -392,18 +502,184 @@ export class RecentSendHistoryService {
|
|
|
392
502
|
txHash
|
|
393
503
|
);
|
|
394
504
|
|
|
395
|
-
this.trackIBCPacketForwardingRecursive(
|
|
505
|
+
this.trackIBCPacketForwardingRecursive((onFulfill, onClose, onError) => {
|
|
506
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
507
|
+
id,
|
|
508
|
+
onFulfill,
|
|
509
|
+
onClose,
|
|
510
|
+
onError
|
|
511
|
+
);
|
|
512
|
+
});
|
|
396
513
|
}
|
|
397
514
|
|
|
398
515
|
return txHash;
|
|
399
516
|
}
|
|
400
517
|
|
|
401
|
-
|
|
518
|
+
@action
|
|
519
|
+
addRecentIBCTransferHistory(
|
|
520
|
+
chainId: string,
|
|
521
|
+
destinationChainId: string,
|
|
522
|
+
sender: string,
|
|
523
|
+
recipient: string,
|
|
524
|
+
amount: {
|
|
525
|
+
amount: string;
|
|
526
|
+
denom: string;
|
|
527
|
+
}[],
|
|
528
|
+
memo: string,
|
|
529
|
+
ibcChannels:
|
|
530
|
+
| {
|
|
531
|
+
portId: string;
|
|
532
|
+
channelId: string;
|
|
533
|
+
counterpartyChainId: string;
|
|
534
|
+
}[],
|
|
535
|
+
notificationInfo: {
|
|
536
|
+
currencies: AppCurrency[];
|
|
537
|
+
},
|
|
538
|
+
txHash: Uint8Array
|
|
539
|
+
): string {
|
|
540
|
+
const id = (this.recentIBCHistorySeq++).toString();
|
|
541
|
+
|
|
542
|
+
const history: IBCHistory = {
|
|
543
|
+
id,
|
|
544
|
+
chainId,
|
|
545
|
+
destinationChainId,
|
|
546
|
+
timestamp: Date.now(),
|
|
547
|
+
sender,
|
|
548
|
+
recipient,
|
|
549
|
+
amount,
|
|
550
|
+
memo,
|
|
551
|
+
|
|
552
|
+
ibcHistory: ibcChannels.map((channel) => {
|
|
553
|
+
return {
|
|
554
|
+
portId: channel.portId,
|
|
555
|
+
channelId: channel.channelId,
|
|
556
|
+
counterpartyChainId: channel.counterpartyChainId,
|
|
557
|
+
|
|
558
|
+
completed: false,
|
|
559
|
+
};
|
|
560
|
+
}),
|
|
561
|
+
notificationInfo,
|
|
562
|
+
txHash: Buffer.from(txHash).toString("hex"),
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
this.recentIBCHistoryMap.set(id, history);
|
|
566
|
+
|
|
567
|
+
return id;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
@action
|
|
571
|
+
addRecentIBCSwapHistory(
|
|
572
|
+
swapType: "amount-in" | "amount-out",
|
|
573
|
+
chainId: string,
|
|
574
|
+
destinationChainId: string,
|
|
575
|
+
sender: string,
|
|
576
|
+
amount: {
|
|
577
|
+
amount: string;
|
|
578
|
+
denom: string;
|
|
579
|
+
}[],
|
|
580
|
+
memo: string,
|
|
581
|
+
ibcChannels:
|
|
582
|
+
| {
|
|
583
|
+
portId: string;
|
|
584
|
+
channelId: string;
|
|
585
|
+
counterpartyChainId: string;
|
|
586
|
+
}[],
|
|
587
|
+
destinationAsset: {
|
|
588
|
+
chainId: string;
|
|
589
|
+
denom: string;
|
|
590
|
+
},
|
|
591
|
+
swapChannelIndex: number,
|
|
592
|
+
swapReceiver: string[],
|
|
593
|
+
notificationInfo: {
|
|
594
|
+
currencies: AppCurrency[];
|
|
595
|
+
},
|
|
596
|
+
txHash: Uint8Array
|
|
597
|
+
): string {
|
|
598
|
+
const id = (this.recentIBCHistorySeq++).toString();
|
|
599
|
+
|
|
600
|
+
const history: IBCHistory = {
|
|
601
|
+
id,
|
|
602
|
+
swapType,
|
|
603
|
+
chainId,
|
|
604
|
+
destinationChainId,
|
|
605
|
+
timestamp: Date.now(),
|
|
606
|
+
sender,
|
|
607
|
+
amount,
|
|
608
|
+
memo,
|
|
609
|
+
|
|
610
|
+
ibcHistory: ibcChannels.map((channel) => {
|
|
611
|
+
return {
|
|
612
|
+
portId: channel.portId,
|
|
613
|
+
channelId: channel.channelId,
|
|
614
|
+
counterpartyChainId: channel.counterpartyChainId,
|
|
615
|
+
|
|
616
|
+
completed: false,
|
|
617
|
+
};
|
|
618
|
+
}),
|
|
619
|
+
destinationAsset,
|
|
620
|
+
swapChannelIndex,
|
|
621
|
+
swapReceiver,
|
|
622
|
+
resAmount: [],
|
|
623
|
+
notificationInfo,
|
|
624
|
+
txHash: Buffer.from(txHash).toString("hex"),
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
this.recentIBCHistoryMap.set(id, history);
|
|
628
|
+
|
|
629
|
+
return id;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
getRecentIBCHistory(id: string): IBCHistory | undefined {
|
|
633
|
+
return this.recentIBCHistoryMap.get(id);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
getRecentIBCHistories(): IBCHistory[] {
|
|
637
|
+
return Array.from(this.recentIBCHistoryMap.values()).filter((history) => {
|
|
638
|
+
if (!this.chainsService.hasChainInfo(history.chainId)) {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (!this.chainsService.hasChainInfo(history.destinationChainId)) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (
|
|
647
|
+
history.ibcHistory.some((history) => {
|
|
648
|
+
return !this.chainsService.hasChainInfo(history.counterpartyChainId);
|
|
649
|
+
})
|
|
650
|
+
) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return true;
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
@action
|
|
659
|
+
removeRecentIBCHistory(id: string): boolean {
|
|
660
|
+
return this.recentIBCHistoryMap.delete(id);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
@action
|
|
664
|
+
clearAllRecentIBCHistory(): void {
|
|
665
|
+
this.recentIBCHistoryMap.clear();
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ============================================================================
|
|
669
|
+
// Common functions for history tracking (IBC, Skip, Swap V2)
|
|
670
|
+
// ============================================================================
|
|
671
|
+
|
|
672
|
+
trackIBCPacketForwardingRecursive(
|
|
673
|
+
trackHandler: (
|
|
674
|
+
onFulfill: () => void,
|
|
675
|
+
onClose: () => void,
|
|
676
|
+
onError: () => void
|
|
677
|
+
) => void
|
|
678
|
+
): void {
|
|
402
679
|
retry(
|
|
403
680
|
() => {
|
|
404
681
|
return new Promise<void>((resolve, reject) => {
|
|
405
|
-
|
|
406
|
-
id,
|
|
682
|
+
trackHandler(
|
|
407
683
|
() => {
|
|
408
684
|
resolve();
|
|
409
685
|
},
|
|
@@ -449,718 +725,624 @@ export class RecentSendHistoryService {
|
|
|
449
725
|
return;
|
|
450
726
|
}
|
|
451
727
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
728
|
+
if (!history.txFulfilled) {
|
|
729
|
+
this.trackIBCTxFulfillment({
|
|
730
|
+
chainId: history.chainId,
|
|
731
|
+
txHash: history.txHash,
|
|
732
|
+
ibcHistory: history.ibcHistory,
|
|
733
|
+
swapReceiver:
|
|
734
|
+
"swapReceiver" in history ? history.swapReceiver : undefined,
|
|
735
|
+
onTxFulfilled: (_tx, firstHopResAmount) => {
|
|
736
|
+
runInAction(() => {
|
|
737
|
+
history.txFulfilled = true;
|
|
738
|
+
if ("swapReceiver" in history && firstHopResAmount) {
|
|
739
|
+
history.resAmount.push(firstHopResAmount);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
this.trackIBCPacketForwardingRecursive(
|
|
743
|
+
(onFulfill, onClose, onError) => {
|
|
744
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
745
|
+
id,
|
|
746
|
+
onFulfill,
|
|
747
|
+
onClose,
|
|
748
|
+
onError
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
);
|
|
752
|
+
},
|
|
753
|
+
onTxError: () => {
|
|
754
|
+
this.removeRecentIBCHistory(id);
|
|
755
|
+
},
|
|
756
|
+
onFulfill: onFulfill,
|
|
757
|
+
onClose: onClose,
|
|
758
|
+
onError: onError,
|
|
474
759
|
});
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (lastRewoundChannelIndex === 0) {
|
|
478
|
-
return undefined;
|
|
479
|
-
}
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
480
762
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
763
|
+
if (
|
|
764
|
+
this.handleIbcRewindIfNeeded({
|
|
765
|
+
sourceChainId: history.chainId,
|
|
766
|
+
ibcHistory: history.ibcHistory,
|
|
767
|
+
packetTimeout: history.packetTimeout,
|
|
768
|
+
swapContext:
|
|
769
|
+
"swapReceiver" in history && "swapChannelIndex" in history
|
|
770
|
+
? {
|
|
771
|
+
swapReceiver: history.swapReceiver,
|
|
772
|
+
swapChannelIndex: history.swapChannelIndex,
|
|
773
|
+
setSwapRefundInfo: (refundInfo) => {
|
|
774
|
+
runInAction(() => {
|
|
775
|
+
history.swapRefundInfo = refundInfo;
|
|
776
|
+
});
|
|
777
|
+
},
|
|
778
|
+
}
|
|
779
|
+
: undefined,
|
|
780
|
+
onFulfill,
|
|
781
|
+
onClose,
|
|
782
|
+
onError,
|
|
783
|
+
onRewindComplete: () => {
|
|
784
|
+
this.trackIBCPacketForwardingRecursive(
|
|
785
|
+
(onFulfill, onClose, onError) => {
|
|
786
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
787
|
+
id,
|
|
788
|
+
onFulfill,
|
|
789
|
+
onClose,
|
|
790
|
+
onError
|
|
791
|
+
);
|
|
792
|
+
}
|
|
510
793
|
);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
? {
|
|
517
|
-
// "timeout_packet.packet_src_port": targetChannel.portId,
|
|
518
|
-
"timeout_packet.packet_src_channel":
|
|
519
|
-
targetChannel.channelId,
|
|
520
|
-
"timeout_packet.packet_sequence": targetChannel.sequence,
|
|
521
|
-
}
|
|
522
|
-
: {
|
|
523
|
-
// "acknowledge_packet.packet_src_port": targetChannel.portId,
|
|
524
|
-
"acknowledge_packet.packet_src_channel":
|
|
525
|
-
targetChannel.channelId,
|
|
526
|
-
"acknowledge_packet.packet_sequence":
|
|
527
|
-
targetChannel.sequence,
|
|
528
|
-
}
|
|
529
|
-
)
|
|
530
|
-
.then((res: any) => {
|
|
531
|
-
txTracer.close();
|
|
794
|
+
},
|
|
795
|
+
})
|
|
796
|
+
) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
532
799
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
800
|
+
this.trackIbcHopFlowWithTimeout({
|
|
801
|
+
ibcHistory: history.ibcHistory,
|
|
802
|
+
sourceChainId: history.chainId,
|
|
803
|
+
swapReceiver:
|
|
804
|
+
"swapReceiver" in history ? history.swapReceiver : undefined,
|
|
805
|
+
onHopCompleted: (resAmount) => {
|
|
806
|
+
runInAction(() => {
|
|
807
|
+
if (resAmount && "resAmount" in history) {
|
|
808
|
+
history.resAmount.push(resAmount);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
},
|
|
812
|
+
onAllCompleted: () => {
|
|
813
|
+
const notificationInfo = history.notificationInfo;
|
|
814
|
+
if (notificationInfo && !history.notified) {
|
|
815
|
+
runInAction(() => {
|
|
816
|
+
history.notified = true;
|
|
817
|
+
});
|
|
536
818
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
)
|
|
552
|
-
: this.getIBCAcknowledgementPacketIndexFromTx(
|
|
553
|
-
tx,
|
|
554
|
-
targetChannel.portId,
|
|
555
|
-
targetChannel.channelId,
|
|
556
|
-
targetChannel.sequence
|
|
557
|
-
);
|
|
558
|
-
if (index >= 0) {
|
|
559
|
-
// 좀 빡치게 timeout packet은 refund 로직이 실행되고 나서 "timeout_packet" event가 발생한다.
|
|
560
|
-
const refunded = isTimeoutPacket
|
|
561
|
-
? this.getIBCSwapResAmountFromTx(
|
|
562
|
-
tx,
|
|
563
|
-
history.swapReceiver[
|
|
564
|
-
history.swapChannelIndex + 1
|
|
565
|
-
],
|
|
566
|
-
(() => {
|
|
567
|
-
const i =
|
|
568
|
-
this.getLastIBCTimeoutPacketBeforeIndexFromTx(
|
|
569
|
-
tx,
|
|
570
|
-
index
|
|
571
|
-
);
|
|
572
|
-
|
|
573
|
-
if (i < 0) {
|
|
574
|
-
return 0;
|
|
575
|
-
}
|
|
576
|
-
return i;
|
|
577
|
-
})(),
|
|
578
|
-
index
|
|
579
|
-
)
|
|
580
|
-
: this.getIBCSwapResAmountFromTx(
|
|
581
|
-
tx,
|
|
582
|
-
history.swapReceiver[
|
|
583
|
-
history.swapChannelIndex + 1
|
|
584
|
-
],
|
|
585
|
-
index
|
|
586
|
-
);
|
|
587
|
-
history.swapRefundInfo = {
|
|
588
|
-
chainId: prevChainInfo.chainId,
|
|
589
|
-
amount: refunded,
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
targetChannel.rewoundButNextRewindingBlocked = true;
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
819
|
+
const chainInfo = this.chainsService.getChainInfo(
|
|
820
|
+
history.destinationChainId
|
|
821
|
+
);
|
|
822
|
+
if (chainInfo) {
|
|
823
|
+
if ("swapType" in history) {
|
|
824
|
+
if (history.resAmount.length > 0) {
|
|
825
|
+
const amount = history.resAmount[history.resAmount.length - 1];
|
|
826
|
+
const assetsText = amount
|
|
827
|
+
.map((amt) => {
|
|
828
|
+
const currency = notificationInfo.currencies.find(
|
|
829
|
+
(cur) => cur.coinMinimalDenom === amt.denom
|
|
830
|
+
);
|
|
831
|
+
if (!currency) {
|
|
832
|
+
return undefined;
|
|
596
833
|
}
|
|
597
|
-
|
|
834
|
+
return new CoinPretty(currency, amt.amount)
|
|
835
|
+
.hideIBCMetadata(true)
|
|
836
|
+
.shrink(true)
|
|
837
|
+
.maxDecimals(6)
|
|
838
|
+
.inequalitySymbol(true)
|
|
839
|
+
.trim(true)
|
|
840
|
+
.toString();
|
|
841
|
+
})
|
|
842
|
+
.filter((text): text is string => Boolean(text));
|
|
843
|
+
if (assetsText.length > 0) {
|
|
844
|
+
this.notification.create({
|
|
845
|
+
iconRelativeUrl: "assets/logo-256.png",
|
|
846
|
+
title: "IBC Swap Succeeded",
|
|
847
|
+
message: `${assetsText.join(", ")} received on ${
|
|
848
|
+
chainInfo.chainName
|
|
849
|
+
}`,
|
|
850
|
+
});
|
|
598
851
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
852
|
+
}
|
|
853
|
+
} else {
|
|
854
|
+
const assetsText = history.amount
|
|
855
|
+
.map((amt) => {
|
|
856
|
+
const currency = notificationInfo.currencies.find(
|
|
857
|
+
(cur) => cur.coinMinimalDenom === amt.denom
|
|
858
|
+
);
|
|
859
|
+
if (!currency) {
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
return new CoinPretty(currency, amt.amount)
|
|
863
|
+
.hideIBCMetadata(true)
|
|
864
|
+
.shrink(true)
|
|
865
|
+
.maxDecimals(6)
|
|
866
|
+
.inequalitySymbol(true)
|
|
867
|
+
.trim(true)
|
|
868
|
+
.toString();
|
|
869
|
+
})
|
|
870
|
+
.filter((text): text is string => Boolean(text));
|
|
871
|
+
if (assetsText.length > 0) {
|
|
872
|
+
this.notification.create({
|
|
873
|
+
iconRelativeUrl: "assets/logo-256.png",
|
|
874
|
+
title: "IBC Transfer Succeeded",
|
|
875
|
+
message: `${assetsText.join(", ")} sent to ${
|
|
876
|
+
chainInfo.chainName
|
|
877
|
+
}`,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
604
882
|
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
883
|
+
},
|
|
884
|
+
onContinue: () => {
|
|
885
|
+
this.trackIBCPacketForwardingRecursive(
|
|
886
|
+
(onFulfill, onClose, onError) => {
|
|
887
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
888
|
+
id,
|
|
889
|
+
onFulfill,
|
|
890
|
+
onClose,
|
|
891
|
+
onError
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
},
|
|
896
|
+
onRetry: () => {
|
|
897
|
+
this.trackIBCPacketForwardingRecursive(
|
|
898
|
+
(onFulfill, onClose, onError) => {
|
|
899
|
+
this.trackIBCPacketForwardingRecursiveInternal(
|
|
900
|
+
id,
|
|
901
|
+
onFulfill,
|
|
902
|
+
onClose,
|
|
903
|
+
onError
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
);
|
|
907
|
+
},
|
|
908
|
+
onPacketTimeout: () => {
|
|
909
|
+
runInAction(() => {
|
|
910
|
+
history.packetTimeout = true;
|
|
911
|
+
});
|
|
912
|
+
},
|
|
913
|
+
onFulfill,
|
|
914
|
+
onClose,
|
|
915
|
+
onError,
|
|
916
|
+
});
|
|
917
|
+
};
|
|
610
918
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
919
|
+
protected checkAndTrackSwapTxFulfilledRecursive = (params: {
|
|
920
|
+
chainId: string;
|
|
921
|
+
txHash: string;
|
|
922
|
+
onSuccess: () => void;
|
|
923
|
+
onPending: () => void;
|
|
924
|
+
onFailed: () => void;
|
|
925
|
+
onError: () => void;
|
|
926
|
+
}): void => {
|
|
927
|
+
const { chainId, txHash, onSuccess, onPending, onFailed, onError } = params;
|
|
928
|
+
const chainInfo = this.chainsService.getChainInfo(chainId);
|
|
929
|
+
if (!chainInfo) {
|
|
930
|
+
onFailed();
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
615
933
|
|
|
616
|
-
|
|
617
|
-
|
|
934
|
+
this.resolveTxExecutionStatus(chainInfo, chainId, txHash)
|
|
935
|
+
.then((status) => {
|
|
936
|
+
switch (status) {
|
|
937
|
+
case "success":
|
|
938
|
+
onSuccess();
|
|
939
|
+
break;
|
|
940
|
+
case "pending":
|
|
941
|
+
onPending();
|
|
942
|
+
break;
|
|
943
|
+
case "failed":
|
|
944
|
+
onFailed();
|
|
945
|
+
break;
|
|
946
|
+
default:
|
|
947
|
+
onError();
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
})
|
|
951
|
+
.catch(() => {
|
|
952
|
+
onError();
|
|
953
|
+
});
|
|
954
|
+
};
|
|
618
955
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
956
|
+
protected async resolveTxExecutionStatus(
|
|
957
|
+
chainInfo: ChainInfo,
|
|
958
|
+
chainId: string,
|
|
959
|
+
txHash: string
|
|
960
|
+
): Promise<"success" | "failed" | "pending" | "error"> {
|
|
961
|
+
if (this.chainsService.isEvmChain(chainId)) {
|
|
962
|
+
const evmInfo = chainInfo.evm;
|
|
963
|
+
if (!evmInfo) {
|
|
964
|
+
return Promise.resolve("error");
|
|
965
|
+
}
|
|
623
966
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const resAmount = this.getIBCSwapResAmountFromTx(
|
|
630
|
-
tx,
|
|
631
|
-
history.swapReceiver[0]
|
|
632
|
-
);
|
|
967
|
+
const res = await requestEthTxReceipt({
|
|
968
|
+
rpc: evmInfo.rpc,
|
|
969
|
+
txHash,
|
|
970
|
+
origin,
|
|
971
|
+
});
|
|
633
972
|
|
|
634
|
-
|
|
635
|
-
|
|
973
|
+
if (res.data.error) {
|
|
974
|
+
return "error";
|
|
975
|
+
}
|
|
636
976
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
firstChannel.sequence = this.getIBCPacketSequenceFromTx(
|
|
641
|
-
tx,
|
|
642
|
-
firstChannel.portId,
|
|
643
|
-
firstChannel.channelId
|
|
644
|
-
);
|
|
645
|
-
firstChannel.dstChannelId = this.getDstChannelIdFromTx(
|
|
646
|
-
tx,
|
|
647
|
-
firstChannel.portId,
|
|
648
|
-
firstChannel.channelId
|
|
649
|
-
);
|
|
650
|
-
|
|
651
|
-
onFulfill();
|
|
652
|
-
this.trackIBCPacketForwardingRecursive(id);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
});
|
|
977
|
+
const txReceipt = res.data.result;
|
|
978
|
+
if (!txReceipt) {
|
|
979
|
+
return "pending";
|
|
657
980
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
981
|
+
if (txReceipt.status === EthTxStatus.Success) {
|
|
982
|
+
return "success";
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
return "failed";
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const txTracer = new TendermintTxTracer(chainInfo.rpc, "/websocket");
|
|
989
|
+
txTracer.addEventListener("error", () => {
|
|
990
|
+
txTracer.close();
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
return txTracer
|
|
994
|
+
.traceTx(Buffer.from(txHash.replace("0x", ""), "hex"))
|
|
995
|
+
.then((res: any) => {
|
|
996
|
+
txTracer.close();
|
|
997
|
+
|
|
998
|
+
const txResult = Array.isArray(res.txs)
|
|
999
|
+
? res.txs && res.txs.length > 0
|
|
1000
|
+
? res.txs[0].tx_result
|
|
1001
|
+
: undefined
|
|
1002
|
+
: res;
|
|
1003
|
+
|
|
1004
|
+
if (!txResult) {
|
|
1005
|
+
return "pending";
|
|
1006
|
+
}
|
|
1007
|
+
if (typeof txResult.code !== "number") {
|
|
1008
|
+
return "error";
|
|
1009
|
+
}
|
|
1010
|
+
return txResult.code === 0 ? "success" : "failed";
|
|
1011
|
+
})
|
|
1012
|
+
.catch(() => {
|
|
1013
|
+
txTracer.close();
|
|
1014
|
+
return "error";
|
|
661
1015
|
});
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
onClose();
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
protected trackDestinationAssetAmount(params: {
|
|
1019
|
+
chainId: string;
|
|
1020
|
+
txHash: string;
|
|
1021
|
+
recipient: string;
|
|
1022
|
+
targetDenom: string;
|
|
1023
|
+
onResult: (resAmount: { amount: string; denom: string }[]) => void;
|
|
1024
|
+
onRefund?: (
|
|
1025
|
+
refundInfo: {
|
|
1026
|
+
chainId: string;
|
|
1027
|
+
amount: { amount: string; denom: string }[];
|
|
1028
|
+
},
|
|
1029
|
+
error?: string
|
|
1030
|
+
) => void;
|
|
1031
|
+
onFulfill: () => void;
|
|
1032
|
+
}) {
|
|
1033
|
+
const {
|
|
1034
|
+
chainId,
|
|
1035
|
+
txHash,
|
|
1036
|
+
recipient,
|
|
1037
|
+
targetDenom,
|
|
1038
|
+
onResult,
|
|
1039
|
+
onRefund,
|
|
1040
|
+
onFulfill,
|
|
1041
|
+
} = params;
|
|
1042
|
+
|
|
1043
|
+
const chainInfo = this.chainsService.getChainInfo(chainId);
|
|
1044
|
+
if (!chainInfo) {
|
|
1045
|
+
onFulfill();
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (this.chainsService.isEvmChain(chainId)) {
|
|
1050
|
+
this.traceEVMTransactionResult({
|
|
1051
|
+
chainId,
|
|
1052
|
+
txHash,
|
|
1053
|
+
recipient,
|
|
1054
|
+
targetDenom,
|
|
1055
|
+
onResult: (result) => {
|
|
1056
|
+
if (result.resAmount) {
|
|
1057
|
+
onResult(result.resAmount);
|
|
705
1058
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const onErrorOnce = () => {
|
|
709
|
-
if (!_onErrorOnce) {
|
|
710
|
-
_onErrorOnce = true;
|
|
711
|
-
closables.forEach((closable) => {
|
|
712
|
-
if (
|
|
713
|
-
closable.readyState === WsReadyState.OPEN ||
|
|
714
|
-
closable.readyState === WsReadyState.CONNECTING
|
|
715
|
-
) {
|
|
716
|
-
closable.close();
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
onError();
|
|
1059
|
+
if (result.refundInfo && onRefund) {
|
|
1060
|
+
onRefund(result.refundInfo, result.error);
|
|
720
1061
|
}
|
|
721
|
-
}
|
|
1062
|
+
},
|
|
1063
|
+
onFulfill,
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
722
1067
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
};
|
|
1068
|
+
this.traceCosmosTransactionResult({
|
|
1069
|
+
chainInfo,
|
|
1070
|
+
txHash,
|
|
1071
|
+
recipient,
|
|
1072
|
+
onResult,
|
|
1073
|
+
onFulfill,
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
732
1076
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1077
|
+
protected traceCosmosTransactionResult(params: {
|
|
1078
|
+
chainInfo: ChainInfo;
|
|
1079
|
+
txHash: string;
|
|
1080
|
+
recipient: string;
|
|
1081
|
+
onResult: (resAmount: { amount: string; denom: string }[]) => void;
|
|
1082
|
+
onFulfill: () => void;
|
|
1083
|
+
}) {
|
|
1084
|
+
const { chainInfo, txHash, recipient, onResult, onFulfill } = params;
|
|
1085
|
+
const txTracer = new TendermintTxTracer(chainInfo.rpc, "/websocket");
|
|
1086
|
+
txTracer.addEventListener("error", () => onFulfill());
|
|
1087
|
+
txTracer
|
|
1088
|
+
.queryTx({
|
|
1089
|
+
"tx.hash": txHash,
|
|
1090
|
+
})
|
|
1091
|
+
.then((res: any) => {
|
|
1092
|
+
txTracer.close();
|
|
739
1093
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1094
|
+
if (!res) {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
const txs = res.txs
|
|
1098
|
+
? res.txs.map((r: any) => r.tx_result || r)
|
|
1099
|
+
: [res.tx_result || res];
|
|
1100
|
+
for (const tx of txs) {
|
|
1101
|
+
const resAmount = this.getIBCSwapResAmountFromTx(tx, recipient);
|
|
1102
|
+
onResult(resAmount);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
})
|
|
1106
|
+
.finally(() => {
|
|
1107
|
+
onFulfill();
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
743
1110
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1111
|
+
// CHECK: move tracing logic (requestEthTxReceipt, requestEthTxTrace, parseEVMTxReceiptLogs) to tx-ethereum service
|
|
1112
|
+
protected traceEVMTransactionResult(params: {
|
|
1113
|
+
chainId: string;
|
|
1114
|
+
txHash: string;
|
|
1115
|
+
recipient: string;
|
|
1116
|
+
targetDenom: string;
|
|
1117
|
+
onResult: (result: {
|
|
1118
|
+
success: boolean;
|
|
1119
|
+
resAmount?: { amount: string; denom: string }[];
|
|
1120
|
+
refundInfo?: {
|
|
1121
|
+
chainId: string;
|
|
1122
|
+
amount: { amount: string; denom: string }[];
|
|
1123
|
+
};
|
|
1124
|
+
error?: string;
|
|
1125
|
+
}) => void;
|
|
1126
|
+
onFulfill: () => void;
|
|
1127
|
+
}): void {
|
|
1128
|
+
const { chainId, txHash, recipient, targetDenom, onResult, onFulfill } =
|
|
1129
|
+
params;
|
|
1130
|
+
|
|
1131
|
+
const chainInfo = this.chainsService.getChainInfo(chainId);
|
|
1132
|
+
if (!chainInfo) {
|
|
1133
|
+
onResult({ success: false });
|
|
1134
|
+
onFulfill();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
759
1137
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
// XXX: {key: 'packet_ack', value: '{"error":"ABCI code: 6: error handling packet: see events for details"}'}
|
|
766
|
-
// 오류가 있을 경우 이딴식으로 오류가 나오기 때문에 뭐 유저에게 보여줄 방법이 없다...
|
|
767
|
-
targetChannel.error = "Packet processing failed";
|
|
768
|
-
onFulfillOnce();
|
|
769
|
-
this.trackIBCPacketForwardingRecursive(id);
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
} catch (e) {
|
|
773
|
-
// decode가 실패한 경우 사실 방법이 없다.
|
|
774
|
-
// 일단 packet이 성공했다고 치고 진행한다.
|
|
775
|
-
console.log(e);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
1138
|
+
if (!this.chainsService.isEvmChain(chainId)) {
|
|
1139
|
+
onResult({ success: false, error: "Not an EVM chain" });
|
|
1140
|
+
onFulfill();
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
778
1143
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
targetChannel.sequence!
|
|
786
|
-
);
|
|
1144
|
+
const evmInfo = chainInfo.evm;
|
|
1145
|
+
if (!evmInfo) {
|
|
1146
|
+
onResult({ success: false });
|
|
1147
|
+
onFulfill();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
787
1150
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1151
|
+
requestEthTxReceipt({
|
|
1152
|
+
rpc: evmInfo.rpc,
|
|
1153
|
+
txHash,
|
|
1154
|
+
origin,
|
|
1155
|
+
})
|
|
1156
|
+
.then((res) => {
|
|
1157
|
+
const txReceipt = res.data.result;
|
|
1158
|
+
if (!txReceipt) {
|
|
1159
|
+
onResult({ success: false });
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
798
1162
|
|
|
799
|
-
|
|
800
|
-
|
|
1163
|
+
requestEthTxTrace({
|
|
1164
|
+
rpc: evmInfo.rpc,
|
|
1165
|
+
txHash,
|
|
1166
|
+
origin,
|
|
1167
|
+
}).then((traceRes) => {
|
|
1168
|
+
let isFoundFromCall = false;
|
|
1169
|
+
const foundResAmount: { amount: string; denom: string }[] = [];
|
|
1170
|
+
|
|
1171
|
+
if (traceRes.data.result) {
|
|
1172
|
+
const searchForTransfers = (calls: any) => {
|
|
1173
|
+
for (const call of calls) {
|
|
1174
|
+
if (
|
|
1175
|
+
call.type === "CALL" &&
|
|
1176
|
+
call.to?.toLowerCase() === recipient.toLowerCase()
|
|
1177
|
+
) {
|
|
1178
|
+
const isERC20Transfer = call.input?.startsWith("0xa9059cbb");
|
|
1179
|
+
const value = BigInt(
|
|
1180
|
+
isERC20Transfer
|
|
1181
|
+
? `0x${call.input.substring(74)}`
|
|
1182
|
+
: call.value || "0x0"
|
|
1183
|
+
);
|
|
801
1184
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
index
|
|
808
|
-
);
|
|
809
|
-
nextChannel.dstChannelId = this.getDstChannelIdFromTx(
|
|
810
|
-
tx,
|
|
811
|
-
nextChannel.portId,
|
|
812
|
-
nextChannel.channelId,
|
|
813
|
-
index
|
|
814
|
-
);
|
|
815
|
-
onFulfillOnce();
|
|
816
|
-
this.trackIBCPacketForwardingRecursive(id);
|
|
817
|
-
break;
|
|
818
|
-
} else {
|
|
819
|
-
// Packet received to destination chain.
|
|
820
|
-
if (history.notificationInfo && !history.notified) {
|
|
821
|
-
runInAction(() => {
|
|
822
|
-
history.notified = true;
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
const chainInfo = this.chainsService.getChainInfo(
|
|
826
|
-
history.destinationChainId
|
|
827
|
-
);
|
|
828
|
-
if (chainInfo) {
|
|
829
|
-
if ("swapType" in history) {
|
|
830
|
-
if (history.resAmount.length > 0) {
|
|
831
|
-
const amount =
|
|
832
|
-
history.resAmount[
|
|
833
|
-
history.resAmount.length - 1
|
|
834
|
-
];
|
|
835
|
-
const assetsText = amount
|
|
836
|
-
.filter((amt) =>
|
|
837
|
-
history.notificationInfo!.currencies.find(
|
|
838
|
-
(cur) =>
|
|
839
|
-
cur.coinMinimalDenom === amt.denom
|
|
840
|
-
)
|
|
841
|
-
)
|
|
842
|
-
.map((amt) => {
|
|
843
|
-
const currency =
|
|
844
|
-
history.notificationInfo!.currencies.find(
|
|
845
|
-
(cur) =>
|
|
846
|
-
cur.coinMinimalDenom === amt.denom
|
|
847
|
-
);
|
|
848
|
-
return new CoinPretty(currency!, amt.amount)
|
|
849
|
-
.hideIBCMetadata(true)
|
|
850
|
-
.shrink(true)
|
|
851
|
-
.maxDecimals(6)
|
|
852
|
-
.inequalitySymbol(true)
|
|
853
|
-
.trim(true)
|
|
854
|
-
.toString();
|
|
855
|
-
});
|
|
856
|
-
if (assetsText.length > 0) {
|
|
857
|
-
// Notify user
|
|
858
|
-
this.notification.create({
|
|
859
|
-
iconRelativeUrl: "assets/logo-256.png",
|
|
860
|
-
title: "IBC Swap Succeeded",
|
|
861
|
-
message: `${assetsText.join(
|
|
862
|
-
", "
|
|
863
|
-
)} received on ${chainInfo.chainName}`,
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
} else {
|
|
868
|
-
const assetsText = history.amount
|
|
869
|
-
.filter((amt) =>
|
|
870
|
-
history.notificationInfo!.currencies.find(
|
|
871
|
-
(cur) => cur.coinMinimalDenom === amt.denom
|
|
872
|
-
)
|
|
873
|
-
)
|
|
874
|
-
.map((amt) => {
|
|
875
|
-
const currency =
|
|
876
|
-
history.notificationInfo!.currencies.find(
|
|
877
|
-
(cur) =>
|
|
878
|
-
cur.coinMinimalDenom === amt.denom
|
|
879
|
-
);
|
|
880
|
-
return new CoinPretty(currency!, amt.amount)
|
|
881
|
-
.hideIBCMetadata(true)
|
|
882
|
-
.shrink(true)
|
|
883
|
-
.maxDecimals(6)
|
|
884
|
-
.inequalitySymbol(true)
|
|
885
|
-
.trim(true)
|
|
886
|
-
.toString();
|
|
887
|
-
});
|
|
888
|
-
if (assetsText.length > 0) {
|
|
889
|
-
// Notify user
|
|
890
|
-
this.notification.create({
|
|
891
|
-
iconRelativeUrl: "assets/logo-256.png",
|
|
892
|
-
title: "IBC Transfer Succeeded",
|
|
893
|
-
message: `${assetsText.join(", ")} sent to ${
|
|
894
|
-
chainInfo.chainName
|
|
895
|
-
}`,
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
onFulfillOnce();
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
} catch {
|
|
906
|
-
// noop
|
|
907
|
-
}
|
|
1185
|
+
foundResAmount.push({
|
|
1186
|
+
amount: value.toString(10),
|
|
1187
|
+
denom: targetDenom,
|
|
1188
|
+
});
|
|
1189
|
+
isFoundFromCall = true;
|
|
908
1190
|
}
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
1191
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
} else {
|
|
919
|
-
prevChainId = history.chainId;
|
|
920
|
-
}
|
|
921
|
-
if (prevChainId) {
|
|
922
|
-
const prevChainInfo = this.chainsService.getChainInfo(prevChainId);
|
|
923
|
-
if (prevChainInfo) {
|
|
924
|
-
const queryEvents: any = {
|
|
925
|
-
// acknowledge_packet과는 다르게 timeout_packet은 이전의 체인의 이벤트로부터만 알 수 있다.
|
|
926
|
-
// 방법이 없기 때문에 여기서 이전의 체인으로부터 subscribe를 해서 이벤트를 받아야 한다.
|
|
927
|
-
// 하지만 이 경우 ibc error tracking 로직에서 이것과 똑같은 subscription을 한번 더 하게 된다.
|
|
928
|
-
// 이미 로직이 많이 복잡하기 때문에 로직을 덜 복잡하게 하기 위해서 이러한 비효율성(?)을 감수한다.
|
|
929
|
-
// "timeout_packet.packet_src_port": targetChannel.portId,
|
|
930
|
-
"timeout_packet.packet_src_channel": targetChannel.channelId,
|
|
931
|
-
"timeout_packet.packet_sequence": targetChannel.sequence,
|
|
1192
|
+
if (call.calls && call.calls.length > 0) {
|
|
1193
|
+
searchForTransfers(call.calls);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
932
1196
|
};
|
|
933
1197
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
"/websocket"
|
|
937
|
-
);
|
|
938
|
-
closables.push(txTracer);
|
|
939
|
-
txTracer.addEventListener("close", onCloseOnce);
|
|
940
|
-
txTracer.addEventListener("error", onErrorOnce);
|
|
941
|
-
txTracer.traceTx(queryEvents).then((res) => {
|
|
942
|
-
txTracer.close();
|
|
943
|
-
|
|
944
|
-
if (!res) {
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
1198
|
+
searchForTransfers(traceRes.data.result.calls || []);
|
|
1199
|
+
}
|
|
947
1200
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1201
|
+
if (isFoundFromCall) {
|
|
1202
|
+
onResult({ success: true, resAmount: foundResAmount });
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// fallback to logs if debug_traceTransaction fails
|
|
1207
|
+
this.parseEVMTxReceiptLogs({
|
|
1208
|
+
txReceipt,
|
|
1209
|
+
recipient,
|
|
1210
|
+
targetChainId: chainId,
|
|
1211
|
+
targetDenom,
|
|
1212
|
+
onResult,
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
})
|
|
1216
|
+
.finally(() => {
|
|
1217
|
+
onFulfill();
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
protected parseEVMTxReceiptLogs(params: {
|
|
1222
|
+
txReceipt: EthTxReceipt;
|
|
1223
|
+
recipient: string;
|
|
1224
|
+
targetChainId: string;
|
|
1225
|
+
targetDenom: string;
|
|
1226
|
+
onResult: (result: {
|
|
1227
|
+
success: boolean;
|
|
1228
|
+
resAmount?: { amount: string; denom: string }[];
|
|
1229
|
+
refundInfo?: {
|
|
1230
|
+
chainId: string;
|
|
1231
|
+
amount: { amount: string; denom: string }[];
|
|
1232
|
+
};
|
|
1233
|
+
error?: string;
|
|
1234
|
+
}) => void;
|
|
1235
|
+
}): void {
|
|
1236
|
+
const { txReceipt, recipient, targetChainId, targetDenom, onResult } =
|
|
1237
|
+
params;
|
|
1238
|
+
|
|
1239
|
+
const logs = txReceipt.logs;
|
|
1240
|
+
const transferTopic = id("Transfer(address,address,uint256)");
|
|
1241
|
+
const withdrawTopic = id("Withdrawal(address,uint256)");
|
|
1242
|
+
const hyperlaneReceiveTopic = id(
|
|
1243
|
+
"ReceivedTransferRemote(uint32,bytes32,uint256)"
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
for (const log of logs) {
|
|
1247
|
+
if (log.topics[0] === transferTopic) {
|
|
1248
|
+
const to = "0x" + log.topics[2].slice(26);
|
|
1249
|
+
if (to.toLowerCase() === recipient.toLowerCase()) {
|
|
1250
|
+
const expectedAssetDenom = targetDenom.replace("erc20:", "");
|
|
1251
|
+
const amount = BigInt(log.data).toString(10);
|
|
1252
|
+
|
|
1253
|
+
if (log.address.toLowerCase() === expectedAssetDenom.toLowerCase()) {
|
|
1254
|
+
onResult({
|
|
1255
|
+
success: true,
|
|
1256
|
+
resAmount: [{ amount, denom: targetDenom }],
|
|
1257
|
+
});
|
|
1258
|
+
} else {
|
|
1259
|
+
console.log("refunded", log.address);
|
|
1260
|
+
// Transfer 토픽인 경우엔 ERC20의 tranfer 호출일텐데
|
|
1261
|
+
// 받을 토큰의 컨트랙트가 아닌 다른 컨트랙트에서 호출된 경우는 Swap을 실패한 것으로 추측
|
|
1262
|
+
// 고로 실제로 받은 토큰의 컨트랙트 주소로 환불 정보에 저장한다.
|
|
1263
|
+
onResult({
|
|
1264
|
+
success: false,
|
|
1265
|
+
error: "Swap failed",
|
|
1266
|
+
refundInfo: {
|
|
1267
|
+
chainId: targetChainId,
|
|
1268
|
+
amount: [
|
|
1269
|
+
{
|
|
1270
|
+
amount,
|
|
1271
|
+
denom: `erc20:${log.address.toLowerCase()}`,
|
|
1272
|
+
},
|
|
1273
|
+
],
|
|
1274
|
+
},
|
|
957
1275
|
});
|
|
958
1276
|
}
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
} else if (log.topics[0] === withdrawTopic) {
|
|
1280
|
+
const to = "0x" + log.topics[1].slice(26);
|
|
1281
|
+
if (to.toLowerCase() === txReceipt.to?.toLowerCase()) {
|
|
1282
|
+
const amount = BigInt(log.data).toString(10);
|
|
1283
|
+
onResult({
|
|
1284
|
+
success: true,
|
|
1285
|
+
resAmount: [{ amount, denom: targetDenom }],
|
|
1286
|
+
});
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
} else if (log.topics[0] === hyperlaneReceiveTopic) {
|
|
1290
|
+
const to = "0x" + log.topics[2].slice(26);
|
|
1291
|
+
if (to.toLowerCase() === recipient.toLowerCase()) {
|
|
1292
|
+
const amount = BigInt(log.data).toString(10);
|
|
1293
|
+
// Hyperlane을 통해 Forma로 TIA를 받는 경우 토큰 수량이 decimal 6으로 기록되는데,
|
|
1294
|
+
// Forma에서는 decimal 18이기 때문에 12자리 만큼 0을 붙여준다.
|
|
1295
|
+
onResult({
|
|
1296
|
+
success: true,
|
|
1297
|
+
resAmount: [
|
|
1298
|
+
{
|
|
1299
|
+
amount:
|
|
1300
|
+
targetDenom === "forma-native"
|
|
1301
|
+
? `${amount}000000000000`
|
|
1302
|
+
: amount,
|
|
1303
|
+
denom: targetDenom,
|
|
1304
|
+
},
|
|
1305
|
+
],
|
|
1306
|
+
});
|
|
1307
|
+
return;
|
|
959
1308
|
}
|
|
960
1309
|
}
|
|
961
1310
|
}
|
|
962
|
-
};
|
|
963
1311
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return (this.recentSendHistoryMap.get(key) ?? []).slice(0, 20);
|
|
1312
|
+
// 결과를 찾지 못한 경우
|
|
1313
|
+
onResult({ success: false });
|
|
967
1314
|
}
|
|
968
1315
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
type: string,
|
|
973
|
-
history: Omit<RecentSendHistory, "timestamp">
|
|
974
|
-
) {
|
|
975
|
-
const key = `${ChainIdHelper.parse(chainId).identifier}/${type}`;
|
|
976
|
-
|
|
977
|
-
let histories = this.recentSendHistoryMap.get(key) ?? [];
|
|
978
|
-
histories.unshift({
|
|
979
|
-
timestamp: Date.now(),
|
|
980
|
-
...history,
|
|
981
|
-
});
|
|
982
|
-
histories = histories.slice(0, 20);
|
|
983
|
-
|
|
984
|
-
this.recentSendHistoryMap.set(key, histories);
|
|
985
|
-
}
|
|
1316
|
+
// ============================================================================
|
|
1317
|
+
// Skip swap history
|
|
1318
|
+
// ============================================================================
|
|
986
1319
|
|
|
987
1320
|
@action
|
|
988
|
-
|
|
989
|
-
|
|
1321
|
+
recordTxWithSkipSwap(
|
|
1322
|
+
sourceChainId: string,
|
|
990
1323
|
destinationChainId: string,
|
|
1324
|
+
destinationAsset: {
|
|
1325
|
+
chainId: string;
|
|
1326
|
+
denom: string;
|
|
1327
|
+
expectedAmount: string;
|
|
1328
|
+
},
|
|
1329
|
+
simpleRoute: {
|
|
1330
|
+
isOnlyEvm: boolean;
|
|
1331
|
+
chainId: string;
|
|
1332
|
+
receiver: string;
|
|
1333
|
+
}[],
|
|
991
1334
|
sender: string,
|
|
992
1335
|
recipient: string,
|
|
993
1336
|
amount: {
|
|
994
1337
|
amount: string;
|
|
995
1338
|
denom: string;
|
|
996
1339
|
}[],
|
|
997
|
-
memo: string,
|
|
998
|
-
ibcChannels:
|
|
999
|
-
| {
|
|
1000
|
-
portId: string;
|
|
1001
|
-
channelId: string;
|
|
1002
|
-
counterpartyChainId: string;
|
|
1003
|
-
}[],
|
|
1004
1340
|
notificationInfo: {
|
|
1005
1341
|
currencies: AppCurrency[];
|
|
1006
1342
|
},
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
const history: IBCHistory = {
|
|
1012
|
-
id,
|
|
1013
|
-
chainId,
|
|
1014
|
-
destinationChainId,
|
|
1015
|
-
timestamp: Date.now(),
|
|
1016
|
-
sender,
|
|
1017
|
-
recipient,
|
|
1018
|
-
amount,
|
|
1019
|
-
memo,
|
|
1020
|
-
|
|
1021
|
-
ibcHistory: ibcChannels.map((channel) => {
|
|
1022
|
-
return {
|
|
1023
|
-
portId: channel.portId,
|
|
1024
|
-
channelId: channel.channelId,
|
|
1025
|
-
counterpartyChainId: channel.counterpartyChainId,
|
|
1026
|
-
|
|
1027
|
-
completed: false,
|
|
1028
|
-
};
|
|
1029
|
-
}),
|
|
1030
|
-
notificationInfo,
|
|
1031
|
-
txHash: Buffer.from(txHash).toString("hex"),
|
|
1032
|
-
};
|
|
1033
|
-
|
|
1034
|
-
this.recentIBCHistoryMap.set(id, history);
|
|
1035
|
-
|
|
1036
|
-
return id;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
@action
|
|
1040
|
-
addRecentIBCSwapHistory(
|
|
1041
|
-
swapType: "amount-in" | "amount-out",
|
|
1042
|
-
chainId: string,
|
|
1043
|
-
destinationChainId: string,
|
|
1044
|
-
sender: string,
|
|
1045
|
-
amount: {
|
|
1046
|
-
amount: string;
|
|
1047
|
-
denom: string;
|
|
1048
|
-
}[],
|
|
1049
|
-
memo: string,
|
|
1050
|
-
ibcChannels:
|
|
1051
|
-
| {
|
|
1052
|
-
portId: string;
|
|
1053
|
-
channelId: string;
|
|
1054
|
-
counterpartyChainId: string;
|
|
1055
|
-
}[],
|
|
1056
|
-
destinationAsset: {
|
|
1057
|
-
chainId: string;
|
|
1058
|
-
denom: string;
|
|
1059
|
-
},
|
|
1060
|
-
swapChannelIndex: number,
|
|
1061
|
-
swapReceiver: string[],
|
|
1062
|
-
notificationInfo: {
|
|
1063
|
-
currencies: AppCurrency[];
|
|
1064
|
-
},
|
|
1065
|
-
txHash: Uint8Array
|
|
1066
|
-
): string {
|
|
1067
|
-
const id = (this.recentIBCHistorySeq++).toString();
|
|
1068
|
-
|
|
1069
|
-
const history: IBCHistory = {
|
|
1070
|
-
id,
|
|
1071
|
-
swapType,
|
|
1072
|
-
chainId,
|
|
1073
|
-
destinationChainId,
|
|
1074
|
-
timestamp: Date.now(),
|
|
1075
|
-
sender,
|
|
1076
|
-
amount,
|
|
1077
|
-
memo,
|
|
1078
|
-
|
|
1079
|
-
ibcHistory: ibcChannels.map((channel) => {
|
|
1080
|
-
return {
|
|
1081
|
-
portId: channel.portId,
|
|
1082
|
-
channelId: channel.channelId,
|
|
1083
|
-
counterpartyChainId: channel.counterpartyChainId,
|
|
1084
|
-
|
|
1085
|
-
completed: false,
|
|
1086
|
-
};
|
|
1087
|
-
}),
|
|
1088
|
-
destinationAsset,
|
|
1089
|
-
swapChannelIndex,
|
|
1090
|
-
swapReceiver,
|
|
1091
|
-
resAmount: [],
|
|
1092
|
-
notificationInfo,
|
|
1093
|
-
txHash: Buffer.from(txHash).toString("hex"),
|
|
1094
|
-
};
|
|
1095
|
-
|
|
1096
|
-
this.recentIBCHistoryMap.set(id, history);
|
|
1097
|
-
|
|
1098
|
-
return id;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
getRecentIBCHistory(id: string): IBCHistory | undefined {
|
|
1102
|
-
return this.recentIBCHistoryMap.get(id);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
getRecentIBCHistories(): IBCHistory[] {
|
|
1106
|
-
return Array.from(this.recentIBCHistoryMap.values()).filter((history) => {
|
|
1107
|
-
if (!this.chainsService.hasChainInfo(history.chainId)) {
|
|
1108
|
-
return false;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
if (!this.chainsService.hasChainInfo(history.destinationChainId)) {
|
|
1112
|
-
return false;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
if (
|
|
1116
|
-
history.ibcHistory.some((history) => {
|
|
1117
|
-
return !this.chainsService.hasChainInfo(history.counterpartyChainId);
|
|
1118
|
-
})
|
|
1119
|
-
) {
|
|
1120
|
-
return false;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
return true;
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
@action
|
|
1128
|
-
removeRecentIBCHistory(id: string): boolean {
|
|
1129
|
-
return this.recentIBCHistoryMap.delete(id);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
@action
|
|
1133
|
-
clearAllRecentIBCHistory(): void {
|
|
1134
|
-
this.recentIBCHistoryMap.clear();
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// skip related methods
|
|
1138
|
-
@action
|
|
1139
|
-
recordTxWithSkipSwap(
|
|
1140
|
-
sourceChainId: string,
|
|
1141
|
-
destinationChainId: string,
|
|
1142
|
-
destinationAsset: {
|
|
1143
|
-
chainId: string;
|
|
1144
|
-
denom: string;
|
|
1145
|
-
expectedAmount: string;
|
|
1146
|
-
},
|
|
1147
|
-
simpleRoute: {
|
|
1148
|
-
isOnlyEvm: boolean;
|
|
1149
|
-
chainId: string;
|
|
1150
|
-
receiver: string;
|
|
1151
|
-
}[],
|
|
1152
|
-
sender: string,
|
|
1153
|
-
recipient: string,
|
|
1154
|
-
amount: {
|
|
1155
|
-
amount: string;
|
|
1156
|
-
denom: string;
|
|
1157
|
-
}[],
|
|
1158
|
-
notificationInfo: {
|
|
1159
|
-
currencies: AppCurrency[];
|
|
1160
|
-
},
|
|
1161
|
-
routeDurationSeconds: number = 0,
|
|
1162
|
-
txHash: string,
|
|
1163
|
-
isOnlyUseBridge?: boolean
|
|
1343
|
+
routeDurationSeconds: number = 0,
|
|
1344
|
+
txHash: string,
|
|
1345
|
+
isOnlyUseBridge?: boolean
|
|
1164
1346
|
): string {
|
|
1165
1347
|
const id = (this.recentIBCHistorySeq++).toString();
|
|
1166
1348
|
|
|
@@ -1224,34 +1406,49 @@ export class RecentSendHistoryService {
|
|
|
1224
1406
|
retry(
|
|
1225
1407
|
() => {
|
|
1226
1408
|
return new Promise<void>((txFulfilledResolve, txFulfilledReject) => {
|
|
1227
|
-
this.
|
|
1228
|
-
history,
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1409
|
+
this.checkAndTrackSwapTxFulfilledRecursive({
|
|
1410
|
+
chainId: history.chainId,
|
|
1411
|
+
txHash: history.txHash,
|
|
1412
|
+
onSuccess: () => {
|
|
1413
|
+
this.requestSkipTxTrackInternal({
|
|
1414
|
+
chainId: history.chainId,
|
|
1415
|
+
txHash: history.txHash,
|
|
1416
|
+
onRemoveHistory: () => this.removeRecentSkipHistory(id),
|
|
1417
|
+
onFulfill: (keepTracking: boolean) => {
|
|
1418
|
+
txFulfilledResolve();
|
|
1419
|
+
|
|
1420
|
+
if (!keepTracking) {
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1235
1423
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1424
|
+
retry(
|
|
1425
|
+
() => {
|
|
1426
|
+
return new Promise<void>((resolve, reject) => {
|
|
1427
|
+
this.checkAndUpdateSkipSwapHistoryRecursive(
|
|
1428
|
+
id,
|
|
1429
|
+
resolve,
|
|
1430
|
+
reject
|
|
1431
|
+
);
|
|
1432
|
+
});
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
maxRetries: 50,
|
|
1436
|
+
waitMsAfterError: 500,
|
|
1437
|
+
maxWaitMsAfterError: 15000,
|
|
1438
|
+
}
|
|
1439
|
+
);
|
|
1245
1440
|
},
|
|
1246
|
-
|
|
1247
|
-
maxRetries: 50,
|
|
1248
|
-
waitMsAfterError: 500,
|
|
1249
|
-
maxWaitMsAfterError: 15000,
|
|
1250
|
-
}
|
|
1251
|
-
);
|
|
1441
|
+
});
|
|
1252
1442
|
},
|
|
1253
|
-
txFulfilledReject
|
|
1254
|
-
|
|
1443
|
+
onPending: txFulfilledReject,
|
|
1444
|
+
onFailed: () => {
|
|
1445
|
+
this.removeRecentSkipHistory(id);
|
|
1446
|
+
txFulfilledResolve();
|
|
1447
|
+
},
|
|
1448
|
+
onError: () => {
|
|
1449
|
+
txFulfilledResolve();
|
|
1450
|
+
},
|
|
1451
|
+
});
|
|
1255
1452
|
});
|
|
1256
1453
|
},
|
|
1257
1454
|
{
|
|
@@ -1262,161 +1459,34 @@ export class RecentSendHistoryService {
|
|
|
1262
1459
|
);
|
|
1263
1460
|
}
|
|
1264
1461
|
|
|
1265
|
-
protected
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
simpleFetch<{
|
|
1284
|
-
result: EthTxReceipt | null;
|
|
1285
|
-
error?: Error;
|
|
1286
|
-
}>(evmInfo.rpc, {
|
|
1287
|
-
method: "POST",
|
|
1288
|
-
headers: {
|
|
1289
|
-
"content-type": "application/json",
|
|
1290
|
-
"request-source": origin,
|
|
1291
|
-
},
|
|
1292
|
-
body: JSON.stringify({
|
|
1293
|
-
jsonrpc: "2.0",
|
|
1294
|
-
method: "eth_getTransactionReceipt",
|
|
1295
|
-
params: [history.txHash],
|
|
1296
|
-
id: 1,
|
|
1297
|
-
}),
|
|
1462
|
+
protected requestSkipTxTrackInternal(params: {
|
|
1463
|
+
chainId: string;
|
|
1464
|
+
txHash: string;
|
|
1465
|
+
onFulfill: (keepTracking: boolean) => void;
|
|
1466
|
+
onRemoveHistory: () => void;
|
|
1467
|
+
}) {
|
|
1468
|
+
const { chainId, txHash, onFulfill, onRemoveHistory } = params;
|
|
1469
|
+
const chainIdForApi = this.chainsService.isEvmChain(chainId)
|
|
1470
|
+
? chainId.replace("eip155:", "")
|
|
1471
|
+
: chainId;
|
|
1472
|
+
|
|
1473
|
+
setTimeout(() => {
|
|
1474
|
+
requestSkipTxTrack({
|
|
1475
|
+
endpoint: SWAP_API_ENDPOINT,
|
|
1476
|
+
chainId: chainIdForApi,
|
|
1477
|
+
txHash,
|
|
1298
1478
|
})
|
|
1299
|
-
.then((
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
if (txReceipt.status === EthTxStatus.Success) {
|
|
1303
|
-
setTimeout(() => {
|
|
1304
|
-
simpleFetch(SWAP_API_ENDPOINT, "/v1/swap/tx", {
|
|
1305
|
-
method: "POST",
|
|
1306
|
-
headers: {
|
|
1307
|
-
"content-type": "application/json",
|
|
1308
|
-
...(() => {
|
|
1309
|
-
const res: {
|
|
1310
|
-
authorization?: string;
|
|
1311
|
-
} = {};
|
|
1312
|
-
if (process.env["SKIP_API_KEY"]) {
|
|
1313
|
-
res.authorization = process.env["SKIP_API_KEY"];
|
|
1314
|
-
}
|
|
1315
|
-
return res;
|
|
1316
|
-
})(),
|
|
1317
|
-
},
|
|
1318
|
-
body: JSON.stringify({
|
|
1319
|
-
tx_hash: history.txHash,
|
|
1320
|
-
chain_id: history.chainId.replace("eip155:", ""),
|
|
1321
|
-
}),
|
|
1322
|
-
})
|
|
1323
|
-
.then((result) => {
|
|
1324
|
-
console.log(
|
|
1325
|
-
`Skip tx track result: ${JSON.stringify(result)}`
|
|
1326
|
-
);
|
|
1327
|
-
onFulfill(true);
|
|
1328
|
-
})
|
|
1329
|
-
.catch((e) => {
|
|
1330
|
-
console.log(e);
|
|
1331
|
-
this.removeRecentSkipHistory(history.id);
|
|
1332
|
-
onFulfill(false);
|
|
1333
|
-
});
|
|
1334
|
-
}, 2000);
|
|
1335
|
-
} else {
|
|
1336
|
-
// tx가 실패한거면 종료
|
|
1337
|
-
this.removeRecentSkipHistory(history.id);
|
|
1338
|
-
onFulfill(false);
|
|
1339
|
-
}
|
|
1340
|
-
} else {
|
|
1341
|
-
onError();
|
|
1342
|
-
}
|
|
1343
|
-
})
|
|
1344
|
-
.catch(() => {
|
|
1345
|
-
// 오류가 발생하면 종료
|
|
1346
|
-
onFulfill(false);
|
|
1347
|
-
});
|
|
1348
|
-
} else {
|
|
1349
|
-
const txTracer = new TendermintTxTracer(chainInfo.rpc, "/websocket");
|
|
1350
|
-
txTracer.addEventListener("error", () => onFulfill(false));
|
|
1351
|
-
txTracer
|
|
1352
|
-
.traceTx(Buffer.from(history.txHash.replace("0x", ""), "hex"))
|
|
1353
|
-
.then((res: any) => {
|
|
1354
|
-
txTracer.close();
|
|
1355
|
-
|
|
1356
|
-
let txResult;
|
|
1357
|
-
|
|
1358
|
-
if (Array.isArray(res.txs)) {
|
|
1359
|
-
if (res.txs && res.txs.length > 0) {
|
|
1360
|
-
txResult = res.txs[0].tx_result;
|
|
1361
|
-
} else {
|
|
1362
|
-
// In case tx is not confirmed, just wait for next check
|
|
1363
|
-
onError();
|
|
1364
|
-
return;
|
|
1365
|
-
}
|
|
1366
|
-
} else {
|
|
1367
|
-
txResult = res;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
if (!txResult || typeof txResult.code !== "number") {
|
|
1371
|
-
onError();
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
if (txResult.code === 0) {
|
|
1376
|
-
setTimeout(() => {
|
|
1377
|
-
simpleFetch(SWAP_API_ENDPOINT, "/v1/swap/tx", {
|
|
1378
|
-
method: "POST",
|
|
1379
|
-
headers: {
|
|
1380
|
-
"content-type": "application/json",
|
|
1381
|
-
...(() => {
|
|
1382
|
-
const res: {
|
|
1383
|
-
authorization?: string;
|
|
1384
|
-
} = {};
|
|
1385
|
-
if (process.env["SKIP_API_KEY"]) {
|
|
1386
|
-
res.authorization = process.env["SKIP_API_KEY"];
|
|
1387
|
-
}
|
|
1388
|
-
return res;
|
|
1389
|
-
})(),
|
|
1390
|
-
},
|
|
1391
|
-
body: JSON.stringify({
|
|
1392
|
-
tx_hash: history.txHash,
|
|
1393
|
-
chain_id: history.chainId,
|
|
1394
|
-
}),
|
|
1395
|
-
})
|
|
1396
|
-
.then((result) => {
|
|
1397
|
-
console.log(
|
|
1398
|
-
`Skip tx track result: ${JSON.stringify(result)}`
|
|
1399
|
-
);
|
|
1400
|
-
onFulfill(true);
|
|
1401
|
-
})
|
|
1402
|
-
.catch((e) => {
|
|
1403
|
-
console.log(e);
|
|
1404
|
-
this.removeRecentSkipHistory(history.id);
|
|
1405
|
-
onFulfill(false);
|
|
1406
|
-
});
|
|
1407
|
-
}, 2000);
|
|
1408
|
-
} else {
|
|
1409
|
-
// tx가 실패한거면 종료
|
|
1410
|
-
this.removeRecentSkipHistory(history.id);
|
|
1411
|
-
onFulfill(false);
|
|
1412
|
-
}
|
|
1479
|
+
.then((result) => {
|
|
1480
|
+
console.log(`Skip tx track result: ${JSON.stringify(result)}`);
|
|
1481
|
+
onFulfill(true);
|
|
1413
1482
|
})
|
|
1414
|
-
.catch(() => {
|
|
1415
|
-
|
|
1483
|
+
.catch((e) => {
|
|
1484
|
+
console.log(e);
|
|
1485
|
+
onRemoveHistory();
|
|
1416
1486
|
onFulfill(false);
|
|
1417
1487
|
});
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1488
|
+
}, 2000);
|
|
1489
|
+
}
|
|
1420
1490
|
|
|
1421
1491
|
protected checkAndUpdateSkipSwapHistoryRecursive = (
|
|
1422
1492
|
id: string,
|
|
@@ -1454,30 +1524,11 @@ export class RecentSendHistoryService {
|
|
|
1454
1524
|
return;
|
|
1455
1525
|
}
|
|
1456
1526
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
}
|
|
1462
|
-
const requestParams = new URLSearchParams(request).toString();
|
|
1463
|
-
|
|
1464
|
-
simpleFetch<TxStatusResponse>(
|
|
1465
|
-
SWAP_API_ENDPOINT,
|
|
1466
|
-
`/v1/swap/tx?${requestParams}`,
|
|
1467
|
-
{
|
|
1468
|
-
method: "GET",
|
|
1469
|
-
headers: {
|
|
1470
|
-
"content-type": "application/json",
|
|
1471
|
-
...(() => {
|
|
1472
|
-
const res: { authorization?: string } = {};
|
|
1473
|
-
if (process.env["SKIP_API_KEY"]) {
|
|
1474
|
-
res.authorization = process.env["SKIP_API_KEY"];
|
|
1475
|
-
}
|
|
1476
|
-
return res;
|
|
1477
|
-
})(),
|
|
1478
|
-
},
|
|
1479
|
-
}
|
|
1480
|
-
)
|
|
1527
|
+
requestSkipTxStatus({
|
|
1528
|
+
endpoint: SWAP_API_ENDPOINT,
|
|
1529
|
+
chainId: chainId.replace("eip155:", ""),
|
|
1530
|
+
txHash,
|
|
1531
|
+
})
|
|
1481
1532
|
.then((res) => {
|
|
1482
1533
|
const {
|
|
1483
1534
|
state,
|
|
@@ -1760,265 +1811,1815 @@ export class RecentSendHistoryService {
|
|
|
1760
1811
|
// 최종 routeIndex 갱신
|
|
1761
1812
|
history.routeIndex = nextRouteIndex;
|
|
1762
1813
|
|
|
1763
|
-
// state에 따라 트래킹 완료/재시도 결정
|
|
1764
|
-
switch (state) {
|
|
1765
|
-
case "STATE_ABANDONED":
|
|
1766
|
-
case "STATE_COMPLETED_ERROR":
|
|
1767
|
-
case "STATE_COMPLETED_SUCCESS":
|
|
1768
|
-
// 성공 상태인데 라우트가 남았다면 마지막 라우트로 이동
|
|
1769
|
-
if (
|
|
1770
|
-
state === "STATE_COMPLETED_SUCCESS" &&
|
|
1771
|
-
nextRouteIndex !== simpleRoute.length - 1
|
|
1772
|
-
) {
|
|
1773
|
-
history.routeIndex = simpleRoute.length - 1;
|
|
1774
|
-
}
|
|
1814
|
+
// state에 따라 트래킹 완료/재시도 결정
|
|
1815
|
+
switch (state) {
|
|
1816
|
+
case "STATE_ABANDONED":
|
|
1817
|
+
case "STATE_COMPLETED_ERROR":
|
|
1818
|
+
case "STATE_COMPLETED_SUCCESS":
|
|
1819
|
+
// 성공 상태인데 라우트가 남았다면 마지막 라우트로 이동
|
|
1820
|
+
if (
|
|
1821
|
+
state === "STATE_COMPLETED_SUCCESS" &&
|
|
1822
|
+
nextRouteIndex !== simpleRoute.length - 1
|
|
1823
|
+
) {
|
|
1824
|
+
history.routeIndex = simpleRoute.length - 1;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
if (receiveTxHash) {
|
|
1828
|
+
this.trackSkipDestinationAssetAmount(
|
|
1829
|
+
id,
|
|
1830
|
+
receiveTxHash,
|
|
1831
|
+
onFulfill
|
|
1832
|
+
);
|
|
1833
|
+
} else {
|
|
1834
|
+
history.trackDone = true;
|
|
1835
|
+
onFulfill();
|
|
1836
|
+
}
|
|
1837
|
+
break;
|
|
1838
|
+
|
|
1839
|
+
case "STATE_PENDING":
|
|
1840
|
+
case "STATE_PENDING_ERROR":
|
|
1841
|
+
// 아직 트래킹 중이거나 에러 상태 전파 중 => 재시도
|
|
1842
|
+
onError();
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
})
|
|
1846
|
+
.catch((e) => {
|
|
1847
|
+
console.error(e);
|
|
1848
|
+
onError();
|
|
1849
|
+
});
|
|
1850
|
+
};
|
|
1851
|
+
|
|
1852
|
+
protected trackSkipDestinationAssetAmount(
|
|
1853
|
+
historyId: string,
|
|
1854
|
+
txHash: string,
|
|
1855
|
+
onFulfill: () => void
|
|
1856
|
+
) {
|
|
1857
|
+
const history = this.getRecentSkipHistory(historyId);
|
|
1858
|
+
if (!history) {
|
|
1859
|
+
onFulfill();
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
const chainInfo = this.chainsService.getChainInfo(
|
|
1864
|
+
history.destinationChainId
|
|
1865
|
+
);
|
|
1866
|
+
if (!chainInfo) {
|
|
1867
|
+
onFulfill();
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
this.trackDestinationAssetAmount({
|
|
1872
|
+
chainId: history.destinationChainId,
|
|
1873
|
+
txHash,
|
|
1874
|
+
recipient: history.recipient,
|
|
1875
|
+
targetDenom: history.destinationAsset.denom,
|
|
1876
|
+
onResult: (resAmount) => {
|
|
1877
|
+
runInAction(() => {
|
|
1878
|
+
history.resAmount.push(resAmount);
|
|
1879
|
+
history.trackDone = true;
|
|
1880
|
+
});
|
|
1881
|
+
},
|
|
1882
|
+
onRefund: (refundInfo, error) => {
|
|
1883
|
+
runInAction(() => {
|
|
1884
|
+
history.trackError = error;
|
|
1885
|
+
history.swapRefundInfo = refundInfo;
|
|
1886
|
+
history.trackDone = true;
|
|
1887
|
+
});
|
|
1888
|
+
},
|
|
1889
|
+
onFulfill: () => {
|
|
1890
|
+
// ensure completion even if no result parsed
|
|
1891
|
+
runInAction(() => {
|
|
1892
|
+
history.trackDone = true;
|
|
1893
|
+
});
|
|
1894
|
+
onFulfill();
|
|
1895
|
+
},
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
@action
|
|
1900
|
+
removeRecentSkipHistory(id: string): boolean {
|
|
1901
|
+
return this.recentSkipHistoryMap.delete(id);
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
@action
|
|
1905
|
+
clearAllRecentSkipHistory(): void {
|
|
1906
|
+
this.recentSkipHistoryMap.clear();
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// ============================================================================
|
|
1910
|
+
// Swap V2 history
|
|
1911
|
+
// ============================================================================
|
|
1912
|
+
|
|
1913
|
+
@action
|
|
1914
|
+
recordTxWithSwapV2(
|
|
1915
|
+
fromChainId: string,
|
|
1916
|
+
toChainId: string,
|
|
1917
|
+
provider: SwapProvider,
|
|
1918
|
+
destinationAsset: {
|
|
1919
|
+
chainId: string;
|
|
1920
|
+
denom: string;
|
|
1921
|
+
expectedAmount: string;
|
|
1922
|
+
},
|
|
1923
|
+
simpleRoute: {
|
|
1924
|
+
isOnlyEvm: boolean;
|
|
1925
|
+
chainId: string;
|
|
1926
|
+
receiver: string;
|
|
1927
|
+
}[],
|
|
1928
|
+
sender: string,
|
|
1929
|
+
recipient: string,
|
|
1930
|
+
amount: {
|
|
1931
|
+
amount: string;
|
|
1932
|
+
denom: string;
|
|
1933
|
+
}[],
|
|
1934
|
+
notificationInfo: {
|
|
1935
|
+
currencies: AppCurrency[];
|
|
1936
|
+
},
|
|
1937
|
+
routeDurationSeconds: number = 0,
|
|
1938
|
+
txHash: string,
|
|
1939
|
+
isOnlyUseBridge?: boolean,
|
|
1940
|
+
backgroundExecutionId?: string
|
|
1941
|
+
): string {
|
|
1942
|
+
const id = (this.recentSwapV2HistorySeq++).toString();
|
|
1943
|
+
|
|
1944
|
+
const history: SwapV2History = {
|
|
1945
|
+
id,
|
|
1946
|
+
fromChainId,
|
|
1947
|
+
toChainId,
|
|
1948
|
+
provider,
|
|
1949
|
+
timestamp: Date.now(),
|
|
1950
|
+
sender,
|
|
1951
|
+
recipient,
|
|
1952
|
+
amount,
|
|
1953
|
+
notificationInfo,
|
|
1954
|
+
routeDurationSeconds,
|
|
1955
|
+
txHash,
|
|
1956
|
+
isOnlyUseBridge,
|
|
1957
|
+
status: SwapV2TxStatus.IN_PROGRESS,
|
|
1958
|
+
simpleRoute,
|
|
1959
|
+
routeIndex: -1,
|
|
1960
|
+
destinationAsset,
|
|
1961
|
+
resAmount: [],
|
|
1962
|
+
assetLocationInfo: undefined,
|
|
1963
|
+
notified: undefined,
|
|
1964
|
+
backgroundExecutionId,
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
this.recentSwapV2HistoryMap.set(id, history);
|
|
1968
|
+
this.trackSwapV2Recursive(id);
|
|
1969
|
+
|
|
1970
|
+
return id;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
trackSwapV2Recursive(id: string): void {
|
|
1974
|
+
const history = this.getRecentSwapV2History(id);
|
|
1975
|
+
if (!history) {
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
retry(
|
|
1980
|
+
() => {
|
|
1981
|
+
return new Promise<void>((txFulfilledResolve, txFulfilledReject) => {
|
|
1982
|
+
this.checkAndTrackSwapTxFulfilledRecursive({
|
|
1983
|
+
chainId: history.fromChainId,
|
|
1984
|
+
txHash: history.txHash,
|
|
1985
|
+
onSuccess: () => {
|
|
1986
|
+
txFulfilledResolve();
|
|
1987
|
+
|
|
1988
|
+
retry(
|
|
1989
|
+
() => {
|
|
1990
|
+
return new Promise<void>((resolve, reject) => {
|
|
1991
|
+
this.checkAndUpdateSwapV2HistoryRecursive(
|
|
1992
|
+
id,
|
|
1993
|
+
resolve,
|
|
1994
|
+
reject
|
|
1995
|
+
);
|
|
1996
|
+
});
|
|
1997
|
+
},
|
|
1998
|
+
{
|
|
1999
|
+
maxRetries: 60,
|
|
2000
|
+
waitMsAfterError: 1000,
|
|
2001
|
+
maxWaitMsAfterError: 45000,
|
|
2002
|
+
}
|
|
2003
|
+
);
|
|
2004
|
+
},
|
|
2005
|
+
onPending: txFulfilledReject,
|
|
2006
|
+
onFailed: () => {
|
|
2007
|
+
this.removeRecentSwapV2History(id);
|
|
2008
|
+
txFulfilledResolve();
|
|
2009
|
+
},
|
|
2010
|
+
onError: txFulfilledResolve,
|
|
2011
|
+
});
|
|
2012
|
+
});
|
|
2013
|
+
},
|
|
2014
|
+
{
|
|
2015
|
+
maxRetries: 60,
|
|
2016
|
+
waitMsAfterError: 1000,
|
|
2017
|
+
maxWaitMsAfterError: 45000,
|
|
2018
|
+
}
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
protected checkAndUpdateSwapV2HistoryRecursive(
|
|
2023
|
+
id: string,
|
|
2024
|
+
onFulfill: () => void,
|
|
2025
|
+
onError: () => void
|
|
2026
|
+
): void {
|
|
2027
|
+
const history = this.getRecentSwapV2History(id);
|
|
2028
|
+
if (!history) {
|
|
2029
|
+
onFulfill();
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
// if already tracked, fulfill
|
|
2034
|
+
if (history.trackDone) {
|
|
2035
|
+
onFulfill();
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
const { txHash, fromChainId, toChainId, provider } = history;
|
|
2040
|
+
|
|
2041
|
+
const normalizeChainId = (chainId: string): string => {
|
|
2042
|
+
return chainId.replace("eip155:", "");
|
|
2043
|
+
};
|
|
2044
|
+
|
|
2045
|
+
requestSwapV2TxStatus({
|
|
2046
|
+
endpoint: SWAP_API_ENDPOINT,
|
|
2047
|
+
fromChainId: normalizeChainId(fromChainId),
|
|
2048
|
+
toChainId: normalizeChainId(toChainId),
|
|
2049
|
+
provider,
|
|
2050
|
+
txHash,
|
|
2051
|
+
})
|
|
2052
|
+
.then((res) => {
|
|
2053
|
+
this.processSwapV2StatusResponse(id, res.data, onFulfill, onError);
|
|
2054
|
+
})
|
|
2055
|
+
.catch((e) => {
|
|
2056
|
+
console.error("SwapV2 status tracking error:", e);
|
|
2057
|
+
onError();
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
@action
|
|
2062
|
+
protected processSwapV2StatusResponse(
|
|
2063
|
+
id: string,
|
|
2064
|
+
response: SwapV2TxStatusResponse,
|
|
2065
|
+
onFulfill: () => void,
|
|
2066
|
+
onError: () => void
|
|
2067
|
+
): void {
|
|
2068
|
+
const history = this.getRecentSwapV2History(id);
|
|
2069
|
+
if (!history) {
|
|
2070
|
+
onFulfill();
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
const { status, steps, asset_location } = response;
|
|
2075
|
+
const { simpleRoute } = history;
|
|
2076
|
+
const prevRouteIndex = history.routeIndex;
|
|
2077
|
+
|
|
2078
|
+
// 모든 상태 즉시 업데이트 (UNKNOWN 포함)
|
|
2079
|
+
history.status = status;
|
|
2080
|
+
history.trackError = undefined;
|
|
2081
|
+
|
|
2082
|
+
// This might be the state where tracking has just started,
|
|
2083
|
+
// so handle the error and retry
|
|
2084
|
+
if (!steps || steps.length === 0) {
|
|
2085
|
+
if (
|
|
2086
|
+
status === SwapV2TxStatus.IN_PROGRESS ||
|
|
2087
|
+
status === SwapV2TxStatus.UNKNOWN
|
|
2088
|
+
) {
|
|
2089
|
+
onError();
|
|
2090
|
+
} else {
|
|
2091
|
+
// swap on single evm chain might not have steps
|
|
2092
|
+
history.trackDone = true;
|
|
2093
|
+
onFulfill();
|
|
2094
|
+
}
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// find current step (not success first, otherwise last step)
|
|
2099
|
+
const currentStep =
|
|
2100
|
+
steps.find((s) => s.status !== SwapV2RouteStepStatus.SUCCESS) ??
|
|
2101
|
+
steps[steps.length - 1];
|
|
2102
|
+
|
|
2103
|
+
const normalizeChainId = (chainId: string): string => {
|
|
2104
|
+
return chainId.replace("eip155:", "").toLowerCase();
|
|
2105
|
+
};
|
|
2106
|
+
|
|
2107
|
+
// Find the LAST step that matches the actual destination chain (history.toChainId)
|
|
2108
|
+
// Use reverse + find to handle routes that visit the destination chain multiple times
|
|
2109
|
+
// This handles cases where intermediate steps fail but the final destination is reached
|
|
2110
|
+
const destinationStep = [...steps].reverse().find((s) => {
|
|
2111
|
+
if (!s.chain_id) {
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
return (
|
|
2115
|
+
normalizeChainId(s.chain_id) === normalizeChainId(history.toChainId)
|
|
2116
|
+
);
|
|
2117
|
+
});
|
|
2118
|
+
const isDestinationStepSuccessful =
|
|
2119
|
+
destinationStep &&
|
|
2120
|
+
destinationStep.status === SwapV2RouteStepStatus.SUCCESS &&
|
|
2121
|
+
!!destinationStep.tx_hash;
|
|
2122
|
+
|
|
2123
|
+
const findSimpleRouteIndex = (chainId: string): number => {
|
|
2124
|
+
const normalizedChainId = normalizeChainId(chainId);
|
|
2125
|
+
for (let i = 0; i < simpleRoute.length; i++) {
|
|
2126
|
+
const routeChainId = normalizeChainId(simpleRoute[i].chainId);
|
|
2127
|
+
if (routeChainId === normalizedChainId) {
|
|
2128
|
+
return i;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return -1;
|
|
2132
|
+
};
|
|
2133
|
+
|
|
2134
|
+
// NOTE: The lengths of simpleRoute and steps may differ.
|
|
2135
|
+
let updatedRouteIndex = Math.max(0, history.routeIndex);
|
|
2136
|
+
|
|
2137
|
+
// 1. Find highest completed simpleRoute index from all SUCCESS steps
|
|
2138
|
+
let highestCompletedIndex = -1;
|
|
2139
|
+
for (const step of steps) {
|
|
2140
|
+
if (step.status === SwapV2RouteStepStatus.SUCCESS && step.chain_id) {
|
|
2141
|
+
const routeIdx = findSimpleRouteIndex(step.chain_id);
|
|
2142
|
+
if (routeIdx > highestCompletedIndex) {
|
|
2143
|
+
highestCompletedIndex = routeIdx;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// 2. Also check if currentStep is in simpleRoute
|
|
2149
|
+
let currentStepIndex = -1;
|
|
2150
|
+
if (currentStep.chain_id) {
|
|
2151
|
+
currentStepIndex = findSimpleRouteIndex(currentStep.chain_id);
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
// 3. Use the higher value as updatedRouteIndex
|
|
2155
|
+
const candidateIndex = Math.max(highestCompletedIndex, currentStepIndex);
|
|
2156
|
+
if (candidateIndex >= 0) {
|
|
2157
|
+
updatedRouteIndex = candidateIndex;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
const publishExecutableChains = (chainIds?: string[]) => {
|
|
2161
|
+
if (!history.backgroundExecutionId) {
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
const executableChainIds =
|
|
2165
|
+
chainIds ?? this.getExecutableChainIdsFromSwapV2History(history);
|
|
2166
|
+
this.publisher.publish({
|
|
2167
|
+
type: "executable",
|
|
2168
|
+
executionId: history.backgroundExecutionId,
|
|
2169
|
+
executableChainIds,
|
|
2170
|
+
});
|
|
2171
|
+
};
|
|
2172
|
+
|
|
2173
|
+
const isUnknownStatus =
|
|
2174
|
+
status === SwapV2TxStatus.UNKNOWN ||
|
|
2175
|
+
steps.some((s) => s.status === SwapV2RouteStepStatus.UNKNOWN);
|
|
2176
|
+
|
|
2177
|
+
if (isUnknownStatus) {
|
|
2178
|
+
if (!history.unknownStatusFirstSeenAt) {
|
|
2179
|
+
// UNKNOWN 상태 처음 발견 - 타임스탬프 기록
|
|
2180
|
+
history.unknownStatusFirstSeenAt = Date.now();
|
|
2181
|
+
} else {
|
|
2182
|
+
const elapsedMs = Date.now() - history.unknownStatusFirstSeenAt;
|
|
2183
|
+
if (elapsedMs >= UNKNOWN_TX_STATUS_TIMEOUT_MS) {
|
|
2184
|
+
this.analyticsService.logEventIgnoreError(
|
|
2185
|
+
"swapV2UnknownTxStatusWithTimeout",
|
|
2186
|
+
{
|
|
2187
|
+
provider: history.provider,
|
|
2188
|
+
fromChainId: history.fromChainId,
|
|
2189
|
+
toChainId: history.toChainId,
|
|
2190
|
+
txHash: history.txHash,
|
|
2191
|
+
executedAt: history.timestamp,
|
|
2192
|
+
}
|
|
2193
|
+
);
|
|
2194
|
+
|
|
2195
|
+
history.trackDone = true;
|
|
2196
|
+
onFulfill();
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
} else {
|
|
2201
|
+
// UNKNOWN 상태가 아니면 타임스탬프 초기화
|
|
2202
|
+
if (history.unknownStatusFirstSeenAt !== undefined) {
|
|
2203
|
+
history.unknownStatusFirstSeenAt = undefined;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
switch (status) {
|
|
2208
|
+
case SwapV2TxStatus.IN_PROGRESS:
|
|
2209
|
+
case SwapV2TxStatus.UNKNOWN:
|
|
2210
|
+
// publish executable chains if routeIndex increased
|
|
2211
|
+
if (updatedRouteIndex > prevRouteIndex) {
|
|
2212
|
+
history.routeIndex = updatedRouteIndex;
|
|
2213
|
+
publishExecutableChains();
|
|
2214
|
+
}
|
|
2215
|
+
// Continue polling
|
|
2216
|
+
onError();
|
|
2217
|
+
break;
|
|
2218
|
+
|
|
2219
|
+
case SwapV2TxStatus.SUCCESS:
|
|
2220
|
+
case SwapV2TxStatus.PARTIAL_SUCCESS:
|
|
2221
|
+
case SwapV2TxStatus.FAILED:
|
|
2222
|
+
// If current step is still in progress, retry a few more times before finalizing
|
|
2223
|
+
if (currentStep.status === SwapV2RouteStepStatus.IN_PROGRESS) {
|
|
2224
|
+
const maxRetries = 3;
|
|
2225
|
+
const retryCount = history.finalizationRetryCount ?? 0;
|
|
2226
|
+
|
|
2227
|
+
if (retryCount < maxRetries) {
|
|
2228
|
+
history.finalizationRetryCount = retryCount + 1;
|
|
2229
|
+
onError();
|
|
2230
|
+
break;
|
|
2231
|
+
}
|
|
2232
|
+
// Max retries reached, fall through to finalize
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
let executableChainIdsToPublish: string[] | undefined;
|
|
2236
|
+
|
|
2237
|
+
// NOTE: 현재 asset_location은 skip의 multichain operation인 경우에만 주어지는 값이다.
|
|
2238
|
+
if (asset_location) {
|
|
2239
|
+
const chainId = asset_location.chain_id;
|
|
2240
|
+
const evmLikeChainId = Number(chainId);
|
|
2241
|
+
const isEVMChainId =
|
|
2242
|
+
!Number.isNaN(evmLikeChainId) && evmLikeChainId > 0;
|
|
2243
|
+
const chainIdInKeplr = isEVMChainId ? `eip155:${chainId}` : chainId;
|
|
2244
|
+
const denomInKeplr = isEVMChainId
|
|
2245
|
+
? `erc20:${asset_location.denom}`
|
|
2246
|
+
: asset_location.denom;
|
|
2247
|
+
|
|
2248
|
+
// destination chain에 destination denom으로 도착했으면 완전 성공이므로
|
|
2249
|
+
// assetLocationInfo를 설정하지 않음
|
|
2250
|
+
const isDestinationReached =
|
|
2251
|
+
chainIdInKeplr === history.toChainId &&
|
|
2252
|
+
denomInKeplr.toLowerCase() ===
|
|
2253
|
+
history.destinationAsset.denom.toLowerCase();
|
|
2254
|
+
if (isDestinationReached) {
|
|
2255
|
+
history.routeIndex = simpleRoute.length - 1;
|
|
2256
|
+
executableChainIdsToPublish =
|
|
2257
|
+
this.getExecutableChainIdsFromSwapV2History(history, true);
|
|
2258
|
+
history.resAmount.push([
|
|
2259
|
+
{
|
|
2260
|
+
amount: asset_location.amount,
|
|
2261
|
+
denom: denomInKeplr,
|
|
2262
|
+
},
|
|
2263
|
+
]);
|
|
2264
|
+
|
|
2265
|
+
this.notifySwapV2Success(history);
|
|
2266
|
+
} else {
|
|
2267
|
+
/*
|
|
2268
|
+
Determine the type of asset location:
|
|
2269
|
+
- "intermediate": SUCCESS 상태이지만 asset_location이 최종 목적지가 아닌 경우
|
|
2270
|
+
(예: base USDC -> osmosis OSMO 스왑 시, noble USDC가 먼저 도착하고
|
|
2271
|
+
이후 noble USDC -> osmosis OSMO로 ibc swap하는 transaction이 필요한 경우)
|
|
2272
|
+
이 경우 추가 transaction을 실행하거나 현재 받은 자산을 그대로 둘 수 있음
|
|
2273
|
+
- "refund": 중간에서 또는 destination에서 스왑 실패 등으로 destination asset이 아닌 자산이 릴리즈된 경우
|
|
2274
|
+
backgroundExecutionId가 있으면 멀티 transaction 케이스이므로 다음 transaction을 실행할 수 있도록 'intermediate'로 설정
|
|
2275
|
+
*/
|
|
2276
|
+
const assetLocationType: "refund" | "intermediate" =
|
|
2277
|
+
status === SwapV2TxStatus.SUCCESS && history.backgroundExecutionId
|
|
2278
|
+
? "intermediate"
|
|
2279
|
+
: "refund";
|
|
2280
|
+
|
|
2281
|
+
history.assetLocationInfo = {
|
|
2282
|
+
chainId: chainIdInKeplr,
|
|
2283
|
+
amount: [
|
|
2284
|
+
{
|
|
2285
|
+
amount: asset_location.amount,
|
|
2286
|
+
denom: denomInKeplr,
|
|
2287
|
+
},
|
|
2288
|
+
],
|
|
2289
|
+
type: assetLocationType,
|
|
2290
|
+
};
|
|
2291
|
+
|
|
2292
|
+
// refund 타입인 경우 status도 FAILED로 변경하여 UI에서 refund 상황을 표시할 수 있도록 함
|
|
2293
|
+
if (assetLocationType === "refund") {
|
|
2294
|
+
history.status = SwapV2TxStatus.FAILED;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// asset location chain까지 routeIndex가 이동해야 하는지 확인
|
|
2298
|
+
const assetLocationChainIndex = simpleRoute.findIndex(
|
|
2299
|
+
(route) => route.chainId === chainIdInKeplr
|
|
2300
|
+
);
|
|
2301
|
+
if (
|
|
2302
|
+
assetLocationChainIndex !== -1 &&
|
|
2303
|
+
assetLocationChainIndex > updatedRouteIndex
|
|
2304
|
+
) {
|
|
2305
|
+
history.routeIndex = assetLocationChainIndex;
|
|
2306
|
+
}
|
|
2307
|
+
executableChainIdsToPublish =
|
|
2308
|
+
this.getExecutableChainIdsFromSwapV2History(history);
|
|
2309
|
+
}
|
|
2310
|
+
} else if (status === SwapV2TxStatus.SUCCESS) {
|
|
2311
|
+
// For SUCCESS without asset_location, move routeIndex to end
|
|
2312
|
+
history.routeIndex = simpleRoute.length - 1;
|
|
2313
|
+
executableChainIdsToPublish =
|
|
2314
|
+
this.getExecutableChainIdsFromSwapV2History(history, true);
|
|
2315
|
+
|
|
2316
|
+
this.notifySwapV2Success(history);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// Publish executable chains
|
|
2320
|
+
publishExecutableChains(executableChainIdsToPublish);
|
|
2321
|
+
|
|
2322
|
+
// destination 체인/denom 기준으로만 추가 추적을 시도한다.
|
|
2323
|
+
const targetChainId = history.toChainId;
|
|
2324
|
+
const targetDenom = history.destinationAsset.denom;
|
|
2325
|
+
|
|
2326
|
+
// 해당 위치의 tx_hash를 찾아서 자산 추적, 없을 수도 있다.
|
|
2327
|
+
const targetTxHash = destinationStep?.tx_hash ?? currentStep.tx_hash;
|
|
2328
|
+
|
|
2329
|
+
const isAtDestinationChain = (() => {
|
|
2330
|
+
// Top-level SUCCESS + destination step successful → check against destination step
|
|
2331
|
+
if (
|
|
2332
|
+
status === SwapV2TxStatus.SUCCESS &&
|
|
2333
|
+
isDestinationStepSuccessful &&
|
|
2334
|
+
destinationStep?.chain_id
|
|
2335
|
+
) {
|
|
2336
|
+
return (
|
|
2337
|
+
normalizeChainId(destinationStep.chain_id) ===
|
|
2338
|
+
normalizeChainId(targetChainId)
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// Default logic (other cases)
|
|
2343
|
+
if (!currentStep.chain_id) {
|
|
2344
|
+
return false;
|
|
2345
|
+
}
|
|
2346
|
+
return (
|
|
2347
|
+
normalizeChainId(currentStep.chain_id) ===
|
|
2348
|
+
normalizeChainId(targetChainId)
|
|
2349
|
+
);
|
|
2350
|
+
})();
|
|
2351
|
+
|
|
2352
|
+
const skipAssetTracking =
|
|
2353
|
+
history.resAmount.length > 0 || history.assetLocationInfo != null;
|
|
2354
|
+
|
|
2355
|
+
// resAmount 또는 assetLocationInfo가 없으면 추가적으로 자산 추적을 해야 한다.
|
|
2356
|
+
if (targetTxHash && !skipAssetTracking && isAtDestinationChain) {
|
|
2357
|
+
console.log("trackSwapV2ReleasedAssetAmount", id, targetTxHash);
|
|
2358
|
+
|
|
2359
|
+
this.trackSwapV2ReleasedAssetAmount(
|
|
2360
|
+
id,
|
|
2361
|
+
targetTxHash,
|
|
2362
|
+
targetChainId,
|
|
2363
|
+
targetDenom,
|
|
2364
|
+
onFulfill
|
|
2365
|
+
);
|
|
2366
|
+
} else if (
|
|
2367
|
+
status === SwapV2TxStatus.SUCCESS &&
|
|
2368
|
+
!skipAssetTracking &&
|
|
2369
|
+
!isAtDestinationChain
|
|
2370
|
+
) {
|
|
2371
|
+
// response status는 SUCCESS인데 destination chain에 도달하지 않음 → 실패 처리
|
|
2372
|
+
runInAction(() => {
|
|
2373
|
+
history.status = SwapV2TxStatus.FAILED;
|
|
2374
|
+
history.trackDone = true;
|
|
2375
|
+
});
|
|
2376
|
+
|
|
2377
|
+
// TODO: additional tracking for failed case...
|
|
2378
|
+
|
|
2379
|
+
onFulfill();
|
|
2380
|
+
} else {
|
|
2381
|
+
history.trackDone = true;
|
|
2382
|
+
onFulfill();
|
|
2383
|
+
}
|
|
2384
|
+
break;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
/**
|
|
2389
|
+
* Track released asset amount from tx receipt.
|
|
2390
|
+
* - SUCCESS: destination asset 추적
|
|
2391
|
+
* - FAILED/PARTIAL_SUCCESS + assetLocationInfo: refund된 자산 추적
|
|
2392
|
+
*/
|
|
2393
|
+
protected trackSwapV2ReleasedAssetAmount(
|
|
2394
|
+
historyId: string,
|
|
2395
|
+
txHash: string,
|
|
2396
|
+
targetChainId: string,
|
|
2397
|
+
targetDenom: string,
|
|
2398
|
+
onFulfill: () => void
|
|
2399
|
+
) {
|
|
2400
|
+
const history = this.getRecentSwapV2History(historyId);
|
|
2401
|
+
if (!history) {
|
|
2402
|
+
onFulfill();
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const chainInfo = this.chainsService.getChainInfo(targetChainId);
|
|
2407
|
+
if (!chainInfo) {
|
|
2408
|
+
onFulfill();
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
this.trackDestinationAssetAmount({
|
|
2413
|
+
chainId: targetChainId,
|
|
2414
|
+
txHash,
|
|
2415
|
+
recipient: history.recipient,
|
|
2416
|
+
targetDenom,
|
|
2417
|
+
onResult: (resAmount) => {
|
|
2418
|
+
runInAction(() => {
|
|
2419
|
+
history.resAmount.push(resAmount);
|
|
2420
|
+
history.trackDone = true;
|
|
2421
|
+
|
|
2422
|
+
this.notifySwapV2Success(history);
|
|
2423
|
+
});
|
|
2424
|
+
},
|
|
2425
|
+
onRefund: (refundInfo, error) => {
|
|
2426
|
+
runInAction(() => {
|
|
2427
|
+
history.trackError = error;
|
|
2428
|
+
history.assetLocationInfo = {
|
|
2429
|
+
...refundInfo,
|
|
2430
|
+
type: "refund",
|
|
2431
|
+
};
|
|
2432
|
+
history.trackDone = true;
|
|
2433
|
+
});
|
|
2434
|
+
},
|
|
2435
|
+
onFulfill: () => {
|
|
2436
|
+
runInAction(() => {
|
|
2437
|
+
history.trackDone = true;
|
|
2438
|
+
});
|
|
2439
|
+
onFulfill();
|
|
2440
|
+
},
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
getRecentSwapV2History(id: string): SwapV2History | undefined {
|
|
2445
|
+
return this.recentSwapV2HistoryMap.get(id);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
getRecentSwapV2Histories(): SwapV2History[] {
|
|
2449
|
+
return Array.from(this.recentSwapV2HistoryMap.values()).filter(
|
|
2450
|
+
(history) => {
|
|
2451
|
+
if (!this.chainsService.hasChainInfo(history.fromChainId)) {
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
if (!this.chainsService.hasChainInfo(history.toChainId)) {
|
|
2456
|
+
return false;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
if (
|
|
2460
|
+
history.simpleRoute.some((route) => {
|
|
2461
|
+
return !this.chainsService.hasChainInfo(route.chainId);
|
|
2462
|
+
})
|
|
2463
|
+
) {
|
|
2464
|
+
return false;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
return true;
|
|
2468
|
+
}
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
@action
|
|
2473
|
+
removeRecentSwapV2History(id: string): boolean {
|
|
2474
|
+
const history = this.getRecentSwapV2History(id);
|
|
2475
|
+
const removed = this.recentSwapV2HistoryMap.delete(id);
|
|
2476
|
+
|
|
2477
|
+
if (removed && history?.backgroundExecutionId) {
|
|
2478
|
+
this.publisher.publish({
|
|
2479
|
+
type: "remove",
|
|
2480
|
+
executionId: history.backgroundExecutionId,
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
return removed;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
@action
|
|
2488
|
+
clearAllRecentSwapV2History(): void {
|
|
2489
|
+
const executionIds: string[] = [];
|
|
2490
|
+
for (const history of this.recentSwapV2HistoryMap.values()) {
|
|
2491
|
+
if (history.backgroundExecutionId) {
|
|
2492
|
+
executionIds.push(history.backgroundExecutionId);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
this.recentSwapV2HistoryMap.clear();
|
|
2497
|
+
|
|
2498
|
+
for (const executionId of executionIds) {
|
|
2499
|
+
this.publisher.publish({
|
|
2500
|
+
type: "remove",
|
|
2501
|
+
executionId,
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
protected notifySwapV2Success(history: SwapV2History) {
|
|
2507
|
+
const notificationInfo = history.notificationInfo;
|
|
2508
|
+
if (!notificationInfo || history.notified) {
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
const latestResAmount =
|
|
2513
|
+
history.resAmount.length > 0
|
|
2514
|
+
? history.resAmount[history.resAmount.length - 1]
|
|
2515
|
+
: undefined;
|
|
2516
|
+
if (!latestResAmount || latestResAmount.length === 0) {
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
const chainInfo = this.chainsService.getChainInfo(history.toChainId);
|
|
2521
|
+
if (!chainInfo) {
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
const assetsText = latestResAmount
|
|
2526
|
+
.map((amt) => {
|
|
2527
|
+
const currency = notificationInfo.currencies.find(
|
|
2528
|
+
(cur) => cur.coinMinimalDenom === amt.denom
|
|
2529
|
+
);
|
|
2530
|
+
if (!currency) {
|
|
2531
|
+
return undefined;
|
|
2532
|
+
}
|
|
2533
|
+
return new CoinPretty(currency, amt.amount)
|
|
2534
|
+
.hideIBCMetadata(true)
|
|
2535
|
+
.shrink(true)
|
|
2536
|
+
.maxDecimals(6)
|
|
2537
|
+
.inequalitySymbol(true)
|
|
2538
|
+
.trim(true)
|
|
2539
|
+
.toString();
|
|
2540
|
+
})
|
|
2541
|
+
.filter((text): text is string => Boolean(text));
|
|
2542
|
+
|
|
2543
|
+
if (assetsText.length === 0) {
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
runInAction(() => {
|
|
2548
|
+
history.notified = true;
|
|
2549
|
+
});
|
|
2550
|
+
|
|
2551
|
+
this.notification.create({
|
|
2552
|
+
iconRelativeUrl: "assets/logo-256.png",
|
|
2553
|
+
title: "Swap Succeeded",
|
|
2554
|
+
message: `${assetsText.join(", ")} received on ${chainInfo.chainName}`,
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
@action
|
|
2559
|
+
hideSwapV2History(id: string): boolean {
|
|
2560
|
+
const history = this.getRecentSwapV2History(id);
|
|
2561
|
+
if (!history) {
|
|
2562
|
+
return false;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
if (!history.backgroundExecutionId) {
|
|
2566
|
+
return false;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// only hide if multi tx case
|
|
2570
|
+
history.hidden = true;
|
|
2571
|
+
return true;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
@action
|
|
2575
|
+
showSwapV2History(id: string): boolean {
|
|
2576
|
+
const history = this.getRecentSwapV2History(id);
|
|
2577
|
+
if (!history) {
|
|
2578
|
+
return false;
|
|
2579
|
+
}
|
|
2580
|
+
history.hidden = false;
|
|
2581
|
+
return true;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
@action
|
|
2585
|
+
setSwapV2HistoryError(id: string, error: string): boolean {
|
|
2586
|
+
const history = this.getRecentSwapV2History(id);
|
|
2587
|
+
if (!history) {
|
|
2588
|
+
return false;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
history.trackError = error;
|
|
2592
|
+
history.trackDone = true;
|
|
2593
|
+
return true;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
@action
|
|
2597
|
+
clearSwapV2HistoryBackgroundExecutionId(id: string): boolean {
|
|
2598
|
+
const history = this.getRecentSwapV2History(id);
|
|
2599
|
+
if (!history) {
|
|
2600
|
+
return false;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
history.backgroundExecutionId = undefined;
|
|
2604
|
+
return true;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
@action
|
|
2608
|
+
setSwapV2AdditionalTrackingData(
|
|
2609
|
+
id: string,
|
|
2610
|
+
data:
|
|
2611
|
+
| { type: "evm"; chainId: string; txHash: string }
|
|
2612
|
+
| {
|
|
2613
|
+
type: "cosmos-ibc";
|
|
2614
|
+
ibcSwapData: IBCSwapMinimalTrackingData;
|
|
2615
|
+
txHash: string;
|
|
2616
|
+
}
|
|
2617
|
+
): boolean {
|
|
2618
|
+
const history = this.getRecentSwapV2History(id);
|
|
2619
|
+
if (!history) {
|
|
2620
|
+
return false;
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
if (data.type === "cosmos-ibc") {
|
|
2624
|
+
history.additionalTrackingData = {
|
|
2625
|
+
type: "cosmos-ibc",
|
|
2626
|
+
chainId: data.ibcSwapData.chainId,
|
|
2627
|
+
swapReceiver: data.ibcSwapData.swapReceiver,
|
|
2628
|
+
swapChannelIndex: data.ibcSwapData.swapChannelIndex,
|
|
2629
|
+
txHash: data.txHash,
|
|
2630
|
+
txFulfilled: false,
|
|
2631
|
+
packetTimeout: false,
|
|
2632
|
+
ibcHistory: data.ibcSwapData.ibcChannels.map((ch) => ({
|
|
2633
|
+
portId: ch.portId,
|
|
2634
|
+
channelId: ch.channelId,
|
|
2635
|
+
counterpartyChainId: ch.counterpartyChainId,
|
|
2636
|
+
completed: false,
|
|
2637
|
+
})),
|
|
2638
|
+
};
|
|
2639
|
+
} else {
|
|
2640
|
+
history.additionalTrackingData = data;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
history.additionalTrackDone = false;
|
|
2644
|
+
history.additionalTrackError = undefined;
|
|
2645
|
+
|
|
2646
|
+
this.trackSwapV2AdditionalRecursive(id);
|
|
2647
|
+
|
|
2648
|
+
return true;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
trackSwapV2AdditionalRecursive(id: string): void {
|
|
2652
|
+
const history = this.getRecentSwapV2History(id);
|
|
2653
|
+
if (!history) {
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// no additional tracking data
|
|
2658
|
+
if (!history.additionalTrackingData) {
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// already done
|
|
2663
|
+
if (history.additionalTrackDone) {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
if (history.additionalTrackingData.type === "evm") {
|
|
2668
|
+
this.trackSwapV2AdditionalEVM(id);
|
|
2669
|
+
} else if (history.additionalTrackingData.type === "cosmos-ibc") {
|
|
2670
|
+
this.trackIBCPacketForwardingRecursive((onFulfill, onClose, onError) => {
|
|
2671
|
+
this.trackSwapV2AdditionalCosmosIBCInternal(
|
|
2672
|
+
id,
|
|
2673
|
+
onFulfill,
|
|
2674
|
+
onClose,
|
|
2675
|
+
onError
|
|
2676
|
+
);
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
protected trackSwapV2AdditionalEVM(id: string): void {
|
|
2682
|
+
const history = this.getRecentSwapV2History(id);
|
|
2683
|
+
if (!history) {
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
if (history.additionalTrackingData?.type !== "evm") {
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
const txHash = history.additionalTrackingData.txHash;
|
|
2692
|
+
|
|
2693
|
+
this.traceEVMTransactionResult({
|
|
2694
|
+
chainId: history.toChainId,
|
|
2695
|
+
txHash,
|
|
2696
|
+
recipient: history.recipient,
|
|
2697
|
+
targetDenom: history.destinationAsset.denom,
|
|
2698
|
+
onResult: (result) => {
|
|
2699
|
+
runInAction(() => {
|
|
2700
|
+
if (result.success && result.resAmount) {
|
|
2701
|
+
history.resAmount.push(result.resAmount);
|
|
2702
|
+
history.assetLocationInfo = undefined;
|
|
2703
|
+
this.notifySwapV2Success(history);
|
|
2704
|
+
} else if (result.refundInfo) {
|
|
2705
|
+
history.additionalTrackError = result.error;
|
|
2706
|
+
history.assetLocationInfo = {
|
|
2707
|
+
...result.refundInfo,
|
|
2708
|
+
type: "refund",
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
history.additionalTrackDone = true;
|
|
2712
|
+
});
|
|
2713
|
+
},
|
|
2714
|
+
onFulfill: () => {
|
|
2715
|
+
runInAction(() => {
|
|
2716
|
+
history.additionalTrackDone = true;
|
|
2717
|
+
});
|
|
2718
|
+
},
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
protected trackSwapV2AdditionalCosmosIBCInternal(
|
|
2723
|
+
id: string,
|
|
2724
|
+
onFulfill: () => void,
|
|
2725
|
+
onClose: () => void,
|
|
2726
|
+
onError: () => void
|
|
2727
|
+
): void {
|
|
2728
|
+
const history = this.getRecentSwapV2History(id);
|
|
2729
|
+
if (!history) {
|
|
2730
|
+
onFulfill();
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
if (history.additionalTrackDone) {
|
|
2735
|
+
onFulfill();
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
const existingTrackingData = history.additionalTrackingData;
|
|
2740
|
+
if (!existingTrackingData || existingTrackingData.type !== "cosmos-ibc") {
|
|
2741
|
+
onFulfill();
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
const trackingData = existingTrackingData;
|
|
2746
|
+
|
|
2747
|
+
const { chainId, txHash, ibcHistory, swapReceiver, txFulfilled } =
|
|
2748
|
+
trackingData;
|
|
2749
|
+
|
|
2750
|
+
if (!txFulfilled) {
|
|
2751
|
+
this.trackIBCTxFulfillment({
|
|
2752
|
+
chainId,
|
|
2753
|
+
txHash,
|
|
2754
|
+
ibcHistory,
|
|
2755
|
+
swapReceiver,
|
|
2756
|
+
onTxFulfilled: (_tx, firstHopResAmount) => {
|
|
2757
|
+
runInAction(() => {
|
|
2758
|
+
trackingData.txFulfilled = true;
|
|
2759
|
+
|
|
2760
|
+
if (firstHopResAmount) {
|
|
2761
|
+
history.resAmount.push(firstHopResAmount);
|
|
2762
|
+
for (
|
|
2763
|
+
let i = history.routeIndex;
|
|
2764
|
+
i < history.simpleRoute.length;
|
|
2765
|
+
i++
|
|
2766
|
+
) {
|
|
2767
|
+
const route = history.simpleRoute[i];
|
|
2768
|
+
if (route.chainId === chainId) {
|
|
2769
|
+
history.routeIndex = i + 1; // move to next route
|
|
2770
|
+
break;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
});
|
|
2775
|
+
this.trackIBCPacketForwardingRecursive(
|
|
2776
|
+
(onFulfill, onClose, onError) => {
|
|
2777
|
+
this.trackSwapV2AdditionalCosmosIBCInternal(
|
|
2778
|
+
id,
|
|
2779
|
+
onFulfill,
|
|
2780
|
+
onClose,
|
|
2781
|
+
onError
|
|
2782
|
+
);
|
|
2783
|
+
}
|
|
2784
|
+
);
|
|
2785
|
+
},
|
|
2786
|
+
onTxError: (error) => {
|
|
2787
|
+
runInAction(() => {
|
|
2788
|
+
history.additionalTrackError = error;
|
|
2789
|
+
history.additionalTrackDone = true;
|
|
2790
|
+
});
|
|
2791
|
+
},
|
|
2792
|
+
onFulfill,
|
|
2793
|
+
onClose,
|
|
2794
|
+
onError,
|
|
2795
|
+
});
|
|
2796
|
+
return;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
if (
|
|
2800
|
+
this.handleIbcRewindIfNeeded({
|
|
2801
|
+
sourceChainId: chainId,
|
|
2802
|
+
ibcHistory,
|
|
2803
|
+
packetTimeout: trackingData.packetTimeout,
|
|
2804
|
+
swapContext:
|
|
2805
|
+
history.additionalTrackingData?.type === "cosmos-ibc"
|
|
2806
|
+
? {
|
|
2807
|
+
swapReceiver,
|
|
2808
|
+
swapChannelIndex:
|
|
2809
|
+
history.additionalTrackingData.swapChannelIndex,
|
|
2810
|
+
setSwapRefundInfo: (refundInfo) => {
|
|
2811
|
+
runInAction(() => {
|
|
2812
|
+
history.assetLocationInfo = {
|
|
2813
|
+
...refundInfo,
|
|
2814
|
+
type: "refund",
|
|
2815
|
+
};
|
|
2816
|
+
});
|
|
2817
|
+
},
|
|
2818
|
+
}
|
|
2819
|
+
: undefined,
|
|
2820
|
+
onFulfill,
|
|
2821
|
+
onClose,
|
|
2822
|
+
onError,
|
|
2823
|
+
onRewindComplete: () => {
|
|
2824
|
+
// rewound되었다면 실패 처리하는 것이 맞겠지...
|
|
2825
|
+
runInAction(() => {
|
|
2826
|
+
history.status = SwapV2TxStatus.FAILED;
|
|
2827
|
+
history.additionalTrackDone = true;
|
|
2828
|
+
|
|
2829
|
+
const rewoundChainId = ibcHistory.find(
|
|
2830
|
+
(h) => h.rewound
|
|
2831
|
+
)?.counterpartyChainId;
|
|
2832
|
+
if (rewoundChainId) {
|
|
2833
|
+
for (let i = history.simpleRoute.length - 1; i >= 0; i--) {
|
|
2834
|
+
const route = history.simpleRoute[i];
|
|
2835
|
+
if (route.chainId === rewoundChainId) {
|
|
2836
|
+
history.routeIndex = i; // 실패한 채널 인덱스로 이동
|
|
2837
|
+
break;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
});
|
|
2842
|
+
|
|
2843
|
+
this.trackIBCPacketForwardingRecursive(
|
|
2844
|
+
(onFulfill, onClose, onError) => {
|
|
2845
|
+
this.trackSwapV2AdditionalCosmosIBCInternal(
|
|
2846
|
+
id,
|
|
2847
|
+
onFulfill,
|
|
2848
|
+
onClose,
|
|
2849
|
+
onError
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2852
|
+
);
|
|
2853
|
+
},
|
|
2854
|
+
})
|
|
2855
|
+
) {
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
this.trackIbcHopFlowWithTimeout({
|
|
2860
|
+
ibcHistory,
|
|
2861
|
+
sourceChainId: chainId,
|
|
2862
|
+
swapReceiver,
|
|
2863
|
+
destinationAsset: history.destinationAsset,
|
|
2864
|
+
onHopCompleted: (resAmount) => {
|
|
2865
|
+
runInAction(() => {
|
|
2866
|
+
if (resAmount) {
|
|
2867
|
+
history.resAmount.push(resAmount);
|
|
2868
|
+
}
|
|
2869
|
+
});
|
|
2870
|
+
},
|
|
2871
|
+
onAllCompleted: () => {
|
|
2872
|
+
runInAction(() => {
|
|
2873
|
+
history.additionalTrackDone = true;
|
|
2874
|
+
history.assetLocationInfo = undefined;
|
|
2875
|
+
// Update routeIndex to the last index to show completion state in UI
|
|
2876
|
+
history.routeIndex = history.simpleRoute.length;
|
|
2877
|
+
this.notifySwapV2Success(history);
|
|
2878
|
+
});
|
|
2879
|
+
},
|
|
2880
|
+
onContinue: () => {
|
|
2881
|
+
this.trackIBCPacketForwardingRecursive(
|
|
2882
|
+
(onFulfill, onClose, onError) => {
|
|
2883
|
+
this.trackSwapV2AdditionalCosmosIBCInternal(
|
|
2884
|
+
id,
|
|
2885
|
+
onFulfill,
|
|
2886
|
+
onClose,
|
|
2887
|
+
onError
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
);
|
|
2891
|
+
},
|
|
2892
|
+
onRetry: () => {
|
|
2893
|
+
this.trackIBCPacketForwardingRecursive(
|
|
2894
|
+
(onFulfill, onClose, onError) => {
|
|
2895
|
+
this.trackSwapV2AdditionalCosmosIBCInternal(
|
|
2896
|
+
id,
|
|
2897
|
+
onFulfill,
|
|
2898
|
+
onClose,
|
|
2899
|
+
onError
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
);
|
|
2903
|
+
},
|
|
2904
|
+
onPacketTimeout: () => {
|
|
2905
|
+
runInAction(() => {
|
|
2906
|
+
trackingData.packetTimeout = true;
|
|
2907
|
+
});
|
|
2908
|
+
},
|
|
2909
|
+
onDynamicHopDetected: () => {
|
|
2910
|
+
runInAction(() => {
|
|
2911
|
+
trackingData.dynamicHopDetected = true;
|
|
2912
|
+
});
|
|
2913
|
+
},
|
|
2914
|
+
onFulfill,
|
|
2915
|
+
onClose,
|
|
2916
|
+
onError,
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// ============================================================================
|
|
2921
|
+
// IBC Packet Tracking Core Functions
|
|
2922
|
+
// ============================================================================
|
|
2923
|
+
|
|
2924
|
+
/**
|
|
2925
|
+
* IBC tx 완료 대기 및 첫 번째 hop res amount 추출
|
|
2926
|
+
*/
|
|
2927
|
+
protected trackIBCTxFulfillment(params: {
|
|
2928
|
+
chainId: string;
|
|
2929
|
+
txHash: string;
|
|
2930
|
+
ibcHistory: IbcHop[];
|
|
2931
|
+
swapReceiver?: string[];
|
|
2932
|
+
onTxFulfilled: (
|
|
2933
|
+
tx: any,
|
|
2934
|
+
firstHopResAmount?: { amount: string; denom: string }[]
|
|
2935
|
+
) => void;
|
|
2936
|
+
onTxError: (error: string) => void;
|
|
2937
|
+
onFulfill: () => void;
|
|
2938
|
+
onClose: () => void;
|
|
2939
|
+
onError: () => void;
|
|
2940
|
+
}): void {
|
|
2941
|
+
const {
|
|
2942
|
+
chainId,
|
|
2943
|
+
txHash,
|
|
2944
|
+
ibcHistory,
|
|
2945
|
+
swapReceiver,
|
|
2946
|
+
onTxFulfilled,
|
|
2947
|
+
onTxError,
|
|
2948
|
+
onFulfill,
|
|
2949
|
+
onClose,
|
|
2950
|
+
onError,
|
|
2951
|
+
} = params;
|
|
2952
|
+
|
|
2953
|
+
const chainInfo = this.chainsService.getChainInfo(chainId);
|
|
2954
|
+
if (!chainInfo) {
|
|
2955
|
+
onFulfill();
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
const txTracer = new TendermintTxTracer(chainInfo.rpc, "/websocket");
|
|
2960
|
+
txTracer.addEventListener("close", onClose);
|
|
2961
|
+
txTracer.addEventListener("error", onError);
|
|
2962
|
+
|
|
2963
|
+
txTracer.traceTx(Buffer.from(txHash, "hex")).then((tx) => {
|
|
2964
|
+
txTracer.close();
|
|
2965
|
+
|
|
2966
|
+
if (tx.code != null && tx.code !== 0) {
|
|
2967
|
+
onTxError(tx.log || tx.raw_log || "Tx failed");
|
|
2968
|
+
onFulfill();
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
let resAmount: { amount: string; denom: string }[] | undefined;
|
|
2973
|
+
|
|
2974
|
+
if (swapReceiver && swapReceiver.length > 0) {
|
|
2975
|
+
resAmount = this.getIBCSwapResAmountFromTx(tx, swapReceiver[0]);
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
if (ibcHistory.length > 0) {
|
|
2979
|
+
const firstChannel = ibcHistory[0];
|
|
2980
|
+
|
|
2981
|
+
firstChannel.sequence = this.getIBCPacketSequenceFromTx(
|
|
2982
|
+
tx,
|
|
2983
|
+
firstChannel.portId,
|
|
2984
|
+
firstChannel.channelId
|
|
2985
|
+
);
|
|
2986
|
+
firstChannel.dstChannelId = this.getDstChannelIdFromTx(
|
|
2987
|
+
tx,
|
|
2988
|
+
firstChannel.portId,
|
|
2989
|
+
firstChannel.channelId
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
onTxFulfilled(tx, resAmount);
|
|
2993
|
+
onFulfill();
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* IBC rewind 필요 여부 확인 및 rewind 처리
|
|
2999
|
+
*/
|
|
3000
|
+
protected handleIbcRewindIfNeeded(params: {
|
|
3001
|
+
sourceChainId: string;
|
|
3002
|
+
ibcHistory: IbcHop[];
|
|
3003
|
+
packetTimeout?: boolean;
|
|
3004
|
+
swapContext?: {
|
|
3005
|
+
swapReceiver: string[];
|
|
3006
|
+
swapChannelIndex: number;
|
|
3007
|
+
setSwapRefundInfo?: (refundInfo: {
|
|
3008
|
+
chainId: string;
|
|
3009
|
+
amount: { amount: string; denom: string }[];
|
|
3010
|
+
}) => void;
|
|
3011
|
+
};
|
|
3012
|
+
onFulfill: () => void;
|
|
3013
|
+
onClose: () => void;
|
|
3014
|
+
onError: () => void;
|
|
3015
|
+
onRewindComplete: () => void;
|
|
3016
|
+
}): boolean {
|
|
3017
|
+
const {
|
|
3018
|
+
sourceChainId,
|
|
3019
|
+
ibcHistory,
|
|
3020
|
+
packetTimeout,
|
|
3021
|
+
swapContext,
|
|
3022
|
+
onFulfill,
|
|
3023
|
+
onClose,
|
|
3024
|
+
onError,
|
|
3025
|
+
onRewindComplete,
|
|
3026
|
+
} = params;
|
|
3027
|
+
|
|
3028
|
+
if (ibcHistory.length === 0) {
|
|
3029
|
+
return false;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
const needRewind = ibcHistory.find((h) => h.error != null) != null;
|
|
3033
|
+
if (!needRewind) {
|
|
3034
|
+
return false;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
if (ibcHistory.find((h) => h.rewoundButNextRewindingBlocked)) {
|
|
3038
|
+
onFulfill();
|
|
3039
|
+
return true;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
const isTimeoutPacket = packetTimeout ?? false;
|
|
3043
|
+
const lastRewoundChannelIndex = ibcHistory.findIndex((h) => {
|
|
3044
|
+
if (h.rewound) {
|
|
3045
|
+
return true;
|
|
3046
|
+
}
|
|
3047
|
+
});
|
|
3048
|
+
const targetChannel = (() => {
|
|
3049
|
+
if (lastRewoundChannelIndex >= 0) {
|
|
3050
|
+
if (lastRewoundChannelIndex === 0) {
|
|
3051
|
+
return undefined;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
return ibcHistory[lastRewoundChannelIndex - 1];
|
|
3055
|
+
}
|
|
3056
|
+
return ibcHistory.find((h) => h.error != null);
|
|
3057
|
+
})();
|
|
3058
|
+
const isSwapTargetChannel = !!(
|
|
3059
|
+
targetChannel &&
|
|
3060
|
+
swapContext &&
|
|
3061
|
+
ibcHistory.indexOf(targetChannel) === swapContext.swapChannelIndex + 1
|
|
3062
|
+
);
|
|
3063
|
+
if (targetChannel !== undefined && targetChannel.sequence !== undefined) {
|
|
3064
|
+
const targetSequence = targetChannel.sequence;
|
|
3065
|
+
const prevChainInfo = (() => {
|
|
3066
|
+
const targetChannelIndex = ibcHistory.findIndex(
|
|
3067
|
+
(h) => h === targetChannel
|
|
3068
|
+
);
|
|
3069
|
+
if (targetChannelIndex < 0) {
|
|
3070
|
+
return undefined;
|
|
3071
|
+
}
|
|
3072
|
+
if (targetChannelIndex === 0) {
|
|
3073
|
+
return this.chainsService.getChainInfo(sourceChainId);
|
|
3074
|
+
}
|
|
3075
|
+
return this.chainsService.getChainInfo(
|
|
3076
|
+
ibcHistory[targetChannelIndex - 1].counterpartyChainId
|
|
3077
|
+
);
|
|
3078
|
+
})();
|
|
3079
|
+
|
|
3080
|
+
if (prevChainInfo) {
|
|
3081
|
+
const txTracer = new TendermintTxTracer(
|
|
3082
|
+
prevChainInfo.rpc,
|
|
3083
|
+
"/websocket"
|
|
3084
|
+
);
|
|
3085
|
+
txTracer.addEventListener("close", onClose);
|
|
3086
|
+
txTracer.addEventListener("error", onError);
|
|
3087
|
+
txTracer
|
|
3088
|
+
.traceTx(
|
|
3089
|
+
isTimeoutPacket
|
|
3090
|
+
? {
|
|
3091
|
+
// "timeout_packet.packet_src_port": targetChannel.portId,
|
|
3092
|
+
"timeout_packet.packet_src_channel": targetChannel.channelId,
|
|
3093
|
+
"timeout_packet.packet_sequence": targetChannel.sequence,
|
|
3094
|
+
}
|
|
3095
|
+
: {
|
|
3096
|
+
// "acknowledge_packet.packet_src_port": targetChannel.portId,
|
|
3097
|
+
"acknowledge_packet.packet_src_channel":
|
|
3098
|
+
targetChannel.channelId,
|
|
3099
|
+
"acknowledge_packet.packet_sequence": targetChannel.sequence,
|
|
3100
|
+
}
|
|
3101
|
+
)
|
|
3102
|
+
.then((res: any) => {
|
|
3103
|
+
txTracer.close();
|
|
3104
|
+
|
|
3105
|
+
if (!res) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
runInAction(() => {
|
|
3110
|
+
if (isSwapTargetChannel) {
|
|
3111
|
+
const txs = res.txs
|
|
3112
|
+
? res.txs.map((res: any) => res.tx_result || res)
|
|
3113
|
+
: [res.tx_result || res];
|
|
3114
|
+
if (txs && Array.isArray(txs)) {
|
|
3115
|
+
for (const tx of txs) {
|
|
3116
|
+
const index = isTimeoutPacket
|
|
3117
|
+
? this.getIBCTimeoutPacketIndexFromTx(
|
|
3118
|
+
tx,
|
|
3119
|
+
targetChannel.portId,
|
|
3120
|
+
targetChannel.channelId,
|
|
3121
|
+
targetSequence
|
|
3122
|
+
)
|
|
3123
|
+
: this.getIBCAcknowledgementPacketIndexFromTx(
|
|
3124
|
+
tx,
|
|
3125
|
+
targetChannel.portId,
|
|
3126
|
+
targetChannel.channelId,
|
|
3127
|
+
targetSequence
|
|
3128
|
+
);
|
|
3129
|
+
if (index >= 0) {
|
|
3130
|
+
// 좀 빡치게 timeout packet은 refund 로직이 실행되고 나서 "timeout_packet" event가 발생한다.
|
|
3131
|
+
const refunded = isTimeoutPacket
|
|
3132
|
+
? this.getIBCSwapResAmountFromTx(
|
|
3133
|
+
tx,
|
|
3134
|
+
swapContext.swapReceiver[
|
|
3135
|
+
swapContext.swapChannelIndex + 1
|
|
3136
|
+
],
|
|
3137
|
+
(() => {
|
|
3138
|
+
const i =
|
|
3139
|
+
this.getLastIBCTimeoutPacketBeforeIndexFromTx(
|
|
3140
|
+
tx,
|
|
3141
|
+
index
|
|
3142
|
+
);
|
|
3143
|
+
|
|
3144
|
+
if (i < 0) {
|
|
3145
|
+
return 0;
|
|
3146
|
+
}
|
|
3147
|
+
return i;
|
|
3148
|
+
})(),
|
|
3149
|
+
index
|
|
3150
|
+
)
|
|
3151
|
+
: this.getIBCSwapResAmountFromTx(
|
|
3152
|
+
tx,
|
|
3153
|
+
swapContext.swapReceiver[
|
|
3154
|
+
swapContext.swapChannelIndex + 1
|
|
3155
|
+
],
|
|
3156
|
+
index
|
|
3157
|
+
);
|
|
3158
|
+
|
|
3159
|
+
swapContext.setSwapRefundInfo?.({
|
|
3160
|
+
chainId: prevChainInfo.chainId,
|
|
3161
|
+
amount: refunded,
|
|
3162
|
+
});
|
|
3163
|
+
|
|
3164
|
+
targetChannel.rewoundButNextRewindingBlocked = true;
|
|
3165
|
+
break;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
targetChannel.rewound = true;
|
|
3171
|
+
});
|
|
3172
|
+
onFulfill();
|
|
3173
|
+
onRewindComplete();
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
return true;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
protected trackIbcHopFlowWithTimeout(params: {
|
|
3181
|
+
ibcHistory: IbcHop[];
|
|
3182
|
+
sourceChainId: string;
|
|
3183
|
+
swapReceiver?: string[];
|
|
3184
|
+
destinationAsset?: { chainId: string; denom: string };
|
|
3185
|
+
onHopCompleted?: (
|
|
3186
|
+
resAmount?: { amount: string; denom: string }[],
|
|
3187
|
+
tx?: any
|
|
3188
|
+
) => void;
|
|
3189
|
+
onAllCompleted: () => void;
|
|
3190
|
+
onContinue: () => void;
|
|
3191
|
+
onRetry: () => void;
|
|
3192
|
+
onPacketTimeout?: () => void;
|
|
3193
|
+
onDynamicHopDetected?: () => void;
|
|
3194
|
+
onFulfill: () => void;
|
|
3195
|
+
onClose: () => void;
|
|
3196
|
+
onError: () => void;
|
|
3197
|
+
}): void {
|
|
3198
|
+
const {
|
|
3199
|
+
ibcHistory,
|
|
3200
|
+
sourceChainId,
|
|
3201
|
+
swapReceiver,
|
|
3202
|
+
destinationAsset,
|
|
3203
|
+
onHopCompleted,
|
|
3204
|
+
onAllCompleted,
|
|
3205
|
+
onContinue,
|
|
3206
|
+
onRetry,
|
|
3207
|
+
onPacketTimeout,
|
|
3208
|
+
onDynamicHopDetected,
|
|
3209
|
+
onFulfill,
|
|
3210
|
+
onClose,
|
|
3211
|
+
onError,
|
|
3212
|
+
} = params;
|
|
3213
|
+
|
|
3214
|
+
const targetChannelIndex = ibcHistory.findIndex((history) => {
|
|
3215
|
+
return !history.completed;
|
|
3216
|
+
});
|
|
3217
|
+
|
|
3218
|
+
const targetChannel = ibcHistory[targetChannelIndex];
|
|
3219
|
+
if (!targetChannel || !targetChannel.sequence) {
|
|
3220
|
+
onFulfill();
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
const closables: {
|
|
3225
|
+
readyState: WsReadyState;
|
|
3226
|
+
close: () => void;
|
|
3227
|
+
}[] = [];
|
|
3228
|
+
|
|
3229
|
+
const closeClosables = () => {
|
|
3230
|
+
closables.forEach((closable) => {
|
|
3231
|
+
if (
|
|
3232
|
+
closable.readyState === WsReadyState.OPEN ||
|
|
3233
|
+
closable.readyState === WsReadyState.CONNECTING
|
|
3234
|
+
) {
|
|
3235
|
+
closable.close();
|
|
3236
|
+
}
|
|
3237
|
+
});
|
|
3238
|
+
};
|
|
3239
|
+
|
|
3240
|
+
let _onFulfillOnce = false;
|
|
3241
|
+
const onFulfillOnce = () => {
|
|
3242
|
+
if (!_onFulfillOnce) {
|
|
3243
|
+
_onFulfillOnce = true;
|
|
3244
|
+
closeClosables();
|
|
3245
|
+
onFulfill();
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
|
|
3249
|
+
let _onCloseOnce = false;
|
|
3250
|
+
const onCloseOnce = () => {
|
|
3251
|
+
if (!_onCloseOnce) {
|
|
3252
|
+
_onCloseOnce = true;
|
|
3253
|
+
closeClosables();
|
|
3254
|
+
onClose();
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
|
|
3258
|
+
let _onErrorOnce = false;
|
|
3259
|
+
const onErrorOnce = () => {
|
|
3260
|
+
if (!_onErrorOnce) {
|
|
3261
|
+
_onErrorOnce = true;
|
|
3262
|
+
closeClosables();
|
|
3263
|
+
onError();
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
|
|
3267
|
+
const registerClosable = (closable: {
|
|
3268
|
+
readyState: WsReadyState;
|
|
3269
|
+
close: () => void;
|
|
3270
|
+
}) => closables.push(closable);
|
|
3271
|
+
|
|
3272
|
+
let hopFailed = false;
|
|
3273
|
+
|
|
3274
|
+
const hopTracer = this.trackIBCHopRecvPacket({
|
|
3275
|
+
ibcHistory,
|
|
3276
|
+
targetChannelIndex,
|
|
3277
|
+
swapReceiver,
|
|
3278
|
+
destinationAsset,
|
|
3279
|
+
onHopCompleted: (resAmount, tx) => {
|
|
3280
|
+
onHopCompleted?.(resAmount, tx);
|
|
3281
|
+
|
|
3282
|
+
const sequence = targetChannel.sequence;
|
|
3283
|
+
if (!sequence) {
|
|
3284
|
+
hopFailed = true;
|
|
3285
|
+
onFulfillOnce();
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
if (!tx) {
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
const txs = tx.txs
|
|
3294
|
+
? tx.txs.map((res: any) => res.tx_result || res)
|
|
3295
|
+
: [tx.tx_result || tx];
|
|
3296
|
+
|
|
3297
|
+
for (const txItem of txs) {
|
|
3298
|
+
try {
|
|
3299
|
+
const ack = this.getIBCWriteAcknowledgementAckFromTx(
|
|
3300
|
+
txItem,
|
|
3301
|
+
targetChannel.portId,
|
|
3302
|
+
targetChannel.channelId,
|
|
3303
|
+
sequence
|
|
3304
|
+
);
|
|
3305
|
+
|
|
3306
|
+
if (ack && ack.length > 0) {
|
|
3307
|
+
const str = Buffer.from(ack);
|
|
3308
|
+
try {
|
|
3309
|
+
const decoded = JSON.parse(str.toString());
|
|
3310
|
+
if (decoded.error) {
|
|
3311
|
+
runInAction(() => {
|
|
3312
|
+
targetChannel.error = "Packet processing failed";
|
|
3313
|
+
});
|
|
3314
|
+
hopFailed = true;
|
|
3315
|
+
onFulfillOnce();
|
|
3316
|
+
onRetry();
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
} catch (e) {
|
|
3320
|
+
console.log(e);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
} catch {
|
|
3324
|
+
// noop
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
const index = this.getIBCRecvPacketIndexFromTx(
|
|
3328
|
+
txItem,
|
|
3329
|
+
targetChannel.portId,
|
|
3330
|
+
targetChannel.channelId,
|
|
3331
|
+
sequence
|
|
3332
|
+
);
|
|
3333
|
+
|
|
3334
|
+
if (index >= 0) {
|
|
3335
|
+
break;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
},
|
|
3339
|
+
onAllCompleted: () => {
|
|
3340
|
+
if (hopFailed) {
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
onAllCompleted();
|
|
3344
|
+
onFulfillOnce();
|
|
3345
|
+
},
|
|
3346
|
+
onContinue: () => {
|
|
3347
|
+
if (hopFailed) {
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
onContinue();
|
|
3351
|
+
onFulfillOnce();
|
|
3352
|
+
},
|
|
3353
|
+
onFulfill: onFulfillOnce,
|
|
3354
|
+
onClose: onCloseOnce,
|
|
3355
|
+
onError: onErrorOnce,
|
|
3356
|
+
onDynamicHopDetected: (hopInfo) => {
|
|
3357
|
+
if (hopFailed) {
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// 동적으로 감지된 새 홉을 ibcHistory에 추가한다.
|
|
3362
|
+
runInAction(() => {
|
|
3363
|
+
ibcHistory.push({
|
|
3364
|
+
portId: hopInfo.portId,
|
|
3365
|
+
channelId: hopInfo.channelId,
|
|
3366
|
+
counterpartyChainId: hopInfo.counterpartyChainId,
|
|
3367
|
+
sequence: hopInfo.sequence,
|
|
3368
|
+
dstChannelId: hopInfo.dstChannelId,
|
|
3369
|
+
completed: false,
|
|
3370
|
+
});
|
|
3371
|
+
|
|
3372
|
+
if (swapReceiver && hopInfo.packetData.receiver) {
|
|
3373
|
+
swapReceiver.push(hopInfo.packetData.receiver);
|
|
3374
|
+
}
|
|
3375
|
+
});
|
|
3376
|
+
|
|
3377
|
+
onDynamicHopDetected?.();
|
|
3378
|
+
onContinue();
|
|
3379
|
+
onFulfillOnce();
|
|
3380
|
+
},
|
|
3381
|
+
});
|
|
3382
|
+
|
|
3383
|
+
if (hopTracer) {
|
|
3384
|
+
registerClosable(hopTracer);
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
let prevChainId: string | undefined;
|
|
3388
|
+
if (targetChannelIndex > 0) {
|
|
3389
|
+
prevChainId = ibcHistory[targetChannelIndex - 1].counterpartyChainId;
|
|
3390
|
+
} else {
|
|
3391
|
+
prevChainId = sourceChainId;
|
|
3392
|
+
}
|
|
3393
|
+
if (prevChainId) {
|
|
3394
|
+
const prevChainInfo = this.chainsService.getChainInfo(prevChainId);
|
|
3395
|
+
if (prevChainInfo) {
|
|
3396
|
+
const queryEvents: any = {
|
|
3397
|
+
// acknowledge_packet과는 다르게 timeout_packet은 이전의 체인의 이벤트로부터만 알 수 있다.
|
|
3398
|
+
// 방법이 없기 때문에 여기서 이전의 체인으로부터 subscribe를 해서 이벤트를 받아야 한다.
|
|
3399
|
+
// 하지만 이 경우 ibc error tracking 로직에서 이것과 똑같은 subscription을 한번 더 하게 된다.
|
|
3400
|
+
// 이미 로직이 많이 복잡하기 때문에 로직을 덜 복잡하게 하기 위해서 이러한 비효율성(?)을 감수한다.
|
|
3401
|
+
// "timeout_packet.packet_src_port": targetChannel.portId,
|
|
3402
|
+
"timeout_packet.packet_src_channel": targetChannel.channelId,
|
|
3403
|
+
"timeout_packet.packet_sequence": targetChannel.sequence,
|
|
3404
|
+
};
|
|
3405
|
+
|
|
3406
|
+
const txTracer = new TendermintTxTracer(
|
|
3407
|
+
prevChainInfo.rpc,
|
|
3408
|
+
"/websocket"
|
|
3409
|
+
);
|
|
3410
|
+
registerClosable(txTracer);
|
|
3411
|
+
txTracer.addEventListener("close", onCloseOnce);
|
|
3412
|
+
txTracer.addEventListener("error", onErrorOnce);
|
|
3413
|
+
txTracer.traceTx(queryEvents).then((res) => {
|
|
3414
|
+
txTracer.close();
|
|
1775
3415
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
history.trackDone = true;
|
|
1780
|
-
onFulfill();
|
|
1781
|
-
}
|
|
1782
|
-
break;
|
|
3416
|
+
if (!res) {
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
1783
3419
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
};
|
|
3420
|
+
runInAction(() => {
|
|
3421
|
+
targetChannel.error = "Packet timeout";
|
|
3422
|
+
onPacketTimeout?.();
|
|
3423
|
+
hopFailed = true;
|
|
3424
|
+
onFulfillOnce();
|
|
3425
|
+
onRetry();
|
|
3426
|
+
});
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
1796
3431
|
|
|
1797
|
-
protected
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
3432
|
+
protected trackIBCHopRecvPacket(params: {
|
|
3433
|
+
ibcHistory: IbcHop[];
|
|
3434
|
+
targetChannelIndex: number;
|
|
3435
|
+
swapReceiver?: string[];
|
|
3436
|
+
destinationAsset?: { chainId: string; denom: string };
|
|
3437
|
+
onHopCompleted: (
|
|
3438
|
+
resAmount?: { amount: string; denom: string }[],
|
|
3439
|
+
tx?: any
|
|
3440
|
+
) => void;
|
|
3441
|
+
onAllCompleted: () => void;
|
|
3442
|
+
onContinue: () => void;
|
|
3443
|
+
onFulfill: () => void;
|
|
3444
|
+
onClose: () => void;
|
|
3445
|
+
onError: () => void;
|
|
3446
|
+
onDynamicHopDetected?: (hopInfo: {
|
|
3447
|
+
portId: string;
|
|
3448
|
+
channelId: string;
|
|
3449
|
+
counterpartyChainId: string;
|
|
3450
|
+
sequence: string;
|
|
3451
|
+
dstChannelId: string;
|
|
3452
|
+
packetData: { denom: string; amount: string; receiver: string };
|
|
3453
|
+
}) => void;
|
|
3454
|
+
}): TendermintTxTracer | undefined {
|
|
3455
|
+
const {
|
|
3456
|
+
ibcHistory,
|
|
3457
|
+
targetChannelIndex,
|
|
3458
|
+
swapReceiver,
|
|
3459
|
+
destinationAsset,
|
|
3460
|
+
onHopCompleted,
|
|
3461
|
+
onAllCompleted,
|
|
3462
|
+
onContinue,
|
|
3463
|
+
onFulfill,
|
|
3464
|
+
onClose,
|
|
3465
|
+
onError,
|
|
3466
|
+
onDynamicHopDetected,
|
|
3467
|
+
} = params;
|
|
3468
|
+
|
|
3469
|
+
const targetChannel = ibcHistory[targetChannelIndex];
|
|
3470
|
+
const nextChannel =
|
|
3471
|
+
targetChannelIndex + 1 < ibcHistory.length
|
|
3472
|
+
? ibcHistory[targetChannelIndex + 1]
|
|
3473
|
+
: undefined;
|
|
3474
|
+
|
|
3475
|
+
if (!targetChannel || !targetChannel.sequence) {
|
|
3476
|
+
onAllCompleted();
|
|
1805
3477
|
return;
|
|
1806
3478
|
}
|
|
3479
|
+
const sequence = targetChannel.sequence;
|
|
1807
3480
|
|
|
1808
|
-
const
|
|
1809
|
-
|
|
3481
|
+
const counterpartyChainInfo = this.chainsService.getChainInfo(
|
|
3482
|
+
targetChannel.counterpartyChainId
|
|
1810
3483
|
);
|
|
1811
|
-
|
|
3484
|
+
|
|
3485
|
+
if (!counterpartyChainInfo) {
|
|
1812
3486
|
onFulfill();
|
|
1813
3487
|
return;
|
|
1814
3488
|
}
|
|
1815
3489
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
3490
|
+
const queryEvents: any = {
|
|
3491
|
+
// "recv_packet.packet_src_port": targetChannel.portId,
|
|
3492
|
+
"recv_packet.packet_dst_channel": targetChannel.dstChannelId,
|
|
3493
|
+
"recv_packet.packet_sequence": targetChannel.sequence,
|
|
3494
|
+
};
|
|
3495
|
+
|
|
3496
|
+
const txTracer = new TendermintTxTracer(
|
|
3497
|
+
counterpartyChainInfo.rpc,
|
|
3498
|
+
"/websocket"
|
|
3499
|
+
);
|
|
3500
|
+
txTracer.addEventListener("close", onClose);
|
|
3501
|
+
txTracer.addEventListener("error", onError);
|
|
3502
|
+
|
|
3503
|
+
txTracer.traceTx(queryEvents).then((res) => {
|
|
3504
|
+
txTracer.close();
|
|
3505
|
+
|
|
3506
|
+
if (!res) {
|
|
3507
|
+
onError();
|
|
1820
3508
|
return;
|
|
1821
3509
|
}
|
|
1822
3510
|
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}>(evmInfo.rpc, {
|
|
1827
|
-
method: "POST",
|
|
1828
|
-
headers: {
|
|
1829
|
-
"content-type": "application/json",
|
|
1830
|
-
"request-source": origin,
|
|
1831
|
-
},
|
|
1832
|
-
body: JSON.stringify({
|
|
1833
|
-
jsonrpc: "2.0",
|
|
1834
|
-
method: "eth_getTransactionReceipt",
|
|
1835
|
-
params: [txHash],
|
|
1836
|
-
id: 1,
|
|
1837
|
-
}),
|
|
1838
|
-
})
|
|
1839
|
-
.then((res) => {
|
|
1840
|
-
const txReceipt = res.data.result;
|
|
1841
|
-
if (txReceipt) {
|
|
1842
|
-
simpleFetch<{
|
|
1843
|
-
result: any;
|
|
1844
|
-
error?: Error;
|
|
1845
|
-
}>(evmInfo.rpc, {
|
|
1846
|
-
method: "POST",
|
|
1847
|
-
headers: {
|
|
1848
|
-
"content-type": "application/json",
|
|
1849
|
-
"request-source": origin,
|
|
1850
|
-
},
|
|
1851
|
-
body: JSON.stringify({
|
|
1852
|
-
jsonrpc: "2.0",
|
|
1853
|
-
method: "debug_traceTransaction",
|
|
1854
|
-
params: [txHash, { tracer: "callTracer" }],
|
|
1855
|
-
id: 1,
|
|
1856
|
-
}),
|
|
1857
|
-
}).then((res) => {
|
|
1858
|
-
runInAction(() => {
|
|
1859
|
-
let isFoundFromCall = false;
|
|
1860
|
-
if (res.data.result) {
|
|
1861
|
-
const searchForTransfers = (calls: any) => {
|
|
1862
|
-
for (const call of calls) {
|
|
1863
|
-
if (
|
|
1864
|
-
call.type === "CALL" &&
|
|
1865
|
-
call.to.toLowerCase() ===
|
|
1866
|
-
history.recipient.toLowerCase()
|
|
1867
|
-
) {
|
|
1868
|
-
const isERC20Transfer =
|
|
1869
|
-
call.input.startsWith("0xa9059cbb");
|
|
1870
|
-
const value = BigInt(
|
|
1871
|
-
isERC20Transfer
|
|
1872
|
-
? `0x${call.input.substring(74)}`
|
|
1873
|
-
: call.value
|
|
1874
|
-
);
|
|
3511
|
+
const txs = res.txs
|
|
3512
|
+
? res.txs.map((t: any) => t.tx_result || t)
|
|
3513
|
+
: [res.tx_result || res];
|
|
1875
3514
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
if (call.calls && call.calls.length > 0) {
|
|
1886
|
-
searchForTransfers(call.calls);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
3515
|
+
const matchedTx = txs.find((tx: any) => {
|
|
3516
|
+
const idx = this.getIBCRecvPacketIndexFromTx(
|
|
3517
|
+
tx,
|
|
3518
|
+
targetChannel.portId,
|
|
3519
|
+
targetChannel.channelId,
|
|
3520
|
+
sequence
|
|
3521
|
+
);
|
|
3522
|
+
return idx >= 0;
|
|
3523
|
+
});
|
|
1890
3524
|
|
|
1891
|
-
|
|
1892
|
-
}
|
|
3525
|
+
const tx = matchedTx || txs[0];
|
|
1893
3526
|
|
|
1894
|
-
|
|
1895
|
-
history.trackDone = true;
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
3527
|
+
targetChannel.completed = true;
|
|
1898
3528
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
const withdrawTopic = id("Withdrawal(address,uint256)");
|
|
1902
|
-
const hyperlaneReceiveTopic = id(
|
|
1903
|
-
"ReceivedTransferRemote(uint32,bytes32,uint256)"
|
|
1904
|
-
);
|
|
1905
|
-
for (const log of logs) {
|
|
1906
|
-
if (log.topics[0] === transferTopic) {
|
|
1907
|
-
const to = "0x" + log.topics[2].slice(26);
|
|
1908
|
-
if (to.toLowerCase() === history.recipient.toLowerCase()) {
|
|
1909
|
-
const destinationAssetDenom =
|
|
1910
|
-
history.destinationAsset.denom.replace("erc20:", "");
|
|
1911
|
-
|
|
1912
|
-
const amount = BigInt(log.data).toString(10);
|
|
1913
|
-
if (log.address === destinationAssetDenom) {
|
|
1914
|
-
history.resAmount.push([
|
|
1915
|
-
{
|
|
1916
|
-
amount,
|
|
1917
|
-
denom: history.destinationAsset.denom,
|
|
1918
|
-
},
|
|
1919
|
-
]);
|
|
1920
|
-
} else {
|
|
1921
|
-
console.log("refunded", log.address);
|
|
1922
|
-
// Transfer 토픽인 경우엔 ERC20의 tranfer 호출일텐데
|
|
1923
|
-
// 받을 토큰의 컨트랙트가 아닌 다른 컨트랙트에서 호출된 경우는 Swap을 실패한 것으로 추측
|
|
1924
|
-
// 고로 실제로 받은 토큰의 컨트랙트 주소로 환불 정보에 저장한다.
|
|
1925
|
-
history.trackError = "Swap failed";
|
|
1926
|
-
history.swapRefundInfo = {
|
|
1927
|
-
chainId: history.destinationChainId,
|
|
1928
|
-
amount: [
|
|
1929
|
-
{
|
|
1930
|
-
amount,
|
|
1931
|
-
denom: `erc20:${log.address.toLowerCase()}`,
|
|
1932
|
-
},
|
|
1933
|
-
],
|
|
1934
|
-
};
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
history.trackDone = true;
|
|
1938
|
-
return;
|
|
1939
|
-
}
|
|
1940
|
-
} else if (log.topics[0] === withdrawTopic) {
|
|
1941
|
-
const to = "0x" + log.topics[1].slice(26);
|
|
1942
|
-
if (to.toLowerCase() === txReceipt.to.toLowerCase()) {
|
|
1943
|
-
const amount = BigInt(log.data).toString(10);
|
|
1944
|
-
history.resAmount.push([
|
|
1945
|
-
{ amount, denom: history.destinationAsset.denom },
|
|
1946
|
-
]);
|
|
1947
|
-
history.trackDone = true;
|
|
1948
|
-
return;
|
|
1949
|
-
}
|
|
1950
|
-
} else if (log.topics[0] === hyperlaneReceiveTopic) {
|
|
1951
|
-
const to = "0x" + log.topics[2].slice(26);
|
|
1952
|
-
if (to.toLowerCase() === history.recipient.toLowerCase()) {
|
|
1953
|
-
const amount = BigInt(log.data).toString(10);
|
|
1954
|
-
// Hyperlane을 통해 Forma로 TIA를 받는 경우 토큰 수량이 decimal 6으로 기록되는데,
|
|
1955
|
-
// Forma에서는 decimal 18이기 때문에 12자리 만큼 0을 붙여준다.
|
|
1956
|
-
history.resAmount.push([
|
|
1957
|
-
{
|
|
1958
|
-
amount:
|
|
1959
|
-
history.destinationAsset.denom === "forma-native"
|
|
1960
|
-
? `${amount}000000000000`
|
|
1961
|
-
: amount,
|
|
1962
|
-
denom: history.destinationAsset.denom,
|
|
1963
|
-
},
|
|
1964
|
-
]);
|
|
1965
|
-
history.trackDone = true;
|
|
1966
|
-
return;
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
});
|
|
1971
|
-
});
|
|
1972
|
-
}
|
|
1973
|
-
})
|
|
1974
|
-
.finally(() => {
|
|
1975
|
-
history.trackDone = true;
|
|
1976
|
-
onFulfill();
|
|
1977
|
-
});
|
|
1978
|
-
} else {
|
|
1979
|
-
const txTracer = new TendermintTxTracer(chainInfo.rpc, "/websocket");
|
|
1980
|
-
txTracer.addEventListener("error", () => onFulfill());
|
|
1981
|
-
txTracer
|
|
1982
|
-
.queryTx({
|
|
1983
|
-
"tx.hash": txHash,
|
|
1984
|
-
})
|
|
1985
|
-
.then((res: any) => {
|
|
1986
|
-
txTracer.close();
|
|
3529
|
+
let resAmount: { amount: string; denom: string }[] | undefined;
|
|
3530
|
+
const receiverIndex = targetChannelIndex + 1;
|
|
1987
3531
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
for (const tx of txs) {
|
|
1996
|
-
const resAmount = this.getIBCSwapResAmountFromTx(
|
|
1997
|
-
tx,
|
|
1998
|
-
history.recipient
|
|
1999
|
-
);
|
|
3532
|
+
if (tx && swapReceiver && receiverIndex < swapReceiver.length) {
|
|
3533
|
+
const index = this.getIBCRecvPacketIndexFromTx(
|
|
3534
|
+
tx,
|
|
3535
|
+
targetChannel.portId,
|
|
3536
|
+
targetChannel.channelId,
|
|
3537
|
+
sequence
|
|
3538
|
+
);
|
|
2000
3539
|
|
|
2001
|
-
|
|
2002
|
-
|
|
3540
|
+
if (index >= 0) {
|
|
3541
|
+
resAmount = this.getIBCSwapResAmountFromTx(
|
|
3542
|
+
tx,
|
|
3543
|
+
swapReceiver[receiverIndex],
|
|
3544
|
+
index,
|
|
3545
|
+
undefined
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
onHopCompleted(resAmount, tx);
|
|
3551
|
+
|
|
3552
|
+
// 1. 다음 채널이 있고, tx가 있는 경우 => 다음 채널로 이동
|
|
3553
|
+
// 2. 마지막 채널이면서, destinationAsset가 있고, onDynamicHopDetected가 있는 경우 => 동적 홉 감지
|
|
3554
|
+
// 3. 그 외의 경우 => 종료(allCompleted)
|
|
3555
|
+
if (nextChannel && tx) {
|
|
3556
|
+
const index = this.getIBCRecvPacketIndexFromTx(
|
|
3557
|
+
tx,
|
|
3558
|
+
targetChannel.portId,
|
|
3559
|
+
targetChannel.channelId,
|
|
3560
|
+
sequence
|
|
3561
|
+
);
|
|
3562
|
+
|
|
3563
|
+
if (index >= 0) {
|
|
3564
|
+
nextChannel.sequence = this.getIBCPacketSequenceFromTx(
|
|
3565
|
+
tx,
|
|
3566
|
+
nextChannel.portId,
|
|
3567
|
+
nextChannel.channelId,
|
|
3568
|
+
index
|
|
3569
|
+
);
|
|
3570
|
+
nextChannel.dstChannelId = this.getDstChannelIdFromTx(
|
|
3571
|
+
tx,
|
|
3572
|
+
nextChannel.portId,
|
|
3573
|
+
nextChannel.channelId,
|
|
3574
|
+
index
|
|
3575
|
+
);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
onContinue();
|
|
3579
|
+
} else if (tx && destinationAsset && onDynamicHopDetected) {
|
|
3580
|
+
// 마지막 채널에서 recv_packet 이후 새로운 send_packet이 있는 경우를 감지
|
|
3581
|
+
const index = this.getIBCRecvPacketIndexFromTx(
|
|
3582
|
+
tx,
|
|
3583
|
+
targetChannel.portId,
|
|
3584
|
+
targetChannel.channelId,
|
|
3585
|
+
sequence
|
|
3586
|
+
);
|
|
3587
|
+
|
|
3588
|
+
if (index >= 0) {
|
|
3589
|
+
const sendPackets = this.getSendPacketInfoFromTx(tx, index);
|
|
3590
|
+
if (sendPackets.length > 0) {
|
|
3591
|
+
// 마지막 send_packet을 확인
|
|
3592
|
+
const lastSendPacket = sendPackets[sendPackets.length - 1];
|
|
3593
|
+
|
|
3594
|
+
// destination chain에서 IBC wrapped token을 unwrap하는 경우를 가정한다.
|
|
3595
|
+
// 예를 들어, source chain -> osmosis (swap) -> destination chain으로 이동하는 경우,
|
|
3596
|
+
// osmosis에서 IBC wrapped token이 destination chain으로 전송되고,
|
|
3597
|
+
// destination chain에서 IBC wrapped token을 unwrap하는 케이스가 있을 수 있다.
|
|
3598
|
+
const destinationChainInfo = this.chainsService.getChainInfo(
|
|
3599
|
+
destinationAsset.chainId
|
|
3600
|
+
);
|
|
3601
|
+
|
|
3602
|
+
if (destinationChainInfo) {
|
|
3603
|
+
onDynamicHopDetected({
|
|
3604
|
+
portId: "transfer", // CHECK: transfer를 하드코딩해도 되는지 확인
|
|
3605
|
+
channelId: lastSendPacket.srcChannel,
|
|
3606
|
+
counterpartyChainId: destinationChainInfo.chainId,
|
|
3607
|
+
sequence: lastSendPacket.sequence,
|
|
3608
|
+
dstChannelId: lastSendPacket.dstChannel,
|
|
3609
|
+
packetData: lastSendPacket.packetData,
|
|
3610
|
+
});
|
|
2003
3611
|
return;
|
|
2004
3612
|
}
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
.finally(() => {
|
|
2008
|
-
history.trackDone = true;
|
|
2009
|
-
onFulfill();
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
2013
3615
|
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
3616
|
+
onAllCompleted();
|
|
3617
|
+
} else {
|
|
3618
|
+
onAllCompleted();
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
2018
3621
|
|
|
2019
|
-
|
|
2020
|
-
clearAllRecentSkipHistory(): void {
|
|
2021
|
-
this.recentSkipHistoryMap.clear();
|
|
3622
|
+
return txTracer;
|
|
2022
3623
|
}
|
|
2023
3624
|
|
|
2024
3625
|
protected getIBCWriteAcknowledgementAckFromTx(
|
|
@@ -2197,6 +3798,7 @@ export class RecentSendHistoryService {
|
|
|
2197
3798
|
if (split.length === 5) {
|
|
2198
3799
|
const amount = split[1];
|
|
2199
3800
|
const denom = split[3];
|
|
3801
|
+
|
|
2200
3802
|
return [
|
|
2201
3803
|
{
|
|
2202
3804
|
denom,
|
|
@@ -2210,6 +3812,124 @@ export class RecentSendHistoryService {
|
|
|
2210
3812
|
return [];
|
|
2211
3813
|
}
|
|
2212
3814
|
|
|
3815
|
+
/**
|
|
3816
|
+
* TX에서 send_packet 이벤트 정보를 추출하여 추가 IBC 홉을 감지하기 위해 사용됩니다.
|
|
3817
|
+
*
|
|
3818
|
+
* 예를 들어, 마지막 목적지 체인에서 IBC wrapped token을 unwrap하는 경우,
|
|
3819
|
+
* send_packet 이벤트를 통해 추가 IBC 홉을 감지할 수 있습니다.
|
|
3820
|
+
*/
|
|
3821
|
+
protected getSendPacketInfoFromTx(
|
|
3822
|
+
tx: any,
|
|
3823
|
+
startEventsIndex: number = 0
|
|
3824
|
+
): {
|
|
3825
|
+
srcChannel: string;
|
|
3826
|
+
dstChannel: string;
|
|
3827
|
+
sequence: string;
|
|
3828
|
+
packetData: { denom: string; amount: string; receiver: string };
|
|
3829
|
+
}[] {
|
|
3830
|
+
const events = tx.events;
|
|
3831
|
+
if (!events || !Array.isArray(events)) {
|
|
3832
|
+
return [];
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
const compareStringWithBase64OrPlain = (
|
|
3836
|
+
target: string,
|
|
3837
|
+
value: string
|
|
3838
|
+
): [boolean, boolean] => {
|
|
3839
|
+
if (target === value) {
|
|
3840
|
+
return [true, false];
|
|
3841
|
+
}
|
|
3842
|
+
if (target === Buffer.from(value).toString("base64")) {
|
|
3843
|
+
return [true, true];
|
|
3844
|
+
}
|
|
3845
|
+
return [false, false];
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3848
|
+
const results: {
|
|
3849
|
+
srcChannel: string;
|
|
3850
|
+
dstChannel: string;
|
|
3851
|
+
sequence: string;
|
|
3852
|
+
packetData: { denom: string; amount: string; receiver: string };
|
|
3853
|
+
}[] = [];
|
|
3854
|
+
|
|
3855
|
+
const slicedEvents = events.slice(startEventsIndex);
|
|
3856
|
+
|
|
3857
|
+
for (const event of slicedEvents) {
|
|
3858
|
+
if (event.type !== "send_packet") {
|
|
3859
|
+
continue;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
let isBase64 = false;
|
|
3863
|
+
|
|
3864
|
+
const srcChannelAttr = event.attributes?.find((attr: { key: string }) => {
|
|
3865
|
+
const c = compareStringWithBase64OrPlain(
|
|
3866
|
+
attr.key,
|
|
3867
|
+
"packet_src_channel"
|
|
3868
|
+
);
|
|
3869
|
+
isBase64 = c[1];
|
|
3870
|
+
return c[0];
|
|
3871
|
+
});
|
|
3872
|
+
if (!srcChannelAttr) {
|
|
3873
|
+
continue;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
const dstChannelAttr = event.attributes?.find((attr: { key: string }) => {
|
|
3877
|
+
return compareStringWithBase64OrPlain(
|
|
3878
|
+
attr.key,
|
|
3879
|
+
"packet_dst_channel"
|
|
3880
|
+
)[0];
|
|
3881
|
+
});
|
|
3882
|
+
if (!dstChannelAttr) continue;
|
|
3883
|
+
|
|
3884
|
+
const sequenceAttr = event.attributes?.find((attr: { key: string }) => {
|
|
3885
|
+
return compareStringWithBase64OrPlain(attr.key, "packet_sequence")[0];
|
|
3886
|
+
});
|
|
3887
|
+
if (!sequenceAttr) continue;
|
|
3888
|
+
|
|
3889
|
+
const packetDataAttr = event.attributes?.find((attr: { key: string }) => {
|
|
3890
|
+
return compareStringWithBase64OrPlain(attr.key, "packet_data")[0];
|
|
3891
|
+
});
|
|
3892
|
+
if (!packetDataAttr) continue;
|
|
3893
|
+
|
|
3894
|
+
try {
|
|
3895
|
+
const srcChannel = isBase64
|
|
3896
|
+
? Buffer.from(srcChannelAttr.value, "base64").toString()
|
|
3897
|
+
: srcChannelAttr.value;
|
|
3898
|
+
const dstChannel = isBase64
|
|
3899
|
+
? Buffer.from(dstChannelAttr.value, "base64").toString()
|
|
3900
|
+
: dstChannelAttr.value;
|
|
3901
|
+
const sequence = isBase64
|
|
3902
|
+
? Buffer.from(sequenceAttr.value, "base64").toString()
|
|
3903
|
+
: sequenceAttr.value;
|
|
3904
|
+
|
|
3905
|
+
const packetDataValue = isBase64
|
|
3906
|
+
? Buffer.from(packetDataAttr.value, "base64").toString()
|
|
3907
|
+
: packetDataAttr.value.startsWith("{")
|
|
3908
|
+
? packetDataAttr.value
|
|
3909
|
+
: Buffer.from(packetDataAttr.value, "base64").toString();
|
|
3910
|
+
|
|
3911
|
+
const packetData = JSON.parse(packetDataValue);
|
|
3912
|
+
|
|
3913
|
+
if (packetData.denom && packetData.amount && packetData.receiver) {
|
|
3914
|
+
results.push({
|
|
3915
|
+
srcChannel,
|
|
3916
|
+
dstChannel,
|
|
3917
|
+
sequence,
|
|
3918
|
+
packetData: {
|
|
3919
|
+
denom: packetData.denom,
|
|
3920
|
+
amount: packetData.amount,
|
|
3921
|
+
receiver: packetData.receiver,
|
|
3922
|
+
},
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
} catch {
|
|
3926
|
+
// noop
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
return results;
|
|
3931
|
+
}
|
|
3932
|
+
|
|
2213
3933
|
protected getIBCAcknowledgementPacketIndexFromTx(
|
|
2214
3934
|
tx: any,
|
|
2215
3935
|
sourcePortId: string,
|
|
@@ -2684,31 +4404,172 @@ export class RecentSendHistoryService {
|
|
|
2684
4404
|
throw new Error("Invalid tx");
|
|
2685
4405
|
}
|
|
2686
4406
|
|
|
4407
|
+
// ============================================================================
|
|
4408
|
+
// Chain removed handler
|
|
4409
|
+
// ============================================================================
|
|
2687
4410
|
protected readonly onChainRemoved = (chainInfo: ChainInfo) => {
|
|
2688
4411
|
const chainIdentifier = ChainIdHelper.parse(chainInfo.chainId).identifier;
|
|
4412
|
+
try {
|
|
4413
|
+
this.removeIBCHistoriesByChainIdentifier(chainIdentifier);
|
|
4414
|
+
this.removeSkipHistoriesByChainIdentifier(chainIdentifier);
|
|
4415
|
+
this.removeSwapV2HistoriesByChainIdentifier(chainIdentifier);
|
|
4416
|
+
} catch (e) {
|
|
4417
|
+
console.error(e);
|
|
4418
|
+
}
|
|
4419
|
+
};
|
|
2689
4420
|
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
4421
|
+
@action
|
|
4422
|
+
protected removeIBCHistoriesByChainIdentifier(chainIdentifier: string): void {
|
|
4423
|
+
const removingIds: string[] = [];
|
|
4424
|
+
for (const history of this.recentIBCHistoryMap.values()) {
|
|
4425
|
+
if (ChainIdHelper.parse(history.chainId).identifier === chainIdentifier) {
|
|
4426
|
+
removingIds.push(history.id);
|
|
4427
|
+
continue;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
if (
|
|
4431
|
+
ChainIdHelper.parse(history.destinationChainId).identifier ===
|
|
4432
|
+
chainIdentifier
|
|
4433
|
+
) {
|
|
4434
|
+
removingIds.push(history.id);
|
|
4435
|
+
continue;
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
if (
|
|
4439
|
+
history.ibcHistory.some((history) => {
|
|
4440
|
+
return (
|
|
4441
|
+
ChainIdHelper.parse(history.counterpartyChainId).identifier ===
|
|
4442
|
+
chainIdentifier
|
|
4443
|
+
);
|
|
4444
|
+
})
|
|
4445
|
+
) {
|
|
4446
|
+
removingIds.push(history.id);
|
|
4447
|
+
continue;
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4451
|
+
for (const id of removingIds) {
|
|
4452
|
+
this.recentIBCHistoryMap.delete(id);
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
@action
|
|
4457
|
+
protected removeSkipHistoriesByChainIdentifier(
|
|
4458
|
+
chainIdentifier: string
|
|
4459
|
+
): void {
|
|
4460
|
+
const removingIds: string[] = [];
|
|
4461
|
+
for (const history of this.recentSkipHistoryMap.values()) {
|
|
4462
|
+
if (ChainIdHelper.parse(history.chainId).identifier === chainIdentifier) {
|
|
4463
|
+
removingIds.push(history.id);
|
|
4464
|
+
continue;
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
if (
|
|
4468
|
+
ChainIdHelper.parse(history.destinationChainId).identifier ===
|
|
4469
|
+
chainIdentifier
|
|
4470
|
+
) {
|
|
4471
|
+
removingIds.push(history.id);
|
|
4472
|
+
continue;
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
if (
|
|
4476
|
+
ChainIdHelper.parse(history.destinationAsset.chainId).identifier ===
|
|
4477
|
+
chainIdentifier
|
|
4478
|
+
) {
|
|
4479
|
+
removingIds.push(history.id);
|
|
4480
|
+
continue;
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
if (
|
|
4484
|
+
history.simpleRoute.some(
|
|
4485
|
+
(route) =>
|
|
4486
|
+
ChainIdHelper.parse(route.chainId).identifier === chainIdentifier
|
|
4487
|
+
)
|
|
4488
|
+
) {
|
|
4489
|
+
removingIds.push(history.id);
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
if (
|
|
4494
|
+
history.swapRefundInfo &&
|
|
4495
|
+
ChainIdHelper.parse(history.swapRefundInfo.chainId).identifier ===
|
|
4496
|
+
chainIdentifier
|
|
4497
|
+
) {
|
|
4498
|
+
removingIds.push(history.id);
|
|
4499
|
+
continue;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
if (history.transferAssetRelease) {
|
|
4503
|
+
const chainId = history.transferAssetRelease.chain_id;
|
|
4504
|
+
const isOnlyEvm = parseInt(chainId) > 0;
|
|
4505
|
+
const chainIdInKeplr = isOnlyEvm ? `eip155:${chainId}` : chainId;
|
|
2693
4506
|
if (
|
|
2694
|
-
ChainIdHelper.parse(
|
|
4507
|
+
ChainIdHelper.parse(chainIdInKeplr).identifier === chainIdentifier
|
|
2695
4508
|
) {
|
|
2696
4509
|
removingIds.push(history.id);
|
|
2697
4510
|
continue;
|
|
2698
4511
|
}
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
2699
4514
|
|
|
2700
|
-
|
|
2701
|
-
|
|
4515
|
+
for (const id of removingIds) {
|
|
4516
|
+
this.recentSkipHistoryMap.delete(id);
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
@action
|
|
4521
|
+
protected removeSwapV2HistoriesByChainIdentifier(
|
|
4522
|
+
chainIdentifier: string
|
|
4523
|
+
): void {
|
|
4524
|
+
const removingIds: string[] = [];
|
|
4525
|
+
for (const history of this.recentSwapV2HistoryMap.values()) {
|
|
4526
|
+
if (
|
|
4527
|
+
ChainIdHelper.parse(history.fromChainId).identifier === chainIdentifier
|
|
4528
|
+
) {
|
|
4529
|
+
removingIds.push(history.id);
|
|
4530
|
+
continue;
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
if (
|
|
4534
|
+
ChainIdHelper.parse(history.toChainId).identifier === chainIdentifier
|
|
4535
|
+
) {
|
|
4536
|
+
removingIds.push(history.id);
|
|
4537
|
+
continue;
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
if (
|
|
4541
|
+
history.simpleRoute.some(
|
|
4542
|
+
(route) =>
|
|
4543
|
+
ChainIdHelper.parse(route.chainId).identifier === chainIdentifier
|
|
4544
|
+
)
|
|
4545
|
+
) {
|
|
4546
|
+
removingIds.push(history.id);
|
|
4547
|
+
continue;
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
if (
|
|
4551
|
+
history.assetLocationInfo &&
|
|
4552
|
+
ChainIdHelper.parse(history.assetLocationInfo.chainId).identifier ===
|
|
2702
4553
|
chainIdentifier
|
|
4554
|
+
) {
|
|
4555
|
+
removingIds.push(history.id);
|
|
4556
|
+
continue;
|
|
4557
|
+
}
|
|
4558
|
+
|
|
4559
|
+
if (history.additionalTrackingData) {
|
|
4560
|
+
if (
|
|
4561
|
+
ChainIdHelper.parse(history.additionalTrackingData.chainId)
|
|
4562
|
+
.identifier === chainIdentifier
|
|
2703
4563
|
) {
|
|
2704
4564
|
removingIds.push(history.id);
|
|
2705
4565
|
continue;
|
|
2706
4566
|
}
|
|
2707
4567
|
|
|
2708
4568
|
if (
|
|
2709
|
-
history.
|
|
4569
|
+
history.additionalTrackingData.type === "cosmos-ibc" &&
|
|
4570
|
+
history.additionalTrackingData.ibcHistory.some((h) => {
|
|
2710
4571
|
return (
|
|
2711
|
-
ChainIdHelper.parse(
|
|
4572
|
+
ChainIdHelper.parse(h.counterpartyChainId).identifier ===
|
|
2712
4573
|
chainIdentifier
|
|
2713
4574
|
);
|
|
2714
4575
|
})
|
|
@@ -2717,10 +4578,38 @@ export class RecentSendHistoryService {
|
|
|
2717
4578
|
continue;
|
|
2718
4579
|
}
|
|
2719
4580
|
}
|
|
4581
|
+
}
|
|
2720
4582
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
4583
|
+
for (const id of removingIds) {
|
|
4584
|
+
this.recentSwapV2HistoryMap.delete(id);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
// ============================================================================
|
|
4589
|
+
// Helper Functions
|
|
4590
|
+
// ============================================================================
|
|
4591
|
+
|
|
4592
|
+
private getExecutableChainIdsFromSwapV2History(
|
|
4593
|
+
history: SwapV2History,
|
|
4594
|
+
includeAllChainIds: boolean = false
|
|
4595
|
+
): string[] {
|
|
4596
|
+
const chainIds: string[] = [];
|
|
4597
|
+
|
|
4598
|
+
const endIndex = includeAllChainIds
|
|
4599
|
+
? history.simpleRoute.length
|
|
4600
|
+
: Math.max(0, history.routeIndex + 1);
|
|
4601
|
+
|
|
4602
|
+
for (let i = 0; i < endIndex; i++) {
|
|
4603
|
+
chainIds.push(history.simpleRoute[i].chainId);
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
if (
|
|
4607
|
+
history.assetLocationInfo &&
|
|
4608
|
+
history.assetLocationInfo.type === "intermediate"
|
|
4609
|
+
) {
|
|
4610
|
+
chainIds.push(history.assetLocationInfo.chainId);
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
return chainIds;
|
|
4614
|
+
}
|
|
2726
4615
|
}
|