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, 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..331b3fc8 100644 --- a/apps/maestro/src/server/routers/sui/utils/utils.ts +++ b/apps/maestro/src/server/routers/sui/utils/utils.ts @@ -3,6 +3,7 @@ import { getFullnodeUrl, SuiClient, SuiObjectChange, + type DynamicFieldInfo, type DynamicFieldPage, } from "@mysten/sui/client"; import { Transaction } from "@mysten/sui/transactions"; @@ -86,13 +87,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; try { const suiClient = new SuiClient({ url: config["sui"].rpc, @@ -109,29 +103,20 @@ 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, - }); - 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); + ?.fields?.value?.fields.registered_coins.fields.id.id as string; + + 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("Token ID not found."); + console.log("getCoinAddressAndManagerByTokenId: Token ID not found."); return null; } } catch (error) { @@ -142,18 +127,42 @@ export const getCoinAddressAndManagerByTokenId = async (input: { } }; -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]; +export async function findInPaginatedDynamicFields( + suiClient: SuiClient, + parentId: string, + filterFn: (item: DynamicFieldInfo) => boolean +): Promise { + let cursor: string | null = null; + let result: DynamicFieldPage | null; + let filteredResult: DynamicFieldInfo[] = []; + + 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[0] : null; +} + +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?.[1] as string; + return { + tokenManager, + address, + }; } diff --git a/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx b/apps/maestro/src/ui/pages/InterchainTokenDetailsPage/ConnectedInterchainTokensPage.tsx index 2b04abe3..ad0e1340 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 && @@ -257,9 +260,10 @@ const ConnectedInterchainTokensPage: FC = ( setAlreadyUpdatingRemoteSui(false); refetchPageData(); }) - .catch((error) => { - setAlreadyUpdatingRemoteSui(false); - console.error("Failed to update Sui remote token addresses:", error); + .catch(() => { + setTimeout(() => { + setAlreadyUpdatingRemoteSui(false); + }, 5000); // space requests while waiting for the tx to be executed and data to be available on sui chain }); } }, [ @@ -273,6 +277,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 +293,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 +317,7 @@ const ConnectedInterchainTokensPage: FC = ( updateEVMAddresses, isUpdating, refetchPageData, + setChainUpdateStatus, ]); const remoteChainsExecuted = useMemo(