plugin-coti
Version:
COTI blockchain plugin for ElizaOS - enables private token operations and encrypted transactions
1,021 lines (1,013 loc) • 127 kB
JavaScript
// src/actions/getNativeBalance.ts
import {
elizaLogger as elizaLogger2
} from "@elizaos/core";
// src/providers/wallet.ts
import {
elizaLogger
} from "@elizaos/core";
import { ethers } from "ethers";
import { Wallet, getDefaultProvider, CotiNetwork } from "@coti-io/coti-ethers";
var CotiWalletProvider = class {
wallet;
provider;
runtime;
aesKey;
constructor(runtime, config) {
this.runtime = runtime;
this.provider = getDefaultProvider(CotiNetwork.Testnet);
const privateKey = config.privateKey || process.env.COTI_PRIVATE_KEY;
if (!privateKey) {
throw new Error("COTI private key not provided");
}
this.aesKey = config.aesKey || process.env.COTI_AES_KEY || "";
if (!this.aesKey) {
elizaLogger.warn("COTI AES key not provided. Private token operations may not work properly.");
}
this.wallet = new Wallet(privateKey, this.provider);
if (this.aesKey) {
this.wallet.setAesKey(this.aesKey);
}
elizaLogger.info(`COTI Wallet initialized with address: ${this.wallet.address}`);
}
getAddress() {
return this.wallet.address;
}
getAesKey() {
return this.aesKey;
}
async getBalance(address) {
const targetAddress = address || this.wallet.address;
return await this.provider.getBalance(targetAddress);
}
async transfer(to, amount) {
const amountWei = ethers.utils.parseEther(amount);
elizaLogger.info(`Transferring ${amount} COTI to ${to}`);
const tx = await this.wallet.sendTransaction({
to,
value: amountWei
});
elizaLogger.info(`Transaction sent: ${tx.hash}`);
return tx;
}
async getTransactionReceipt(txHash) {
return await this.provider.getTransactionReceipt(txHash);
}
async estimateGas(to, amount) {
const amountWei = ethers.utils.parseEther(amount);
const gasEstimate = await this.wallet.estimateGas({
to,
value: amountWei
});
return ethers.BigNumber.from(gasEstimate.toString());
}
getProvider() {
return this.provider;
}
getWallet() {
return this.wallet;
}
};
var walletProviderInstance = null;
function initCotiWalletProvider(runtime) {
if (!walletProviderInstance) {
const config = {
privateKey: process.env.COTI_PRIVATE_KEY,
rpcUrl: process.env.COTI_RPC_URL,
networkId: parseInt(process.env.COTI_NETWORK_ID || "7701")
};
walletProviderInstance = new CotiWalletProvider(runtime, config);
}
return walletProviderInstance;
}
var cotiWalletProvider = {
name: "COTI_WALLET_PROVIDER",
description: "Provides COTI wallet functionality for blockchain operations",
get: async (runtime, message, state) => {
try {
const walletProvider = initCotiWalletProvider(runtime);
const address = walletProvider.getAddress();
const balance = await walletProvider.getBalance();
return {
text: `COTI Wallet Address: ${address}, Balance: ${ethers.utils.formatEther(balance)} COTI`,
values: {
address,
balance: ethers.utils.formatEther(balance),
balanceWei: balance.toString()
},
data: {
address,
balance: ethers.utils.formatEther(balance),
balanceWei: balance.toString()
}
};
} catch (error) {
elizaLogger.error("Error in COTI wallet provider:", error);
return {
text: `Error accessing COTI wallet: ${error instanceof Error ? error.message : String(error)}`,
values: {},
data: { error: error instanceof Error ? error.message : String(error) }
};
}
}
};
// src/types/index.ts
import { z } from "zod";
var GetNativeBalanceSchema = z.object({
accountAddress: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
});
var TransferNativeSchema = z.object({
to: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid recipient address format"),
amount: z.string().min(1, "Amount must be specified")
});
var WalletInfoSchema = z.object({
includeBalance: z.boolean().optional().default(true)
});
// src/actions/getNativeBalance.ts
import { ethers as ethers2 } from "ethers";
var getNativeBalanceAction = {
name: "GET_NATIVE_BALANCE",
similes: [
"CHECK_COTI_BALANCE",
"GET_COTI_BALANCE",
"NATIVE_BALANCE",
"BALANCE_CHECK",
"CHECK_BALANCE",
"SHOW_BALANCE",
"GET_BALANCE",
"BALANCE_OF",
"CHECK_ADDRESS_BALANCE",
"GET_ADDRESS_BALANCE"
],
description: "Get the native COTI token balance of a COTI blockchain account. Returns the account balance in COTI tokens.",
validate: async (runtime, message) => {
try {
const text = message.content?.text || "";
elizaLogger2.info(`GET_NATIVE_BALANCE validation - text: "${text}"`);
const isBalanceQuery = /\b(balance|check.*balance|get.*balance|show.*balance|help.*check.*balance)\b/i.test(text);
elizaLogger2.info(`GET_NATIVE_BALANCE validation - isBalanceQuery: ${isBalanceQuery}`);
if (message.content?.accountAddress) {
const params = GetNativeBalanceSchema.parse(message.content);
elizaLogger2.info(`GET_NATIVE_BALANCE validation - accountAddress field found: ${params.accountAddress}`);
return ethers2.utils.isAddress(params.accountAddress);
}
const addressMatch = text.match(/0x[a-fA-F0-9]{40}/);
elizaLogger2.info(`GET_NATIVE_BALANCE validation - addressMatch: ${addressMatch?.[0] || "none"}`);
if (addressMatch && isBalanceQuery) {
const isValid = ethers2.utils.isAddress(addressMatch[0]);
elizaLogger2.info(`GET_NATIVE_BALANCE validation - address valid: ${isValid}, returning true`);
return isValid;
}
if (isBalanceQuery && !addressMatch) {
try {
const walletProvider = initCotiWalletProvider(runtime);
const hasAddress = walletProvider.getAddress() !== "";
elizaLogger2.info(`GET_NATIVE_BALANCE validation - general balance query, has wallet: ${hasAddress}`);
return hasAddress;
} catch {
elizaLogger2.info(`GET_NATIVE_BALANCE validation - wallet provider failed`);
return false;
}
}
elizaLogger2.info(`GET_NATIVE_BALANCE validation - returning false`);
return false;
} catch (error) {
elizaLogger2.error(`GET_NATIVE_BALANCE validation error:`, error);
return false;
}
},
handler: async (runtime, message, state, options = {}, callback) => {
try {
elizaLogger2.info("Starting GET_NATIVE_BALANCE action");
let accountAddress;
if (message.content?.accountAddress) {
const params = GetNativeBalanceSchema.parse(message.content);
accountAddress = params.accountAddress;
} else {
const text = message.content?.text || "";
const addressMatch = text.match(/0x[a-fA-F0-9]{40}/);
if (addressMatch) {
accountAddress = addressMatch[0];
} else {
const walletProvider2 = initCotiWalletProvider(runtime);
accountAddress = walletProvider2.getAddress();
}
}
if (!ethers2.utils.isAddress(accountAddress)) {
throw new Error("Invalid account address");
}
const walletProvider = initCotiWalletProvider(runtime);
const balance = await walletProvider.getBalance(accountAddress);
const responseText = `COTI Balance for ${accountAddress}: ${ethers2.utils.formatEther(balance)} COTI`;
if (callback) {
await callback({
text: responseText,
content: {
success: true,
accountAddress,
balance: ethers2.utils.formatEther(balance),
balanceWei: balance.toString()
}
});
}
return {
text: responseText,
success: true,
data: {
accountAddress,
balance: ethers2.utils.formatEther(balance),
balanceWei: balance.toString()
}
};
} catch (error) {
elizaLogger2.error("Error in GET_NATIVE_BALANCE action:", error);
const errorMessage = `Failed to get COTI balance: ${error instanceof Error ? error.message : String(error)}`;
if (callback) {
await callback({
text: errorMessage,
content: { success: false, error: errorMessage }
});
}
return {
text: errorMessage,
success: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "What's my COTI balance?"
}
},
{
name: "{{user2}}",
content: {
text: "I'll check your COTI balance for you. Let me retrieve your account information.",
action: "GET_NATIVE_BALANCE"
}
}
],
[
{
name: "{{user1}}",
content: {
text: "Check balance for 0x1234567890123456789012345678901234567890"
}
},
{
name: "{{user2}}",
content: {
text: "I'll check the COTI balance for that address.",
action: "GET_NATIVE_BALANCE"
}
}
]
]
};
// src/actions/transferNative.ts
import {
elizaLogger as elizaLogger3
} from "@elizaos/core";
import { ethers as ethers3 } from "ethers";
var transferNativeAction = {
name: "TRANSFER_NATIVE",
similes: [
"SEND_COTI",
"TRANSFER_COTI",
"SEND_TOKENS",
"TRANSFER_TOKENS",
"SEND_NATIVE"
],
description: "Transfer native COTI tokens to another address on the COTI blockchain",
validate: async (runtime, message) => {
try {
if (message.content?.to && message.content?.amount) {
const params = TransferNativeSchema.parse(message.content);
return ethers3.utils.isAddress(params.to) && parseFloat(params.amount) > 0;
}
const text = message.content?.text || "";
const transferMatch = text.match(/(?:send|transfer)\s+(\d+(?:\.\d+)?)\s+(?:coti\s+)?to\s+(0x[a-fA-F0-9]{40})/i);
if (transferMatch) {
const amount = transferMatch[1];
const address = transferMatch[2];
return ethers3.utils.isAddress(address) && parseFloat(amount) > 0;
}
return false;
} catch {
return false;
}
},
handler: async (runtime, message, state, options = {}, callback) => {
try {
elizaLogger3.info("Starting TRANSFER_NATIVE action");
let to;
let amount;
if (message.content?.to && message.content?.amount) {
const params = TransferNativeSchema.parse(message.content);
to = params.to;
amount = params.amount;
} else {
const text = message.content?.text || "";
const transferMatch = text.match(/(?:send|transfer)\s+(\d+(?:\.\d+)?)\s+(?:coti\s+)?to\s+(0x[a-fA-F0-9]{40})/i);
if (!transferMatch) {
throw new Error("Could not parse transfer parameters from message");
}
amount = transferMatch[1];
to = transferMatch[2];
}
if (!ethers3.utils.isAddress(to)) {
throw new Error("Invalid recipient address");
}
if (parseFloat(amount) <= 0) {
throw new Error("Amount must be greater than 0");
}
const walletProvider = initCotiWalletProvider(runtime);
const fromAddress = walletProvider.getAddress();
const balance = await walletProvider.getBalance();
const amountWei = ethers3.utils.parseEther(amount);
if (balance.lt(amountWei)) {
throw new Error(`Insufficient balance. Available: ${ethers3.utils.formatEther(balance)} COTI, Required: ${amount} COTI`);
}
const estimatedGas = await walletProvider.estimateGas(to, amount);
elizaLogger3.info(`Estimated gas: ${estimatedGas.toString()}`);
const tx = await walletProvider.transfer(to, amount);
const responseText = `Successfully transferred ${amount} COTI to ${to}. Transaction hash: ${tx.hash}`;
if (callback) {
await callback({
text: responseText,
content: {
success: true,
transactionHash: tx.hash,
from: fromAddress,
to,
amount
}
});
}
return {
text: responseText,
success: true,
data: {
transactionHash: tx.hash,
from: fromAddress,
to,
amount,
amountWei: amountWei.toString(),
estimatedGas: estimatedGas.toString()
}
};
} catch (error) {
elizaLogger3.error("Error in TRANSFER_NATIVE action:", error);
const errorMessage = `Failed to transfer COTI: ${error instanceof Error ? error.message : String(error)}`;
if (callback) {
await callback({
text: errorMessage,
content: { success: false, error: errorMessage }
});
}
return {
text: errorMessage,
success: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "Send 10 COTI to 0x1234567890123456789012345678901234567890"
}
},
{
name: "{{user2}}",
content: {
text: "I'll transfer 10 COTI tokens to that address for you.",
action: "TRANSFER_NATIVE"
}
}
],
[
{
name: "{{user1}}",
content: {
text: "Transfer 5.5 COTI to alice"
}
},
{
name: "{{user2}}",
content: {
text: "I'll send 5.5 COTI tokens to alice's address.",
action: "TRANSFER_NATIVE"
}
}
]
]
};
// src/actions/listAccounts.ts
import {
elizaLogger as elizaLogger4
} from "@elizaos/core";
import { ethers as ethers4 } from "ethers";
var listAccountsAction = {
name: "LIST_ACCOUNTS",
similes: [
"SHOW_ACCOUNTS",
"GET_ACCOUNTS",
"LIST_WALLETS",
"SHOW_WALLETS",
"MY_ACCOUNTS"
],
description: "List all configured COTI accounts and their information",
validate: async (runtime, message) => {
try {
const walletProvider = initCotiWalletProvider(runtime);
return walletProvider.getAddress() !== "";
} catch {
return false;
}
},
handler: async (runtime, message, state, options = {}, callback) => {
try {
elizaLogger4.info("Starting LIST_ACCOUNTS action");
const walletProvider = initCotiWalletProvider(runtime);
const address = walletProvider.getAddress();
const balance = await walletProvider.getBalance();
const provider = walletProvider.getProvider();
const transactionCount = await provider.getTransactionCount(address);
const network = await provider.getNetwork();
const accountInfo = {
address,
balance: ethers4.utils.formatEther(balance),
balanceWei: balance.toString(),
transactionCount,
network: {
name: network.name,
chainId: network.chainId.toString()
}
};
const responseText = `COTI Account Information:
Address: ${address}
Balance: ${ethers4.utils.formatEther(balance)} COTI
Transaction Count: ${transactionCount}
Network: ${network.name} (Chain ID: ${network.chainId})`;
if (callback) {
await callback({
text: responseText,
content: {
success: true,
accounts: [accountInfo]
}
});
}
return {
text: responseText,
success: true,
data: {
accounts: [accountInfo],
totalAccounts: 1
}
};
} catch (error) {
elizaLogger4.error("Error in LIST_ACCOUNTS action:", error);
const errorMessage = `Failed to list COTI accounts: ${error instanceof Error ? error.message : String(error)}`;
if (callback) {
await callback({
text: errorMessage,
content: { success: false, error: errorMessage }
});
}
return {
text: errorMessage,
success: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "Show me my COTI accounts"
}
},
{
name: "{{user2}}",
content: {
text: "I'll list all your configured COTI accounts.",
action: "LIST_ACCOUNTS"
}
}
],
[
{
name: "{{user1}}",
content: {
text: "What accounts do I have?"
}
},
{
name: "{{user2}}",
content: {
text: "Let me show you your COTI account information.",
action: "LIST_ACCOUNTS"
}
}
]
]
};
// src/actions/walletInfo.ts
import {
elizaLogger as elizaLogger5
} from "@elizaos/core";
import { ethers as ethers5 } from "ethers";
var walletInfoAction = {
name: "WALLET_INFO",
similes: [
"GET_WALLET_INFO",
"WALLET_DETAILS",
"WALLET_STATUS",
"ACCOUNT_INFO",
"WALLET_SUMMARY"
],
description: "Get detailed information about the COTI wallet including address, balance, and network details",
validate: async (runtime, message) => {
try {
const walletProvider = initCotiWalletProvider(runtime);
return walletProvider.getAddress() !== "";
} catch {
return false;
}
},
handler: async (runtime, message, state, options = {}, callback) => {
try {
elizaLogger5.info("Starting WALLET_INFO action");
let includeBalance = true;
if (message.content?.includeBalance !== void 0) {
const params = WalletInfoSchema.parse(message.content);
includeBalance = params.includeBalance;
}
const walletProvider = initCotiWalletProvider(runtime);
const address = walletProvider.getAddress();
const provider = walletProvider.getProvider();
const network = await provider.getNetwork();
const blockNumber = await provider.getBlockNumber();
const gasPrice = await provider.getGasPrice();
let walletInfo = {
address,
network: {
name: network.name,
chainId: network.chainId.toString(),
blockNumber,
gasPrice: ethers5.utils.formatUnits(gasPrice, "gwei")
}
};
let responseText = `COTI Wallet Information:
Address: ${address}
Network: ${network.name} (Chain ID: ${network.chainId})
Current Block: ${blockNumber}
Gas Price: ${ethers5.utils.formatUnits(gasPrice, "gwei")} gwei`;
if (includeBalance) {
const balance = await walletProvider.getBalance();
const transactionCount = await provider.getTransactionCount(address);
walletInfo.balance = ethers5.utils.formatEther(balance);
walletInfo.balanceWei = balance.toString();
walletInfo.transactionCount = transactionCount;
responseText += `
Balance: ${ethers5.utils.formatEther(balance)} COTI
Transaction Count: ${transactionCount}`;
}
if (callback) {
await callback({
text: responseText,
content: {
success: true,
walletInfo
}
});
}
return {
text: responseText,
success: true,
data: {
walletInfo
}
};
} catch (error) {
elizaLogger5.error("Error in WALLET_INFO action:", error);
const errorMessage = `Failed to get wallet information: ${error instanceof Error ? error.message : String(error)}`;
if (callback) {
await callback({
text: errorMessage,
content: { success: false, error: errorMessage }
});
}
return {
text: errorMessage,
success: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "Show me my wallet information"
}
},
{
name: "{{user2}}",
content: {
text: "I'll get your COTI wallet details for you.",
action: "WALLET_INFO"
}
}
],
[
{
name: "{{user1}}",
content: {
text: "What's my wallet status?"
}
},
{
name: "{{user2}}",
content: {
text: "Let me check your COTI wallet status and information.",
action: "WALLET_INFO"
}
}
]
]
};
// src/actions/deployPrivateErc721Contract.ts
import {
elizaLogger as elizaLogger6
} from "@elizaos/core";
import { ethers as ethers6 } from "ethers";
import { z as z2 } from "zod";
var DeployPrivateERC721Schema = z2.object({
name: z2.string().describe("Name of the NFT collection"),
symbol: z2.string().describe("Symbol of the NFT collection"),
gasLimit: z2.number().optional().describe("Optional gas limit")
});
var ERC721_ABI = [
// Constructor
{
inputs: [
{ internalType: "string", name: "name_", type: "string" },
{ internalType: "string", name: "symbol_", type: "string" }
],
stateMutability: "nonpayable",
type: "constructor"
},
// Standard ERC721 functions
"function name() view returns (string)",
"function symbol() view returns (string)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address owner) view returns (uint256)",
"function ownerOf(uint256 tokenId) view returns (address)",
// COTI private mint function
{
inputs: [
{ internalType: "address", name: "to", type: "address" },
{
components: [
{
components: [
{ internalType: "ctUint64[]", name: "value", type: "uint256[]" }
],
internalType: "struct ctString",
name: "ciphertext",
type: "tuple"
},
{ internalType: "bytes[]", name: "signature", type: "bytes[]" }
],
internalType: "struct itString",
name: "itTokenURI",
type: "tuple"
}
],
name: "mint",
outputs: [],
stateMutability: "nonpayable",
type: "function"
},
// Token URI function
{
inputs: [{ internalType: "uint256", name: "tokenId", type: "uint256" }],
name: "tokenURI",
outputs: [
{
components: [
{ internalType: "ctUint64[]", name: "value", type: "uint256[]" }
],
internalType: "struct ctString",
name: "",
type: "tuple"
}
],
stateMutability: "view",
type: "function"
}
];
var ERC721_BYTECODE = "0x60806040523480156200001157600080fd5b5060405162001eeb38038062001eeb833981016040819052620000349162000123565b818160006200004483826200021c565b5060016200005382826200021c565b5050505050620002e8565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200008657600080fd5b81516001600160401b0380821115620000a357620000a36200005e565b604051601f8301601f19908116603f01168101908282118183101715620000ce57620000ce6200005e565b81604052838152602092508683858801011115620000eb57600080fd5b600091505b838210156200010f5785820183015181830184015290820190620000f0565b600093810190920192909252949350505050565b600080604083850312156200013757600080fd5b82516001600160401b03808211156200014f57600080fd5b6200015d8683870162000074565b935060208501519150808211156200017457600080fd5b50620001838582860162000074565b9150509250929050565b600181811c90821680620001a257607f821691505b602082108103620001c357634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200021757600081815260208120601f850160051c81016020861015620001f25750805b601f850160051c820191505b818110156200021357828155600101620001fe565b5050505b505050565b81516001600160401b038111156200023857620002386200005e565b62000250816200024984546200018d565b84620001c9565b602080601f8311600181146200028857600084156200026f5750858301515b600019600386901b1c1916600185901b17855562000213565b600085815260208120601f198616915b82811015620002b95788860151825594840194600190910190840162000298565b5085821015620002d85787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b611bf380620002f86000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80636352211e11610097578063a22cb46511610066578063a22cb465146101f0578063b88d4fde14610203578063c87b56dd14610216578063e985e9c51461023657600080fd5b80636352211e146101af57806368862e1b146101c257806370a08231146101d557806395d89b41146101e857600080fd5b8063095ea7b3116100d3578063095ea7b31461016257806318160ddd1461017757806323b872dd1461018957806342842e0e1461019c57600080fd5b806301ffc9a7146100fa57806306fdde0314610122578063081812fc14610137575b600080fd5b61010d61010836600461153e565b610272565b60405190151581526020015b60405180910390f35b61012a6102ad565b60405161011991906115a1565b61014a6101453660046115b4565b61033f565b6040516001600160a01b039091168152602001610119565b6101756101703660046115e9565b610368565b005b6007545b604051908152602001610119565b610175610197366004611613565b610377565b6101756101aa366004611613565b610420565b61014a6101bd3660046115b4565b610440565b6101756101d036600461164f565b61044b565b61017b6101e33660046116a4565b610480565b61012a6104e1565b6101756101fe3660046116bf565b6104f0565b6101756102113660046117f3565b6104fb565b6102296102243660046115b4565b610512565b604051610119919061185b565b61010d6102443660046118ab565b6001600160a01b03918216600090815260056020908152604080832093909416825291909152205460ff1690565b60007fffffffff00000000000000000000000000000000000000000000000000000000821615806102a757506102a78261058f565b92915050565b6060600080546102bc906118de565b80601f01602080910402602001604051908101604052809291908181526020018280546102e8906118de565b80156103355780601f1061030a57610100808354040283529160200191610335565b820191906000526020600020905b81548152906001019060200180831161031857829003601f168201915b5050505050905090565b600061034a82610626565b506000828152600460205260409020546001600160a01b03166102a7565b610373828233610678565b5050565b6001600160a01b0382166103a657604051633250574960e11b8152600060048201526024015b60405180910390fd5b60006103b3838333610685565b9050836001600160a01b0316816001600160a01b03161461041a576040517f64283d7b0000000000000000000000000000000000000000000000000000000081526001600160a01b038086166004830152602482018490528216604482015260640161039d565b50505050565b61043b838383604051806020016040528060008152506104fb565b505050565b60006102a782610626565b600754610458838261071b565b610463338284610799565b600160076000828254610476919061192e565b9091555050505050565b60006001600160a01b0382166104c5576040517f89c62b640000000000000000000000000000000000000000000000000000000081526000600482015260240161039d565b506001600160a01b031660009081526003602052604090205490565b6060600180546102bc906118de565b6103733383836107bb565b610506848484610377565b61041a84848484610891565b604080516020808201835260608252600084815260068252839020835160019091018054808402830186018652928201838152939491939092849284919084018282801561057f57602002820191906000526020600020905b81548152602001906001019080831161056b575b5050505050815250509050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd0000000000000000000000000000000000000000000000000000000014806102a757507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000008316146102a7565b6000818152600260205260408120546001600160a01b0316806102a7576040517f7e2732890000000000000000000000000000000000000000000000000000000081526004810184905260240161039d565b61043b8383836001610a04565b60008281526006602090815260408083208151815480850282018401845293810184815285946106f49492939284929184918401828280156106e657602002820191906000526020600020905b8154815260200190600101908083116106d2575b505050505081525050610b5a565b90506000610703868686610c2a565b90506107128686846000610d3b565b95945050505050565b6001600160a01b03821661074557604051633250574960e11b81526000600482015260240161039d565b600061075383836000610685565b90506001600160a01b0381161561043b576040517f73c6ac6e0000000000000000000000000000000000000000000000000000000081526000600482015260240161039d565b60006107ac6107a7836119ea565b610e2a565b905061041a8484836001610d3b565b6001600160a01b038216610806576040517f5b08ba180000000000000000000000000000000000000000000000000000000081526001600160a01b038316600482015260240161039d565b6001600160a01b0383811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b6001600160a01b0383163b1561041a576040517f150b7a020000000000000000000000000000000000000000000000000000000081526001600160a01b0384169063150b7a02906108ec903390889087908790600401611ada565b6020604051808303816000875af1925050508015610927575060408051601f3d908101601f1916820190925261092491810190611b16565b60015b610990573d808015610955576040519150601f19603f3d011682016040523d82523d6000602084013e61095a565b606091505b50805160000361098857604051633250574960e11b81526001600160a01b038516600482015260240161039d565b805181602001fd5b7fffffffff0000000000000000000000000000000000000000000000000000000081167f150b7a0200000000000000000000000000000000000000000000000000000000146109fd57604051633250574960e11b81526001600160a01b038516600482015260240161039d565b5050505050565b8080610a1857506001600160a01b03821615155b15610b12576000610a2884610626565b90506001600160a01b03831615801590610a545750826001600160a01b0316816001600160a01b031614155b8015610a8657506001600160a01b0380821660009081526005602090815260408083209387168352929052205460ff16155b15610ac8576040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526001600160a01b038416600482015260240161039d565b8115610b105783856001600160a01b0316826001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b5050600090815260046020526040902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b6040805160208101909152606081528151516040805160208101909152600090808367ffffffffffffffff811115610b9457610b946116f0565b604051908082528060200260200182016040528015610bbd578160200160208202803683370190505b509052905060005b82811015610c2257610bf385600001518281518110610be657610be6611b33565b6020026020010151610faa565b8251805183908110610c0757610c07611b33565b6020908102919091010152610c1b81611b49565b9050610bc5565b509392505050565b6000828152600260205260408120546001600160a01b0390811690831615610c5757610c57818486611047565b6001600160a01b03811615610c9557610c74600085600080610a04565b6001600160a01b038116600090815260036020526040902080546000190190555b6001600160a01b03851615610cc4576001600160a01b0385166000908152600360205260409020805460010190555b60008481526002602052604080822080547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0389811691821790925591518793918516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4949350505050565b6000610d4684610440565b6001600160a01b031603610d89576040517f868439130000000000000000000000000000000000000000000000000000000081526004810184905260240161039d565b6000610d9583866110dd565b90508115610df457600084815260066020908152604090912082518051805185948492610dc892849291909101906114ad565b5050506020828101518051805191926001850192610de992849201906114ad565b5050509050506109fd565b60208082015160008681526006835260409020815180519293600190920192610e2092849201906114ad565b5050505050505050565b604080516020810190915260608152602082015151825151518114610eab576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4d50435f434f52453a20494e56414c49445f494e5055545f5445585400000000604482015260640161039d565b600060405180602001604052808367ffffffffffffffff811115610ed157610ed16116f0565b604051908082528060200260200182016040528015610efa578160200160208202803683370190505b50905260408051808201909152600081526060602082015290915060005b83811015610fa057855151805182908110610f3557610f35611b33565b6020908102919091018101518352860151805182908110610f5857610f58611b33565b60200260200101518260200181905250610f7182611128565b8351805183908110610f8557610f85611b33565b6020908102919091010152610f9981611b49565b9050610f18565b5090949350505050565b6000606463d2c135e560045b60f81b846040518363ffffffff1660e01b81526004016110049291907fff00000000000000000000000000000000000000000000000000000000000000929092168252602082015260400190565b6020604051808303816000875af1158015611023573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102a79190611b63565b611052838383611191565b61043b576001600160a01b038316611099576040517f7e2732890000000000000000000000000000000000000000000000000000000081526004810182905260240161039d565b6040517f177e802f0000000000000000000000000000000000000000000000000000000081526001600160a01b03831660048201526024810182905260440161039d565b6111086040805160608082018352818301818152825282516020818101909452908152909182015290565b61111183611217565b815261111d83836112df565b602082015292915050565b805160208201516040517fe4f36e1000000000000000000000000000000000000000000000000000000000815260009260649263e4f36e1092611004927f0400000000000000000000000000000000000000000000000000000000000000929091600401611b7c565b60006001600160a01b0383161580159061120f5750826001600160a01b0316846001600160a01b031614806111eb57506001600160a01b0380851660009081526005602090815260408083209387168352929052205460ff165b8061120f57506000828152600460205260409020546001600160a01b038481169116145b949350505050565b6040805160208101909152606081528151516040805160208101909152600090808367ffffffffffffffff811115611251576112516116f0565b60405190808252806020026020018201604052801561127a578160200160208202803683370190505b509052905060005b82811015610c22576112b0856000015182815181106112a3576112a3611b33565b60200260200101516113b1565b82518051839081106112c4576112c4611b33565b60209081029190910101526112d881611b49565b9050611282565b6040805160208101909152606081528251516040805160208101909152600090808367ffffffffffffffff811115611319576113196116f0565b604051908082528060200260200182016040528015611342578160200160208202803683370190505b509052905060005b828110156113a8576113798660000151828151811061136b5761136b611b33565b6020026020010151866113c1565b825180518390811061138d5761138d611b33565b60209081029190910101526113a181611b49565b905061134a565b50949350505050565b6000606463c50c9c026004610fb6565b60408051606083901b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000001660208201528151601481830301815260348201928390527f3c6f0e6800000000000000000000000000000000000000000000000000000000909252600091606491633c6f0e6891611463917f0400000000000000000000000000000000000000000000000000000000000000918891603801611b7c565b6020604051808303816000875af1158015611482573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114a69190611b63565b9392505050565b8280548282559060005260206000209081019282156114e8579160200282015b828111156114e85782518255916020019190600101906114cd565b506114f49291506114f8565b5090565b5b808211156114f457600081556001016114f9565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461153b57600080fd5b50565b60006020828403121561155057600080fd5b81356114a68161150d565b6000815180845260005b8181101561158157602081850181015186830182015201611565565b506000602082860101526020601f19601f83011685010191505092915050565b6020815260006114a6602083018461155b565b6000602082840312156115c657600080fd5b5035919050565b80356001600160a01b03811681146115e457600080fd5b919050565b600080604083850312156115fc57600080fd5b611605836115cd565b946020939093013593505050565b60008060006060848603121561162857600080fd5b611631846115cd565b925061163f602085016115cd565b9150604084013590509250925092565b6000806040838503121561166257600080fd5b61166b836115cd565b9150602083013567ffffffffffffffff81111561168757600080fd5b83016040818603121561169957600080fd5b809150509250929050565b6000602082840312156116b657600080fd5b6114a6826115cd565b600080604083850312156116d257600080fd5b6116db836115cd565b91506020830135801515811461169957600080fd5b634e487b7160e01b600052604160045260246000fd5b6040805190810167ffffffffffffffff81118282101715611729576117296116f0565b60405290565b6040516020810167ffffffffffffffff81118282101715611729576117296116f0565b604051601f8201601f1916810167ffffffffffffffff8111828210171561177b5761177b6116f0565b604052919050565b600082601f83011261179457600080fd5b813567ffffffffffffffff8111156117ae576117ae6116f0565b6117c16020601f19601f84011601611752565b8181528460208386010111156117d657600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000806080858703121561180957600080fd5b611812856115cd565b9350611820602086016115cd565b925060408501359150606085013567ffffffffffffffff81111561184357600080fd5b61184f87828801611783565b91505092959194509250565b6020808252825182820182905280516040840181905260009291820190839060608601905b808310156118a05783518252928401926001929092019190840190611880565b509695505050505050565b600080604083850312156118be57600080fd5b6118c7836115cd565b91506118d5602084016115cd565b90509250929050565b600181811c908216806118f257607f821691505b60208210810361191257634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b808201808211156102a7576102a7611918565b600067ffffffffffffffff82111561195b5761195b6116f0565b5060051b60200190565b600082601f83011261197657600080fd5b8135602061198b61198683611941565b611752565b82815260059290921b840181019181810190868411156119aa57600080fd5b8286015b848110156118a057803567ffffffffffffffff8111156119ce5760008081fd5b6119dc8986838b0101611783565b8452509183019183016119ae565b6000604082360312156119fc57600080fd5b611a04611706565b823567ffffffffffffffff80821115611a1c57600080fd5b81850191506020808336031215611a3257600080fd5b611a3a61172f565b833583811115611a4957600080fd5b939093019236601f850112611a5d57600080fd5b8335611a6b61198682611941565b81815260059190911b85018301908381019036831115611a8a57600080fd5b958401955b82871015611aa857863582529584019590840190611a8f565b83525050845285810135925081831115611ac157600080fd5b611acd36848801611965565b9084015250909392505050565b60006001600160a01b03808716835280861660208401525083604083015260806060830152611b0c608083018461155b565b9695505050505050565b600060208284031215611b2857600080fd5b81516114a68161150d565b634e487b7160e01b600052603260045260246000fd5b60006000198203611b5c57611b5c611918565b5060010190565b600060208284031215611b7557600080fd5b5051919050565b7fff0000000000000000000000000000000000000000000000000000000000000084168152826020820152606060408201526000610712606083018461155b56fea264697066735822122032cc9569a57d1b107f163af7fd375b58451772f82279883207242aa0372e125964736f6c63430008130033";
var deployPrivateERC721ContractAction = {
name: "DEPLOY_PRIVATE_ERC721_CONTRACT",
similes: [
"DEPLOY_ERC721",
"CREATE_NFT",
"DEPLOY_NFT",
"CREATE_PRIVATE_NFT",
"DEPLOY_PRIVATE_NFT",
"CREATE_NFT_COLLECTION",
"DEPLOY_NFT_COLLECTION"
],
description: "Deploy a new private ERC721 NFT contract on the COTI blockchain. This creates a new private NFT collection with the specified name and symbol.",
validate: async (runtime, message) => {
try {
const text = message.content?.text || "";
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - text: "${text}"`);
const isDeployQuery = /\b(deploy|create|new).*\b(nft|erc721|collection)\b/i.test(text);
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - isDeployQuery: ${isDeployQuery}`);
if (message.content?.name && message.content?.symbol) {
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - structured params found`);
return true;
}
const nameMatch = text.match(/(?:name[d]?|called)\s*['""]?([^'",]+)['""]?/i);
const symbolMatch = text.match(/(?:symbol|ticker)\s*['""]?([^'",\s]+)['""]?|\(([^)]+)\)/i);
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - nameMatch: ${nameMatch?.[1] || nameMatch?.[2] || "none"}`);
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - symbolMatch: ${symbolMatch?.[1] || symbolMatch?.[2] || symbolMatch?.[3] || "none"}`);
if (isDeployQuery && nameMatch && symbolMatch) {
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - returning true`);
return true;
}
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT validation - returning false`);
return false;
} catch (error) {
elizaLogger6.error(`DEPLOY_PRIVATE_ERC721_CONTRACT validation error:`, error);
return false;
}
},
handler: async (runtime, message, state, options = {}, callback) => {
try {
elizaLogger6.info("Starting DEPLOY_PRIVATE_ERC721_CONTRACT action");
let params;
if (message.content?.name && message.content?.symbol) {
params = DeployPrivateERC721Schema.parse(message.content);
} else {
const text = message.content?.text || "";
const nameMatch = text.match(/(?:name[d]?|called)\s*['""]?([^'",]+)['""]?/i);
const symbolMatch = text.match(/(?:symbol|ticker)\s*['""]?([^'",\s]+)['""]?|\(([^)]+)\)/i);
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT handler - nameMatch: ${nameMatch?.[1] || "none"}`);
elizaLogger6.info(`DEPLOY_PRIVATE_ERC721_CONTRACT handler - symbolMatch: ${symbolMatch?.[1] || symbolMatch?.[2] || "none"}`);
if (!nameMatch || !symbolMatch) {
throw new Error("Could not extract name and symbol from message");
}
params = {
name: nameMatch[1].trim(),
symbol: (symbolMatch[1] || symbolMatch[2]).trim()
};
}
const walletProvider = initCotiWalletProvider(runtime);
const wallet = walletProvider.getWallet();
const factory = new ethers6.ContractFactory(
ERC721_ABI,
ERC721_BYTECODE
);
const deployTx = factory.getDeployTransaction(
params.name,
params.symbol
);
const tx = await wallet.sendTransaction({
data: deployTx.data,
gasLimit: params.gasLimit || 2e6
});
const receipt = await tx.wait();
if (!receipt) {
throw new Error("Deployment transaction failed to be mined");
}
const contractAddress = receipt.contractAddress;
if (!contractAddress) {
throw new Error("Could not determine contract address from transaction receipt");
}
const responseText = `Successfully deployed private ERC721 contract: ${params.name} (${params.symbol})
Contract Address: ${contractAddress}
Transaction Hash: ${tx.hash}`;
if (callback) {
await callback({
text: responseText,
content: {
success: true,
contractAddress,
transactionHash: tx.hash,
name: params.name,
symbol: params.symbol
}
});
}
return {
text: responseText,
success: true,
data: {
contractAddress,
transactionHash: tx.hash,
name: params.name,
symbol: params.symbol
}
};
} catch (error) {
elizaLogger6.error("Error in DEPLOY_PRIVATE_ERC721_CONTRACT action:", error);
const errorMessage = `Failed to deploy private ERC721 contract: ${error instanceof Error ? error.message : String(error)}`;
if (callback) {
await callback({
text: errorMessage,
content: { success: false, error: errorMessage }
});
}
return {
text: errorMessage,
success: false,
error: error instanceof Error ? error : new Error(String(error))
};
}
},
examples: [
[
{
name: "{{user1}}",
content: {
text: "Deploy a new NFT collection called 'My Art Gallery' with symbol 'ART'"
}
},
{
name: "{{user2}}",
content: {
text: "I'll deploy a private ERC721 NFT collection for you on the COTI blockchain.",
action: "DEPLOY_PRIVATE_ERC721_CONTRACT"
}
}
]
]
};
// src/actions/mintPrivateErc721Token.ts
import {
elizaLogger as elizaLogger7
} from "@elizaos/core";
import { ethers as ethers7 } from "ethers";
import { z as z3 } from "zod";
import { buildStringInputText } from "@coti-io/coti-sdk-typescript";
var MintPrivateERC721Schema = z3.object({
tokenAddress: z3.string().describe("ERC721 token contract address"),
toAddress: z3.string().describe("Address to receive the minted NFT"),
tokenUri: z3.string().describe("URI for the token metadata"),
gasLimit: z3.number().optional().describe("Optional gas limit")
});
var ERC721_ABI2 = [
// COTI private mint function
{
inputs: [
{ internalType: "address", name: "to", type: "address" },
{
components: [
{
components: [
{ internalType: "ctUint64[]", name: "value", type: "uint256[]" }
],
internalType: "struct ctString",
name: "ciphertext",
type: "tuple"
},
{ internalType: "bytes[]", name: "signature", type: "bytes[]" }
],
internalType: "struct itString",
name: "itTokenURI",
type: "tuple"
}
],
name: "mint",
outputs: [],
stateMutability: "nonpayable",
type: "function"
},
// Standard view functions
"function tokenURI(uint256 tokenId) view returns (tuple(uint256[]))",
"function ownerOf(uint256 tokenId) view returns (address)",
"function totalSupply() view returns (uint256)"
];
var mintPrivateErc721TokenAction = {
name: "MINT_PRIVATE_ERC721_TOKEN",
similes: [
"MINT_ERC721",
"MINT_NFT",
"CREATE_NFT",
"MINT_PRIVATE_NFT",
"ISSUE_NFT",
"CREATE_TOKEN",
"MINT_TOKEN",
"MINT_PRIVATE_ERC721_TOKEN",
"MINT_PRIVATE_ERC721",
"CREATE_PRIVATE_NFT"
],
description: "Mint a new private ERC721 NFT token on the COTI blockchain. This creates a new NFT with the specified metadata URI and assigns it to the recipient address.",
validate: async (runtime, message) => {
try {
const text = message.content?.text || "";
elizaLogger7.info(`\u{1F50D} MINT_PRIVATE_ERC721_TOKEN validation called!`);
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - text: "${text}"`);
const isMintQuery = /\b(mint|create).*\b(nft|token)\b/i.test(text);
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - isMintQuery: ${isMintQuery}`);
if (message.content?.tokenAddress && message.content?.toAddress && message.content?.tokenUri) {
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - structured params found`);
return true;
}
const addressMatches = text.match(/0x[a-fA-F0-9]{40}/g);
const uriMatch = text.match(/https?:\/\/[^\s]+/i);
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - addresses found: ${addressMatches?.length || 0}`);
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - URI found: ${!!uriMatch}`);
if (isMintQuery && addressMatches && addressMatches.length >= 2 && uriMatch) {
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - returning true`);
return true;
}
elizaLogger7.info(`MINT_PRIVATE_ERC721_TOKEN validation - returning false`);
return false;
} catch (error) {
elizaLogger7.error(`MINT_PRIVATE_ERC721_TOKEN validation error:`, error);
return false;
}
},
handler: async (runtime, message, state, options = {}, callback) => {
try {
elizaLogger7.info("\u{1F680} MINT_PRIVATE_ERC721_TOKEN handler called!");
elizaLogger7.info(`Message content: ${JSON.stringify(message.content, null, 2)}`);
let params;
if (message.content?.tokenAddress && message.content?.toAddress && message.content?.tokenUri) {
params = MintPrivateERC721Schema.parse(message.content);
} else {
const text = message.content?.text || "";
const addressMatches = text.match(/0x[a-fA-F0-9]{40}/g);
const uriMatch = text.match(/https?:\/\/[^\s]+/i);
if (!addressMatches || addressMatches.length < 2 || !uriMatch) {
throw new Error("Could not extract required parameters from message");
}
params = {
tokenAddress: addressMatches[0],
toAddress: addressMatches[1],
tokenUri: uriMatch[0]
};
}
const walletProvider = initCotiWalletProvider(runtime);
const wallet = walletProvider.getWallet();
const provider = new ethers7.providers.JsonRpcProvider(process.env.COTI_RPC_URL || "https://testnet.coti.io/rpc");
const contractReader = new ethers7.Contract(
params.tokenAddress,
ERC721_ABI2,
provider
);
const contractWriter = new ethers7.Contract(
params.tokenAddress,
ERC721_ABI2,
wallet
);
elizaLogger7.info(`Minting NFT to ${params.toAddress} with URI: ${params.tokenUri}`);
const mintSelector = contractReader.interface.getSighash("mint(address,(bytes,bytes[]))");
elizaLogger7.info(`Mint function selector: ${mintSelector}`);
const aesKey = walletProvider.getAesKey();
if (!aesKey) {
throw new Error("AES key not found in wallet provider. Cannot encrypt token URI for private NFT minting.");
}
elizaLogger7.info("Encrypting token URI for private NFT minting...");
const encryptedInputText = buildStringInputText(
params.tokenUri,
{ wallet, userKey: aesKey },
params.tokenAddress,
mintSelector
);
elizaLogger7.info("Token URI encrypted successfully. Proceeding with mint transaction...");
const tx = await contractWriter.mint(
params.toAddress,
encryptedInputText,
{
gasLimit: params.gasLimit || 5e5
}
);
const receipt = await tx.wait();
let tokenId = "N/A";
if (receipt.logs && receipt.logs.length > 0) {
try {
const transferEvent = receipt.logs.find(
(log) => log.topics && log.topics[0] === "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
);
if (transferEvent && transferEvent.topics && transferEvent.topics[3]) {
tokenId = parseInt(transferEvent.topics[3], 16).toString();
}
} catch (e) {
elizaLogge