From 5ee1a720f24aa692d7573c881a58cb0b628e18a8 Mon Sep 17 00:00:00 2001 From: npty <78221556+npty@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:46:18 +0700 Subject: [PATCH] feat: fix tx progress for its hub tx (#489) --- .../CanonicalTokenDeployment.state.ts | 6 +-- ...getTransactionStatusOnDestinationChains.ts | 40 ++++++++++---- ...tTransactionStatusesOnDestinationChains.ts | 54 ++++++++++++++++--- apps/maestro/src/services/axelarscan/hooks.ts | 30 +++++++---- apps/maestro/src/services/gmp/hooks.ts | 25 +++------ .../GMPTxStatusMonitor/GMPTxStatusMonitor.tsx | 17 +++--- packages/api/src/gmp/types.ts | 24 ++++++++- 7 files changed, 135 insertions(+), 61 deletions(-) diff --git a/apps/maestro/src/features/CanonicalTokenDeployment/CanonicalTokenDeployment.state.ts b/apps/maestro/src/features/CanonicalTokenDeployment/CanonicalTokenDeployment.state.ts index 0d5f5eca7..55c8e3a0f 100644 --- a/apps/maestro/src/features/CanonicalTokenDeployment/CanonicalTokenDeployment.state.ts +++ b/apps/maestro/src/features/CanonicalTokenDeployment/CanonicalTokenDeployment.state.ts @@ -92,17 +92,15 @@ function useCanonicalTokenDeploymentState( }, actions: { reset: () => { - console.log("reset"); setState((draft) => { Object.assign(draft, initialState); }); }, - setTokenDetails: (detatils: Partial) => { - console.log("setTokenDetails", detatils); + setTokenDetails: (details: Partial) => { setState((draft) => { draft.tokenDetails = { ...draft.tokenDetails, - ...detatils, + ...details, }; }); }, diff --git a/apps/maestro/src/server/routers/gmp/getTransactionStatusOnDestinationChains.ts b/apps/maestro/src/server/routers/gmp/getTransactionStatusOnDestinationChains.ts index 04f5ff2d4..c024d7c1a 100644 --- a/apps/maestro/src/server/routers/gmp/getTransactionStatusOnDestinationChains.ts +++ b/apps/maestro/src/server/routers/gmp/getTransactionStatusOnDestinationChains.ts @@ -14,6 +14,7 @@ export const SEARCHGMP_SOURCE = { "confirm", "executed", "callback", + "interchain_token_deployment_started.destinationChain", ], excludes: [ "call.transaction", @@ -54,31 +55,52 @@ export const getTransactionStatusOnDestinationChains = publicProcedure }); if (data.length) { - const result = data.reduce( - (acc, gmpData) => { - const { call, status } = gmpData; - const destinationChain = gmpData.callback?.returnValues.destinationChain.toLowerCase() || call.returnValues.destinationChain.toLowerCase(); + const pendingResult = data.reduce( + async (acc, gmpData) => { + const { + call, + status: firstHopStatus, + interchain_token_deployment_started: tokenDeployment, + } = gmpData; + + const chainType = gmpData.call.chain_type; + let secondHopStatus = "pending" + + if (gmpData.callback) { + const secondHopMessageId = gmpData.callback.returnValues.messageId; + const secondHopData = await ctx.services.gmp.searchGMP({ + txHash: secondHopMessageId, + _source: SEARCHGMP_SOURCE, + }); + + secondHopStatus = secondHopData[0].status; + } + + const destinationChain = + tokenDeployment?.destinationChain?.toLowerCase() || + call.returnValues.destinationChain.toLowerCase(); + return { ...acc, [destinationChain]: { - status, + status: chainType === "evm" ? firstHopStatus : secondHopStatus, txHash: call.transactionHash, logIndex: call.logIndex ?? call._logIndex ?? 0, - txId: call.id, + txId: gmpData.message_id, }, }; }, - {} as { + {} as Promise<{ [chainId: string]: { status: GMPTxStatus; txHash: `0x${string}`; txId: string; logIndex: number; }; - } + }> ); - return result; + return await pendingResult; } // If we don't find the transaction, we throw a 404 error diff --git a/apps/maestro/src/server/routers/gmp/getTransactionStatusesOnDestinationChains.ts b/apps/maestro/src/server/routers/gmp/getTransactionStatusesOnDestinationChains.ts index a4ceb7a63..c516f3942 100644 --- a/apps/maestro/src/server/routers/gmp/getTransactionStatusesOnDestinationChains.ts +++ b/apps/maestro/src/server/routers/gmp/getTransactionStatusesOnDestinationChains.ts @@ -18,6 +18,7 @@ export const getTransactionStatusesOnDestinationChains = publicProcedure .input(INPUT_SCHEMA) .query(async ({ ctx, input }) => { try { + // Fetch all first hop transactions const results = await Promise.all( input.txHashes.map((txHash) => ctx.services.gmp.searchGMP({ @@ -27,15 +28,52 @@ export const getTransactionStatusesOnDestinationChains = publicProcedure ) ); - return results.flat().reduce( - (acc, { call, status }) => ({ + // Process all transactions and their second hops + const processedResults = await Promise.all( + results.flat().map(async (gmpData) => { + const { + call, + status: firstHopStatus, + interchain_token_deployment_started: tokenDeployment, + } = gmpData; + + const chainType = gmpData.call.chain_type; + let secondHopStatus = "pending" as GMPTxStatus; + + // Check for second hop if callback exists + if (gmpData.callback) { + const secondHopMessageId = gmpData.callback.returnValues.messageId; + const secondHopData = await ctx.services.gmp.searchGMP({ + txHash: secondHopMessageId, + _source: SEARCHGMP_SOURCE, + }); + + if (secondHopData.length > 0) { + secondHopStatus = secondHopData[0].status; + } + } + + const destinationChain = + tokenDeployment?.destinationChain?.toLowerCase() || + call.returnValues.destinationChain.toLowerCase(); + + return { + destinationChain, + data: { + status: chainType === "evm" ? firstHopStatus : secondHopStatus, + txHash: call.transactionHash, + logIndex: call.logIndex ?? call._logIndex ?? 0, + txId: gmpData.message_id, + }, + }; + }) + ); + + // Combine all results into a single object + return processedResults.reduce( + (acc, { destinationChain, data }) => ({ ...acc, - [call.returnValues.destinationChain.toLowerCase()]: { - status, - txHash: call.transactionHash, - logIndex: call.logIndex ?? call._logIndex ?? 0, - txId: call.id, - }, + [destinationChain]: data, }), {} as { [chainId: string]: { diff --git a/apps/maestro/src/services/axelarscan/hooks.ts b/apps/maestro/src/services/axelarscan/hooks.ts index aeb9fd7de..1f22e8e71 100644 --- a/apps/maestro/src/services/axelarscan/hooks.ts +++ b/apps/maestro/src/services/axelarscan/hooks.ts @@ -14,8 +14,16 @@ const EVM_CHAIN_CONFIGS_BY_ID = indexBy(prop("id"), WAGMI_CHAIN_CONFIGS); const VM_CHAIN_CONFIGS_BY_ID = indexBy(prop("id"), WAGMI_CHAIN_CONFIGS); export function useAllChainConfigsQuery() { - const { computed: evmComputed, data: evmChains, isLoading: isLoadingEVM } = useEVMChainConfigsQuery(); - const { computed: vmComputed, data: vmChains, isLoading: isLoadingVM } = useVMChainConfigsQuery(); + const { + computed: evmComputed, + data: evmChains, + ...evmChainsQuery + } = useEVMChainConfigsQuery(); + const { + computed: vmComputed, + data: vmChains, + ...vmChainsQuery + } = useVMChainConfigsQuery(); const combinedComputed = useMemo( () => ({ indexedById: { @@ -26,10 +34,7 @@ export function useAllChainConfigsQuery() { ...vmComputed.indexedByChainId, ...evmComputed.indexedByChainId, }, - wagmiChains: [ - ...vmComputed.wagmiChains, - ...evmComputed.wagmiChains - ] + wagmiChains: [...vmComputed.wagmiChains, ...evmComputed.wagmiChains], }), [evmComputed, vmComputed] ); @@ -60,8 +65,15 @@ export function useAllChainConfigsQuery() { return Array.from(chainMap.values()); }, [evmChains, vmChains]); - - return {combinedComputed, allChains, isLoading: isLoadingEVM || isLoadingVM}; + return { + combinedComputed, + allChains, + isLoading: evmChainsQuery.isLoading || vmChainsQuery.isLoading, + isError: evmChainsQuery.isError || vmChainsQuery.isError, + error: evmChainsQuery.error || vmChainsQuery.error, + isFetching: evmChainsQuery.isFetching || vmChainsQuery.isFetching, + isSuccess: evmChainsQuery.isSuccess || vmChainsQuery.isSuccess, + }; } export function useEVMChainConfigsQuery() { @@ -150,7 +162,7 @@ export function useVMChainConfigsQuery() { computed: { indexedByChainId: indexBy(prop("chain_id"), configured), indexedById: indexBy(prop("id"), configured), - wagmiChains + wagmiChains, }, }; } diff --git a/apps/maestro/src/services/gmp/hooks.ts b/apps/maestro/src/services/gmp/hooks.ts index 3f6714d1d..c183bf9c6 100644 --- a/apps/maestro/src/services/gmp/hooks.ts +++ b/apps/maestro/src/services/gmp/hooks.ts @@ -4,27 +4,14 @@ import { isAddress } from "viem"; import { trpc } from "~/lib/trpc"; import { hex64 } from "~/lib/utils/validation"; -import { useEVMChainConfigsQuery, useVMChainConfigsQuery } from "../axelarscan/hooks"; +import { useAllChainConfigsQuery } from "../axelarscan/hooks"; export function useInterchainTokensQuery(input: { chainId?: number; tokenAddress?: `0x${string}`; strict?: boolean; }) { - const { computed: evmComputed, ...evmChainsQuery } = useEVMChainConfigsQuery(); - const { computed: vmComputed, ...vmChainsQuery } = useVMChainConfigsQuery(); - - const combinedComputed = useMemo(() => ({ - indexedById: { - ...vmComputed.indexedById, - ...evmComputed.indexedById, - }, - indexedByChainId: { - ...vmComputed.indexedByChainId, - ...evmComputed.indexedByChainId, - }, - wagmiChains: evmComputed.wagmiChains, // Keep wagmiChains for EVM compatibility - }), [evmComputed, vmComputed]); + const { combinedComputed, isLoading, isError, error, isFetching } = useAllChainConfigsQuery(); const { data, ...queryResult } = trpc.interchainToken.searchInterchainToken.useQuery( @@ -58,10 +45,10 @@ export function useInterchainTokensQuery(input: { combinedComputed.wagmiChains?.find((x) => x?.id === chainId) ), }, - isLoading: evmChainsQuery.isLoading || vmChainsQuery.isLoading || queryResult.isLoading, - isFetching: evmChainsQuery.isFetching || vmChainsQuery.isFetching || queryResult.isFetching, - isError: evmChainsQuery.isError || vmChainsQuery.isError || queryResult.isError, - error: evmChainsQuery.error || vmChainsQuery.error || queryResult.error, + isLoading, + isFetching, + isError, + error, }; } diff --git a/apps/maestro/src/ui/compounds/GMPTxStatusMonitor/GMPTxStatusMonitor.tsx b/apps/maestro/src/ui/compounds/GMPTxStatusMonitor/GMPTxStatusMonitor.tsx index 144aafdd0..608699ba2 100644 --- a/apps/maestro/src/ui/compounds/GMPTxStatusMonitor/GMPTxStatusMonitor.tsx +++ b/apps/maestro/src/ui/compounds/GMPTxStatusMonitor/GMPTxStatusMonitor.tsx @@ -132,36 +132,31 @@ const TxFinalityProgress: FC<{ txHash: `0x${string}`; chainId: number }> = ({ }; const GMPTxStatusMonitor = ({ txHash, onAllChainsExecuted }: Props) => { + const chainId = useChainId(); + const { combinedComputed } = useAllChainConfigsQuery(); const { data: statuses, computed: { chains: total, executed }, isLoading, } = useGetTransactionStatusOnDestinationChainsQuery({ txHash }); - const chainId = useChainId(); - - const { combinedComputed } = useAllChainConfigsQuery(); - const statusList = Object.values(statuses ?? {}); - const pendingItsHubTx = - Object.keys(statuses).includes("axelarnet") || - Object.keys(statuses).includes("axelar"); useEffect(() => { if ( statusList.length && - !pendingItsHubTx && statusList?.every((s) => s.status === "executed") ) { onAllChainsExecuted?.(); } - }, [pendingItsHubTx, statusList, onAllChainsExecuted]); + }, [statusList, onAllChainsExecuted]); - if (!statuses || Object.keys(statuses).length === 0 || pendingItsHubTx) { - if (!isLoading && !pendingItsHubTx) { + if (!statuses || Object.keys(statuses).length === 0) { + if (!isLoading) { // nothing to show return null; } + return (
Loading transaction status...
diff --git a/packages/api/src/gmp/types.ts b/packages/api/src/gmp/types.ts index bc8e56a8f..097cb4034 100644 --- a/packages/api/src/gmp/types.ts +++ b/packages/api/src/gmp/types.ts @@ -1,3 +1,5 @@ +import type { Log } from "viem"; + type BaseGMPResponse = T & { time_spent: number; }; @@ -69,6 +71,23 @@ export type SearchGMPParams = Omit & { }; }; +export type TransactionReceipt = { + blockHash: `0x${string}`; + blockNumber: number; + contractAddress: string | null; + cumulativeGasUsed: string; + effectiveGasPrice: string; + from: `0x${string}`; + gasUsed: string; + logs: Array; + status: number; + to: `0x${string}`; + transactionHash: `0x${string}`; + transactionIndex: number; + type: number; + gasLimit: string; +}; + export type SearchGMPCall = { blockNumber: number; blockHash: `0x${string}`; @@ -84,11 +103,13 @@ export type SearchGMPCall = { event: string; eventIndex: number; eventSignature: string; - id: string; + _id: string; chain: string; contract_address: `0x${string}`; chain_type: ChainType; destination_chain_type: ChainType; + receipt?: TransactionReceipt; + messageIdIndex: number; returnValues: { sender: string; destinationChain: string; @@ -296,6 +317,7 @@ export type SearchGMPResponseData = { status: GMPTxStatus; executed?: SearchGMPExecuted; error?: SearchGMPDataError; + message_id: string; time_spent: SearchGMPTimespent; gas_paid: SearchGMPGasPaid; gas_status: SearchGMPGasStatus;