permamind
Version:
An MCP server that provides an immortal memory layer for AI agents and clients
130 lines (129 loc) • 5.96 kB
JavaScript
import { z } from "zod";
import { CommonSchemas, ToolCommand, } from "../../core/index.js";
import { resolveAddress, resolveToken } from "../utils/TokenResolver.js";
export class TransferTokensCommand extends ToolCommand {
context;
metadata = {
description: "Core MVP functionality: Transfer tokens from your account to another address. Supports token names/tickers and contact names from registry.",
name: "transferTokens",
openWorldHint: false,
readOnlyHint: false,
title: "Transfer Tokens",
};
parametersSchema = z.object({
confirmed: z
.boolean()
.optional()
.describe("Set to true to confirm resolved tokens/addresses"),
processId: z.string().describe("The AO token process ID, name, or ticker"),
quantity: CommonSchemas.quantity.describe("Amount of tokens to transfer (will be converted based on token denomination unless rawAmount is true)"),
rawAmount: z
.boolean()
.optional()
.describe("Set to true to send exact amount without denomination conversion"),
recipient: z.string().describe("Address or contact name to send tokens to"),
});
constructor(context) {
super();
this.context = context;
}
async execute(args) {
try {
// Dynamic import to avoid circular dependencies
const { read, send } = await import("../../../process.js");
// Resolve token processId if needed
const tokenResolution = await resolveToken(args.processId, this.context.hubId);
if (!tokenResolution.resolved) {
return JSON.stringify({
error: "Token resolution failed",
message: tokenResolution.verificationMessage,
success: false,
suggestion: "Use saveTokenMapping to register this token or provide a valid processId",
});
}
if (tokenResolution.requiresVerification && !args.confirmed) {
return JSON.stringify({
instruction: "Add 'confirmed: true' to your request to proceed with this token",
message: tokenResolution.verificationMessage,
requiresConfirmation: true,
resolvedToken: tokenResolution.value,
success: false,
});
}
const processId = tokenResolution.value;
// Resolve recipient address if needed
const addressResolution = await resolveAddress(args.recipient, this.context.hubId);
if (!addressResolution.resolved) {
return JSON.stringify({
error: "Recipient resolution failed",
message: addressResolution.verificationMessage,
success: false,
suggestion: "Use saveAddressMapping to register this contact or provide a valid address",
});
}
if (addressResolution.requiresVerification && !args.confirmed) {
return JSON.stringify({
instruction: "Add 'confirmed: true' to your request to proceed with this recipient",
message: addressResolution.verificationMessage,
requiresConfirmation: true,
resolvedRecipient: addressResolution.value,
success: false,
});
}
const recipient = addressResolution.value;
// Get token info to check denomination
const tokenInfo = await read(processId, [
{ name: "Action", value: "Info" },
]);
let denomination = 12; // Default denomination
let actualQuantity = args.quantity;
if (tokenInfo && tokenInfo.Data) {
try {
const info = JSON.parse(tokenInfo.Data);
if (info.Denomination) {
denomination = parseInt(info.Denomination);
}
}
catch {
// Use default denomination if parsing fails
}
}
// Convert human-readable amount to token units based on denomination
if (!args.quantity.includes(".") && !args.rawAmount) {
// If it's a whole number and not marked as raw, apply denomination
const numericQuantity = parseFloat(args.quantity);
actualQuantity = (numericQuantity * Math.pow(10, denomination)).toString();
}
// First check current balance
const balanceResult = await read(processId, [
{ name: "Action", value: "Balance" },
{ name: "Target", value: this.context.publicKey },
]);
// Then attempt transfer
const tags = [
{ name: "Action", value: "Transfer" },
{ name: "Recipient", value: recipient },
{ name: "Quantity", value: actualQuantity },
];
const result = await send(this.context.keyPair, processId, tags, null);
return JSON.stringify({
balance: balanceResult?.Data ? JSON.parse(balanceResult.Data) : null,
message: `Transfer initiated: ${args.quantity} tokens to ${args.recipient}`,
result,
success: true,
transfer: {
actualQuantity,
processId,
quantity: args.quantity,
recipient,
},
});
}
catch (error) {
return JSON.stringify({
error: `Failed to transfer tokens: ${error instanceof Error ? error.message : "Unknown error"}`,
success: false,
});
}
}
}