diff --git a/typescript/agentkit/src/action-providers/helixa/constants.ts b/typescript/agentkit/src/action-providers/helixa/constants.ts new file mode 100644 index 000000000..edd20bc0a --- /dev/null +++ b/typescript/agentkit/src/action-providers/helixa/constants.ts @@ -0,0 +1,135 @@ +/** + * Helixa AgentDNA contract on Base mainnet (Chain ID 8453). + * ERC-8004 compliant identity NFT for AI agents. + * + * IMPORTANT: This contract does NOT implement ERC721Enumerable. + * totalSupply() and paused() will REVERT. Use totalAgents() instead. + * balanceOf() and ownerOf() work fine (standard ERC721). + */ +export const AGENTDNA_CONTRACT = "0x665971e7bf8ec90c3066162c5b396604b3cd7711"; + +/** + * Helixa AgentNames contract on Base mainnet. + * .agent naming registry for AI agents. + */ +export const AGENTNAMES_CONTRACT = "0xDE8c422D2076CbAE0cA8f5dA9027A03D48928F2d"; + +/** + * Max uint256 — pass as parentTokenId when minting without a parent. + */ +export const NO_PARENT = + "115792089237316195423570985008687907853269984665640564039457584007913129639935"; + +export const AGENTDNA_ABI = [ + { + inputs: [], + name: "totalAgents", + outputs: [{ type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ name: "tokenId", type: "uint256" }], + name: "getAgent", + outputs: [ + { + components: [ + { name: "agentAddress", type: "address" }, + { name: "name", type: "string" }, + { name: "framework", type: "string" }, + { name: "mintedAt", type: "uint256" }, + { name: "verified", type: "bool" }, + { name: "soulbound", type: "bool" }, + { name: "generation", type: "uint256" }, + { name: "parentDNA", type: "uint256" }, + { name: "currentVersion", type: "string" }, + { name: "mutationCount", type: "uint256" }, + ], + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ name: "agentAddress", type: "address" }], + name: "addressToTokenId", + outputs: [{ type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ name: "tokenId", type: "uint256" }], + name: "getPoints", + outputs: [{ type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "mintPrice", + outputs: [{ type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { name: "agentAddress", type: "address" }, + { name: "name", type: "string" }, + { name: "framework", type: "string" }, + { name: "tokenURI_", type: "string" }, + { name: "soulbound", type: "bool" }, + { name: "parentTokenId", type: "uint256" }, + ], + name: "mint", + outputs: [{ type: "uint256" }], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { name: "tokenId", type: "uint256" }, + { name: "newVersion", type: "string" }, + { name: "reason", type: "string" }, + ], + name: "mutate", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { name: "tokenId", type: "uint256" }, + { name: "traitType", type: "string" }, + { name: "traitValue", type: "string" }, + ], + name: "addTrait", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; + +export const AGENTNAMES_ABI = [ + { + inputs: [{ name: "name", type: "string" }], + name: "resolve", + outputs: [{ type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ name: "name", type: "string" }], + name: "available", + outputs: [{ type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ name: "name", type: "string" }], + name: "register", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/typescript/agentkit/src/action-providers/helixa/helixaActionProvider.ts b/typescript/agentkit/src/action-providers/helixa/helixaActionProvider.ts new file mode 100644 index 000000000..634172b03 --- /dev/null +++ b/typescript/agentkit/src/action-providers/helixa/helixaActionProvider.ts @@ -0,0 +1,450 @@ +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { EvmWalletProvider } from "../../wallet-providers"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { encodeFunctionData, Hex } from "viem"; +import { + RegisterAgentSchema, + GetAgentSchema, + GetAgentByAddressSchema, + MutateAgentSchema, + AddTraitSchema, + ResolveNameSchema, + CheckNameSchema, + GetStatsSchema, +} from "./schemas"; +import { + AGENTDNA_CONTRACT, + AGENTNAMES_CONTRACT, + AGENTDNA_ABI, + AGENTNAMES_ABI, + NO_PARENT, +} from "./constants"; + +/** + * HelixaActionProvider provides actions for Helixa AgentDNA — the onchain identity + * and reputation protocol for AI agents on Base (ERC-8004). + * + * Actions: + * - register_agent: Mint an onchain identity NFT for an AI agent + * - get_agent: Look up an agent by token ID + * - get_agent_by_address: Look up an agent by wallet address + * - mutate_agent: Record a version change + * - add_trait: Add a personality trait or skill + * - resolve_name: Resolve a .agent name to an address + * - check_name: Check .agent name availability + * - get_helixa_stats: Get protocol statistics + * + * @see https://helixa.xyz + * @see https://basescan.org/address/0x665971e7bf8ec90c3066162c5b396604b3cd7711 + */ +export class HelixaActionProvider extends ActionProvider { + /** + * Constructor for the HelixaActionProvider class. + */ + constructor() { + super("helixa", []); + } + + /** + * Register a new AI agent on Helixa AgentDNA. + * + * Mints an ERC-8004 compliant identity NFT on Base with: + * - Agent name and framework + * - Soulbound option (non-transferable) + * - Optional .agent name + * - Points toward future token allocation (2x for first 100 agents) + * + * Pricing: Free (0-100 agents) → 0.005 ETH (101-500) → 0.01 ETH (501-1000) → 0.02 ETH (1001+) + * + * @param walletProvider - The wallet provider to mint from. + * @param args - The registration arguments. + * @returns A message with mint details. + */ + @CreateAction({ + name: "register_agent", + description: ` +Register a new AI agent on Helixa AgentDNA — the onchain identity protocol for AI agents on Base. + +This mints an ERC-8004 compliant identity NFT that includes: +- Agent name and framework +- Soulbound option (non-transferable, recommended for production agents) +- Optional .agent name (e.g. 'mybot' becomes mybot.agent) +- Points toward future token allocation (2x for first 100 agents) + +Pricing: Free for first 100 agents, then 0.005 ETH (101-500), 0.01 ETH (501-1000), 0.02 ETH (1001+). + +IMPORTANT: Do NOT call totalSupply() or paused() on this contract — they will revert. Use totalAgents() instead. +`, + schema: RegisterAgentSchema, + }) + async registerAgent( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + // Check current mint price + const priceData = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "mintPrice", + }); + + const priceResult = await walletProvider.readContract(AGENTDNA_CONTRACT, priceData); + const mintPrice = BigInt(priceResult as string); + const agentAddress = await walletProvider.getAddress(); + + // Mint the agent identity + const data = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "mint", + args: [ + agentAddress as Hex, + args.name, + args.framework, + args.tokenURI || "", + args.soulbound, + BigInt(NO_PARENT), + ], + }); + + const hash = await walletProvider.sendTransaction({ + to: AGENTDNA_CONTRACT as `0x${string}`, + data, + value: mintPrice, + }); + + await walletProvider.waitForTransactionReceipt(hash); + + // Register .agent name if requested + if (args.agentName) { + try { + const nameData = encodeFunctionData({ + abi: AGENTNAMES_ABI, + functionName: "register", + args: [args.agentName], + }); + + const nameHash = await walletProvider.sendTransaction({ + to: AGENTNAMES_CONTRACT as `0x${string}`, + data: nameData, + }); + + await walletProvider.waitForTransactionReceipt(nameHash); + } catch (nameError) { + return `Agent registered successfully (TX: https://basescan.org/tx/${hash}) but .agent name registration failed: ${nameError}`; + } + } + + let msg = `Successfully registered agent "${args.name}" on Helixa AgentDNA.\n`; + msg += `TX: https://basescan.org/tx/${hash}\n`; + msg += `Contract: ${AGENTDNA_CONTRACT}\n`; + msg += `Framework: ${args.framework}\n`; + msg += `Soulbound: ${args.soulbound}\n`; + if (mintPrice > 0n) { + msg += `Fee: ${Number(mintPrice) / 1e18} ETH\n`; + } else { + msg += `Fee: Free (beta)\n`; + } + if (args.agentName) { + msg += `Name: ${args.agentName}.agent\n`; + } + msg += `View: https://helixa.xyz/directory.html`; + return msg; + } catch (error) { + return `Error registering agent: ${error}`; + } + } + + /** + * Look up an agent by token ID. + * + * @param walletProvider - The wallet provider. + * @param args - The lookup arguments. + * @returns Agent details. + */ + @CreateAction({ + name: "get_agent", + description: ` +Look up an AI agent's onchain identity on Helixa AgentDNA by token ID. +Returns name, framework, mint date, verification status, soulbound status, generation, version, mutation count, and points. +`, + schema: GetAgentSchema, + }) + async getAgent( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const data = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "getAgent", + args: [BigInt(args.tokenId)], + }); + + const result = await walletProvider.readContract(AGENTDNA_CONTRACT, data); + + const pointsData = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "getPoints", + args: [BigInt(args.tokenId)], + }); + + const points = await walletProvider.readContract(AGENTDNA_CONTRACT, pointsData); + + return `Agent #${args.tokenId}:\n${JSON.stringify(result, null, 2)}\nPoints: ${points}`; + } catch (error) { + return `Error looking up agent #${args.tokenId}: ${error}`; + } + } + + /** + * Look up an agent by wallet address. + * + * @param walletProvider - The wallet provider. + * @param args - The lookup arguments. + * @returns Token ID for the address. + */ + @CreateAction({ + name: "get_agent_by_address", + description: `Look up an AI agent's onchain identity by wallet address. Returns the token ID associated with that address.`, + schema: GetAgentByAddressSchema, + }) + async getAgentByAddress( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const data = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "addressToTokenId", + args: [args.agentAddress as Hex], + }); + + const tokenId = await walletProvider.readContract(AGENTDNA_CONTRACT, data); + + if (tokenId === "0" || tokenId === 0n) { + return `No Helixa identity found for address ${args.agentAddress}. Register at https://helixa.xyz/mint.html`; + } + + return `Address ${args.agentAddress} is registered as Agent #${tokenId}`; + } catch (error) { + return `Error looking up address: ${error}`; + } + } + + /** + * Mutate (version update) an agent. + * + * @param walletProvider - The wallet provider. + * @param args - The mutation arguments. + * @returns Mutation confirmation. + */ + @CreateAction({ + name: "mutate_agent", + description: ` +Record a version change (mutation) for an AI agent on Helixa. Tracks the agent's evolution over time. +Only the agent's owner can mutate. Awards 50 mutation points. +`, + schema: MutateAgentSchema, + }) + async mutateAgent( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const data = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "mutate", + args: [BigInt(args.tokenId), args.newVersion, args.reason], + }); + + const hash = await walletProvider.sendTransaction({ + to: AGENTDNA_CONTRACT as `0x${string}`, + data, + }); + + await walletProvider.waitForTransactionReceipt(hash); + + return `Successfully mutated Agent #${args.tokenId} to version ${args.newVersion}. TX: https://basescan.org/tx/${hash}`; + } catch (error) { + return `Error mutating agent: ${error}`; + } + } + + /** + * Add a trait to an agent. + * + * @param walletProvider - The wallet provider. + * @param args - The trait arguments. + * @returns Trait addition confirmation. + */ + @CreateAction({ + name: "add_trait", + description: ` +Add a trait to an AI agent's onchain identity. Traits are key-value pairs like personality:analytical, skill:defi-trading, alignment:chaotic-good. +Only the agent's owner can add traits. Awards 10 trait points. +`, + schema: AddTraitSchema, + }) + async addTrait( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const data = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "addTrait", + args: [BigInt(args.tokenId), args.traitType, args.traitValue], + }); + + const hash = await walletProvider.sendTransaction({ + to: AGENTDNA_CONTRACT as `0x${string}`, + data, + }); + + await walletProvider.waitForTransactionReceipt(hash); + + return `Successfully added trait to Agent #${args.tokenId}: ${args.traitType} = ${args.traitValue}. TX: https://basescan.org/tx/${hash}`; + } catch (error) { + return `Error adding trait: ${error}`; + } + } + + /** + * Resolve a .agent name to a wallet address. + * + * @param walletProvider - The wallet provider. + * @param args - The resolution arguments. + * @returns The resolved address. + */ + @CreateAction({ + name: "resolve_name", + description: `Resolve a .agent name to a wallet address. For example, resolving "helixa" returns the address that owns helixa.agent.`, + schema: ResolveNameSchema, + }) + async resolveName( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const data = encodeFunctionData({ + abi: AGENTNAMES_ABI, + functionName: "resolve", + args: [args.name], + }); + + const result = await walletProvider.readContract(AGENTNAMES_CONTRACT, data); + + if (result === "0x0000000000000000000000000000000000000000") { + return `${args.name}.agent is not registered.`; + } + + return `${args.name}.agent resolves to: ${result}`; + } catch (error) { + return `Error resolving name: ${error}`; + } + } + + /** + * Check .agent name availability. + * + * @param walletProvider - The wallet provider. + * @param args - The check arguments. + * @returns Availability status. + */ + @CreateAction({ + name: "check_name", + description: `Check if a .agent name is available for registration on Helixa.`, + schema: CheckNameSchema, + }) + async checkName( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const data = encodeFunctionData({ + abi: AGENTNAMES_ABI, + functionName: "available", + args: [args.name], + }); + + const result = await walletProvider.readContract(AGENTNAMES_CONTRACT, data); + + return result + ? `${args.name}.agent is available!` + : `${args.name}.agent is already taken.`; + } catch (error) { + return `Error checking name: ${error}`; + } + } + + /** + * Get Helixa protocol statistics. + * + * @param walletProvider - The wallet provider. + * @param _args - Empty args. + * @returns Protocol stats. + */ + @CreateAction({ + name: "get_helixa_stats", + description: `Get Helixa AgentDNA protocol statistics — total agents registered, current mint price, and free mints remaining.`, + schema: GetStatsSchema, + }) + async getStats( + walletProvider: EvmWalletProvider, + _args: z.infer, + ): Promise { + try { + const totalData = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "totalAgents", + }); + + const priceData = encodeFunctionData({ + abi: AGENTDNA_ABI, + functionName: "mintPrice", + }); + + const [total, price] = await Promise.all([ + walletProvider.readContract(AGENTDNA_CONTRACT, totalData), + walletProvider.readContract(AGENTDNA_CONTRACT, priceData), + ]); + + const totalNum = Number(total); + const priceEth = Number(price) / 1e18; + const freeRemaining = totalNum < 100 ? 100 - totalNum : 0; + + let msg = `Helixa AgentDNA Stats:\n`; + msg += `Total Agents: ${totalNum}\n`; + msg += `Mint Price: ${priceEth === 0 ? "Free (beta)" : `${priceEth} ETH`}\n`; + if (freeRemaining > 0) { + msg += `Free Mints Remaining: ${freeRemaining}\n`; + } + msg += `Contract: ${AGENTDNA_CONTRACT}\n`; + msg += `Explorer: https://basescan.org/address/${AGENTDNA_CONTRACT}`; + + return msg; + } catch (error) { + return `Error fetching stats: ${error}`; + } + } + + /** + * Checks if the action provider supports the given network. + * Helixa AgentDNA is deployed on Base (chain 8453) only. + * + * @param network - The network to check. + * @returns True if the network is Base. + */ + supportsNetwork(network: Network): boolean { + return network.chainId === "8453" || (network.chainId as unknown as number) === 8453; + } +} + +/** + * Factory function for creating a HelixaActionProvider. + * + * @returns A new HelixaActionProvider instance. + */ +export const helixaActionProvider = () => new HelixaActionProvider(); diff --git a/typescript/agentkit/src/action-providers/helixa/index.ts b/typescript/agentkit/src/action-providers/helixa/index.ts new file mode 100644 index 000000000..23c85cb15 --- /dev/null +++ b/typescript/agentkit/src/action-providers/helixa/index.ts @@ -0,0 +1,12 @@ +export { HelixaActionProvider, helixaActionProvider } from "./helixaActionProvider"; +export { + RegisterAgentSchema, + GetAgentSchema, + GetAgentByAddressSchema, + MutateAgentSchema, + AddTraitSchema, + ResolveNameSchema, + CheckNameSchema, + GetStatsSchema, +} from "./schemas"; +export { AGENTDNA_CONTRACT, AGENTNAMES_CONTRACT } from "./constants"; diff --git a/typescript/agentkit/src/action-providers/helixa/schemas.ts b/typescript/agentkit/src/action-providers/helixa/schemas.ts new file mode 100644 index 000000000..db774e3cc --- /dev/null +++ b/typescript/agentkit/src/action-providers/helixa/schemas.ts @@ -0,0 +1,105 @@ +import { z } from "zod"; + +/** + * Input schema for registering an AI agent on Helixa AgentDNA. + */ +export const RegisterAgentSchema = z + .object({ + name: z.string().describe("The agent's name (e.g. 'MyTradingBot')"), + framework: z + .string() + .describe( + "The framework the agent runs on (e.g. 'AgentKit', 'LangChain', 'ElizaOS', 'OpenClaw', 'CrewAI')", + ), + soulbound: z + .boolean() + .default(true) + .describe( + "Whether the identity NFT is soulbound (non-transferable). Default true for production agents.", + ), + agentName: z + .string() + .optional() + .describe( + "Optional .agent name to register (e.g. 'mybot' becomes mybot.agent). Lowercase a-z, 0-9, hyphens, 3-32 chars.", + ), + tokenURI: z.string().optional().describe("Optional metadata URI for the agent's identity"), + }) + .strip() + .describe("Instructions for registering an AI agent on Helixa AgentDNA"); + +/** + * Input schema for looking up an agent by token ID. + */ +export const GetAgentSchema = z + .object({ + tokenId: z.string().describe("The token ID of the agent to look up"), + }) + .strip() + .describe("Instructions for looking up an agent by token ID"); + +/** + * Input schema for looking up an agent by wallet address. + */ +export const GetAgentByAddressSchema = z + .object({ + agentAddress: z.string().describe("The wallet address to look up agent identity for"), + }) + .strip() + .describe("Instructions for looking up an agent by wallet address"); + +/** + * Input schema for mutating (version updating) an agent. + */ +export const MutateAgentSchema = z + .object({ + tokenId: z.string().describe("The token ID of the agent to mutate"), + newVersion: z.string().describe("The new version string (e.g. '2.0.0')"), + reason: z.string().describe("The reason for the mutation"), + }) + .strip() + .describe("Instructions for recording a version change on an agent"); + +/** + * Input schema for adding a trait to an agent. + */ +export const AddTraitSchema = z + .object({ + tokenId: z.string().describe("The token ID of the agent to add a trait to"), + traitType: z + .string() + .describe("The trait category (e.g. 'personality', 'skill', 'alignment')"), + traitValue: z + .string() + .describe("The trait value (e.g. 'analytical', 'defi-trading', 'chaotic-good')"), + }) + .strip() + .describe("Instructions for adding a trait to an agent"); + +/** + * Input schema for resolving a .agent name. + */ +export const ResolveNameSchema = z + .object({ + name: z.string().describe("The .agent name to resolve (without the .agent suffix)"), + }) + .strip() + .describe("Instructions for resolving a .agent name to a wallet address"); + +/** + * Input schema for checking .agent name availability. + */ +export const CheckNameSchema = z + .object({ + name: z.string().describe("The .agent name to check availability for (without the .agent suffix)"), + }) + .strip() + .describe("Instructions for checking .agent name availability"); + +/** + * Input schema for getting Helixa protocol statistics. + */ +export const GetStatsSchema = z + .object({}) + .strip() + .describe("Instructions for getting Helixa protocol statistics"); diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 7f2b0233a..e985913ea 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -32,6 +32,7 @@ export * from "./weth"; export * from "./wow"; export * from "./allora"; export * from "./flaunch"; +export * from "./helixa"; export * from "./onramp"; export * from "./vaultsfyi"; export * from "./x402";