cotiv2-mcp
Version:
> A plug-and-play MCP tool server to **send COTI**, **transfer BEP-20 tokens**, **deploy tokens**, and **interact with smart contracts** on the **COTI v2 Network (COTI)** — built for **Claude Desktop**, **AI agents**, and **developers.**
228 lines (212 loc) • 9.14 kB
JavaScript
import { exec } from "child_process";
import fs from "fs";
import path from "path";
import os from "os";
import { decryptPrivateKey } from "./PrivateAES.js";
import { privateKeyToAccount } from "viem/accounts";
import { publicClient } from "./config.js";
import { supportedChain } from "./chains/index.js";
import { getStorage } from "./lib/storage.js";
const platform = os.platform();
export function buildTxUrl(txHash) {
if (!txHash) {
return undefined;
}
const txUrl = `${supportedChain.blockExplorers.default.url}/tx/${txHash}`;
return txUrl;
}
export async function checkTransactionHash(txHash) {
const txReceipt = await publicClient.waitForTransactionReceipt({
hash: txHash,
retryCount: 300,
retryDelay: 100,
});
const txUrl = `${supportedChain.blockExplorers.default.url}/tx/${txHash}`;
if (txReceipt.status !== "success") {
throw new Error(`Please check the transaction results on block explorer, ${txUrl}`);
}
return txUrl;
}
export function bigIntReplacer(key, value) {
return typeof value === "bigint" ? value.toString() : value;
}
export async function getPassword(isRetry) {
const passwordResp = await showInputBoxWithTerms(isRetry);
if (!passwordResp.value) {
throw new Error("You did not enter a password.");
}
if (passwordResp.value.length != 8) {
throw new Error("The password must be 8 characters long");
}
const password = passwordResp.value;
const storage = await getStorage();
const WALLET_PRIVATE_KEY = storage.WALLET_PRIVATE_KEY;
if (!WALLET_PRIVATE_KEY) {
throw new Error("WALLET_PRIVATE_KEY is not defined");
}
const pk = await decryptPrivateKey(WALLET_PRIVATE_KEY, password);
const account = privateKeyToAccount(pk);
const address = storage.WALLET_ADDRESS;
if (!address) {
throw new Error("WALLET_ADDRESS is not defined");
}
if (account.address != address) {
return await getPassword(true);
}
return passwordResp;
}
export function showInputBoxWithTerms(isRetry) {
let message = "Enter your Wallet Password:";
if (isRetry) {
message = "Wrong password, please try again:";
}
return new Promise((resolve, reject) => {
switch (platform) {
case "darwin":
// For macOS, we use AppleScript to show a dialog with both input and checkbox
// The AppleScript is more complex but allows for a better UX
if (isRetry) {
message = "❌" + message;
}
const appleScript = `
tell application "System Events"
set userPassword to ""
set buttonPressed to ""
repeat
try
set userInput to display dialog "${message}" default answer "" with hidden answer buttons {"cancel", "confirm"} default button "confirm" with icon note
set userPassword to text returned of userInput
set buttonPressed to button returned of userInput
if buttonPressed is "cancel" then
exit repeat
end if
if length of userPassword is 8 then
exit repeat
end if
display dialog "Password must be exactly 8 characters." buttons {"confirm"} default button "confirm" with icon caution
on error
-- Handle any errors (like when user clicks the red close button)
exit repeat
end try
end repeat
if buttonPressed is not "cancel" then
set agreeToTerms to button returned of (display dialog "🔒 You will stay signed in for the next hour." buttons {"no", "yes"} default button "no" with icon caution)
return userPassword & "============" & agreeToTerms
else
return "canceled"
end if
end tell
`;
exec(`osascript -e '${appleScript}'`, (error, stdout, stderr) => {
if (error) {
// User cancelled
if (error.code === 1 || error.code === 255) {
resolve({ value: null, agreed: false });
}
else {
reject(error);
}
return;
}
if (stdout.trim() === "canceled") {
reject(new Error("Please enter the password before using ❕"));
return;
}
const [password, agree] = stdout.trim().split("============");
resolve({
value: password,
agreed: agree === "yes",
});
});
break;
case "win32":
const winCommand = `
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'wallet password'
$form.Size = New-Object System.Drawing.Size(450,300)
$form.StartPosition = 'CenterScreen'
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(380,40)
$label.Text = '${message}'
$form.Controls.Add($label)
# User input label
$userLabel = New-Object System.Windows.Forms.Label
$userLabel.Location = New-Object System.Drawing.Point(10,70)
$userLabel.Size = New-Object System.Drawing.Size(150,20)
$userLabel.Text = 'Input Password:'
$form.Controls.Add($userLabel)
# User input textbox
$passwordTextBox = New-Object System.Windows.Forms.TextBox
$passwordTextBox.Location = New-Object System.Drawing.Point(160,70)
$passwordTextBox.Size = New-Object System.Drawing.Size(250,20)
$passwordTextBox.PasswordChar = '*'
$form.Controls.Add($passwordTextBox)
# Error message label
$errorLabel = New-Object System.Windows.Forms.Label
$errorLabel.Location = New-Object System.Drawing.Point(160,95)
$errorLabel.Size = New-Object System.Drawing.Size(250,20)
$errorLabel.ForeColor = [System.Drawing.Color]::Red
$errorLabel.Text = ''
$form.Controls.Add($errorLabel)
$checkbox = New-Object System.Windows.Forms.CheckBox
$checkbox.Location = New-Object System.Drawing.Point(10,130)
$checkbox.Size = New-Object System.Drawing.Size(350,20)
$checkbox.Text = 'You will stay signed in for the next hour.'
$form.Controls.Add($checkbox)
$button = New-Object System.Windows.Forms.Button
$button.Location = New-Object System.Drawing.Point(175,190)
$button.Size = New-Object System.Drawing.Size(100,30)
$button.Text = 'Confirm'
$button.Add_Click({
# Validate password length
if ($passwordTextBox.Text.Length -ne 8) {
$errorLabel.Text = 'Password must be exactly 8 characters.'
} else {
$form.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Close()
}
})
$form.Controls.Add($button)
$form.AcceptButton = $button
$form.Add_Shown({$form.Activate()})
[void]$form.ShowDialog()
if ($form.DialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
$result = @{
agreed = $checkbox.Checked
value = $passwordTextBox.Text
}
$jsonResult = ConvertTo-Json -InputObject $result
Write-Output $jsonResult
}
exit 0
`;
const tempScriptPath = path.join(".", "terms_form.ps1");
fs.writeFileSync(tempScriptPath, winCommand);
exec(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`, (error, stdout, stderr) => {
fs.unlinkSync(tempScriptPath);
if (error && error.code !== 1) {
resolve({
value: null,
agreed: false,
});
return;
}
if (!stdout) {
reject(new Error("Please enter the password before using ❕"));
return;
}
const stdoutJSON = JSON.parse(stdout);
resolve({
value: stdoutJSON.value,
agreed: stdoutJSON.agreed,
});
});
break;
default:
reject(new Error(`Unsupported platform and command-line input is not available: ${platform}`));
}
});
}