@xswap-link/sdk 0.2.0 → 0.2.2
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/CHANGELOG.md +12 -0
- package/dist/index.css +30 -20
- package/dist/index.d.mts +3 -172
- package/dist/index.d.ts +3 -172
- package/dist/index.js +546 -749
- package/dist/index.mjs +565 -725
- package/package.json +5 -3
- package/src/components/TxConfigForm/PoweredBy.tsx +12 -0
- package/src/components/TxConfigForm/index.tsx +3 -27
- package/src/components/{TxHistoryButton → TxStatusButton}/index.tsx +27 -27
- package/src/components/global.css +4 -0
- package/src/components/index.ts +1 -1
- package/src/config/index.ts +1 -0
- package/src/config/init.ts +31 -0
- package/src/constants/index.ts +14 -0
- package/src/index.ts +6 -6
- package/src/services/integrations/index.ts +1 -0
- package/src/services/integrations/monitoring.ts +148 -0
- package/src/services/integrations/transactions.ts +1 -147
- package/tailwind.config.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xswap-link/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "JavaScript SDK for XSwap platform",
|
|
5
5
|
"homepage": "https://github.com/xswap-link/xswap-sdk",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@tanstack/react-query": "^5.35.5",
|
|
24
24
|
"@types/react": "18.2.0",
|
|
25
25
|
"@types/react-dom": "18.2.0",
|
|
26
|
+
"async-retry": "^1.3.3",
|
|
26
27
|
"bignumber.js": "4.0.4",
|
|
27
28
|
"date-fns": "^3.6.0",
|
|
28
29
|
"ethers": "^5.7.2",
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@changesets/cli": "^2.27.1",
|
|
37
|
+
"@types/async-retry": "^1.4.8",
|
|
36
38
|
"@types/jest": "^29.5.12",
|
|
37
39
|
"@types/node": "^20.11.17",
|
|
38
40
|
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
|
@@ -56,8 +58,8 @@
|
|
|
56
58
|
},
|
|
57
59
|
"scripts": {
|
|
58
60
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
59
|
-
"watch": "nodemon",
|
|
60
|
-
"release": "pnpm
|
|
61
|
+
"watch": "pnpm build && nodemon",
|
|
62
|
+
"release": "pnpm build && changeset publish",
|
|
61
63
|
"lint": "tsc && eslint . --ext .ts",
|
|
62
64
|
"test": "jest"
|
|
63
65
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ChainlinkCCIPIcon, XSwapBadgeIcon } from "@src/components/icons";
|
|
2
|
+
|
|
3
|
+
export const PoweredBy = () => {
|
|
4
|
+
return (
|
|
5
|
+
<div className="flex justify-center items-center gap-2 my-3">
|
|
6
|
+
<span className="text-x_grey">Powered by</span>
|
|
7
|
+
<XSwapBadgeIcon />
|
|
8
|
+
<span>✕</span>
|
|
9
|
+
<ChainlinkCCIPIcon />
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import "@src/global.css";
|
|
2
2
|
import { MouseEvent, useMemo } from "react";
|
|
3
|
-
import { createRoot, Root } from "react-dom/client";
|
|
4
3
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
4
|
import { WagmiProvider } from "wagmi";
|
|
6
|
-
import { getWagmiConfig } from "@src/config";
|
|
5
|
+
import { getWagmiConfig, xswapRoot } from "@src/config";
|
|
7
6
|
import { Chain, ContractCall, Route } from "@src/models";
|
|
8
7
|
import { CloseIcon } from "@src/components/icons/CloseIcon";
|
|
8
|
+
import { PoweredBy } from "@src/components/TxConfigForm/PoweredBy";
|
|
9
9
|
import { Form } from "./Form";
|
|
10
|
-
import { ChainlinkCCIPIcon, XSwapBadgeIcon } from "../icons";
|
|
11
|
-
import { version } from "../../../package.json";
|
|
12
|
-
import xswapConfig from "../../../xswap.config";
|
|
13
10
|
|
|
14
11
|
export type TxConfigFormProps = {
|
|
15
12
|
dstChainId: string;
|
|
@@ -67,13 +64,7 @@ const TxConfigForm = ({
|
|
|
67
64
|
>
|
|
68
65
|
<CloseIcon />
|
|
69
66
|
</div>
|
|
70
|
-
|
|
71
|
-
<div className="swappage__poweredby">
|
|
72
|
-
<span>Powered by</span>
|
|
73
|
-
<XSwapBadgeIcon />
|
|
74
|
-
<span>✕</span>
|
|
75
|
-
<ChainlinkCCIPIcon />
|
|
76
|
-
</div>
|
|
67
|
+
<PoweredBy />
|
|
77
68
|
</div>
|
|
78
69
|
</div>
|
|
79
70
|
</QueryClientProvider>
|
|
@@ -111,18 +102,3 @@ export const openTxConfigForm = async ({
|
|
|
111
102
|
throw new Error(`XSwap component error, ${err}`);
|
|
112
103
|
}
|
|
113
104
|
};
|
|
114
|
-
|
|
115
|
-
// skip on server-side integrations
|
|
116
|
-
let xswapRoot: Root;
|
|
117
|
-
if (typeof document !== "undefined") {
|
|
118
|
-
const xswapStyleElement = document.createElement("link");
|
|
119
|
-
xswapStyleElement.rel = "stylesheet";
|
|
120
|
-
xswapStyleElement.type = "text/css";
|
|
121
|
-
xswapStyleElement.href = `${xswapConfig.apiUrl}/sdk/css/${version}`;
|
|
122
|
-
document.head.appendChild(xswapStyleElement);
|
|
123
|
-
|
|
124
|
-
const xswapModalElement = document.createElement("div");
|
|
125
|
-
xswapModalElement.setAttribute("id", "xswap-modal");
|
|
126
|
-
document.body.appendChild(xswapModalElement);
|
|
127
|
-
xswapRoot = createRoot(xswapModalElement);
|
|
128
|
-
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { FC, useState } from "react";
|
|
2
|
-
import { createRoot } from "react-dom/client";
|
|
1
|
+
import { FC, useMemo, useState } from "react";
|
|
3
2
|
import {
|
|
4
3
|
ArrowLeftIcon,
|
|
5
4
|
ArrowRightIcon,
|
|
6
5
|
CheckIcon,
|
|
7
6
|
CircularProgressIcon,
|
|
7
|
+
ArrowUpRightIcon,
|
|
8
|
+
XSwapLogo,
|
|
8
9
|
} from "../icons";
|
|
9
|
-
import { ArrowUpRightIcon, XSwapLogo } from "../icons";
|
|
10
10
|
import { MonitoredTransaction } from "@src/models";
|
|
11
|
+
import { txStatusRoot } from "@src/config";
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
transaction: MonitoredTransaction;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const
|
|
17
|
+
export const TxStatusButton: FC<Props> = ({ transaction }) => {
|
|
17
18
|
const {
|
|
18
19
|
fromChain,
|
|
19
20
|
fromChainImage,
|
|
@@ -27,15 +28,28 @@ export const TxHistoryButton: FC<Props> = ({ transaction }) => {
|
|
|
27
28
|
explorer,
|
|
28
29
|
} = transaction;
|
|
29
30
|
const [isWide, setIsWide] = useState(false);
|
|
31
|
+
|
|
32
|
+
const background = useMemo(
|
|
33
|
+
() =>
|
|
34
|
+
isDone
|
|
35
|
+
? "bg-[rgba(2,2,2,1)] bg-gradient-to-r from-[rgba(76,175,80,0.2)] to-[rgba(46,125,50,0.2)]"
|
|
36
|
+
: "bg-[rgba(2,2,2,1)] bg-gradient-to-r from-[rgba(54,129,198,0.2)] to-[rgba(43,74,157,0.2)]",
|
|
37
|
+
[isDone],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const border = useMemo(
|
|
41
|
+
() =>
|
|
42
|
+
isDone
|
|
43
|
+
? "border border-solid border-x_green_dark"
|
|
44
|
+
: "border border-solid border-x_blue_dark",
|
|
45
|
+
[isDone],
|
|
46
|
+
);
|
|
47
|
+
|
|
30
48
|
return (
|
|
31
49
|
<div className="flex gap-2" onClick={() => setIsWide((x: boolean) => !x)}>
|
|
32
50
|
{isWide && (
|
|
33
51
|
<div
|
|
34
|
-
className={`flex items-center min-w-60 sm:w-[520px] text-white h-14 px-4 text-sm gap-3 rounded-xl
|
|
35
|
-
isDone
|
|
36
|
-
? "from-[rgba(76,175,80,0.1)] to-[rgba(46,125,50,0.1)] border border-solid border-x_green_dark"
|
|
37
|
-
: "from-[rgba(54,129,198,0.1)] to-[rgba(43,74,157,0.1)] border border-solid border-x_blue_dark"
|
|
38
|
-
}`}
|
|
52
|
+
className={`flex items-center min-w-60 sm:w-[520px] text-white h-14 px-4 text-sm gap-3 rounded-xl ${background} ${border}`}
|
|
39
53
|
>
|
|
40
54
|
<div className="flex justify-between flex-col gap-2 sm:gap-5 sm:flex-row w-full">
|
|
41
55
|
<div className="flex items-center gap-2">
|
|
@@ -77,12 +91,7 @@ export const TxHistoryButton: FC<Props> = ({ transaction }) => {
|
|
|
77
91
|
</div>
|
|
78
92
|
)}
|
|
79
93
|
<div
|
|
80
|
-
className={`text-white rounded-xl cursor-pointer
|
|
81
|
-
${
|
|
82
|
-
isDone
|
|
83
|
-
? "from-[rgba(76,175,80,0.1)] to-[rgba(46,125,50,0.1)] border border-solid border-x_green_dark"
|
|
84
|
-
: "from-[rgba(54,129,198,0.1)] to-[rgba(43,74,157,0.1)] border border-solid border-x_blue_dark"
|
|
85
|
-
}`}
|
|
94
|
+
className={`text-white rounded-xl cursor-pointer ${background} ${border}`}
|
|
86
95
|
>
|
|
87
96
|
<div className="flex gap-2 items-center h-14 w-20 justify-center">
|
|
88
97
|
<ArrowLeftIcon />
|
|
@@ -133,18 +142,9 @@ export const updateTransactionDoneInRenderedTransactions = (txHash: string) => {
|
|
|
133
142
|
}
|
|
134
143
|
};
|
|
135
144
|
|
|
136
|
-
const
|
|
137
|
-
historyElement.setAttribute("id", "xswap-history");
|
|
138
|
-
historyElement.setAttribute(
|
|
139
|
-
"class",
|
|
140
|
-
"absolute bottom-5 right-2 flex flex-col gap-3 pl-9 items-end w-full z-50",
|
|
141
|
-
);
|
|
142
|
-
document.body.appendChild(historyElement);
|
|
143
|
-
const txHistoryButtonRoot = createRoot(historyElement);
|
|
144
|
-
|
|
145
|
-
export const renderTxHistoryButtons = () => {
|
|
145
|
+
export const renderTxStatusButtons = () => {
|
|
146
146
|
const buttons = renderedTransactions.map((item) => (
|
|
147
|
-
<
|
|
147
|
+
<TxStatusButton key={item.txHash} transaction={item} />
|
|
148
148
|
));
|
|
149
|
-
|
|
149
|
+
txStatusRoot.render(buttons);
|
|
150
150
|
};
|
package/src/components/index.ts
CHANGED
package/src/config/index.ts
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createRoot, Root } from "react-dom/client";
|
|
2
|
+
import xswapConfig from "../../xswap.config";
|
|
3
|
+
import { version } from "../../package.json";
|
|
4
|
+
|
|
5
|
+
export let xswapRoot: Root;
|
|
6
|
+
export let txStatusRoot: Root;
|
|
7
|
+
|
|
8
|
+
export const initDocument = () => {
|
|
9
|
+
if (typeof document === "undefined") {
|
|
10
|
+
throw new Error("Can't render XPay components from server side.");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fetch(`${xswapConfig.apiUrl}/sdk/css?data={"version": "${version}"}`).then(
|
|
14
|
+
async (css) => {
|
|
15
|
+
const style = document.createElement("style");
|
|
16
|
+
style.textContent = await css.text();
|
|
17
|
+
document.body.appendChild(style);
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const xswapElement = document.createElement("div");
|
|
22
|
+
xswapElement.setAttribute("id", "xswap-modal");
|
|
23
|
+
document.body.appendChild(xswapElement);
|
|
24
|
+
xswapRoot = createRoot(xswapElement);
|
|
25
|
+
|
|
26
|
+
const txStatusElement = document.createElement("div");
|
|
27
|
+
txStatusElement.setAttribute("id", "xswap-tx-status");
|
|
28
|
+
txStatusElement.setAttribute("class", "xswap-tx-status");
|
|
29
|
+
document.body.appendChild(txStatusElement);
|
|
30
|
+
txStatusRoot = createRoot(txStatusElement);
|
|
31
|
+
};
|
package/src/constants/index.ts
CHANGED
|
@@ -10,3 +10,17 @@ export const ROUTE_TIMEOUT_MS = 5000;
|
|
|
10
10
|
export const MINIMUM_DISPLAYED_TOKEN_AMOUNT = 0.0001;
|
|
11
11
|
export const DEFAULT_SOURCE_CHAIN_ID = "1";
|
|
12
12
|
export const CCIP_EXPLORER = "https://ccip.chain.link/";
|
|
13
|
+
export const TX_RECEIPT_STATUS_LIFETIME = 10000;
|
|
14
|
+
|
|
15
|
+
export const MSG_SENT_EVENT_SIG =
|
|
16
|
+
"MessageSent(bytes32,uint64,address,bytes,address,uint256,uint256,address,uint256)";
|
|
17
|
+
|
|
18
|
+
export const MSG_SENT_EVENT_ABI = [
|
|
19
|
+
"event MessageSent(bytes32 indexed messageId, uint64 indexed destinationChainSelector, address indexed sender, bytes data, address token, uint256 tokenAmount, uint256 valueForInstantCcipRecieve, address transferedToken, uint256 transferedTokenAmount)",
|
|
20
|
+
];
|
|
21
|
+
export const MSG_RECEIVED_EVENT_SIG =
|
|
22
|
+
"MessageReceived(bytes32,uint64,address,bytes,address,uint256)";
|
|
23
|
+
|
|
24
|
+
export const MSG_RECEIVED_EVENT_ABI = [
|
|
25
|
+
"event MessageReceived(bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed sender, bytes data, address token, uint256 tokenAmount)",
|
|
26
|
+
];
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export * from "
|
|
6
|
-
export
|
|
1
|
+
import { initDocument } from "@src/config/init";
|
|
2
|
+
|
|
3
|
+
initDocument();
|
|
4
|
+
|
|
5
|
+
export * from "@src/models";
|
|
6
|
+
export { getSwapTx, monitorTransactionStatus } from "@src/services";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Chain, MonitoredTransaction, Web3Environment } from "@src/models";
|
|
2
|
+
import { getChains } from "@src/services";
|
|
3
|
+
import { ethers } from "ethers";
|
|
4
|
+
import { weiToHumanReadable } from "@src/utils";
|
|
5
|
+
import {
|
|
6
|
+
CCIP_EXPLORER,
|
|
7
|
+
MSG_RECEIVED_EVENT_ABI,
|
|
8
|
+
MSG_RECEIVED_EVENT_SIG,
|
|
9
|
+
MSG_SENT_EVENT_ABI,
|
|
10
|
+
MSG_SENT_EVENT_SIG,
|
|
11
|
+
TX_RECEIPT_STATUS_LIFETIME,
|
|
12
|
+
} from "@src/constants";
|
|
13
|
+
import {
|
|
14
|
+
addTransactionToRenderedTransactions,
|
|
15
|
+
removeTransactionFromRenderedTransactions,
|
|
16
|
+
renderTxStatusButtons,
|
|
17
|
+
updateTransactionDoneInRenderedTransactions,
|
|
18
|
+
} from "@src/components";
|
|
19
|
+
import { ADDRESSES } from "@src/contracts";
|
|
20
|
+
import retry from "async-retry";
|
|
21
|
+
|
|
22
|
+
export const monitorTransactionStatus = async (
|
|
23
|
+
txChainId: string,
|
|
24
|
+
txHash: string,
|
|
25
|
+
): Promise<void> => {
|
|
26
|
+
const txReceipt = await getTxReceipt(txChainId, txHash);
|
|
27
|
+
|
|
28
|
+
const supportedChains: Chain[] = (await getChains()).filter(
|
|
29
|
+
({ web3Environment, swapSupported }) =>
|
|
30
|
+
web3Environment === Web3Environment.MAINNET && swapSupported,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const messageSentEvent = txReceipt.logs?.find((log: { topics: string[] }) => {
|
|
34
|
+
return log.topics[0] === ethers.utils.id(MSG_SENT_EVENT_SIG);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!messageSentEvent) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`No transaction event found for chain ${txChainId} tx ${txHash}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const messageId = messageSentEvent?.topics[1] as string;
|
|
44
|
+
const iface = new ethers.utils.Interface(MSG_SENT_EVENT_ABI);
|
|
45
|
+
const decodedMessageSentEvent = iface.parseLog(messageSentEvent);
|
|
46
|
+
|
|
47
|
+
if (!decodedMessageSentEvent) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`No transaction event arguments found for chain ${txChainId} tx ${txHash}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const srcChain = supportedChains.find(
|
|
53
|
+
(chain) => chain.chainId === txChainId.toString(),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const srcToken = srcChain?.tokens.find(
|
|
57
|
+
(token) => token.address === decodedMessageSentEvent.args?.token.toString(),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const dstChain = supportedChains.find(
|
|
61
|
+
(chain) =>
|
|
62
|
+
chain.ccipChainId ===
|
|
63
|
+
decodedMessageSentEvent.args?.destinationChainSelector?.toString(),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const dstChainId = dstChain?.chainId;
|
|
67
|
+
|
|
68
|
+
if (!dstChainId) {
|
|
69
|
+
throw new Error(`Unknown destination chain!`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const transaction: MonitoredTransaction = {
|
|
73
|
+
txHash: txHash,
|
|
74
|
+
fromChain: srcChain?.displayName,
|
|
75
|
+
fromChainImage: srcChain?.image,
|
|
76
|
+
toChain: dstChain.displayName,
|
|
77
|
+
toChainImage: dstChain.image,
|
|
78
|
+
fromToken: srcToken?.symbol,
|
|
79
|
+
fromTokenImage: srcToken?.image,
|
|
80
|
+
fromAmount: weiToHumanReadable({
|
|
81
|
+
amount: decodedMessageSentEvent.args?.tokenAmount.toString(),
|
|
82
|
+
decimals: srcToken?.decimals || 18,
|
|
83
|
+
precisionFractionalPlaces: 4,
|
|
84
|
+
}),
|
|
85
|
+
isDone: false,
|
|
86
|
+
explorer: `${CCIP_EXPLORER}/tx/${txHash}`,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
addTransactionToRenderedTransactions(transaction);
|
|
90
|
+
renderTxStatusButtons();
|
|
91
|
+
|
|
92
|
+
// start listening
|
|
93
|
+
// TODO if someone will provide tx which already is done then we will never get the event. Will need to add status endpoint on backend for transactions to check if they are done or not.
|
|
94
|
+
|
|
95
|
+
const xSwapRouterOnDestinationAddress = ADDRESSES[dstChainId]?.XSwapRouter;
|
|
96
|
+
if (!xSwapRouterOnDestinationAddress) {
|
|
97
|
+
throw new Error(`Unknown destination XSwapRouter!`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const xSwapRouterOnDestination = new ethers.Contract(
|
|
101
|
+
xSwapRouterOnDestinationAddress,
|
|
102
|
+
MSG_RECEIVED_EVENT_ABI,
|
|
103
|
+
ethers.getDefaultProvider(
|
|
104
|
+
supportedChains.find((chain) => chain.chainId === dstChainId)
|
|
105
|
+
?.publicRpcUrls[0],
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const msgReceivedEventFilter = {
|
|
110
|
+
address: xSwapRouterOnDestinationAddress,
|
|
111
|
+
topics: [ethers.utils.id(MSG_RECEIVED_EVENT_SIG), messageId],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const onSuccess = async () => {
|
|
115
|
+
updateTransactionDoneInRenderedTransactions(txHash);
|
|
116
|
+
renderTxStatusButtons();
|
|
117
|
+
await new Promise((resolve) =>
|
|
118
|
+
setTimeout(() => resolve(true), TX_RECEIPT_STATUS_LIFETIME),
|
|
119
|
+
);
|
|
120
|
+
removeTransactionFromRenderedTransactions(txHash);
|
|
121
|
+
renderTxStatusButtons();
|
|
122
|
+
};
|
|
123
|
+
xSwapRouterOnDestination.once(msgReceivedEventFilter, () => onSuccess());
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const getTxReceipt = async (txChainId: string, txHash: string) => {
|
|
127
|
+
try {
|
|
128
|
+
const chain = (await getChains()).find(
|
|
129
|
+
(chain) => chain.chainId === txChainId,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const provider = new ethers.providers.JsonRpcProvider(
|
|
133
|
+
chain?.publicRpcUrls[0],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return await retry(async () => {
|
|
137
|
+
const txReceipt = await provider.getTransactionReceipt(txHash);
|
|
138
|
+
if (!txReceipt) {
|
|
139
|
+
throw new Error(`Couldn't fetch tx receipt`);
|
|
140
|
+
}
|
|
141
|
+
return txReceipt;
|
|
142
|
+
});
|
|
143
|
+
} catch (e) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Couldn't fetch tx ${txHash} receipt on chain ${txChainId}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
@@ -3,20 +3,9 @@ import {
|
|
|
3
3
|
GetSwapTxPayload,
|
|
4
4
|
Transactions,
|
|
5
5
|
Web3Environment,
|
|
6
|
-
MonitoredTransaction,
|
|
7
6
|
} from "@src/models";
|
|
8
7
|
import { getChains } from "@src/services";
|
|
9
|
-
import {
|
|
10
|
-
import { ADDRESSES } from "@src/contracts";
|
|
11
|
-
import {
|
|
12
|
-
openTxConfigForm,
|
|
13
|
-
addTransactionToRenderedTransactions,
|
|
14
|
-
removeTransactionFromRenderedTransactions,
|
|
15
|
-
renderTxHistoryButtons,
|
|
16
|
-
updateTransactionDoneInRenderedTransactions,
|
|
17
|
-
} from "@src/components";
|
|
18
|
-
import { weiToHumanReadable } from "@src/utils";
|
|
19
|
-
import { CCIP_EXPLORER } from "@src/constants";
|
|
8
|
+
import { openTxConfigForm } from "@src/components";
|
|
20
9
|
|
|
21
10
|
export const getSwapTx = async ({
|
|
22
11
|
dstChain,
|
|
@@ -58,138 +47,3 @@ const validateSwapTxData = (
|
|
|
58
47
|
throw new Error(`Provided token '${dstToken}' is not supported`);
|
|
59
48
|
}
|
|
60
49
|
};
|
|
61
|
-
|
|
62
|
-
export const monitorTransactionStatus = async (
|
|
63
|
-
srcChainId: number,
|
|
64
|
-
txHash: string,
|
|
65
|
-
) => {
|
|
66
|
-
const txReceipt = await getTxReceipt(srcChainId, txHash);
|
|
67
|
-
|
|
68
|
-
if (!txReceipt) {
|
|
69
|
-
throw new Error(
|
|
70
|
-
`No transaction found for chain ${srcChainId} tx ${txHash}`,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const supportedChains: Chain[] = (await getChains()).filter(
|
|
75
|
-
({ web3Environment, swapSupported }) =>
|
|
76
|
-
web3Environment === Web3Environment.MAINNET && swapSupported,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const messageSentEventSignature =
|
|
80
|
-
"MessageSent(bytes32,uint64,address,bytes,address,uint256,uint256,address,uint256)";
|
|
81
|
-
|
|
82
|
-
const messageSentEventAbi = [
|
|
83
|
-
"event MessageSent(bytes32 indexed messageId, uint64 indexed destinationChainSelector, address indexed sender, bytes data, address token, uint256 tokenAmount, uint256 valueForInstantCcipRecieve, address transferedToken, uint256 transferedTokenAmount)",
|
|
84
|
-
];
|
|
85
|
-
const messageReceivedEventSignature =
|
|
86
|
-
"MessageReceived(bytes32,uint64,address,bytes,address,uint256)";
|
|
87
|
-
|
|
88
|
-
const messageReceivedEventAbi = [
|
|
89
|
-
"event MessageReceived(bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed sender, bytes data, address token, uint256 tokenAmount)",
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
const messageSentEvent = txReceipt.logs?.find((log) => {
|
|
93
|
-
return log.topics[0] === ethers.utils.id(messageSentEventSignature);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
if (!messageSentEvent) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`No transaction event found for chain ${srcChainId} tx ${txHash}`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const messageId = messageSentEvent?.topics[1] as string;
|
|
103
|
-
const iface = new ethers.utils.Interface(messageSentEventAbi);
|
|
104
|
-
const decodedMessageSentEvent = iface.parseLog(messageSentEvent);
|
|
105
|
-
|
|
106
|
-
if (!decodedMessageSentEvent) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`No transaction event arguments found for chain ${srcChainId} tx ${txHash}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
const srcChain = supportedChains.find(
|
|
112
|
-
(chain) => chain.chainId === srcChainId.toString(),
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
const srcToken = srcChain?.tokens.find(
|
|
116
|
-
(token) => token.address === decodedMessageSentEvent.args?.token.toString(),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const targetChain = supportedChains.find(
|
|
120
|
-
(chain) =>
|
|
121
|
-
chain.ccipChainId ===
|
|
122
|
-
decodedMessageSentEvent.args?.destinationChainSelector?.toString(),
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const toChainId = targetChain?.chainId;
|
|
126
|
-
|
|
127
|
-
if (!toChainId) {
|
|
128
|
-
throw new Error(`Unknown destination chain!`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const transaction: MonitoredTransaction = {
|
|
132
|
-
txHash: txHash,
|
|
133
|
-
fromChain: srcChain?.displayName,
|
|
134
|
-
fromChainImage: srcChain?.image,
|
|
135
|
-
toChain: targetChain.displayName,
|
|
136
|
-
toChainImage: targetChain.image,
|
|
137
|
-
fromToken: srcToken?.symbol,
|
|
138
|
-
fromTokenImage: srcToken?.image,
|
|
139
|
-
fromAmount: weiToHumanReadable({
|
|
140
|
-
amount: decodedMessageSentEvent.args?.tokenAmount.toString(),
|
|
141
|
-
decimals: srcToken?.decimals || 18,
|
|
142
|
-
precisionFractionalPlaces: 4,
|
|
143
|
-
}),
|
|
144
|
-
isDone: false,
|
|
145
|
-
explorer: `${CCIP_EXPLORER}/tx/${txHash}`,
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
addTransactionToRenderedTransactions(transaction);
|
|
149
|
-
renderTxHistoryButtons();
|
|
150
|
-
|
|
151
|
-
// start listening
|
|
152
|
-
// TODO if someone will provide tx which already is done then we will never get the event. Will need to add status endpoint on backend for transactions to check if they are done or not.
|
|
153
|
-
|
|
154
|
-
const xSwapRouterOnDestinationAddress = ADDRESSES[toChainId]?.XSwapRouter;
|
|
155
|
-
if (!xSwapRouterOnDestinationAddress) {
|
|
156
|
-
throw new Error(`Unknown destination XSwapRouter!`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const xSwapRouterOnDestination = new ethers.Contract(
|
|
160
|
-
xSwapRouterOnDestinationAddress,
|
|
161
|
-
messageReceivedEventAbi,
|
|
162
|
-
ethers.getDefaultProvider(
|
|
163
|
-
supportedChains.find((chain) => chain.chainId === toChainId)
|
|
164
|
-
?.publicRpcUrls[0],
|
|
165
|
-
),
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const eventFilter = {
|
|
169
|
-
address: xSwapRouterOnDestinationAddress,
|
|
170
|
-
topics: [ethers.utils.id(messageReceivedEventSignature), messageId],
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const onSuccess = async () => {
|
|
174
|
-
updateTransactionDoneInRenderedTransactions(txHash);
|
|
175
|
-
renderTxHistoryButtons();
|
|
176
|
-
await new Promise((resolve) => setTimeout(() => resolve(true), 5000));
|
|
177
|
-
removeTransactionFromRenderedTransactions(txHash);
|
|
178
|
-
renderTxHistoryButtons();
|
|
179
|
-
};
|
|
180
|
-
xSwapRouterOnDestination.once(eventFilter, () => onSuccess());
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const getTxReceipt = async (txChainId: number, txHash: string) => {
|
|
184
|
-
const chains = await getChains();
|
|
185
|
-
const chain = chains.find((chain) => chain.chainId === txChainId.toString());
|
|
186
|
-
const provider = new ethers.providers.JsonRpcProvider(
|
|
187
|
-
chain?.publicRpcUrls[0],
|
|
188
|
-
);
|
|
189
|
-
try {
|
|
190
|
-
return await provider.getTransactionReceipt(txHash);
|
|
191
|
-
} catch (error) {
|
|
192
|
-
console.error("Error fetching transaction receipt:", error);
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
};
|
package/tailwind.config.js
CHANGED