import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { getAptosClient } from "../../config.js";
import { getDefaultAccount } from "../../utils/account.js";
import { formatAddress } from "../../utils/format.js";
export const REGISTER_COIN: Tool = {
name: "register_coin",
description: "Register your account to receive a specific coin type. This is required before you can receive transfers of custom coins on Aptos. Returns the registration transaction details.",
inputSchema: {
type: "object",
properties: {
coin_type: {
type: "string",
description: "The coin type identifier to register for (e.g., '0x123::coin::T')",
},
max_gas_amount: {
type: "number",
description: "Maximum gas amount for the transaction (optional)",
default: 1000,
},
},
required: ["coin_type"],
},
};
/**
* Registers an account to receive a specific coin type
* @param args The arguments containing coin type
* @returns The registration transaction details
*/
export async function registerCoinHandler(args: Record<string, any> | undefined) {
if (!isRegisterCoinArgs(args)) {
throw new Error("Invalid arguments for register_coin");
}
const { coin_type, max_gas_amount = 1000 } = args;
try {
const results = await performRegisterCoin(coin_type, max_gas_amount);
return {
content: [{ type: "text", text: results }],
isError: false,
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error registering for coin: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Registers an account to receive a specific coin type
* @param coinType The coin type identifier
* @param maxGasAmount Maximum gas amount for the transaction
* @returns The registration transaction details as a formatted string
*/
export async function performRegisterCoin(
coinType: string,
maxGasAmount: number = 1000
): Promise<string> {
try {
const aptos = getAptosClient();
const account = getDefaultAccount();
const accountAddress = account.accountAddress.toString();
// Validate inputs
if (!coinType || coinType.trim().length === 0) {
throw new Error("Coin type cannot be empty");
}
// Validate coin type format
if (!coinType.match(/^0x[a-fA-F0-9]+::[a-zA-Z_][a-zA-Z0-9_]*::[a-zA-Z_][a-zA-Z0-9_]*$/)) {
throw new Error("Invalid coin type format. Expected format: 0xADDRESS::module::Type");
}
// Check if already registered
try {
const balance = await aptos.getAccountCoinAmount({
accountAddress,
coinType: coinType as `${string}::${string}::${string}`,
});
// If we can get the balance, the account is already registered
return `📋 Coin Registration Status:
Account: ${formatAddress(accountAddress)}
Coin Type: ${coinType}
Status: ✅ Already registered
Current Balance: ${balance}
ℹ️ Your account is already registered for this coin type and can receive transfers.`;
} catch (error) {
// If we can't get the balance, the account is not registered yet
// This is expected, so we continue with registration
}
// Build the registration transaction
const transaction = await aptos.transaction.build.simple({
sender: account.accountAddress,
data: {
function: "0x1::managed_coin::register",
typeArguments: [coinType],
functionArguments: [],
},
options: {
maxGasAmount,
},
});
// Sign and submit the transaction
const committedTxn = await aptos.signAndSubmitTransaction({
signer: account,
transaction,
});
// Wait for transaction confirmation
const executedTxn = await aptos.waitForTransaction({
transactionHash: committedTxn.hash,
});
let result = `🪙 Coin Registration Successful!
Registration Details:
Account: ${formatAddress(accountAddress)}
Coin Type: ${coinType}
Status: ✅ Successfully registered
Transaction Information:
Transaction Hash: ${committedTxn.hash}
Gas Used: ${executedTxn.gas_used}
Status: ${executedTxn.success ? 'Success' : 'Failed'}`;
// Note: Coin info details are not available in current SDK version
result += `
📋 Next Steps:
1. You can now receive transfers of this coin type
2. Check your balance using get_coin_balance
3. Transfer these coins using transfer_coin
4. Others can now send you this coin type
⚠️ Important Notes:
- Registration is a one-time process per coin type
- You need to register for each custom coin you want to receive
- APT (native Aptos coin) doesn't require registration
- Keep some APT for transaction fees
✅ Your account is now ready to receive ${coinType}!`;
return result;
} catch (error) {
console.error('Error registering for coin:', error);
if (error instanceof Error) {
if (error.message.includes('insufficient')) {
throw new Error("Insufficient APT balance to pay for registration transaction fees");
}
if (error.message.includes('already registered')) {
throw new Error("Account is already registered for this coin type");
}
if (error.message.includes('not found')) {
throw new Error("Coin type not found. Make sure the coin has been deployed and the type is correct.");
}
if (error.message.includes('invalid')) {
throw new Error("Invalid coin type format or coin type does not exist");
}
}
throw new Error(`Failed to register for coin: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Checks if the provided arguments are valid for the registerCoin tool
* @param args The arguments to check
* @returns True if the arguments are valid, false otherwise
*/
export function isRegisterCoinArgs(args: unknown): args is {
coin_type: string;
max_gas_amount?: number;
} {
return (
typeof args === "object" &&
args !== null &&
"coin_type" in args &&
typeof (args as any).coin_type === "string" &&
(!(args as any).max_gas_amount || typeof (args as any).max_gas_amount === "number")
);
}