Skip to content
Closed
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
2 changes: 2 additions & 0 deletions sui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,8 @@ Deploys a source coin and links it with a destination chain coin. If a `channel`

```bash
ts-node sui/its link-coin <symbol> <name> <decimals> <destinationChain> <destinationAddress>

ts-node sui/its.js link-coin-with-channel <symbol> <destinationChain> <destinationAddress> --channel <channelId> --tokenManagerMode <lock_unlock|mint_burn> --gasValue <amount_in_SUI> --saltAddress <addr>
```

### Deploy Remote Interchain Coin
Expand Down
145 changes: 103 additions & 42 deletions sui/its.js
Original file line number Diff line number Diff line change
Expand Up @@ -515,49 +515,9 @@ async function linkCoin(keypair, client, config, contracts, args, options) {
throw new Error(`error resolving channel id from registration tx, got ${channelId}`);
}

const channel = options.channel ? options.channel : channelId;
printInfo(`ChannelId : ${channelId}`);

// User then calls linkToken on ITS Chain A with the destination token address for Chain B.
// This submits a LinkToken msg type to ITS Hub.
txBuilder = new TxBuilder(client);

// Token manager type
const tokenManagerType = await txBuilder.moveCall({
target: `${itsConfig.address}::token_manager_type::${tokenManager}`,
});

// Salt
const salt = await txBuilder.moveCall({
target: `${AxelarGateway.address}::bytes32::new`,
arguments: [saltAddress],
});

// Link params (only outbound chain supported for now)
const linkParams = options.destinationOperator ? options.destinationOperator : '';

messageTicket = await txBuilder.moveCall({
target: `${itsConfig.address}::interchain_token_service::link_coin`,
arguments: [
InterchainTokenService,
channel,
salt,
destinationChain, // This assumes the chain is already added as a trusted chain
bcs.string().serialize(destinationAddress).toBytes(),
tokenManagerType,
bcs.string().serialize(linkParams).toBytes(),
],
});

await txBuilder.moveCall({
target: `${AxelarGateway.address}::gateway::send_message`,
arguments: [Gateway, messageTicket],
});

await broadcastFromTxBuilder(txBuilder, keypair, `Link Coin (${symbol})`, options);

// Linked tokens (source / destination)
const sourceToken = { metadata, packageId, tokenType, treasuryCap };
const linkedToken = { destinationChain, destinationAddress };

// Save deployed tokens
saveTokenDeployment(
Expand All @@ -569,7 +529,7 @@ async function linkCoin(keypair, client, config, contracts, args, options) {
tokenId,
sourceToken.treasuryCap,
sourceToken.metadata,
[linkedToken],
[],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Coin Linking Logic Moved, Parameters Unused

The linkCoin function and link-coin command no longer perform coin linking, as this logic moved to linkCoinWithChannel. Consequently, linkCoin accepts and validates destinationChain, destinationAddress, and channel parameters that are now unused, potentially causing errors. The command's documentation and name are also misleading.

Additional Locations (2)

Fix in Cursor Fix in Web

saltAddress,
);
}
Expand Down Expand Up @@ -876,6 +836,91 @@ async function mintCoins(keypair, client, config, contracts, args, options) {
return [balance, coinChanged.objectId];
}

async function linkCoinWithChannel(keypair, client, config, contracts, args, options) {
const { InterchainTokenService: itsConfig, AxelarGateway } = contracts;
const { InterchainTokenService } = itsConfig.objects;
const { Gateway } = AxelarGateway.objects;

const [symbol, destinationChain, destinationAddress] = args;

const walletAddress = keypair.toSuiAddress();

if (!options.channel) {
throw new Error('Missing required --channel <channelId>');
}

const tokenManager = options.tokenManagerMode;
const saltAddress = contracts[symbol.toUpperCase()]?.saltAddress || options.saltAddress;

if (!saltAddress) {
throw new Error('Missing saltAddress: pass --saltAddress or ensure coin was registered and saved in config.');
}

let txBuilder = new TxBuilder(client);

const tokenManagerType = await txBuilder.moveCall({
target: `${itsConfig.address}::token_manager_type::${tokenManager}`,
});

const salt = await txBuilder.moveCall({
target: `${AxelarGateway.address}::bytes32::new`,
arguments: [saltAddress],
});

const linkParams = options.destinationOperator ? options.destinationOperator : '';

const messageTicket = await txBuilder.moveCall({
target: `${itsConfig.address}::interchain_token_service::link_coin`,
arguments: [
InterchainTokenService,
options.channel,
salt,
destinationChain,
bcs.string().serialize(destinationAddress).toBytes(),
tokenManagerType,
bcs.string().serialize(linkParams).toBytes(),
],
});

const tx = txBuilder.tx;
const [gas] = tx.splitCoins(tx.gas, [options.gasValue]);

await txBuilder.moveCall({
target: `${contracts.GasService.address}::gas_service::pay_gas`,
typeArguments: [suiCoinId],
arguments: [contracts.GasService.objects.GasService, messageTicket, gas, walletAddress, '0x'],
});

await txBuilder.moveCall({
target: `${AxelarGateway.address}::gateway::send_message`,
arguments: [Gateway, messageTicket],
});

await broadcastFromTxBuilder(txBuilder, keypair, `Link Coin (channel=${options.channel})`, options);

// Save linked token info in config
const symbolKey = symbol.toUpperCase();
const coin = contracts[symbolKey];
if (!coin) {
throw new Error(`Token ${symbolKey} not found in contracts config. Run registration first.`);
}

const linkedToken = { destinationChain, destinationAddress };

saveTokenDeployment(
coin.address,
coin.typeArgument,
contracts,
symbol,
coin.decimals,
coin.objects.TokenId,
coin.objects.TreasuryCap,
coin.objects.Metadata,
[linkedToken],
saltAddress,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Token Linking Overwrites Data, Gas Wasted

The linkCoinWithChannel function overwrites existing linked token data when saving, causing previously linked chains to be lost if a token is linked multiple times. Separately, the token existence check occurs after the transaction is broadcast, potentially wasting gas if the token isn't in the config.

Fix in Cursor Fix in Web

}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Unvalidated Chain and Coin Data Causes Errors

The linkCoinWithChannel function uses destinationChain and several nested properties of the coin object (like address, typeArgument, decimals, and objects fields) without prior validation. This can cause transaction failures or runtime errors if the destination chain is invalid or the coin configuration is incomplete.

Fix in Cursor Fix in Web


async function processCommand(command, config, chain, args, options) {
const [keypair, client] = getWallet(chain, options);

Expand Down Expand Up @@ -1012,6 +1057,21 @@ if (require.main === module) {
mainProcessor(linkCoin, options, [symbol, name, decimals, destinationChain, destinationAddress], processCommand);
});

const linkCoinWithChannelProgram = new Command()
.name('link-coin-with-channel')
.command('link-coin-with-channel <symbol> <destinationChain> <destinationAddress>')
.description('Link a coin using a provided channelId (no registration step)')
.addOption(new Option('--channel <channel>', 'Existing channel ID to use').makeOptionMandatory(true))
.addOption(
new Option('--tokenManagerMode <mode>', 'Token Manager Mode').choices(['lock_unlock', 'mint_burn']).makeOptionMandatory(true),
)
.addOption(new Option('--destinationOperator <address>', 'Operator that can control flow limits on the destination chain'))
.requiredOption('--gasValue <amount>', 'Amount to pay gas (SUI full units)', parseSuiUnitAmount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: Missing import for parseSuiUnitAmount function used in gasValue validation

Prompt To Fix With AI
This is a comment left during a code review.
Path: sui/its.js
Line: 1069:1069

Comment:
syntax: Missing import for `parseSuiUnitAmount` function used in gasValue validation

How can I resolve this? If you propose a fix, please make it concise.

.addOption(new Option('--saltAddress <address>', 'Salt address to derive tokenId'))
.action((symbol, destinationChain, destinationAddress, options) => {
mainProcessor(linkCoinWithChannel, options, [symbol, destinationChain, destinationAddress], processCommand);
});

const deployRemoteCoinProgram = new Command()
.name('deploy-remote-coin')
.command('deploy-remote-coin <coinPackageId> <coinPackageName> <coinModName> <tokenId> <destinationChain>')
Expand Down Expand Up @@ -1087,6 +1147,7 @@ if (require.main === module) {
program.addCommand(giveUnlinkedCoinProgram);
program.addCommand(removeUnlinkedCoinProgram);
program.addCommand(linkCoinProgram);
program.addCommand(linkCoinWithChannelProgram);
program.addCommand(deployRemoteCoinProgram);
program.addCommand(removeTreasuryCapProgram);
program.addCommand(restoreTreasuryCapProgram);
Expand Down
Loading