@xswap-link/sdk
Version:
JavaScript SDK for XSwap platform
190 lines (164 loc) • 5.48 kB
text/typescript
import {
addTransactionToRenderedTransactions,
removeTransactionFromRenderedTransactions,
renderTxStatusButtons,
updateTransactionDoneInRenderedTransactions,
} from "@src/components";
import {
MSG_RECEIVED_EVENT_ABI,
MSG_RECEIVED_EVENT_SIG,
MSG_SENT_EVENT_ABI,
MSG_SENT_EVENT_SIG,
TX_RECEIPT_STATUS_LIFETIME,
} from "@src/constants";
import { ADDRESSES } from "@src/contracts";
import { Chain, Token, Transaction, Web3Environment } from "@src/models";
import { getChains, getTxStatus } from "@src/services";
import retry from "async-retry";
import { ethers } from "ethers";
import { getCustomTokenData } from "../blockchain";
export const renderTxStatus = async (
txChainId: string,
txHash: string,
dstTokenAddress?: string,
dstTokenAmount?: string,
): Promise<void> => {
const txReceipt = await getTxReceipt(txChainId, txHash);
const supportedChains: Chain[] = (await getChains()).filter(
({ web3Environment, swapSupported }) =>
web3Environment === Web3Environment.MAINNET && swapSupported,
);
const messageSentEvent = txReceipt.logs?.find((log: { topics: string[] }) => {
return log.topics[0] === ethers.utils.id(MSG_SENT_EVENT_SIG);
});
if (!messageSentEvent) {
throw new Error(
`No transaction event found for chain ${txChainId} tx ${txHash}`,
);
}
const messageId = messageSentEvent?.topics[1] as string;
const iface = new ethers.utils.Interface(MSG_SENT_EVENT_ABI);
const decodedMessageSentEvent = iface.parseLog(messageSentEvent);
if (!decodedMessageSentEvent) {
throw new Error(
`No transaction event arguments found for chain ${txChainId} tx ${txHash}`,
);
}
const MessageSentEventArgs = decodedMessageSentEvent.args;
const srcChain = supportedChains.find(
(chain) => chain.chainId === txChainId.toString(),
);
if (!srcChain) {
throw new Error(`Unknown src chain ${txChainId}`);
}
const srcTokenAddress = (
MessageSentEventArgs?.token.toString() as string
).toLowerCase();
let srcToken = srcChain.tokens.find(
(token) => token.address.toLowerCase() === srcTokenAddress,
);
if (!srcToken) {
srcToken = await getCustomTokenData(srcChain, srcTokenAddress);
}
const dstChain = supportedChains.find(
(chain) =>
chain.ccipChainId ===
MessageSentEventArgs?.destinationChainSelector?.toString(),
);
const dstChainId = dstChain?.chainId;
if (!dstChainId) {
throw new Error(`Unknown destination chain!`);
}
let dstToken: Token | undefined = undefined;
if (dstTokenAddress) {
dstToken = dstChain.tokens.find(
(token) => token.address.toLowerCase() === dstTokenAddress.toLowerCase(),
);
}
const timestamp = Date.now() / 1000;
const transaction: Transaction = {
hash: txReceipt.transactionHash,
timestamp,
sourceChainId: srcChain.chainId,
targetChainId: dstChain.chainId,
amountWei: MessageSentEventArgs?.tokenAmount.toString(),
tokenAddress: srcToken.address,
tokenOutAddress: dstToken?.address,
tokenOutAmount: dstTokenAmount,
estimatedDeliveryTimestamp:
MessageSentEventArgs?.valueForInstantCcipRecieve.toString() !== "0"
? timestamp * 1000 + 30 * 1000
: timestamp * 1000 + 30 * 60 * 1000,
status: "IN_PROGRESS",
};
addTransactionToRenderedTransactions({
transaction,
supportedChains,
dstToken,
srcToken,
});
renderTxStatusButtons();
const xSwapRouterOnDestinationAddress = ADDRESSES[dstChainId]?.XSwapRouter;
if (!xSwapRouterOnDestinationAddress) {
throw new Error(`Unknown destination XSwapRouter!`);
}
const xSwapRouterOnDestination = new ethers.Contract(
xSwapRouterOnDestinationAddress,
MSG_RECEIVED_EVENT_ABI,
ethers.getDefaultProvider(
supportedChains.find((chain) => chain.chainId === dstChainId)
?.publicRpcUrls[0],
),
);
const msgReceivedEventFilter = {
address: xSwapRouterOnDestinationAddress,
topics: [ethers.utils.id(MSG_RECEIVED_EVENT_SIG), messageId],
};
const onSuccess = async () => {
updateTransactionDoneInRenderedTransactions(txHash);
renderTxStatusButtons();
await new Promise((resolve) =>
setTimeout(() => resolve(true), TX_RECEIPT_STATUS_LIFETIME),
);
removeTransactionFromRenderedTransactions(txHash);
renderTxStatusButtons();
};
// check tx status on backend
getTxStatus({ transferId: messageId }).then((response) => {
if (response.status === "DONE") {
onSuccess();
} else {
setTimeout(
() =>
getTxStatus({ transferId: messageId }).then((response) => {
if (response.status === "DONE") {
onSuccess();
}
}),
30 * 60 * 1000,
); // additional check after 30 min
xSwapRouterOnDestination.once(msgReceivedEventFilter, () => onSuccess());
}
});
};
const getTxReceipt = async (txChainId: string, txHash: string) => {
try {
const chain = (await getChains()).find(
(chain) => chain.chainId === txChainId,
);
const provider = new ethers.providers.JsonRpcProvider(
chain?.publicRpcUrls[0],
);
return await retry(async () => {
const txReceipt = await provider.getTransactionReceipt(txHash);
if (!txReceipt) {
throw new Error(`Couldn't fetch tx receipt`);
}
return txReceipt;
});
} catch (e) {
throw new Error(
`Couldn't fetch tx ${txHash} receipt on chain ${txChainId}`,
);
}
};