From c8cf5cfa99209c2cc9e1e397297464d17e5c130b Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Tue, 11 Feb 2025 18:08:02 -0300 Subject: [PATCH 1/7] chore: fix balance and address recovery retry --- .../erc20/getERC20TokenBalanceForOwner.ts | 15 ++++++++ .../src/server/routers/sui/utils/utils.ts | 20 +++++++---- .../ConnectedInterchainTokensPage.tsx | 34 ++++++++++++------- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/apps/maestro/src/server/routers/erc20/getERC20TokenBalanceForOwner.ts b/apps/maestro/src/server/routers/erc20/getERC20TokenBalanceForOwner.ts index c9f047d3..819a5fff 100644 --- a/apps/maestro/src/server/routers/erc20/getERC20TokenBalanceForOwner.ts +++ b/apps/maestro/src/server/routers/erc20/getERC20TokenBalanceForOwner.ts @@ -22,6 +22,21 @@ export const getERC20TokenBalanceForOwner = publicProcedure }) ) .query(async ({ input, ctx }) => { + // If the token address and owner address are not the same length, the balance is 0 + // This is because a sui address can't have evm balances and vice versa + if (input.tokenAddress?.length !== input.owner?.length) { + return { + isTokenOwner: false, + isTokenMinter: false, + tokenBalance: "0", + decimals: 0, + isTokenPendingOwner: false, + hasPendingOwner: false, + hasMinterRole: false, + hasOperatorRole: false, + hasFlowLimiterRole: false, + }; + } // Sui address length is 66 if (input.tokenAddress?.length === 66) { let isTokenOwner = false; diff --git a/apps/maestro/src/server/routers/sui/utils/utils.ts b/apps/maestro/src/server/routers/sui/utils/utils.ts index 1185dcda..2bb945ef 100644 --- a/apps/maestro/src/server/routers/sui/utils/utils.ts +++ b/apps/maestro/src/server/routers/sui/utils/utils.ts @@ -92,7 +92,7 @@ export const getCoinAddressAndManagerByTokenId = async (input: { hasNextPage: true, nextCursor: null, data: [], - } as DynamicFieldPage; + } as DynamicFieldPage | null; try { const suiClient = new SuiClient({ url: config["sui"].rpc, @@ -112,10 +112,18 @@ export const getCoinAddressAndManagerByTokenId = async (input: { ?.fields?.value?.fields.registered_coins.fields.id.id; do { - result = await suiClient.getDynamicFields({ - parentId: registeredCoinsBagId, - cursor: cursor, - }); + result = await suiClient + .getDynamicFields({ + parentId: registeredCoinsBagId, + cursor: cursor, + }) + .catch((error) => { + console.error("Failed to get dynamic fields:", error); + return null; + }); + if (!result) { + return null; + } cursor = result.nextCursor; filteredResult = result.data.filter((item: any) => { return ( @@ -131,7 +139,7 @@ export const getCoinAddressAndManagerByTokenId = async (input: { if (filteredResult.length > 0) { return extractTokenDetails(filteredResult); } else { - console.log("Token ID not found."); + console.log("getCoinAddressAndManagerByTokenId: Token ID not found."); return null; } } catch (error) { diff --git a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx index 2b04abe3..38430a70 100644 --- a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx +++ b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx @@ -243,6 +243,9 @@ const ConnectedInterchainTokensPage: FC = ( const { mutateAsync: updateEVMAddresses } = trpc.interchainToken.updateEVMRemoteTokenAddress.useMutation(); + // Update Sui remote token addresses + // the address is wrong on the Sui chain on deployment because it's the EVM address, + // we wait for the tx to be executed then we update the address on the Sui chain useEffect(() => { if ( !isAlreadyUpdatingRemoteSui && @@ -251,15 +254,17 @@ const ConnectedInterchainTokensPage: FC = ( ) && props.tokenId ) { + console.log("updating Sui remote token addresses"); setAlreadyUpdatingRemoteSui(true); updateSuiAddresses({ tokenId: props.tokenId }) .then(() => { setAlreadyUpdatingRemoteSui(false); refetchPageData(); }) - .catch((error) => { - setAlreadyUpdatingRemoteSui(false); - console.error("Failed to update Sui remote token addresses:", error); + .catch(() => { + setTimeout(() => { + setAlreadyUpdatingRemoteSui(false); + }, 5000); }); } }, [ @@ -273,6 +278,13 @@ const ConnectedInterchainTokensPage: FC = ( const [isUpdating, setIsUpdating] = useState>({}); + const setChainUpdateStatus = useCallback( + (chainId: string | undefined, status: boolean) => { + setIsUpdating((prev) => ({ ...prev, [chainId ?? ""]: status })); + }, + [] + ); + useEffect(() => { interchainToken?.matchingTokens?.forEach((x) => { // check if the EVM token address is the same as sui, which is wrong @@ -282,22 +294,19 @@ const ConnectedInterchainTokensPage: FC = ( x.tokenAddress === props.tokenAddress && !isUpdating[x.chain?.id ?? ""] ) { - setIsUpdating((prev) => ({ ...prev, [x.chain?.id ?? ""]: true })); + setChainUpdateStatus(x.chain?.id, true); updateEVMAddresses({ tokenId: props?.tokenId as `0x${string}`, axelarChainId: x.chain?.id, }) .then(() => { + setChainUpdateStatus(x.chain?.id, false); refetchPageData(); }) - .catch((error) => { - console.error( - "Failed to update EVM remote token addresses:", - error - ); - }) - .finally(() => { - setIsUpdating((prev) => ({ ...prev, [x.chain?.id ?? ""]: false })); + .catch(() => { + setTimeout(() => { + setChainUpdateStatus(x.chain?.id, false); + }, 5000); }); } }); @@ -309,6 +318,7 @@ const ConnectedInterchainTokensPage: FC = ( updateEVMAddresses, isUpdating, refetchPageData, + setChainUpdateStatus, ]); const remoteChainsExecuted = useMemo( From c3ac39c276afc9dc36be48d7eef0cb4b45a930b8 Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Tue, 11 Feb 2025 18:55:18 -0300 Subject: [PATCH 2/7] chore: remove log --- .../InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx index 38430a70..8435e771 100644 --- a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx +++ b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx @@ -254,7 +254,6 @@ const ConnectedInterchainTokensPage: FC = ( ) && props.tokenId ) { - console.log("updating Sui remote token addresses"); setAlreadyUpdatingRemoteSui(true); updateSuiAddresses({ tokenId: props.tokenId }) .then(() => { From ddafa30f057909b7c32dbd376cf6ecc994b6073b Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Tue, 11 Feb 2025 23:10:09 -0300 Subject: [PATCH 3/7] chore: extract fin in paginated function --- .../src/server/routers/sui/utils/utils.ts | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/apps/maestro/src/server/routers/sui/utils/utils.ts b/apps/maestro/src/server/routers/sui/utils/utils.ts index 2bb945ef..41224d9b 100644 --- a/apps/maestro/src/server/routers/sui/utils/utils.ts +++ b/apps/maestro/src/server/routers/sui/utils/utils.ts @@ -1,10 +1,5 @@ import { TxBuilder } from "@axelar-network/axelar-cgp-sui"; -import { - getFullnodeUrl, - SuiClient, - SuiObjectChange, - type DynamicFieldPage, -} from "@mysten/sui/client"; +import { getFullnodeUrl, SuiClient, SuiObjectChange } from "@mysten/sui/client"; import { Transaction } from "@mysten/sui/transactions"; import config from "../config/devnet-amplifier.json"; @@ -86,13 +81,6 @@ export const getTokenOwner = async (tokenAddress: string) => { export const getCoinAddressAndManagerByTokenId = async (input: { tokenId: string; }) => { - let cursor = null; - let filteredResult = []; - let result = { - hasNextPage: true, - nextCursor: null, - data: [], - } as DynamicFieldPage | null; try { const suiClient = new SuiClient({ url: config["sui"].rpc, @@ -111,32 +99,15 @@ export const getCoinAddressAndManagerByTokenId = async (input: { const registeredCoinsBagId = (registeredCoinsObject.data?.content as any) ?.fields?.value?.fields.registered_coins.fields.id.id; - do { - result = await suiClient - .getDynamicFields({ - parentId: registeredCoinsBagId, - cursor: cursor, - }) - .catch((error) => { - console.error("Failed to get dynamic fields:", error); - return null; - }); - if (!result) { - return null; - } - cursor = result.nextCursor; - filteredResult = result.data.filter((item: any) => { - return ( - item.name.value.id.toString().toLowerCase() === - input.tokenId.toLowerCase() - ); - }); - if (filteredResult.length) { - break; - } - } while (result?.hasNextPage && !filteredResult?.length); + const filteredResult = await findInPaginatedDynamicFields( + suiClient, + registeredCoinsBagId, + (item: any) => + item.name.value.id.toString().toLowerCase() === + input.tokenId.toLowerCase() + ); - if (filteredResult.length > 0) { + if (filteredResult) { return extractTokenDetails(filteredResult); } else { console.log("getCoinAddressAndManagerByTokenId: Token ID not found."); @@ -150,6 +121,32 @@ export const getCoinAddressAndManagerByTokenId = async (input: { } }; +export async function findInPaginatedDynamicFields( + suiClient: SuiClient, + parentId: string, + filterFn: (item: any) => boolean +): Promise { + let cursor: string | null = null; + let result; + let filteredResult: any[] = []; + + do { + result = await suiClient + .getDynamicFields({ + parentId, + cursor: cursor, + }) + .catch((error) => { + console.error("Failed to get dynamic fields:", error); + return null; + }); + + cursor = result?.nextCursor ?? null; + filteredResult = result?.data?.filter(filterFn) ?? []; + } while (result?.hasNextPage && !filteredResult?.length); + return filteredResult?.length ? filteredResult : null; +} + function extractTokenDetails(filteredResult: any[]) { return filteredResult.map((item) => { // Extract the token manager (objectId) From 509444788cd5909566749a090a1dc9c4fb038d59 Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Tue, 11 Feb 2025 23:19:38 -0300 Subject: [PATCH 4/7] chore: fix build --- .../useDeployAndRegisterRemoteCanonicalTokenMutation.ts | 6 +----- .../hooks/useRegisterRemoteCanonicalTokens.ts | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts b/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts index 8c224102..3d2ffb3a 100644 --- a/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts +++ b/apps/maestro/src/features/CanonicalTokenDeployment/hooks/useDeployAndRegisterRemoteCanonicalTokenMutation.ts @@ -76,10 +76,7 @@ export function useDeployAndRegisterRemoteCanonicalTokenMutation( return { destinationChainNames, }; - }, [ - combinedComputed.indexedById, - input?.destinationChainIds, - ]); + }, [combinedComputed.indexedById, input?.destinationChainIds]); const multicallArgs = useMemo(() => { if (!input || !tokenId) { @@ -100,7 +97,6 @@ export function useDeployAndRegisterRemoteCanonicalTokenMutation( const gasValue = input.remoteDeploymentGasFees[i] ?? 0n; const args = { - originalChain: "", originalTokenAddress: input.tokenAddress as `0x{string}`, destinationChain, gasValue, diff --git a/apps/maestro/src/features/RegisterRemoteTokens/hooks/useRegisterRemoteCanonicalTokens.ts b/apps/maestro/src/features/RegisterRemoteTokens/hooks/useRegisterRemoteCanonicalTokens.ts index c722029c..f93060c5 100644 --- a/apps/maestro/src/features/RegisterRemoteTokens/hooks/useRegisterRemoteCanonicalTokens.ts +++ b/apps/maestro/src/features/RegisterRemoteTokens/hooks/useRegisterRemoteCanonicalTokens.ts @@ -66,7 +66,6 @@ export default function useRegisterRemoteCanonicalTokens( return INTERCHAIN_TOKEN_FACTORY_ENCODERS.deployRemoteCanonicalInterchainToken.data( { - originalChain: "", originalTokenAddress: tokenDetails.tokenAddress as `0x{string}`, destinationChain: axelarChainId, gasValue, From cb672ff8b809165c3755e3b71e730646810c5b52 Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Tue, 11 Feb 2025 23:56:59 -0300 Subject: [PATCH 5/7] chore: type utils --- .../src/server/routers/sui/utils/utils.ts | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/apps/maestro/src/server/routers/sui/utils/utils.ts b/apps/maestro/src/server/routers/sui/utils/utils.ts index 41224d9b..02b59b92 100644 --- a/apps/maestro/src/server/routers/sui/utils/utils.ts +++ b/apps/maestro/src/server/routers/sui/utils/utils.ts @@ -1,5 +1,11 @@ import { TxBuilder } from "@axelar-network/axelar-cgp-sui"; -import { getFullnodeUrl, SuiClient, SuiObjectChange } from "@mysten/sui/client"; +import { + getFullnodeUrl, + SuiClient, + SuiObjectChange, + type DynamicFieldInfo, + type DynamicFieldPage, +} from "@mysten/sui/client"; import { Transaction } from "@mysten/sui/transactions"; import config from "../config/devnet-amplifier.json"; @@ -97,7 +103,7 @@ export const getCoinAddressAndManagerByTokenId = async (input: { }, }); const registeredCoinsBagId = (registeredCoinsObject.data?.content as any) - ?.fields?.value?.fields.registered_coins.fields.id.id; + ?.fields?.value?.fields.registered_coins.fields.id.id as string; const filteredResult = await findInPaginatedDynamicFields( suiClient, @@ -121,14 +127,14 @@ export const getCoinAddressAndManagerByTokenId = async (input: { } }; -export async function findInPaginatedDynamicFields( +export async function findInPaginatedDynamicFields( suiClient: SuiClient, parentId: string, - filterFn: (item: any) => boolean -): Promise { + filterFn: (item: DynamicFieldInfo) => boolean +): Promise { let cursor: string | null = null; - let result; - let filteredResult: any[] = []; + let result: DynamicFieldPage | null; + let filteredResult: DynamicFieldInfo[] = []; do { result = await suiClient @@ -144,21 +150,19 @@ export async function findInPaginatedDynamicFields( cursor = result?.nextCursor ?? null; filteredResult = result?.data?.filter(filterFn) ?? []; } while (result?.hasNextPage && !filteredResult?.length); - return filteredResult?.length ? filteredResult : null; + return filteredResult?.length ? filteredResult[0] : null; } -function extractTokenDetails(filteredResult: any[]) { - return filteredResult.map((item) => { - // Extract the token manager (objectId) - const tokenManager = item.objectId; - - // Extract the address from the objectType - const objectType = item.objectType; - const addressMatch = objectType.match(/CoinData<(0x[^:]+)/); - const address = addressMatch ? addressMatch[1] : null; - return { - tokenManager, - address, - }; - })[0]; +function extractTokenDetails(filteredResult: DynamicFieldInfo) { + // Extract the token manager (objectId) + const tokenManager = filteredResult.objectId; + + // Extract the address from the objectType + const objectType = filteredResult.objectType; + const addressMatch = objectType.match(/CoinData<(0x[^:]+)/); + const address = addressMatch ? addressMatch[1] : null; + return { + tokenManager, + address, + }; } From 7744d5527023c9be16aba76e43e8296df5627fe4 Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Wed, 12 Feb 2025 00:09:42 -0300 Subject: [PATCH 6/7] chore: timeout --- .../ConnectedInterchainTokensPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx index 8435e771..ad0e1340 100644 --- a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx +++ b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx @@ -263,7 +263,7 @@ const ConnectedInterchainTokensPage: FC = ( .catch(() => { setTimeout(() => { setAlreadyUpdatingRemoteSui(false); - }, 5000); + }, 5000); // space requests while waiting for the tx to be executed and data to be available on sui chain }); } }, [ From 0de76be13018faf627c48ddd8928a2c8a3bc2566 Mon Sep 17 00:00:00 2001 From: SGiaccobasso Date: Wed, 12 Feb 2025 00:25:22 -0300 Subject: [PATCH 7/7] chore: fix types --- apps/maestro/src/server/routers/sui/utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/maestro/src/server/routers/sui/utils/utils.ts b/apps/maestro/src/server/routers/sui/utils/utils.ts index 02b59b92..331b3fc8 100644 --- a/apps/maestro/src/server/routers/sui/utils/utils.ts +++ b/apps/maestro/src/server/routers/sui/utils/utils.ts @@ -160,7 +160,7 @@ function extractTokenDetails(filteredResult: DynamicFieldInfo) { // Extract the address from the objectType const objectType = filteredResult.objectType; const addressMatch = objectType.match(/CoinData<(0x[^:]+)/); - const address = addressMatch ? addressMatch[1] : null; + const address = addressMatch?.[1] as string; return { tokenManager, address,