diff --git a/packages/builder/src/vector/agent-network.ts b/packages/builder/src/vector/agent-network.ts index 1001f6e..b9a790d 100644 --- a/packages/builder/src/vector/agent-network.ts +++ b/packages/builder/src/vector/agent-network.ts @@ -1,164 +1,29 @@ -// @ts-nocheck -// Agent network tools: register, discover, message, get_profile, update, deregister, transfer - +// Agent-registry family: 2 read-only tools + 5 keyless build_* tools. +// KEYLESS since spec PR 7: no tool here takes key material. Builds are +// unsigned; the 4-call flow is get_address -> build_* -> signer sign -> submit. +// Registry constants and build logic live in registry-build.ts. import { z } from "zod/v4"; -import { Lucid, fromText, toText, Data, Constr, validatorToAddress, validatorToScriptHash, getAddressDetails, credentialToAddress } from '@lucid-evolution/lucid'; -import { blake2b } from '@noble/hashes/blake2b'; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { OgmiosProvider } from '@apexfusion/vector-mcp-shared/provider'; -import { metadataStr, lovelaceToAda, deriveNftAssetName } from '@apexfusion/vector-mcp-shared/tx'; +import { lovelaceToAda } from '@apexfusion/vector-mcp-shared/tx'; import { - VECTOR_OGMIOS_URL, VECTOR_SUBMIT_URL, VECTOR_KOIOS_URL, VECTOR_EXPLORER_URL, explorerTxLink, + VECTOR_OGMIOS_URL, VECTOR_SUBMIT_URL, VECTOR_KOIOS_URL, } from '@apexfusion/vector-mcp-shared/config'; -export { deriveNftAssetName }; -import { safetyLayer } from './safety.js'; import { limiterFor } from './rate-limiter.js'; - -// Registry constants - agent-registry v2 (audited, compliant) -// Source blueprint: vector-ai-agents/agent-registry/deploy/agent-registry/plutus.json -const REGISTRY_SCRIPT_CBOR = "5909d101010029800aba2aba1aba0aab9faab9eaab9dab9a4888888966003300130033754011370e90004dc3a40052300730080019180398041804000c8c01cc020c020c020c02000644b30010018a40011337009001198010011804800a00c91803980418041804000c88c8c8cc004004010896600200300389919912cc004cdc8803801456600266e3c01c00a20030064025133005005300f00440246eb8c020004dd598048009805800a01214bd6f7b630488c8cc00400400c896600200314a115980098019805000c528c4cc008008c02c00500520109ba5480026e1d2004488888888888a60026026019301200c912cc004c034c040dd5000c4c8c8cc004004dd6180b180b980b980b980b980b980b980b980b98099baa0042259800800c528456600266e3cdd7180b800801c528c4cc008008c060005012202a375c602860226ea8006294100f48966002601a60206ea800a2646464646464653001375a6036003375c603600d375c603600b37586036009375c6036007375c60360049111112cc004c08801e2646644b3001301d002899192cc004c09c00a0071640906eb8c094004c084dd5001c566002603800513232598009813801400e2c8120dd7181280098109baa0038b203e407c603c6ea80044c8cc00400401489660020030118991980180198130011bae30240014088604201b16407c301b001301a00130190013018001301700130160013011375400516403d259800980618079baa0018a518a504039300600691119199119801001000912cc00400600713233225980099b910070028acc004cdc78038014400600c80b226600a00a603800880b0dd7180a8009bad3016001301800140586601000800629000488c8cc00400400c896600200314c103d87a80008992cc004c0100062600e6602c00297ae0899801801980c001202430160014050911111114c004c0680226034603601125980099b89371a6eb8c048c060dd5000a41000915980099b89371a6eb8c044c060dd5000a41002115980099b89371a6eb8c040c060dd5000a41000515980099b89371a6eb8c06cc070c070c070c070c070c060dd5000a41001115980099b8930043758601c60306ea80052040899198008009bac300f3019375400444b30010018a518acc004cdc49b8d375c603a00290400144cc008008c078006294101820368a50405914a080b229410164528202c8a50405929800800d220100a44100400c9111192cc004c0600062646644b3001301b0018992cc004c090006264b3001301d30203754003132323322598009814801c0222c8130dd698130009bae302600230260013021375400316407c6046003164084603e6ea80222b3001301a0018acc004c07cdd5004400a2c81022c80e901d0992cc004c068c074dd5003c4c8cc89660026602a6eb0c090c084dd5008919baf30253022375400200915980099912cc0040060051598009813800c4c96600266e3cdd71811800802c4c07cdd69812000c52820443026001801204840902940cc058dd5980c98109baa011005899912cc0040060051598009813800c4c966002602c60466ea8006264660260022b300130123028302537540031598009980a00a981418129baa00189806000c52820468a50408c604e60486ea80062c8110c070c08cdd51813000c009024204814a064660020026eb0c06cc088dd5009112cc004006297ae0899912cc0056600266ebcc0a0c094dd5001002c566002b3001301730243754603c604a6ea800a294629410234566002604130013756603e604a6ea800a013006404113371290406d620498059bab301f3025375400514a0811a29410234528204689981380119802002000c4cc01001000502318130009813800a0488a50407d14a080f8dca1bb300130020033021301e375400f1332259800800c00a2b30013024001899192cc004cdc39bad30220024800626464b300130203023375400313259800980b98121baa0018991980a00089980a80b181498131baa00130283025375400316408c603a60486ea8c078c090dd5181398121baa0018a5040886601a6eb0c098c08cdd500992cc004cdd7981398121baa301e302437540020051301f98009bab301e30243754603c60486ea8006011003403d14a08110c01401a2c8100dd718100009811800c009021204214a0660266eacc058c078dd5007001203823011330203374a9001198101ba90014bd7019810260103d87a80004bd70180e1baa006375c603e60386ea80122b30013017001899199119912cc004c0740062b3001302137540150028b20448acc004c0700062b3001302137540150028b20448b203e407c2b3001301b301e3754003198009811180f9baa00191192cc004c078c084dd5000c4c094c088dd5180e18111baa30253022375400316408066016004466ebcc094c088dd500080148c966002603860406ea800626eb8c090c084dd5000c5901f181198101baa00191192cc004c078c084dd5000c4dd5980e18111baa301c30223754604a60446ea80062c8100cc02c0088cdd7981298111baa00100291192cc004c098006264b3001301e375a60460031375c6044003164084604a00316408c6602c004002911112cc004c080c08cdd5006c4c8cc88cc896600266030032605860526ea802a2b3001302432330010013758605a60546ea8068896600200314800226644b30013375e6060605a6ea8c09cc0b4dd5001004c4cdc0000a4005100140ac605c00266004004605e0028162264b30013026302937540031323259800980f18159baa0018991980d8008acc004cdc39bad3005302d37540026eb4c014c0b4dd5007456600260346060605a6ea80062b30013014001899b89301300730133756604e605a6ea800e294102b452820568a5040ac605e60586ea80062c8150c090c0acdd5000981698151baa0018a5040a0660266eb0c088c0a4dd500c92cc004cdd7981698151baa00100689812cc004dd5981218151baa001802c00d015452820508a50409d14a08138cc0100040088c0acc0b0c0b0c0b0c0b0c0b0c0b0004c010004cc00cdd6181418129baa015008330043758604e60486ea805001e26464b30013301501630293026375400f15980099912cc0040060051598009816000c4c96600266e3cdd7181400080244cdc39bad30290014800629410271815800c009029205214a0660366eacc078c098dd500b00144c9660026046604c6ea8006264660386eb0c084c0a0dd500c12cc004cdd7981618149baa302c30293754002605860526ea800a266e252080dac409300f3756604660526ea80062941027180d19814980d19814981518139baa0014bd7019814a6103d87a80004bd704528204a30293026375400f14a08122294102419801198019bac30283025375402a0100026006660086eb0c09cc090dd500a003a04445901d180f1baa008302000130203021001301c375400916406880d0c068dd500188a4d13656400401"; -const REGISTRY_POLICY_ID = "be1a0a2912da180757ed3cd61b56bb8eab0188c19dc3c0e3912d2c01"; -const MIN_AP3X_DEPOSIT = 10_000_000n; -const AGENT_MESSAGE_LABEL = 674; - -// ─── Helpers ───────────────────────────────────────────────────────────────── +import { lucidForAddress } from './build.js'; +import { + MIN_AP3X_DEPOSIT, getRegistryAddress, resolveAgentUtxo, parseAgentDatum, + buildRegisterAgent, buildUpdateAgent, buildTransferAgent, buildDeregisterAgent, buildMessageAgent, +} from './registry-build.js'; function newProvider() { return new OgmiosProvider({ ogmiosUrl: VECTOR_OGMIOS_URL, submitUrl: VECTOR_SUBMIT_URL, koiosUrl: VECTOR_KOIOS_URL }); } -function buildAgentDatum(vkeyHash, name, description, capabilities, framework, endpoint, registeredAt) { - return Data.to(new Constr(0, [ - new Constr(0, [vkeyHash]), - fromText(name), - fromText(description), - capabilities.map(c => fromText(c)), - fromText(framework), - fromText(endpoint), - BigInt(registeredAt ?? Date.now()), - ])); -} +const SIGN_AND_SUBMIT_COPY = + 'Sign it locally with vector_signer_sign { txCbor }, broadcast with vector_submit_transaction { signedTxCbor }, then confirm with vector_await_transaction { txHash }.'; -function parseAgentDatum(datumCbor, utxoRef, assets) { - try { - const c = Data.from(datumCbor); - if (Number(c.index) !== 0) return null; - const ownerCred = c.fields[0]; - const vkeyHash = ownerCred.fields[0]; - const nftUnit = Object.keys(assets).find( - u => u.startsWith(REGISTRY_POLICY_ID) && assets[u] === 1n - ); - const nftAssetName = nftUnit ? nftUnit.slice(REGISTRY_POLICY_ID.length) : ''; - return { - agentId: `did:vector:agent:${REGISTRY_POLICY_ID}:${nftAssetName}`, - name: toText(c.fields[1]), - description: toText(c.fields[2]), - capabilities: c.fields[3].map(toText), - framework: toText(c.fields[4]), - endpoint: toText(c.fields[5]), - registeredAt: Number(c.fields[6]), - utxoRef, - ownerVkeyHash: vkeyHash, - }; - } catch (err) { - console.warn(`parseAgentDatum failed for ${utxoRef}:`, err?.message || err); - return null; - } -} - -// ─── DID & Agent Resolution ────────────────────────────────────────────────── - -function parseDid(agentId) { - const parts = agentId.split(':'); - if (parts.length !== 5 || parts[0] !== 'did' || parts[1] !== 'vector' || parts[2] !== 'agent') { - throw new Error('Invalid agent DID format. Expected: did:vector:agent:{policyId}:{nftAssetName}'); - } - if (!/^[a-f0-9]+$/.test(parts[3]) || !/^[a-f0-9]+$/.test(parts[4])) { - throw new Error('Invalid agent DID: policyId and assetName must be hex strings.'); - } - return { policyId: parts[3], assetName: parts[4], unit: `${parts[3]}${parts[4]}` }; -} - -async function resolveAgentUtxo(provider, agentId) { - const { unit } = parseDid(agentId); - - let utxo; - try { - utxo = await provider.getUtxoByUnit(unit); - } catch { - // Koios unavailable or asset not found - will try Ogmios fallback below - } - - // If Koios returned a UTxO without datum, or didn't find one, scan via Ogmios - if (!utxo || !utxo.datum) { - const registryAddress = await getRegistryAddress(); - const allUtxos = await provider.getUtxos(registryAddress); - const ogmiosUtxo = allUtxos.find(u => u.assets[unit] && u.assets[unit] > 0n); - if (ogmiosUtxo) { - utxo = ogmiosUtxo; - } - } - - if (!utxo) throw new Error(`Agent not found: no UTxO holds NFT ${unit}. The agent may not exist or may have deregistered.`); - if (!utxo.datum) throw new Error('Registry UTxO found but has no inline datum.'); - const profile = parseAgentDatum(utxo.datum, `${utxo.txHash}#${utxo.outputIndex}`, utxo.assets); - if (!profile) throw new Error('Could not parse agent datum. The on-chain data may be malformed.'); - return { profile, utxo, nftUnit: unit }; -} - -function verifyOwnership(profile, walletVkeyHash) { - if (profile.ownerVkeyHash !== walletVkeyHash) { - throw new Error('Ownership check failed: your wallet does not own this agent. The agent\'s owner verification key does not match your wallet\'s payment key.'); - } -} - -// ─── Validation ────────────────────────────────────────────────────────────── - -function validateEndpoint(endpoint) { - if (!endpoint) return; // empty string is allowed - try { - new URL(endpoint); - } catch { - throw new Error(`Invalid endpoint URL: "${endpoint}". Must be a valid URL (e.g. https://example.com/api) or empty string.`); - } -} - -function validateCapabilities(capabilities) { - for (const cap of capabilities) { - if (typeof cap !== 'string' || cap.trim().length === 0) { - throw new Error('Each capability must be a non-empty string.'); - } - } -} - -// ─── Lucid Init ────────────────────────────────────────────────────────────── - -async function initLucid(mnemonic, accountIndex = 0) { - const provider = newProvider(); - const lucid = await Lucid(provider, 'Mainnet'); - if (!mnemonic) throw new Error('mnemonic is required'); - const trimmed = mnemonic.trim(); - const words = trimmed.split(/\s+/); - const validLengths = [12, 15, 18, 21, 24]; - if (!validLengths.includes(words.length)) { - throw new Error(`Invalid mnemonic: Expected 12, 15, 18, 21 or 24 words, got ${words.length}`); - } - lucid.selectWallet.fromSeed(trimmed, { accountIndex }); - - return lucid; -} - -// Cached registry address (derived from script hash, doesn't need a wallet) -let _registryAddress = null; -async function getRegistryAddress() { - if (_registryAddress) return _registryAddress; - const registryScript = { type: 'PlutusV3' as const, script: REGISTRY_SCRIPT_CBOR }; - _registryAddress = validatorToAddress('Mainnet', registryScript); - return _registryAddress; -} - -// ─── Tool Registration ─────────────────────────────────────────────────────── - -export function registerAgentNetworkTools(server, identity) { +export function registerAgentNetworkTools(server: McpServer, identity: string) { const rateLimiter = limiterFor(identity); // Rate limit wrapper - defined inside the register function (not at module @@ -167,12 +32,12 @@ export function registerAgentNetworkTools(server, identity) { function checkRateLimit() { const rateCheck = rateLimiter.check(); if (!rateCheck.allowed) { - return { content: [{ type: "text", text: `Rate limit exceeded. Retry after ${rateCheck.retryAfterMs}ms.` }] }; + return { content: [{ type: "text" as const, text: `Rate limit exceeded. Retry after ${rateCheck.retryAfterMs}ms.` }] }; } return null; } - // vector_get_agent_profile (read-only - no mnemonic needed) + // vector_get_agent_profile (read-only) — UNCHANGED behavior; body moves to typed helpers server.tool( "vector_get_agent_profile", "Get a registered agent's profile from the on-chain registry by DID (did:vector:agent:...)", @@ -186,11 +51,11 @@ export function registerAgentNetworkTools(server, identity) { const provider = newProvider(); const { profile } = await resolveAgentUtxo(provider, agent_id); const capList = profile.capabilities.length > 0 - ? profile.capabilities.map(c => `- ${c}`).join('\n') + ? profile.capabilities.map((c) => `- ${c}`).join('\n') : '- (none listed)'; return { content: [{ - type: "text", + type: "text" as const, text: `# Agent Profile **DID:** ${profile.agentId} @@ -209,8 +74,8 @@ ${capList} } catch (err) { return { content: [{ - type: "text", - text: `Failed to get agent profile: ${err.message} + type: "text" as const, + text: `Failed to get agent profile: ${err instanceof Error ? err.message : String(err)} **Troubleshooting Tips:** 1. Verify the DID format: did:vector:agent:{policyId}:{nftAssetName} @@ -222,7 +87,7 @@ ${capList} } ); - // vector_discover_agents (read-only - no mnemonic needed) + // vector_discover_agents (read-only) — UNCHANGED behavior server.tool( "vector_discover_agents", "Discover registered agents in the Vector on-chain registry, optionally filtered by capability or framework", @@ -235,37 +100,36 @@ ${capList} const rateLimited = checkRateLimit(); if (rateLimited) return rateLimited; try { - const registryAddress = await getRegistryAddress(); const provider = newProvider(); - const utxos = await provider.getUtxos(registryAddress); + const utxos = await provider.getUtxos(getRegistryAddress()); const profiles = []; for (const utxo of utxos) { if (!utxo.datum) continue; const profile = parseAgentDatum(utxo.datum, `${utxo.txHash}#${utxo.outputIndex}`, utxo.assets); if (!profile) continue; - if (capability && !profile.capabilities.some(c => c.toLowerCase().includes(capability.toLowerCase()))) continue; + if (capability && !profile.capabilities.some((c) => c.toLowerCase().includes(capability.toLowerCase()))) continue; if (framework && profile.framework.toLowerCase() !== framework.toLowerCase()) continue; profiles.push(profile); if (profiles.length >= (limit ?? 20)) break; } if (profiles.length === 0) { const filterDesc = [capability ? `capability="${capability}"` : '', framework ? `framework="${framework}"` : ''].filter(Boolean).join(', '); - return { content: [{ type: "text", text: `No agents found${filterDesc ? ` matching ${filterDesc}` : ''} in the registry.\n\nTotal UTxOs at registry: ${utxos.length}` }] }; + return { content: [{ type: "text" as const, text: `No agents found${filterDesc ? ` matching ${filterDesc}` : ''} in the registry.\n\nTotal UTxOs at registry: ${utxos.length}` }] }; } const agentList = profiles.map((p, i) => `### ${i + 1}. ${p.name}\n**DID:** ${p.agentId}\n**Description:** ${p.description}\n**Capabilities:** ${p.capabilities.join(', ') || '(none)'}\n**Framework:** ${p.framework || '(not specified)'}\n**Endpoint:** ${p.endpoint || '(not specified)'}\n**Registered:** ${new Date(p.registeredAt).toISOString()}` ).join('\n\n'); return { content: [{ - type: "text", + type: "text" as const, text: `# Agent Discovery Results\n\nFound **${profiles.length}** agent${profiles.length !== 1 ? 's' : ''}${capability ? ` with capability "${capability}"` : ''}${framework ? ` using framework "${framework}"` : ''}:\n\n${agentList}`, }], }; } catch (err) { return { content: [{ - type: "text", - text: `Failed to discover agents: ${err.message} + type: "text" as const, + text: `Failed to discover agents: ${err instanceof Error ? err.message : String(err)} **Troubleshooting Tips:** 1. Check that Ogmios is reachable at ${VECTOR_OGMIOS_URL} @@ -277,101 +141,66 @@ ${capList} } ); - // vector_register_agent + // vector_build_register_agent — KEYLESS server.tool( - "vector_register_agent", - "Register an agent in the Vector on-chain agent registry. Mints a soulbound identity NFT and locks a 10 AP3X deposit.", + "vector_build_register_agent", + "Build an UNSIGNED transaction that registers an agent in the Vector on-chain registry (mints a soulbound identity NFT, locks a 10 AP3X deposit). Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", { - mnemonic: z.string().describe("15 or 24-word BIP39 mnemonic for the registering wallet"), + changeAddress: z.string().describe("Your wallet address (source of the deposit and change). Get it from vector_signer_get_address."), name: z.string().min(1).max(64).describe("Agent name (e.g. 'TradingBot', 'ResearchAgent')"), description: z.string().max(256).describe("Short description of the agent's purpose"), capabilities: z.array(z.string()).describe("List of capability tags (e.g. ['investing', 'research', 'environmental'])"), framework: z.string().describe("Framework used (e.g. 'OpenClaw', 'LangChain', 'CrewAI', 'custom')"), endpoint: z.string().describe("A2A communication endpoint URL (or empty string if not applicable)"), }, - async ({ mnemonic, name, description, capabilities, framework, endpoint }) => { + async ({ changeAddress, name, description, capabilities, framework, endpoint }) => { const rateLimited = checkRateLimit(); if (rateLimited) return rateLimited; try { - validateEndpoint(endpoint); - validateCapabilities(capabilities); - - const safetyCheck = safetyLayer.checkTransaction(Number(MIN_AP3X_DEPOSIT)); - if (!safetyCheck.allowed) throw new Error(`Safety limit exceeded: ${safetyCheck.reason}`); - - const lucid = await initLucid(mnemonic); - const walletAddress = await lucid.wallet().address(); - const addressDetails = getAddressDetails(walletAddress); - const vkeyHash = addressDetails.paymentCredential?.hash; - if (!vkeyHash) throw new Error('Cannot derive payment key hash from wallet address'); - - const utxos = await lucid.utxosAt(await lucid.wallet().address()); - const oneShotUtxo = utxos.find(u => { - const keys = Object.keys(u.assets); - return keys.length === 1 && keys[0] === 'lovelace' && u.assets['lovelace'] >= MIN_AP3X_DEPOSIT + 2_000_000n; - }) || utxos[0]; - if (!oneShotUtxo) throw new Error('No UTxOs in wallet. Please fund the wallet first.'); - - const nftAssetName = deriveNftAssetName(oneShotUtxo.txHash, oneShotUtxo.outputIndex); - const nftUnit = `${REGISTRY_POLICY_ID}${nftAssetName}`; - const datum = buildAgentDatum(vkeyHash, name, description, capabilities, framework, endpoint); - const registryScript = { type: 'PlutusV3' as const, script: REGISTRY_SCRIPT_CBOR }; - const registryAddress = validatorToAddress('Mainnet', registryScript); - const registerRedeemer = Data.to(new Constr(0, [new Constr(0, [oneShotUtxo.txHash, BigInt(oneShotUtxo.outputIndex)])])); - - const tx = await lucid.newTx() - .collectFrom([oneShotUtxo]) - .mintAssets({ [nftUnit]: 1n }, registerRedeemer) - .attach.MintingPolicy(registryScript) - .pay.ToAddressWithData(registryAddress, { kind: "inline", value: datum }, { lovelace: MIN_AP3X_DEPOSIT, [nftUnit]: 1n }) - .addSigner(walletAddress) - .complete(); - const signedTx = await tx.sign.withWallet().complete(); - const txHash = await signedTx.submit(); - safetyLayer.recordTransaction(txHash, Number(MIN_AP3X_DEPOSIT), registryAddress); - - const agentId = `did:vector:agent:${REGISTRY_POLICY_ID}:${nftAssetName}`; + const lucid = await lucidForAddress(newProvider(), changeAddress); + const r = await buildRegisterAgent(lucid, { changeAddress, name, description, capabilities, framework, endpoint }); return { content: [{ - type: "text", - text: `# Agent Registered Successfully + type: "text" as const, + text: `# Unsigned Registration Built (Not Submitted) -**Agent DID:** ${agentId} +**Agent DID:** ${r.agentId} **Name:** ${name} -**NFT Asset Name:** ${nftAssetName} -**Registry Address:** ${registryAddress} -**Transaction:** ${txHash} -**Deposit:** ${lovelaceToAda(MIN_AP3X_DEPOSIT)} AP3X (returned on deregistration) +**NFT Asset Name:** ${r.nftAssetName} +**Registry Address:** ${r.registryAddress} +**Deposit:** ${lovelaceToAda(r.depositLovelace)} AP3X (returned on deregistration) +**Estimated Fee:** ${r.feeAda} AP3X -[View on Explorer](${explorerTxLink(txHash)}) +**Unsigned TX CBOR:** +\`\`\` +${r.txCbor} +\`\`\` -Save your Agent DID - you'll need it to update, deregister, or let other agents discover you.`, +The DID above is bound to the exact wallet UTxO this build consumed - if that UTxO is spent by another transaction first, rebuild to get a new DID. Your signer must allow ~${lovelaceToAda((MIN_AP3X_DEPOSIT + 500_000n).toString())} AP3X net outflow (deposit + fee). ${SIGN_AND_SUBMIT_COPY}`, }], }; } catch (err) { - const errMsg = err instanceof Error ? err.message : (typeof err === 'string' ? err : JSON.stringify(err)); return { content: [{ - type: "text", - text: `Failed to register agent: ${errMsg} + type: "text" as const, + text: `Failed to build agent registration: ${err instanceof Error ? err.message : String(err)} **Troubleshooting Tips:** -1. Ensure wallet has at least 12 AP3X (10 deposit + fees) -2. Server spend limits for this tool are set via VECTOR_SPEND_LIMIT_PER_TX / VECTOR_SPEND_LIMIT_DAILY -3. Each wallet can register multiple agents (different one-shot UTxOs) -4. Verify Ogmios endpoint is reachable at ${VECTOR_OGMIOS_URL}`, +1. Ensure the changeAddress wallet holds at least 12 AP3X (10 deposit + fees) +2. Each wallet can register multiple agents (different one-shot UTxOs) +3. Verify Ogmios endpoint is reachable at ${VECTOR_OGMIOS_URL}`, }], }; } } ); - // vector_update_agent + // vector_build_update_agent — KEYLESS server.tool( - "vector_update_agent", - "Update a registered agent's profile fields (name, description, capabilities, framework, endpoint). Only the specified fields are changed; others are preserved.", + "vector_build_update_agent", + "Build an UNSIGNED transaction that updates a registered agent's profile fields. Only the specified fields change; others are preserved. Takes no key material - the on-chain validator requires the owner's signature, which you add locally with vector_signer_sign before vector_submit_transaction, then confirm with vector_await_transaction.", { - mnemonic: z.string().describe("15 or 24-word BIP39 mnemonic for the agent's owner wallet"), + changeAddress: z.string().describe("The agent OWNER's wallet address. Get it from vector_signer_get_address."), agent_id: z.string().describe("Agent DID to update: did:vector:agent:{policyId}:{nftAssetName}"), name: z.string().min(1).max(64).optional().describe("New agent name"), description: z.string().max(256).optional().describe("New description"), @@ -379,300 +208,193 @@ Save your Agent DID - you'll need it to update, deregister, or let other agents framework: z.string().optional().describe("New framework identifier"), endpoint: z.string().optional().describe("New A2A endpoint URL (or empty string to clear)"), }, - async ({ mnemonic, agent_id, name, description, capabilities, framework, endpoint }) => { + async ({ changeAddress, agent_id, name, description, capabilities, framework, endpoint }) => { const rateLimited = checkRateLimit(); if (rateLimited) return rateLimited; try { - if (name === undefined && description === undefined && capabilities === undefined && framework === undefined && endpoint === undefined) { - throw new Error('At least one field must be provided to update (name, description, capabilities, framework, or endpoint).'); - } - if (endpoint !== undefined) validateEndpoint(endpoint); - if (capabilities !== undefined) validateCapabilities(capabilities); - - const lucid = await initLucid(mnemonic); - const walletAddress = await lucid.wallet().address(); - const vkeyHash = getAddressDetails(walletAddress).paymentCredential?.hash; - if (!vkeyHash) throw new Error('Cannot derive payment key hash from wallet address'); - const provider = newProvider(); - const { profile, utxo, nftUnit } = await resolveAgentUtxo(provider, agent_id); - verifyOwnership(profile, vkeyHash); - - // Merge: use new values where provided, keep old values otherwise - const newName = name ?? profile.name; - const newDesc = description ?? profile.description; - const newCaps = capabilities ?? profile.capabilities; - const newFramework = framework ?? profile.framework; - const newEndpoint = endpoint ?? profile.endpoint; - - const newDatum = buildAgentDatum(vkeyHash, newName, newDesc, newCaps, newFramework, newEndpoint, profile.registeredAt); - const registryScript = { type: 'PlutusV3' as const, script: REGISTRY_SCRIPT_CBOR }; - const registryAddress = validatorToAddress('Mainnet', registryScript); - const spendRedeemer = Data.to(new Constr(0, [])); // Update - - const tx = await lucid.newTx() - .collectFrom([utxo], spendRedeemer) - .attach.SpendingValidator(registryScript) - .pay.ToAddressWithData(registryAddress, { kind: "inline", value: newDatum }, { lovelace: MIN_AP3X_DEPOSIT, [nftUnit]: 1n }) - .addSigner(walletAddress) - .complete(); - const signedTx = await tx.sign.withWallet().complete(); - const txHash = await signedTx.submit(); - safetyLayer.recordTransaction(txHash, 0, registryAddress); - - const updatedFields = []; - if (name !== undefined) updatedFields.push('name'); - if (description !== undefined) updatedFields.push('description'); - if (capabilities !== undefined) updatedFields.push('capabilities'); - if (framework !== undefined) updatedFields.push('framework'); - if (endpoint !== undefined) updatedFields.push('endpoint'); - + const lucid = await lucidForAddress(provider, changeAddress); + const r = await buildUpdateAgent(lucid, provider, { changeAddress, agentId: agent_id, name, description, capabilities, framework, endpoint }); return { content: [{ - type: "text", - text: `# Agent Updated Successfully + type: "text" as const, + text: `# Unsigned Update Built (Not Submitted) **Agent DID:** ${agent_id} -**Updated fields:** ${updatedFields.join(', ')} -**Transaction:** ${txHash} +**Fields to update:** ${r.detail} +**Estimated Fee:** ${r.feeAda} AP3X -[View on Explorer](${explorerTxLink(txHash)}) +**Unsigned TX CBOR:** +\`\`\` +${r.txCbor} +\`\`\` -The agent's profile has been updated on-chain. The identity NFT and deposit are unchanged.`, +The identity NFT and deposit are unchanged by this update. ${SIGN_AND_SUBMIT_COPY}`, }], }; } catch (err) { return { content: [{ - type: "text", - text: `Failed to update agent: ${err.message} + type: "text" as const, + text: `Failed to build agent update: ${err instanceof Error ? err.message : String(err)} **Troubleshooting Tips:** 1. Verify the agent DID is correct -2. Your wallet must be the agent's current owner -3. Ensure wallet has enough AP3X for transaction fees -4. Server spend limits for this tool are set via VECTOR_SPEND_LIMIT_PER_TX / VECTOR_SPEND_LIMIT_DAILY`, +2. The changeAddress must be the agent's current owner wallet +3. Use vector_get_agent_profile to check the agent's current state`, }], }; } } ); - // vector_deregister_agent + // vector_build_transfer_agent — KEYLESS server.tool( - "vector_deregister_agent", - "Deregister an agent from the Vector registry. Burns the identity NFT and returns the 10 AP3X deposit to your wallet.", + "vector_build_transfer_agent", + "Build an UNSIGNED transaction that transfers agent ownership to a new address (must be a verification-key address, not a script). Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", { - mnemonic: z.string().describe("15 or 24-word BIP39 mnemonic for the agent's owner wallet"), - agent_id: z.string().describe("Agent DID to deregister: did:vector:agent:{policyId}:{nftAssetName}"), - }, - async ({ mnemonic, agent_id }) => { - const rateLimited = checkRateLimit(); - if (rateLimited) return rateLimited; - try { - const lucid = await initLucid(mnemonic); - const walletAddress = await lucid.wallet().address(); - const vkeyHash = getAddressDetails(walletAddress).paymentCredential?.hash; - if (!vkeyHash) throw new Error('Cannot derive payment key hash from wallet address'); - - const provider = newProvider(); - const { profile, utxo, nftUnit } = await resolveAgentUtxo(provider, agent_id); - verifyOwnership(profile, vkeyHash); - - const registryScript = { type: 'PlutusV3' as const, script: REGISTRY_SCRIPT_CBOR }; - const spendRedeemer = Data.to(new Constr(1, [])); // Deregister - const mintRedeemer = Data.to(new Constr(1, [])); // Burn - - const tx = await lucid.newTx() - .collectFrom([utxo], spendRedeemer) - .attach.SpendingValidator(registryScript) - .mintAssets({ [nftUnit]: -1n }, mintRedeemer) - .attach.MintingPolicy(registryScript) - .addSigner(walletAddress) - .complete(); - const signedTx = await tx.sign.withWallet().complete(); - const txHash = await signedTx.submit(); - safetyLayer.recordTransaction(txHash, 0, walletAddress); - - return { - content: [{ - type: "text", - text: `# Agent Deregistered Successfully - -**Agent DID:** ${agent_id} -**Name:** ${profile.name} -**Deposit returned:** ${lovelaceToAda(MIN_AP3X_DEPOSIT)} AP3X -**Transaction:** ${txHash} - -[View on Explorer](${explorerTxLink(txHash)}) - -The agent's identity NFT has been burned and the 10 AP3X deposit has been returned to your wallet.`, - }], - }; - } catch (err) { - return { - content: [{ - type: "text", - text: `Failed to deregister agent: ${err.message} - -**Troubleshooting Tips:** -1. Verify the agent DID is correct -2. Your wallet must be the agent's current owner -3. Ensure wallet has enough AP3X for transaction fees -4. Use vector_get_agent_profile to check the agent's current state`, - }], - }; - } - } - ); - - // vector_transfer_agent - server.tool( - "vector_transfer_agent", - "Transfer agent ownership to a new address. The new owner must have a verification key credential (not a script address).", - { - mnemonic: z.string().describe("15 or 24-word BIP39 mnemonic for the current owner's wallet"), + changeAddress: z.string().describe("The CURRENT owner's wallet address. Get it from vector_signer_get_address."), agent_id: z.string().describe("Agent DID to transfer: did:vector:agent:{policyId}:{nftAssetName}"), new_owner_address: z.string().describe("Bech32 address of the new owner (must be a verification key address, not a script address)"), }, - async ({ mnemonic, agent_id, new_owner_address }) => { + async ({ changeAddress, agent_id, new_owner_address }) => { const rateLimited = checkRateLimit(); if (rateLimited) return rateLimited; try { - const lucid = await initLucid(mnemonic); - const walletAddress = await lucid.wallet().address(); - const vkeyHash = getAddressDetails(walletAddress).paymentCredential?.hash; - if (!vkeyHash) throw new Error('Cannot derive payment key hash from wallet address'); - - // Validate new owner address - let newOwnerDetails; - try { - newOwnerDetails = getAddressDetails(new_owner_address); - } catch { - throw new Error(`Invalid new owner address: "${new_owner_address}". Must be a valid bech32 address.`); - } - if (newOwnerDetails.paymentCredential?.type !== 'Key') { - throw new Error('New owner address must be a verification key credential, not a script address. The on-chain contract rejects script credentials as owners.'); - } - const newOwnerVkeyHash = newOwnerDetails.paymentCredential.hash; - const provider = newProvider(); - const { profile, utxo, nftUnit } = await resolveAgentUtxo(provider, agent_id); - verifyOwnership(profile, vkeyHash); - - // Build datum with new owner, everything else preserved - const newDatum = buildAgentDatum( - newOwnerVkeyHash, profile.name, profile.description, - profile.capabilities, profile.framework, profile.endpoint, - profile.registeredAt - ); - const registryScript = { type: 'PlutusV3' as const, script: REGISTRY_SCRIPT_CBOR }; - const registryAddress = validatorToAddress('Mainnet', registryScript); - const spendRedeemer = Data.to(new Constr(0, [])); // Update (transfer uses Update redeemer) - - const tx = await lucid.newTx() - .collectFrom([utxo], spendRedeemer) - .attach.SpendingValidator(registryScript) - .pay.ToAddressWithData(registryAddress, { kind: "inline", value: newDatum }, { lovelace: MIN_AP3X_DEPOSIT, [nftUnit]: 1n }) - .addSigner(walletAddress) - .complete(); - const signedTx = await tx.sign.withWallet().complete(); - const txHash = await signedTx.submit(); - safetyLayer.recordTransaction(txHash, 0, registryAddress); - + const lucid = await lucidForAddress(provider, changeAddress); + const r = await buildTransferAgent(lucid, provider, { changeAddress, agentId: agent_id, newOwnerAddress: new_owner_address }); return { content: [{ - type: "text", - text: `# Agent Transferred Successfully + type: "text" as const, + text: `# Unsigned Transfer Built (Not Submitted) **Agent DID:** ${agent_id} -**Name:** ${profile.name} -**Previous owner:** ${walletAddress} +**Name:** ${r.agentName} +**Current owner (changeAddress):** ${changeAddress} **New owner:** ${new_owner_address} -**Transaction:** ${txHash} +**Estimated Fee:** ${r.feeAda} AP3X -[View on Explorer](${explorerTxLink(txHash)}) +**Unsigned TX CBOR:** +\`\`\` +${r.txCbor} +\`\`\` -Ownership has been transferred. The new owner can now update, transfer, or deregister this agent.`, +After this transaction confirms, only the new owner can update, transfer, or deregister the agent. ${SIGN_AND_SUBMIT_COPY}`, }], }; } catch (err) { return { content: [{ - type: "text", - text: `Failed to transfer agent: ${err.message} + type: "text" as const, + text: `Failed to build agent transfer: ${err instanceof Error ? err.message : String(err)} **Troubleshooting Tips:** 1. Verify the agent DID is correct -2. Your wallet must be the agent's current owner -3. The new owner address must be a verification key address (not a script) -4. Ensure wallet has enough AP3X for transaction fees`, +2. The changeAddress must be the agent's current owner wallet +3. The new owner address must be a verification key address (not a script)`, }], }; } } ); - // vector_message_agent + // vector_build_deregister_agent — KEYLESS server.tool( - "vector_message_agent", - "Send an on-chain message to a registered Vector agent via TX metadata (label 674). Delivers 2 AP3X to the agent's owner address.", + "vector_build_deregister_agent", + "Build an UNSIGNED transaction that deregisters an agent (burns the identity NFT, returns the 10 AP3X deposit to the owner wallet). Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", { - agent_id: z.string().describe("Recipient agent DID: did:vector:agent:{policyId}:{nftAssetName}"), - message_type: z.enum(["inquiry", "proposal", "result"]).describe("Type of message"), - payload: z.string().max(512).describe("Message payload (string, max 512 chars)"), - mnemonic: z.string().describe("15 or 24-word BIP39 mnemonic for the sending wallet"), + changeAddress: z.string().describe("The agent OWNER's wallet address (receives the returned deposit). Get it from vector_signer_get_address."), + agent_id: z.string().describe("Agent DID to deregister: did:vector:agent:{policyId}:{nftAssetName}"), }, - async ({ agent_id, message_type, payload, mnemonic }) => { + async ({ changeAddress, agent_id }) => { const rateLimited = checkRateLimit(); if (rateLimited) return rateLimited; try { const provider = newProvider(); - const { profile } = await resolveAgentUtxo(provider, agent_id); - if (!profile.ownerVkeyHash) throw new Error('Could not parse agent owner from registry datum'); - - const lucid = await initLucid(mnemonic); - const senderAddress = await lucid.wallet().address(); - const recipientAddress = credentialToAddress('Mainnet', { type: 'Key', hash: profile.ownerVkeyHash }); - const minAda = 2_000_000n; - const safetyCheck = safetyLayer.checkTransaction(Number(minAda)); - if (!safetyCheck.allowed) throw new Error(`Safety limit exceeded: ${safetyCheck.reason}`); - - const tx = await lucid.newTx() - .pay.ToAddress(recipientAddress, { lovelace: minAda }) - .attachMetadata(AGENT_MESSAGE_LABEL, { msg: ['a2a'], from: metadataStr(senderAddress), to: metadataStr(agent_id), type: message_type, payload: metadataStr(payload) }) - .complete(); - const signedTx = await tx.sign.withWallet().complete(); - const txHash = await signedTx.submit(); - safetyLayer.recordTransaction(txHash, Number(minAda), recipientAddress); - + const lucid = await lucidForAddress(provider, changeAddress); + const r = await buildDeregisterAgent(lucid, provider, { changeAddress, agentId: agent_id }); return { content: [{ - type: "text", - text: `# Message Sent + type: "text" as const, + text: `# Unsigned Deregistration Built (Not Submitted) -**To:** ${profile.name} (${agent_id}) -**Type:** ${message_type} -**Payload:** ${payload} -**Recipient Address:** ${recipientAddress} -**Transaction:** ${txHash} +**Agent DID:** ${agent_id} +**Name:** ${r.agentName} +**Deposit returned on confirmation:** ${lovelaceToAda(r.detail)} AP3X +**Estimated Fee:** ${r.feeAda} AP3X -[View on Explorer](${explorerTxLink(txHash)}) +**Unsigned TX CBOR:** +\`\`\` +${r.txCbor} +\`\`\` -The message is recorded on-chain in TX metadata label 674.`, +The identity NFT is burned by this transaction; the deposit returns to the changeAddress as change. ${SIGN_AND_SUBMIT_COPY}`, }], }; } catch (err) { return { content: [{ - type: "text", - text: `Failed to send message: ${err.message} + type: "text" as const, + text: `Failed to build agent deregistration: ${err instanceof Error ? err.message : String(err)} + +**Troubleshooting Tips:** +1. Verify the agent DID is correct +2. The changeAddress must be the agent's current owner wallet +3. Use vector_get_agent_profile to check the agent's current state +4. If the error mentions needing a plain AP3X-only UTxO: the wallet must hold at least one token-free UTxO; send yourself a small AP3X payment first.`, + }], + }; + } + } + ); + + // vector_build_message_agent — KEYLESS + server.tool( + "vector_build_message_agent", + "Build an UNSIGNED transaction that sends an on-chain message to a registered agent via TX metadata (label 674), delivering 2 AP3X to the agent's owner address. Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", + { + changeAddress: z.string().describe("Your wallet address (pays the 2 AP3X delivery and fee; recorded as the sender). Get it from vector_signer_get_address."), + agent_id: z.string().describe("Recipient agent DID: did:vector:agent:{policyId}:{nftAssetName}"), + message_type: z.enum(["inquiry", "proposal", "result"]).describe("Type of message"), + payload: z.string().max(512).describe("Message payload (string, max 512 chars)"), + }, + async ({ changeAddress, agent_id, message_type, payload }) => { + const rateLimited = checkRateLimit(); + if (rateLimited) return rateLimited; + try { + const provider = newProvider(); + const lucid = await lucidForAddress(provider, changeAddress); + const r = await buildMessageAgent(lucid, provider, { changeAddress, agentId: agent_id, messageType: message_type, payload }); + return { + content: [{ + type: "text" as const, + text: `# Unsigned Message Built (Not Submitted) + +**To:** ${r.agentName} (${agent_id}) +**Type:** ${message_type} +**Payload:** ${payload} +**Recipient Address:** ${r.recipientAddress} +**Delivery:** 2 AP3X + ${r.feeAda} AP3X fee + +**Unsigned TX CBOR:** +\`\`\` +${r.txCbor} +\`\`\` + +The message will be recorded on-chain in TX metadata label 674 once submitted. ${SIGN_AND_SUBMIT_COPY}`, + }], + }; + } catch (err) { + return { + content: [{ + type: "text" as const, + text: `Failed to build agent message: ${err instanceof Error ? err.message : String(err)} **Troubleshooting Tips:** 1. Verify the agent DID is correct: did:vector:agent:{policyId}:{nftAssetName} -2. Ensure wallet has at least 3 AP3X (2 AP3X delivery + fees) -3. Use vector_get_agent_profile to verify the agent exists first -4. Server spend limits for this tool are set via VECTOR_SPEND_LIMIT_PER_TX / VECTOR_SPEND_LIMIT_DAILY`, +2. Ensure the changeAddress wallet holds at least 3 AP3X (2 AP3X delivery + fees) +3. Use vector_get_agent_profile to verify the agent exists first`, }], }; } diff --git a/packages/builder/src/vector/self-improvement.ts b/packages/builder/src/vector/self-improvement.ts index 91afc19..889e793 100644 --- a/packages/builder/src/vector/self-improvement.ts +++ b/packages/builder/src/vector/self-improvement.ts @@ -628,7 +628,7 @@ Each batch UTxO holds ~30 AP3X for adoption rewards.`, } catch { throw new Error( `Agent registry NFT not found on-chain. Expected token: ${AGENT_REGISTRY_POLICY.slice(0, 12)}...${agentDid.slice(0, 12)}... ` + - `The agent may need to re-register with vector_register_agent.` + `The agent may need to re-register with vector_build_register_agent.` ); } diff --git a/packages/builder/test/boundary.test.ts b/packages/builder/test/boundary.test.ts index a7f3ec3..9531a36 100644 --- a/packages/builder/test/boundary.test.ts +++ b/packages/builder/test/boundary.test.ts @@ -14,8 +14,7 @@ import { resolve, join, relative } from 'node:path'; const SRC_ROOT = resolve(import.meta.dirname!, '../src'); const SCRIPTS_ROOT = resolve(import.meta.dirname!, '../scripts'); const CUSTODIAL_UNTIL_LATER_PR = new Set([ - 'vector/agent-network.ts', // family 2 — goes keyless in spec PR 7 - 'vector/self-improvement.ts', // family 3 — goes keyless in spec PR 8 + 'vector/self-improvement.ts', // family 3 (governance) — goes keyless in spec PR 8 ]); // No \b boundaries: this is deliberately a substring match, not a whole-word // one. Word-bounding let `seedPhrase`, `WALLET_SEED` or `bip39x` slip past @@ -70,12 +69,12 @@ describe('builder custody boundary', () => { } }); - // The allowlist may only shrink (PR 7 drops agent-network.ts to reach 1, PR 8 - // drops self-improvement.ts to reach 0 - see the "goes keyless in spec PR N" - // comments above). Pinning the size means any widening is a deliberate, + // The allowlist may only shrink (PR 7 dropped agent-network.ts to reach 1, + // PR 8 drops self-improvement.ts to reach 0 - see the "goes keyless in spec + // PR N" comment above). Pinning the size means any widening is a deliberate, // reviewable number change instead of a silent `.add(...)` slipping through. test('the allowlist may only shrink', () => { - assert.equal(CUSTODIAL_UNTIL_LATER_PR.size, 2, 'the custodial allowlist may only shrink - PR 7 takes it to 1 (drop agent-network), PR 8 to 0'); + assert.equal(CUSTODIAL_UNTIL_LATER_PR.size, 1, 'the custodial allowlist may only shrink - spec PR 8 takes it to 0'); }); // Pins every FORBIDDEN alternative individually against a fixture corpus, so diff --git a/packages/builder/test/smoke/tool-inventory.test.ts b/packages/builder/test/smoke/tool-inventory.test.ts index 78e5099..ee24358 100644 --- a/packages/builder/test/smoke/tool-inventory.test.ts +++ b/packages/builder/test/smoke/tool-inventory.test.ts @@ -21,27 +21,27 @@ import { startServer, stopServer, ServerContext } from '../setup.ts'; const EXPECTED_TOOLS = [ 'vector_await_transaction', 'vector_build_deploy_contract', + 'vector_build_deregister_agent', 'vector_build_interact_contract', + 'vector_build_message_agent', + 'vector_build_register_agent', 'vector_build_send_apex', 'vector_build_send_tokens', 'vector_build_transaction', - 'vector_deregister_agent', + 'vector_build_transfer_agent', + 'vector_build_update_agent', 'vector_discover_agents', 'vector_dry_run', 'vector_get_agent_profile', 'vector_get_balance', 'vector_get_transaction_history', 'vector_get_utxos', - 'vector_message_agent', - 'vector_register_agent', 'vector_self_improvement_analyze_metrics', 'vector_self_improvement_browse', 'vector_self_improvement_critique', 'vector_self_improvement_endorse', 'vector_self_improvement_submit_proposal', 'vector_submit_transaction', - 'vector_transfer_agent', - 'vector_update_agent', ]; /** diff --git a/packages/builder/test/smoke/tool-schemas.snapshot.json b/packages/builder/test/smoke/tool-schemas.snapshot.json index fa1871c..7d30593 100644 --- a/packages/builder/test/smoke/tool-schemas.snapshot.json +++ b/packages/builder/test/smoke/tool-schemas.snapshot.json @@ -60,6 +60,27 @@ "$schema": "http://json-schema.org/draft-07/schema#" } }, + "vector_build_deregister_agent": { + "description": "Build an UNSIGNED transaction that deregisters an agent (burns the identity NFT, returns the 10 AP3X deposit to the owner wallet). Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", + "inputSchema": { + "type": "object", + "properties": { + "changeAddress": { + "description": "The agent OWNER's wallet address (receives the returned deposit). Get it from vector_signer_get_address.", + "type": "string" + }, + "agent_id": { + "description": "Agent DID to deregister: did:vector:agent:{policyId}:{nftAssetName}", + "type": "string" + } + }, + "required": [ + "changeAddress", + "agent_id" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, "vector_build_interact_contract": { "description": "Build an UNSIGNED transaction interacting with a deployed Plutus/Aiken contract - lock AP3X at it or spend from it. Takes no key material - sign with your local signer, then broadcast with vector_submit_transaction and confirm with vector_await_transaction.", "inputSchema": { @@ -138,6 +159,90 @@ "$schema": "http://json-schema.org/draft-07/schema#" } }, + "vector_build_message_agent": { + "description": "Build an UNSIGNED transaction that sends an on-chain message to a registered agent via TX metadata (label 674), delivering 2 AP3X to the agent's owner address. Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", + "inputSchema": { + "type": "object", + "properties": { + "changeAddress": { + "description": "Your wallet address (pays the 2 AP3X delivery and fee; recorded as the sender). Get it from vector_signer_get_address.", + "type": "string" + }, + "agent_id": { + "description": "Recipient agent DID: did:vector:agent:{policyId}:{nftAssetName}", + "type": "string" + }, + "message_type": { + "description": "Type of message", + "type": "string", + "enum": [ + "inquiry", + "proposal", + "result" + ] + }, + "payload": { + "description": "Message payload (string, max 512 chars)", + "type": "string", + "maxLength": 512 + } + }, + "required": [ + "changeAddress", + "agent_id", + "message_type", + "payload" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + "vector_build_register_agent": { + "description": "Build an UNSIGNED transaction that registers an agent in the Vector on-chain registry (mints a soulbound identity NFT, locks a 10 AP3X deposit). Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", + "inputSchema": { + "type": "object", + "properties": { + "changeAddress": { + "description": "Your wallet address (source of the deposit and change). Get it from vector_signer_get_address.", + "type": "string" + }, + "name": { + "description": "Agent name (e.g. 'TradingBot', 'ResearchAgent')", + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "description": { + "description": "Short description of the agent's purpose", + "type": "string", + "maxLength": 256 + }, + "capabilities": { + "description": "List of capability tags (e.g. ['investing', 'research', 'environmental'])", + "type": "array", + "items": { + "type": "string" + } + }, + "framework": { + "description": "Framework used (e.g. 'OpenClaw', 'LangChain', 'CrewAI', 'custom')", + "type": "string" + }, + "endpoint": { + "description": "A2A communication endpoint URL (or empty string if not applicable)", + "type": "string" + } + }, + "required": [ + "changeAddress", + "name", + "description", + "capabilities", + "framework", + "endpoint" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, "vector_build_send_apex": { "description": "Build an UNSIGNED transaction sending AP3X to a recipient. Takes no key material - sign the returned CBOR with your local signer (vector_signer_sign), then broadcast with vector_submit_transaction and confirm with vector_await_transaction.", "inputSchema": { @@ -262,22 +367,74 @@ "$schema": "http://json-schema.org/draft-07/schema#" } }, - "vector_deregister_agent": { - "description": "Deregister an agent from the Vector registry. Burns the identity NFT and returns the 10 AP3X deposit to your wallet.", + "vector_build_transfer_agent": { + "description": "Build an UNSIGNED transaction that transfers agent ownership to a new address (must be a verification-key address, not a script). Takes no key material. Sign locally with vector_signer_sign, broadcast with vector_submit_transaction, then confirm with vector_await_transaction.", "inputSchema": { "type": "object", "properties": { - "mnemonic": { - "description": "15 or 24-word BIP39 mnemonic for the agent's owner wallet", + "changeAddress": { + "description": "The CURRENT owner's wallet address. Get it from vector_signer_get_address.", "type": "string" }, "agent_id": { - "description": "Agent DID to deregister: did:vector:agent:{policyId}:{nftAssetName}", + "description": "Agent DID to transfer: did:vector:agent:{policyId}:{nftAssetName}", + "type": "string" + }, + "new_owner_address": { + "description": "Bech32 address of the new owner (must be a verification key address, not a script address)", "type": "string" } }, "required": [ - "mnemonic", + "changeAddress", + "agent_id", + "new_owner_address" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + } + }, + "vector_build_update_agent": { + "description": "Build an UNSIGNED transaction that updates a registered agent's profile fields. Only the specified fields change; others are preserved. Takes no key material - the on-chain validator requires the owner's signature, which you add locally with vector_signer_sign before vector_submit_transaction, then confirm with vector_await_transaction.", + "inputSchema": { + "type": "object", + "properties": { + "changeAddress": { + "description": "The agent OWNER's wallet address. Get it from vector_signer_get_address.", + "type": "string" + }, + "agent_id": { + "description": "Agent DID to update: did:vector:agent:{policyId}:{nftAssetName}", + "type": "string" + }, + "name": { + "description": "New agent name", + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "description": { + "description": "New description", + "type": "string", + "maxLength": 256 + }, + "capabilities": { + "description": "New capability tags (replaces existing list)", + "type": "array", + "items": { + "type": "string" + } + }, + "framework": { + "description": "New framework identifier", + "type": "string" + }, + "endpoint": { + "description": "New A2A endpoint URL (or empty string to clear)", + "type": "string" + } + }, + "required": [ + "changeAddress", "agent_id" ], "$schema": "http://json-schema.org/draft-07/schema#" @@ -431,90 +588,6 @@ "$schema": "http://json-schema.org/draft-07/schema#" } }, - "vector_message_agent": { - "description": "Send an on-chain message to a registered Vector agent via TX metadata (label 674). Delivers 2 AP3X to the agent's owner address.", - "inputSchema": { - "type": "object", - "properties": { - "agent_id": { - "description": "Recipient agent DID: did:vector:agent:{policyId}:{nftAssetName}", - "type": "string" - }, - "message_type": { - "description": "Type of message", - "type": "string", - "enum": [ - "inquiry", - "proposal", - "result" - ] - }, - "payload": { - "description": "Message payload (string, max 512 chars)", - "type": "string", - "maxLength": 512 - }, - "mnemonic": { - "description": "15 or 24-word BIP39 mnemonic for the sending wallet", - "type": "string" - } - }, - "required": [ - "agent_id", - "message_type", - "payload", - "mnemonic" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - } - }, - "vector_register_agent": { - "description": "Register an agent in the Vector on-chain agent registry. Mints a soulbound identity NFT and locks a 10 AP3X deposit.", - "inputSchema": { - "type": "object", - "properties": { - "mnemonic": { - "description": "15 or 24-word BIP39 mnemonic for the registering wallet", - "type": "string" - }, - "name": { - "description": "Agent name (e.g. 'TradingBot', 'ResearchAgent')", - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "description": { - "description": "Short description of the agent's purpose", - "type": "string", - "maxLength": 256 - }, - "capabilities": { - "description": "List of capability tags (e.g. ['investing', 'research', 'environmental'])", - "type": "array", - "items": { - "type": "string" - } - }, - "framework": { - "description": "Framework used (e.g. 'OpenClaw', 'LangChain', 'CrewAI', 'custom')", - "type": "string" - }, - "endpoint": { - "description": "A2A communication endpoint URL (or empty string if not applicable)", - "type": "string" - } - }, - "required": [ - "mnemonic", - "name", - "description", - "capabilities", - "framework", - "endpoint" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - } - }, "vector_self_improvement_analyze_metrics": { "description": "Analyze proposal metrics: proposal activity, adoption rate, treasury health, and engagement statistics. Read-only - no mnemonic needed.", "inputSchema": { @@ -771,78 +844,5 @@ ], "$schema": "http://json-schema.org/draft-07/schema#" } - }, - "vector_transfer_agent": { - "description": "Transfer agent ownership to a new address. The new owner must have a verification key credential (not a script address).", - "inputSchema": { - "type": "object", - "properties": { - "mnemonic": { - "description": "15 or 24-word BIP39 mnemonic for the current owner's wallet", - "type": "string" - }, - "agent_id": { - "description": "Agent DID to transfer: did:vector:agent:{policyId}:{nftAssetName}", - "type": "string" - }, - "new_owner_address": { - "description": "Bech32 address of the new owner (must be a verification key address, not a script address)", - "type": "string" - } - }, - "required": [ - "mnemonic", - "agent_id", - "new_owner_address" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - } - }, - "vector_update_agent": { - "description": "Update a registered agent's profile fields (name, description, capabilities, framework, endpoint). Only the specified fields are changed; others are preserved.", - "inputSchema": { - "type": "object", - "properties": { - "mnemonic": { - "description": "15 or 24-word BIP39 mnemonic for the agent's owner wallet", - "type": "string" - }, - "agent_id": { - "description": "Agent DID to update: did:vector:agent:{policyId}:{nftAssetName}", - "type": "string" - }, - "name": { - "description": "New agent name", - "type": "string", - "minLength": 1, - "maxLength": 64 - }, - "description": { - "description": "New description", - "type": "string", - "maxLength": 256 - }, - "capabilities": { - "description": "New capability tags (replaces existing list)", - "type": "array", - "items": { - "type": "string" - } - }, - "framework": { - "description": "New framework identifier", - "type": "string" - }, - "endpoint": { - "description": "New A2A endpoint URL (or empty string to clear)", - "type": "string" - } - }, - "required": [ - "mnemonic", - "agent_id" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - } } }