Skip to main content
Glama

agentek-eth

by NaniDAO
client.ts9.75 kB
import { type PublicClient, type WalletClient, type Transport, type Chain, type Account, createPublicClient, createWalletClient, Address, Hex, SignableMessage, TypedDataParameter, TypedDataDomain } from "viem"; import { z } from "zod"; // Base interface for all tools export interface BaseTool { name: string; description: string; parameters: z.ZodObject<any, any, any, any>; execute: (client: AgentekClient, args: any) => Promise<any>; supportedChains?: Chain[]; } export interface Op { target: Address; value: string; // wei encoded data: Hex; } // EIP-191 Personal Sign operation export interface PersonalSign { type: 'personal_sign'; message: SignableMessage; } // EIP-712 Typed Data Sign operation export interface TypedDataSign { type: "typed_data_sign"; domain: TypedDataDomain; types: Record<string, readonly TypedDataParameter[]>; primaryType: string; message: Record<string, unknown>; } // Union type for all signing operations export type Sign = PersonalSign | TypedDataSign; // Extended operation that can be either a transaction or a signature export type Operation = Op | Sign; // RequestIntent - this structure matches what we consume on nani.ooo // You can extend it to include hash when saving if approved by user interface RequestIntent { intent: string; ops: Operation[]; chain: number; } interface CompletedIntent { intent: string; ops: Operation[]; chain: number; hash?: string; signatures?: string[]; } export type Intent = RequestIntent | CompletedIntent; export interface AgentekClientConfig { transports: Transport[]; chains: Chain[]; accountOrAddress: Account | Address; tools: BaseTool[]; } // Type guards for runtime checking export const isTransactionOp = (op: Operation): op is Op => { return 'target' in op && 'value' in op && 'data' in op; }; export const isPersonalSign = (op: Operation): op is PersonalSign => { return 'type' in op && op.type === 'personal_sign'; }; export const isTypedDataSign = (op: Operation): op is TypedDataSign => { return 'type' in op && op.type === 'typed_data_sign'; }; export const isSignOperation = (op: Operation): op is Sign => { return isPersonalSign(op) || isTypedDataSign(op); }; export class AgentekClient { private publicClients: Map<number, PublicClient<Transport, Chain>>; private walletClients: Map<number, WalletClient<Transport, Chain, Account>>; private tools: Map<string, BaseTool>; private chains: Chain[]; private accountOrAddress: Account | Address; constructor(config: AgentekClientConfig) { this.publicClients = new Map(); this.walletClients = new Map(); this.chains = config.chains; this.accountOrAddress = config.accountOrAddress; config.chains.forEach((chain, index) => { const transport = config.transports[index] || config.transports[0]; // Create public client with simple configuration to avoid type issues const publicClient = createPublicClient({ transport, chain, }); this.publicClients.set(chain.id, publicClient as PublicClient<Transport, Chain>); // Create wallet client separately if account is provided as an object // client.ts (inside constructor) if (typeof config.accountOrAddress === "object") { const raw = createWalletClient({ transport, chain, account: config.accountOrAddress, }); // Force‐cast so TS doesn't expand the full generic return type. const walletClient = raw as unknown as WalletClient<Transport, Chain, Account>; this.walletClients.set(chain.id, walletClient); } }); this.tools = new Map(config.tools.map((tool) => [tool.name, tool])); } // Get address public async getAddress(): Promise<Address> { if (typeof this.accountOrAddress === "string") { return this.accountOrAddress; } return this.accountOrAddress.address; } // Get public client for specific chain public getPublicClient(chainId?: number): any { // Use any to avoid type issues if (!chainId) { const defaultClient = this.publicClients.values().next().value; if (!defaultClient) throw new Error("No public clients available"); return defaultClient; } const specificClient = this.publicClients.get(chainId); if (!specificClient) throw new Error(`No public client for chain ${chainId}`); return specificClient; } // Get all public clients public getPublicClients(): Map<number, any> { return this.publicClients; } // Get wallet client for specific chain public getWalletClient(chainId?: number): WalletClient | undefined { if (!chainId) { return this.walletClients.values().next().value; } return this.walletClients.get(chainId); } // Get all wallet clients public getWalletClients(): Map<number, WalletClient> { return this.walletClients; } // Get all available chains public getChains(): Chain[] { return this.chains; } // Get all tools public getTools(): Map<string, BaseTool> { return this.tools; } // Method to filter supported chains public filterSupportedChains( supportedChains: Chain[], chainId?: number, ): Chain[] { let chains = this.getChains(); chains = chains.filter((chain) => supportedChains.map((c) => c.id).includes(chain.id), ); if (chainId !== undefined) { chains = chains.filter((chain) => chain.id === chainId); if (chains.length === 0) { throw new Error(`Chain ${chainId} is not supported`); } } return chains; } // Method to add new tools public addTools(tools: BaseTool[]): void { tools.forEach((tool) => { this.tools.set(tool.name, tool); }); } public async executeOps(ops: Op[], chainId: number): Promise<string> { const walletClient = this.getWalletClient(chainId); const publicClient = this.getPublicClient(chainId); if (!walletClient) { throw new Error(`No wallet client available for chain ${chainId}`); } if (!publicClient) { throw new Error(`No public client available for chain ${chainId}`); } let hash = ""; for (const op of ops) { // Remove the explicit account parameter as it's already set in the wallet client // @ts-expect-error const txHash = await walletClient.sendTransaction({ to: op.target, value: BigInt(op.value), data: op.data, }); await publicClient.waitForTransactionReceipt({ hash: txHash, }); if (ops.length > 1) { hash = hash + txHash + ";"; } else { hash = txHash; } } return hash; } public async executeSign(signs: Sign[], chainId: number): Promise<string[]> { const walletClient = this.getWalletClient(chainId); const account = await this.getAddress() if (!walletClient) { throw new Error(`No wallet client available for chain ${chainId}`); } const signatures: string[] = []; for (const sign of signs) { let signature: string; if (isPersonalSign(sign)) { // EIP-191 Personal Sign signature = await walletClient.signMessage({ account, message: sign.message as SignableMessage, }); } else if (isTypedDataSign(sign)) { // EIP-712 Typed Data Sign signature = await walletClient.signTypedData({ account, domain: sign.domain as TypedDataDomain, types: sign.types, primaryType: sign.primaryType, message: sign.message, }); } else { throw new Error(`Unsupported sign operation type`); } signatures.push(signature); } return signatures; } // Execute mixed operations (both transactions and signatures) public async executeOperations(operations: Operation[], chainId: number): Promise<{ hash?: string; signatures?: string[]; }> { const transactionOps = operations.filter(isTransactionOp); const signOps = operations.filter(isSignOperation); const results: { hash?: string; signatures?: string[] } = {}; // Execute transaction operations if (transactionOps.length > 0) { results.hash = await this.executeOps(transactionOps, chainId); } // Execute signing operations if (signOps.length > 0) { results.signatures = await this.executeSign(signOps, chainId); } return results; } public async execute(method: string, args: any): Promise<any> { const tool = this.tools.get(method); if (!tool) { throw new Error(`Tool ${method} not found`); } if (args.chainId && tool.supportedChains) { if (!tool.supportedChains.map((c) => c.id).includes(args.chainId)) { throw new Error( `Chain ${args.chainId} not supported by tool ${method}`, ); } } const validatedArgs = tool.parameters.safeParse(args); if (!validatedArgs.success) { throw new Error(JSON.stringify(validatedArgs.error)); } return tool.execute(this, validatedArgs.data); } } export function createAgentekClient( config: AgentekClientConfig, ): AgentekClient { return new AgentekClient(config); } export function createTool<T extends z.ZodObject<any, any, any, any>>({ name, description, parameters, execute, supportedChains, }: { name: string; description: string; parameters: T; execute: (client: AgentekClient, args: z.infer<T>) => Promise<any>; supportedChains?: Chain[]; }): BaseTool { return { name, description, parameters, execute, supportedChains, }; } export function createToolCollection(tools: BaseTool[]): BaseTool[] { return tools; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NaniDAO/agentek'

If you have feedback or need assistance with the MCP directory API, please join our Discord server