Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add selecting type and storing correctly to db #517

Merged
merged 6 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ export function useDeployAndRegisterRemoteInterchainTokenMutation(
name: input.tokenName,
decimals: input.decimals,
destinationChainIds: input.destinationChainIds,
minterAddress: input.minterAddress,
});
if (result?.digest && result.deploymentMessageId) {
const token: any = result?.events?.[0]?.parsedJson;
Expand All @@ -279,8 +280,9 @@ export function useDeployAndRegisterRemoteInterchainTokenMutation(
tokenName: input.tokenName,
tokenSymbol: input.tokenSymbol,
tokenDecimals: input.decimals,
tokenManagerType: result.tokenManagerType,
axelarChainId: input.sourceChainId,
originalMinterAddress: input.minterAddress,
originalMinterAddress: result.minterAddress,
destinationAxelarChainIds: input.destinationChainIds,
tokenManagerAddress: result.tokenManagerAddress,
tokenAddress: result.tokenAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Maybe } from "@axelarjs/utils";
import { ComponentRef, useMemo, useRef, type FC } from "react";
import { type FieldError, type SubmitHandler } from "react-hook-form";

import { isValidSuiAddress } from "@mysten/sui/utils";

import "~/features/InterchainTokenDeployment";

import {
Expand All @@ -32,6 +34,33 @@ import {

const MAX_UINT64 = BigInt(2) ** BigInt(64) - BigInt(1);

const validateMinterAddress = (minter: string, chainId: number) => {
if (!minter) {
return {
type: "validate",
message: "Minter address is required",
};
}

if (chainId === SUI_CHAIN_ID) {
if (!isValidSuiAddress(minter)) {
return {
type: "validate",
message: "Invalid Sui minter address",
};
}
} else {
if (!isValidEVMAddress(minter)) {
return {
type: "validate",
message: "Invalid EVM minter address",
};
}
}

return true;
};

const TokenDetails: FC = () => {
const { state, actions } = useInterchainTokenDeploymentStateContainer();
const chainId = useChainId();
Expand All @@ -51,20 +80,13 @@ const TokenDetails: FC = () => {
return;
}

if (!minter) {
return {
type: "required",
message: "Minter address is required",
};
}

if (!isValidEVMAddress(minter)) {
return {
type: "validate",
message: "Invalid minter address",
};
if (minter) {
const minterValidation = validateMinterAddress(minter, chainId);
if (minterValidation !== true) {
return minterValidation;
}
}
}, [isMintable, minter]);
}, [isMintable, minter, chainId]);

const initialSupplyErrorMessage = useMemo<FieldError | undefined>(() => {
if (isMintable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { suiClient as client } from "~/lib/clients/suiClient";
import { useAccount } from "~/lib/hooks";
import { trpc } from "~/lib/trpc";

type RegisterRemoteInterchainTokenOnSuiInput ={
type RegisterRemoteInterchainTokenOnSuiInput = {
axelarChainIds: string[];
originChainId: number;
tokenAddress: string;
Expand Down Expand Up @@ -48,14 +48,13 @@ export function useRegisterRemoteInterchainTokenOnSui() {
}

try {
const registerTokenTxJSON =
await getRegisterRemoteInterchainTokenTx({
tokenAddress: input.tokenAddress,
destinationChainIds: input.axelarChainIds,
originChainId: input.originChainId,
sender: currentAccount.address,
symbol: input.symbol,
});
const registerTokenTxJSON = await getRegisterRemoteInterchainTokenTx({
tokenAddress: input.tokenAddress,
destinationChainIds: input.axelarChainIds,
originChainId: input.originChainId,
sender: currentAccount.address,
symbol: input.symbol,
});

const registerTokenResult = await signAndExecuteTransaction({
transaction: registerTokenTxJSON,
Expand All @@ -74,4 +73,3 @@ export function useRegisterRemoteInterchainTokenOnSui() {
account: currentAccount,
};
}

20 changes: 7 additions & 13 deletions apps/maestro/src/features/suiHooks/useDeployToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export type DeployTokenParams = {
decimals: number;
destinationChainIds: string[];
skipRegister?: boolean;
minterAddress?: string;
};

export type DeployTokenResult = SuiTransactionBlockResponse & {
tokenManagerAddress: string;
tokenAddress: string;
tokenManagerType: "mint/burn" | "lock/unlock";
tokenManagerType: "mint_burn" | "lock_unlock";
deploymentMessageId?: string;
minterAddress?: string;
};

type SuiObjectCreated =
Expand Down Expand Up @@ -95,6 +97,7 @@ export default function useTokenDeploy() {
decimals,
destinationChainIds,
skipRegister = false,
minterAddress,
}: DeployTokenParams): Promise<DeployTokenResult> => {
if (!currentAccount) {
throw new Error("Wallet not connected");
Expand Down Expand Up @@ -135,9 +138,6 @@ export default function useTokenDeploy() {
throw new Error("Failed to deploy token");
}

// Mint tokens before registering, as the treasury cap will be transferred to the ITS contract
// TODO: should merge this with above to avoid multiple transactions.
// we can do this once we know whether the token is mint/burn or lock/unlock
if (treasuryCap) {
const mintTxJSON = await getMintTx({
sender: currentAccount.address,
Expand All @@ -157,6 +157,7 @@ export default function useTokenDeploy() {
tokenPackageId: tokenAddress,
metadataId: metadata.objectId,
destinationChains: destinationChainIds,
minterAddress: minterAddress,
});

if (!sendTokenTxJSON) {
Expand All @@ -170,15 +171,7 @@ export default function useTokenDeploy() {
});
const coinManagementObjectId = findCoinDataObject(sendTokenResult);

// find treasury cap in the sendTokenResult to determine the token manager type
const sendTokenObjects = sendTokenResult?.objectChanges;
const treasuryCapSendTokenResult = findObjectByType(
sendTokenObjects as SuiObjectChange[],
"TreasuryCap"
);
const tokenManagerType = treasuryCapSendTokenResult
? "mint/burn"
: "lock/unlock";
const tokenManagerType = minterAddress ? "lock_unlock" : "mint_burn";
const txIndex = sendTokenResult?.events?.[3]?.id?.eventSeq ?? 0; // TODO: find the correct txIndex, it seems to be always 3
const deploymentMessageId = `${sendTokenResult?.digest}-${txIndex}`;
return {
Expand All @@ -187,6 +180,7 @@ export default function useTokenDeploy() {
tokenManagerAddress: coinManagementObjectId || "0x",
tokenAddress,
tokenManagerType,
minterAddress,
};
} catch (error) {
console.error("Token deployment failed:", error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,10 @@ export const getInterchainTokenBalanceForOwner = publicProcedure
// This happens when the token is deployed on sui as a remote chain
if (!metadata) {
const coinInfo = await getCoinInfoByCoinType(client, coinType);
console.log("coinInfo", coinInfo);
decimals = coinInfo?.decimals;
}

let tokenOwner = null;
// TODO: address this properly
try {
tokenOwner = await getTokenOwner(input.tokenAddress);
} catch (error) {
Expand Down
32 changes: 24 additions & 8 deletions apps/maestro/src/server/routers/sui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "@axelar-network/axelar-cgp-sui";
import { z } from "zod";

import { NEXT_PUBLIC_NETWORK_ENV } from "~/config/env";
import { suiClient } from "~/lib/clients/suiClient";
import { publicProcedure, router } from "~/server/trpc";
import {
Expand Down Expand Up @@ -70,6 +71,7 @@ export const suiRouter = router({
destinationChains: z.array(z.string()),
tokenPackageId: z.string(),
metadataId: z.string(),
minterAddress: z.string().optional(),
})
)
.mutation(async ({ input }) => {
Expand Down Expand Up @@ -97,13 +99,27 @@ export const suiRouter = router({

const { Example, ITS } = chainConfig.contracts;
const itsObjectId = ITS.objects.ITS;

// TODO: handle register type properly, whether it's mint/burn or lock/unlock.
await txBuilder.moveCall({
target: `${Example.address}::its::register_coin`,
typeArguments: [tokenType],
arguments: [itsObjectId, metadataId],
});
const treasuryCap = await getTreasuryCap(tokenPackageId);

if (input.minterAddress) {
await txBuilder.moveCall({
target: `${Example.address}::its::register_coin`,
typeArguments: [tokenType],
arguments: [itsObjectId, metadataId],
});
txBuilder.tx.transferObjects(
[treasuryCap as string],
txBuilder.tx.pure.address(input.minterAddress)
);
} else {
await txBuilder
.moveCall({
target: `${Example.address}::its::register_coin_with_cap`,
typeArguments: [tokenType],
arguments: [itsObjectId, metadataId, treasuryCap],
})
.catch((e) => console.log("error with register coin treasury", e));
}

for (const destinationChain of destinationChains) {
await deployRemoteInterchainToken(
Expand Down Expand Up @@ -241,7 +257,7 @@ export const suiRouter = router({
.mutation(async ({ input }) => {
try {
const response = await fetch(
`${suiServiceBaseUrl}/chain/devnet-amplifier`
`${suiServiceBaseUrl}/chain/${NEXT_PUBLIC_NETWORK_ENV}`
);
const _chainConfig = await response.json();
const chainConfig = _chainConfig.chains.sui;
Expand Down
24 changes: 14 additions & 10 deletions apps/maestro/src/server/routers/sui/utils/txUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { TxBuilder } from "@axelar-network/axelar-cgp-sui";

import { suiClient } from "~/lib/clients/suiClient";
import { suiServiceBaseUrl } from "./utils";
import {
getCoinAddressFromType,
getTokenOwner,
suiServiceBaseUrl,
} from "./utils";

export async function getChainConfig() {
const response = await fetch(`${suiServiceBaseUrl}/chain/devnet-amplifier`);
Expand All @@ -19,14 +23,12 @@ export function setupTxBuilder(sender: string) {
export async function getTokenId(
txBuilder: TxBuilder,
tokenId: string,
ITS: any,
ITS: any
) {
const [TokenId] = await txBuilder.moveCall({
const [TokenId] = await txBuilder.moveCall({
target: `${ITS.address}::token_id::from_u256`,
arguments: [
tokenId.toString(),
],
})
arguments: [tokenId.toString()],
});

return TokenId;
}
Expand All @@ -35,17 +37,19 @@ export async function getTokenIdByCoinMetadata(
txBuilder: TxBuilder,
coinType: string,
ITS: any,
coinMetadata: any,
coinMetadata: any
) {
const [TokenId] = await txBuilder.moveCall({
const address = getCoinAddressFromType(coinType);
const tokenOwner = await getTokenOwner(address);
const [TokenId] = await txBuilder.moveCall({
target: `${ITS.address}::token_id::from_info`,
typeArguments: [coinType],
arguments: [
coinMetadata.name,
coinMetadata.symbol,
txBuilder.tx.pure.u8(coinMetadata.decimals),
txBuilder.tx.pure.bool(false),
txBuilder.tx.pure.bool(false),
txBuilder.tx.pure.bool(!tokenOwner), // true for mint_burn, false for lock_unlock as this checks whether an address owns the treasury cap
],
});
return TokenId;
Expand Down
27 changes: 13 additions & 14 deletions apps/maestro/src/server/routers/sui/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,26 @@ export const getCoinType = async (tokenAddress: string) => {
// get token owner from token address
// TODO: this is wrong the the destination chain is sui where the token owner is axelar relayer
export const getTokenOwner = async (tokenAddress: string) => {
const treasuryCap = await getTreasuryCap(tokenAddress);
const object = await client.getObject({
id: tokenAddress,
id: treasuryCap as string,
options: {
showOwner: true,
showPreviousTransaction: true,
},
});

if (object?.data?.owner === "Immutable") {
const previousTx = object.data.previousTransaction;
const owner = object?.data?.owner as { AddressOwner: string } | undefined;
return owner?.AddressOwner;
};

// Fetch the transaction details to find the sender
const transactionDetails = await client.getTransactionBlock({
digest: previousTx as string,
options: { showInput: true, showEffects: true },
});
return transactionDetails.transaction?.data.sender;
} else {
throw new Error("Coin owner not found");
}
export const getCoinAddressFromType = (coinType: string) => {
// check for long format
let addressMatch = coinType.match(/CoinData<(0x[^:]+)/);
if (addressMatch) return addressMatch?.[1];
// check for the shorter format {address}::{symbol}::{SYMBOL}
addressMatch = coinType.match(/0x[^:]+/);
return addressMatch?.[0] as string;
};

function findTreasuryCap(txData: PaginatedTransactionResponse) {
Expand Down Expand Up @@ -250,8 +250,7 @@ function extractTokenDetails(filteredResult: DynamicFieldInfo) {

// Extract the address from the objectType
const objectType = filteredResult.objectType;
const addressMatch = objectType.match(/CoinData<(0x[^:]+)/);
const address = addressMatch?.[1] as string;
const address = getCoinAddressFromType(objectType);
return {
tokenManager,
address,
Expand Down