import { execFile } from "node:child_process"; import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; const execFileAsync = promisify(execFile); const PLUGIN_ID = "agent-wallet"; const PLUGIN_ROOT = path.dirname(new URL(import.meta.url).pathname); let selectedWalletBackend = null; let selectedSolanaNetwork = null; let selectedEvmNetwork = null; let selectedBtcNetwork = null; const PREVIEW_CACHE_TTL_MS = 15 * 60 * 1000; const PREVIEW_BOUND_SWAP_TOOLS = new Set([ "swap_solana_tokens", "flash_trade_open_position", "flash_trade_close_position", ]); const EVM_CORE_NETWORKS = ["ethereum", "base", "robinhood"]; const AUTONOMOUS_BASE_SWAP_TOOLS = new Set([ "swap_evm_tokens", "swap_evm_uniswap_tokens", ]); const AUTONOMOUS_DEFI_TOOLS = new Set([ "manage_evm_aave_position", "manage_evm_lido_position", "manage_evm_lido_withdrawal", "manage_evm_morpho_market_position", "manage_evm_morpho_vault_position", ]); const approvalPreviewCache = new Map(); const WALLET_TOOL_ONLY_GUIDANCE = "Use this wallet tool instead of shelling out to solana CLI, spl-token CLI, curl, or exec. If it fails, surface the wallet-tool error and stop rather than falling back to terminal commands."; const OPENCLAW_EXECUTE_APPROVAL_GUIDANCE = "Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute with the same semantic params; the OpenClaw plugin handles the internal execution authorization automatically."; const APPROVAL_CONTEXT_MISSING_MESSAGE = `Confirmation context is not ready or expired. ${OPENCLAW_EXECUTE_APPROVAL_GUIDANCE} Do not ask for /approve, buttons, popups, or a manual token.`; const APPROVAL_PREVIEW_TOOL_ALIASES = new Map([ ["x402_pay_request", "x402_preview_request"], ]); function canonicalJsonText(payload) { const normalize = (value) => { if (Array.isArray(value)) { return value.map(normalize); } if (value && typeof value === "object") { return Object.fromEntries( Object.keys(value) .sort() .map((key) => [key, normalize(value[key])]) ); } return value; }; return JSON.stringify(normalize(payload)); } function previewDigest(preview) { return crypto.createHash("sha256").update(canonicalJsonText(preview), "utf8").digest("hex"); } function approvalCacheKey(userId, toolName) { return `${userId}::${toolName}`; } function approvalPreviewToolName(toolName) { const normalized = typeof toolName === "string" ? toolName.trim() : ""; return APPROVAL_PREVIEW_TOOL_ALIASES.get(normalized) || normalized; } function pruneApprovalPreviewCache() { const now = Date.now(); for (const [key, value] of approvalPreviewCache.entries()) { if (!value || typeof value !== "object" || Number(value.expiresAt || 0) <= now) { approvalPreviewCache.delete(key); } } } function cachePreviewForApproval(userId, toolName, payload) { const cacheToolName = approvalPreviewToolName(toolName); if (!payload || payload.ok !== true || !payload.data || typeof payload.data !== "object") return; const approvalSource = payload.data; if (!["preview", "prepare", "intent_preview"].includes(String(approvalSource.mode || ""))) return; if (!approvalSource.confirmation_summary || typeof approvalSource.confirmation_summary !== "object") return; pruneApprovalPreviewCache(); const digest = previewDigest(approvalSource); approvalPreviewCache.set(approvalCacheKey(userId, cacheToolName), { digest, expiresAt: Date.now() + PREVIEW_CACHE_TTL_MS, preview: approvalSource, summary: approvalSource.confirmation_summary, }); } function latestCachedPreview(userId, toolName) { pruneApprovalPreviewCache(); return approvalPreviewCache.get(approvalCacheKey(userId, approvalPreviewToolName(toolName))) || null; } function approvalTokenPreviewDigest(token) { if (typeof token !== "string" || !token.includes(".")) return ""; try { const encoded = token.split(".", 1)[0]; const payload = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")); const summary = payload?.binding?.summary; return summary && typeof summary._preview_digest === "string" ? summary._preview_digest : ""; } catch { return ""; } } function cachedPreviewForToken(userId, toolName, token) { const digest = approvalTokenPreviewDigest(token); if (!digest) return null; const cached = latestCachedPreview(userId, toolName); if (!cached || cached.digest !== digest) return null; return cached.preview && typeof cached.preview === "object" ? cached.preview : null; } function isSolanaSwapIntentPayload(payload) { return ( payload && typeof payload === "object" && (String(payload.asset_type || "") === "solana-swap-intent" || String(payload.mode || "") === "intent_preview") ); } function isSolanaSwapIntentExecute(params) { return String(params?.mode || "") === "intent_execute"; } function requiresApprovedPreviewPayload(toolName, params = null) { if (toolName === "swap_solana_tokens" && isSolanaSwapIntentExecute(params)) return false; return PREVIEW_BOUND_SWAP_TOOLS.has(toolName); } function shouldLetBackendAuthorizeAutonomousExecution(toolName, params, config) { const isBaseSwapTool = AUTONOMOUS_BASE_SWAP_TOOLS.has(toolName); const isDefiTool = AUTONOMOUS_DEFI_TOOLS.has(toolName); if (!isBaseSwapTool && !isDefiTool) return false; if (String(params?.mode || "") !== "execute") return false; if (typeof params?.approval_token === "string" && params.approval_token.trim()) return false; const network = String(params?.network || config?.network || selectedEvmNetwork || "").trim().toLowerCase(); if (isBaseSwapTool) return network === "base"; return network === "base" || network === "ethereum"; } function looksLikeApprovalContextError(message) { const text = String(message || "").toLowerCase(); return ( text.includes("approval_token") || text.includes("approval token") || text.includes("approval context") || text.includes("approved preview") || text.includes("preview payload") || text.includes("previewed operation") ); } function normalizeApprovalContextError(error) { const message = String(error?.message || error || ""); if (!looksLikeApprovalContextError(message)) return error; const wrapped = new Error(`${APPROVAL_CONTEXT_MISSING_MESSAGE} Original wallet error: ${message}`); if (typeof error?.code === "string") wrapped.code = error.code; if (error?.details && typeof error.details === "object") wrapped.details = error.details; return wrapped; } function resolvePluginConfig(api) { const globalConfig = api?.config ?? {}; const pluginEntry = globalConfig?.plugins?.entries?.[PLUGIN_ID]; return pluginEntry?.config ?? globalConfig?.config ?? {}; } function resolveUserId(api, config) { return ( config.userId || process.env.OPENCLAW_AGENT_WALLET_USER_ID || process.env.USER || "openclaw-main" ); } function resolveBackend(api) { const config = resolvePluginConfig(api); return normalizeWalletBackend(config.backend || process.env.AGENT_WALLET_BACKEND || "solana_local"); } function normalizeWalletBackend(value) { const normalized = String(value || "").trim().toLowerCase(); const aliases = { sol: "solana_local", solana: "solana_local", solana_local: "solana_local", "solana-local": "solana_local", evm: "wdk_evm_local", ethereum: "wdk_evm_local", eth: "wdk_evm_local", base: "wdk_evm_local", robinhood: "wdk_evm_local", wdk_evm_local: "wdk_evm_local", "wdk-evm-local": "wdk_evm_local", evm_local: "wdk_evm_local", "evm-local": "wdk_evm_local", btc: "wdk_btc_local", bitcoin: "wdk_btc_local", wdk_btc_local: "wdk_btc_local", "wdk-btc-local": "wdk_btc_local", btc_local: "wdk_btc_local", "btc-local": "wdk_btc_local", }; const backend = aliases[normalized] || normalized; if (!["solana_local", "wdk_evm_local", "wdk_btc_local"].includes(backend)) { throw new Error("Wallet backend must be solana, evm, ethereum, base, robinhood, btc, or bitcoin."); } return backend; } function backendLabel(backend) { if (backend === "wdk_evm_local") return "evm"; if (backend === "wdk_btc_local") return "bitcoin"; return "solana"; } function normalizeEvmNetwork(value) { const normalized = String(value || "").trim().toLowerCase(); const aliases = { mainnet: "ethereum", eth: "ethereum", "eth-mainnet": "ethereum", "base-mainnet": "base", }; return aliases[normalized] || normalized; } function normalizeSelectableEvmNetwork(value) { const network = normalizeEvmNetwork(value); if (["sepolia", "base-sepolia", "base_sepolia"].includes(network)) { throw new Error("EVM testnets are no longer supported. Use ethereum, base, or robinhood."); } if (!EVM_CORE_NETWORKS.includes(network)) { throw new Error("EVM network must be 'ethereum', 'base', or 'robinhood'."); } return network; } function normalizeSolanaNetwork(value) { const network = String(value || "").trim().toLowerCase(); if (!network) return null; const aliases = { solana: "mainnet", "solana-mainnet": "mainnet", mainnet_beta: "mainnet", "mainnet-beta": "mainnet", }; const normalized = aliases[network] || network; if (["devnet", "testnet"].includes(normalized)) { throw new Error("Solana devnet/testnet are no longer supported. Use mainnet."); } if (normalized !== "mainnet") { throw new Error("Solana network must be mainnet."); } return normalized; } function normalizeBtcNetwork(value) { const network = String(value || "").trim().toLowerCase(); if (!network) return null; const aliases = { btc: "bitcoin", bitcoin_mainnet: "bitcoin", "bitcoin-mainnet": "bitcoin", mainnet: "bitcoin", }; const normalized = aliases[network] || network; if (["testnet", "regtest"].includes(normalized)) { throw new Error("Bitcoin testnet/regtest are no longer supported. Use bitcoin."); } if (normalized !== "bitcoin") { throw new Error("Bitcoin network must be bitcoin."); } return normalized; } function defaultSelectableEvmNetwork(api) { const config = resolvePluginConfig(api); const configured = normalizeEvmNetwork(config.network || process.env.WDK_EVM_NETWORK); return EVM_CORE_NETWORKS.includes(configured) ? configured : null; } function defaultSolanaNetwork(api) { const config = resolvePluginConfig(api); try { return normalizeSolanaNetwork(config.network || process.env.SOLANA_NETWORK) || "mainnet"; } catch { return "mainnet"; } } function defaultBtcNetwork(api) { const config = resolvePluginConfig(api); try { return normalizeBtcNetwork(config.network || process.env.WDK_BTC_NETWORK) || "bitcoin"; } catch { return "bitcoin"; } } function inferBackendForTool(toolName) { if ( toolName.startsWith("get_evm_") || toolName.startsWith("manage_evm_") || toolName.startsWith("swap_evm_") || toolName.startsWith("transfer_evm_") || toolName === "set_evm_network" ) { return "wdk_evm_local"; } if (toolName.startsWith("get_btc_") || toolName === "transfer_btc") { return "wdk_btc_local"; } if ( toolName.includes("solana") || toolName.includes("jupiter") || toolName.includes("kamino") || toolName.includes("bags") || toolName === "transfer_sol" || toolName === "transfer_spl_token" || toolName === "sign_wallet_message" || toolName === "close_empty_token_accounts" || toolName === "get_wallet_portfolio" || toolName === "get_solana_token_prices" ) { return "solana_local"; } return null; } function activeBackendForTool(api, toolName) { return selectedWalletBackend || inferBackendForTool(toolName) || resolveBackend(api); } function networkForBackend(api, backend) { const config = resolvePluginConfig(api); if (backend === "wdk_evm_local") { return selectedEvmNetwork || defaultSelectableEvmNetwork(api) || "ethereum"; } if (backend === "wdk_btc_local") { try { return selectedBtcNetwork || defaultBtcNetwork(api); } catch { return "bitcoin"; } } try { return ( selectedSolanaNetwork || normalizeSolanaNetwork(config.network || process.env.SOLANA_NETWORK) || "mainnet" ); } catch { return "mainnet"; } } function effectiveConfigForBackend(api, backend) { const config = resolvePluginConfig(api); return { ...config, backend, network: networkForBackend(api, backend), }; } function resolvePythonBin(config) { return config.pythonBin || process.env.OPENCLAW_AGENT_WALLET_PYTHON || "python3"; } function resolveOpenclawHome(config) { return path.resolve(config.openclawHome || process.env.OPENCLAW_HOME || path.join(os.homedir(), ".openclaw")); } function resolvePackageRoot(config) { const openclawHome = resolveOpenclawHome(config); const candidates = [ config.packageRoot, process.env.OPENCLAW_AGENT_WALLET_PACKAGE_ROOT, path.join(openclawHome, "agent-wallet-runtime/current/agent-wallet"), path.resolve(PLUGIN_ROOT, "../../../agent-wallet"), path.resolve(process.cwd(), "agent-wallet"), ].filter(Boolean); for (const candidate of candidates) { const resolved = path.resolve(candidate); if (fs.existsSync(path.join(resolved, "agent_wallet", "__init__.py"))) { return resolved; } } throw new Error( `Could not resolve agent-wallet package root. Checked ${path.join(openclawHome, "agent-wallet-runtime/current/agent-wallet")} and local workspace fallbacks. Set plugins.entries.agent-wallet.config.packageRoot if your runtime lives elsewhere.` ); } function buildCliEnv(packageRoot) { const env = { ...process.env }; env.PYTHONPATH = env.PYTHONPATH ? `${packageRoot}${path.delimiter}${env.PYTHONPATH}` : packageRoot; return env; } async function callWalletCli(api, command, extraArgs = [], configOverride = null) { const config = configOverride || resolvePluginConfig(api); const packageRoot = resolvePackageRoot(config); const pythonBin = resolvePythonBin(config); const userId = resolveUserId(api, config); const args = [ "-m", "agent_wallet.openclaw_cli", command, "--user-id", userId, "--config-json", JSON.stringify(config), ...extraArgs, ]; let stdout = ""; let stderr = ""; try { const result = await execFileAsync(pythonBin, args, { cwd: packageRoot, env: buildCliEnv(packageRoot), maxBuffer: 1024 * 1024 * 8, }); stdout = result.stdout; stderr = result.stderr; } catch (error) { stdout = typeof error?.stdout === "string" ? error.stdout : ""; stderr = typeof error?.stderr === "string" ? error.stderr : ""; const stderrText = String(stderr || "").trim(); if (stderrText) { try { const payload = JSON.parse(stderrText); const wrapped = new Error(payload?.error || "agent-wallet CLI failed"); if (payload?.code) wrapped.code = payload.code; if (payload?.details && typeof payload.details === "object") { wrapped.details = payload.details; } throw wrapped; } catch (parseError) { if (parseError instanceof Error && parseError !== error) { throw parseError; } } } throw error; } if (stderr && stderr.trim()) { api?.logger?.debug?.(`[agent-wallet] stderr: ${stderr.trim()}`); } const payload = JSON.parse(stdout.trim() || "{}"); if (payload?.ok === false && payload?.error) { const wrapped = new Error(payload.error); if (payload?.error_code) wrapped.code = payload.error_code; if (payload?.error_details && typeof payload.error_details === "object") { wrapped.details = payload.error_details; } throw wrapped; } return payload; } async function issueApprovalToken(api, config, userId, toolName, previewPayload) { const summary = previewPayload?.confirmation_summary; if (!summary || typeof summary !== "object") { throw new Error(`No confirmation_summary available for ${toolName}.`); } const summaryForToken = PREVIEW_BOUND_SWAP_TOOLS.has(toolName) && !isSolanaSwapIntentPayload(previewPayload) ? { ...summary, _preview_digest: previewDigest(previewPayload) } : { ...summary }; const extraArgs = [ "--tool", toolName, "--summary-json", JSON.stringify(summaryForToken), ]; if (previewPayload?.is_mainnet === true) { extraArgs.push("--mainnet-confirmed"); } const payload = await callWalletCli(api, "issue-approval", extraArgs, config); const token = String(payload?.approval_token || "").trim(); if (!token) { throw new Error(`issue-approval did not return an approval_token for ${toolName}.`); } return token; } async function attachApprovalForExecute(api, config, userId, toolName, effectiveParams) { if (!["execute", "intent_execute"].includes(String(effectiveParams.mode || ""))) return null; if (toolName === "swap_solana_tokens" && String(effectiveParams.mode || "") === "execute") { throw new Error( "Legacy exact-preview execute is disabled for Solana Jupiter swaps in OpenClaw. Use intent_preview, ask for explicit chat confirmation, then call intent_execute. The intent path binds approval to risk limits instead of a fragile Jupiter quote payload." ); } const cached = latestCachedPreview(userId, toolName); if (cached?.preview && cached?.summary) { effectiveParams.approval_token = await issueApprovalToken( api, config, userId, toolName, cached.preview ); if (requiresApprovedPreviewPayload(toolName, effectiveParams)) { effectiveParams._approved_preview = cached.preview; } return cached; } if ( requiresApprovedPreviewPayload(toolName, effectiveParams) && typeof effectiveParams.approval_token === "string" && effectiveParams.approval_token.trim() && effectiveParams._approved_preview === undefined ) { const cachedPreview = cachedPreviewForToken(userId, toolName, effectiveParams.approval_token); if (cachedPreview) { effectiveParams._approved_preview = cachedPreview; } } if (effectiveParams.approval_token) return null; if (shouldLetBackendAuthorizeAutonomousExecution(toolName, effectiveParams, config)) return null; throw new Error(APPROVAL_CONTEXT_MISSING_MESSAGE); } function asContent(data) { return { content: [ { type: "text", text: JSON.stringify(data, null, 2), }, ], }; } function registerTool(api, definition) { api.registerTool({ name: definition.name, description: definition.description, parameters: definition.parameters, returns: { type: "object", additionalProperties: true, }, optional: Boolean(definition.optional), async execute(_id, params = {}) { if (definition.name === "get_active_wallet_backend") { const configuredBackend = resolveBackend(api); const activeBackend = selectedWalletBackend || configuredBackend; const activeNetwork = networkForBackend(api, activeBackend); return asContent({ active_backend: activeBackend, active_wallet: backendLabel(activeBackend), active_network: activeNetwork, configured_backend: configuredBackend, configured_network: String(resolvePluginConfig(api).network || "").trim() || null, session_override_active: Boolean(selectedWalletBackend), available_wallets: ["solana", "evm", "bitcoin"], usage: "Use set_wallet_backend to switch between Solana, EVM, and Bitcoin for this OpenClaw plugin session. Do not edit plugin config for normal wallet switching.", }); } if (definition.name === "set_wallet_backend") { const requestedWallet = String(params?.backend || params?.wallet || "").trim().toLowerCase(); const backend = normalizeWalletBackend(requestedWallet); const impliedNetwork = ["base", "base-mainnet"].includes(requestedWallet) ? "base" : ["ethereum", "eth", "mainnet", "eth-mainnet"].includes(requestedWallet) ? "ethereum" : null; if (backend === "wdk_evm_local") { selectedEvmNetwork = normalizeSelectableEvmNetwork( params?.network || impliedNetwork || selectedEvmNetwork || defaultSelectableEvmNetwork(api) || "ethereum" ); } else if (backend === "solana_local") { selectedSolanaNetwork = normalizeSolanaNetwork( params?.network || selectedSolanaNetwork || defaultSolanaNetwork(api) ); } else if (backend === "wdk_btc_local") { selectedBtcNetwork = normalizeBtcNetwork( params?.network || selectedBtcNetwork || defaultBtcNetwork(api) ); } const configOverride = effectiveConfigForBackend(api, backend); const payload = await callWalletCli(api, "invoke", [ "--tool", backend === "wdk_evm_local" ? "get_evm_network" : "get_wallet_capabilities", "--arguments-json", JSON.stringify({}), ], configOverride); if (payload?.ok === false) { throw new Error(payload?.error || "set_wallet_backend failed"); } selectedWalletBackend = backend; return asContent({ selected_backend: backend, selected_wallet: backendLabel(backend), selected_network: networkForBackend(api, backend), configured_backend: resolveBackend(api), session_override_active: true, config_file_changed: false, usage: "Subsequent wallet calls in this OpenClaw plugin session use this wallet backend by default. The startup plugin config remains unchanged.", data: payload?.data ?? {}, }); } if (definition.name === "set_evm_network") { const network = normalizeSelectableEvmNetwork(params?.network); const configOverride = effectiveConfigForBackend(api, "wdk_evm_local"); configOverride.network = network; const payload = await callWalletCli(api, "invoke", [ "--tool", "get_evm_network", "--arguments-json", JSON.stringify({ network }), ], configOverride); if (payload?.ok === false) { throw new Error(payload?.error || "set_evm_network failed"); } selectedWalletBackend = "wdk_evm_local"; selectedEvmNetwork = network; return asContent({ selected_backend: "wdk_evm_local", selected_wallet: "evm", selected_network: network, session_active_network: network, session_override_active: true, network_switch_persistent_for_runtime_session: true, usage: "Subsequent wallet calls in this OpenClaw plugin session use the EVM wallet on this network by default. You can still override a single EVM call with its network parameter.", data: payload?.data ?? {}, }); } const effectiveParams = { ...(params ?? {}) }; const activeBackend = activeBackendForTool(api, definition.name); const userId = resolveUserId(api, resolvePluginConfig(api)); if ( activeBackend === "wdk_evm_local" && selectedEvmNetwork && definition.parameters?.properties?.network && effectiveParams.network === undefined ) { effectiveParams.network = selectedEvmNetwork; } const configOverride = effectiveConfigForBackend(api, activeBackend); if (activeBackend === "wdk_evm_local" && effectiveParams.network !== undefined) { configOverride.network = normalizeSelectableEvmNetwork(effectiveParams.network); } await attachApprovalForExecute(api, configOverride, userId, definition.name, effectiveParams); const executeWalletTool = async () => callWalletCli(api, "invoke", [ "--tool", definition.name, "--arguments-json", JSON.stringify(effectiveParams), ], configOverride); let payload; try { payload = await executeWalletTool(); } catch (error) { throw normalizeApprovalContextError(error); } cachePreviewForApproval(userId, definition.name, payload); if (payload?.ok === false) { throw new Error(payload?.error || `${definition.name} failed`); } return asContent(payload?.data ?? {}); }, }); } const walletSessionToolDefinitions = [ { name: "get_wallet_capabilities", description: "Describe the active wallet backend, chain, network, address, and safety limits. Use set_wallet_backend to switch between Solana, EVM, and Bitcoin instead of editing plugin config.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_wallet_address", description: "Return the active wallet address for the current session-selected backend.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_wallet_balance", description: `Get the active wallet overview. Solana and EVM return native assets, discovered token balances, per-asset USD values when available, and total_value_usd. Use set_wallet_backend first when the user asks to switch wallets. ${WALLET_TOOL_ONLY_GUIDANCE}`, parameters: { type: "object", properties: { address: { type: "string", description: "Optional wallet address override.", }, }, additionalProperties: false, }, }, { name: "agentlayer_autonomous_status", description: "Return AgentLayer high-trust autonomous permission status. The autonomous permission group covers every wallet write tool (transfers, bridges, Solana swaps, staking, x402 payments, generic contract calls, Base swaps, and EVM DeFi management), not just base_swaps/defi_tools.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "agentlayer_autonomous_approve", description: "Enable the high-trust autonomous permission group. The scope parameter accepts \"all\" (recommended); base_swaps and defi_tools are deprecated aliases with identical effect. This covers every wallet write tool -- transfers, bridges, Solana swaps, staking, x402 payments, generic contract calls, Base Velora/Uniswap swap execute calls, and supported EVM DeFi management tools -- until revoked.", parameters: { type: "object", properties: { scope: { type: "string", enum: ["all", "base_swaps", "defi_tools"], description: "Scope to enable. Use \"all\" (recommended) -- base_swaps and defi_tools are deprecated aliases that enable the exact same full autonomous permission group." }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["scope", "purpose", "user_intent"], additionalProperties: false, }, }, { name: "agentlayer_autonomous_revoke", description: "Disable the full high-trust autonomous permission group. The scope parameter accepts \"all\" (recommended); base_swaps and defi_tools are deprecated aliases that revoke the same full group.", parameters: { type: "object", properties: { scope: { type: "string", enum: ["all", "base_swaps", "defi_tools"], description: "Scope to revoke. Use \"all\" (recommended) -- base_swaps and defi_tools are deprecated aliases that revoke the exact same full autonomous permission group." }, }, required: ["scope"], additionalProperties: false, }, }, { name: "x402_search_services", description: "Search x402-paid services through CDP Bazaar or Agentic Market. This is read-only discovery and does not spend funds.", parameters: { type: "object", properties: { query: { type: "string" }, discovery_provider: { type: "string", enum: ["auto", "cdp_bazaar", "agentic_market"], }, network: { type: "string" }, asset: { type: "string" }, scheme: { type: "string" }, max_usd_price: { type: "string" }, limit: { type: "integer" }, }, additionalProperties: false, }, }, { name: "x402_get_service_details", description: "Resolve one x402 service or resource into a normalized details payload. Use a resource URL for CDP Bazaar or a domain/service id for Agentic Market.", parameters: { type: "object", properties: { reference: { type: "string" }, discovery_provider: { type: "string", enum: ["auto", "cdp_bazaar", "agentic_market"], }, }, required: ["reference"], additionalProperties: false, }, }, { name: "x402_preview_request", description: "Make an unpaid HTTP request to an x402 endpoint, detect HTTP 402, parse PAYMENT-REQUIRED, and summarize the payment options. This does not pay or execute.", parameters: { type: "object", properties: { url: { type: "string" }, method: { type: "string" }, headers: { type: "object", additionalProperties: { type: "string" } }, query: { type: "object", additionalProperties: true }, json_body: {}, text_body: { type: "string" }, }, required: ["url"], additionalProperties: false, }, }, { name: "x402_pay_request", description: "Pay for and call an x402 endpoint using the active wallet backend. The tool probes the endpoint, validates compatibility, signs the payment, and returns the service response in one call.", parameters: { type: "object", properties: { url: { type: "string" }, method: { type: "string" }, headers: { type: "object", additionalProperties: { type: "string" } }, query: { type: "object", additionalProperties: true }, json_body: {}, text_body: { type: "string" }, purpose: { type: "string" }, }, required: ["url", "purpose"], additionalProperties: false, }, }, { name: "get_active_wallet_backend", description: "Show which wallet backend is active in this OpenClaw plugin session and whether it differs from the startup plugin config.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "set_wallet_backend", description: "Switch the active wallet backend for this OpenClaw plugin session. Use this for user requests like 'switch to EVM wallet', 'use Base', 'switch back to Solana', or 'use Bitcoin'. This does not edit code, environment variables, or plugin config.", parameters: { type: "object", properties: { backend: { type: "string", enum: ["solana", "sol", "evm", "ethereum", "base", "robinhood", "bitcoin", "btc"], description: "Wallet backend or common alias to make active.", }, network: { type: "string", description: "Optional network for the selected wallet. Examples: mainnet, ethereum, base, robinhood, bitcoin.", }, }, required: ["backend"], additionalProperties: false, }, }, ]; const solanaToolDefinitions = [ { name: "get_wallet_capabilities", description: "Describe the connected wallet backend, chain, and safety limits.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_wallet_address", description: "Return the configured wallet address for the connected backend.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_wallet_balance", description: `Get the wallet overview: native balance, discovered token balances, per-asset USD values when available, and total_value_usd. Solana token discovery uses RPC; pricing uses Jupiter rather than RPC. ${WALLET_TOOL_ONLY_GUIDANCE}`, parameters: { type: "object", properties: { address: { type: "string", description: "Optional wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_lifi_supported_chains", description: "List the LI.FI chains currently allowed for OpenClaw cross-chain routing.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_lifi_quote", description: "Get a read-only LI.FI cross-chain quote for Ethereum/Base/Solana routes. Execution is not enabled by this tool.", parameters: { type: "object", properties: { from_chain: { type: "string", description: "Source chain: ethereum, base, solana, or the LI.FI chain id." }, to_chain: { type: "string", description: "Destination chain: ethereum, base, solana, or the LI.FI chain id." }, from_token: { type: "string", description: "Source token address. Use native/eth/sol for native tokens." }, to_token: { type: "string", description: "Destination token address. Use native/eth/sol for native tokens." }, amount_in_raw: { type: "string", description: "Input amount in token base units as a base-10 integer string." }, from_address: { type: "string", description: "Optional source wallet address. Defaults to the active wallet when the source chain matches it." }, to_address: { type: "string", description: "Optional destination wallet address. Defaults to the active wallet when the destination chain matches it." }, slippage: { type: "number", description: "Optional decimal fraction, for example 0.01 for 1%." }, allow_bridges: { type: "array", items: { type: "string" } }, deny_bridges: { type: "array", items: { type: "string" } }, prefer_bridges: { type: "array", items: { type: "string" } }, }, required: ["from_chain", "to_chain", "from_token", "to_token", "amount_in_raw"], additionalProperties: false, }, }, { name: "get_lifi_transfer_status", description: "Get LI.FI cross-chain transfer status using a source/destination transaction hash or LI.FI step id.", parameters: { type: "object", properties: { tx_hash: { type: "string" }, bridge: { type: "string" }, from_chain: { type: "string" }, to_chain: { type: "string" }, }, required: ["tx_hash"], additionalProperties: false, }, }, { name: "get_wallet_portfolio", description: `Get the Solana wallet portfolio. This is the detailed equivalent of get_wallet_balance and includes native SOL, non-zero SPL token accounts, USD pricing when available, and total_value_usd. ${WALLET_TOOL_ONLY_GUIDANCE}`, parameters: { type: "object", properties: { address: { type: "string", description: "Optional wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_solana_token_prices", description: "Get current token prices for one or more Solana mint addresses via Jupiter.", parameters: { type: "object", properties: { mints: { type: "array", items: { type: "string" }, description: "List of Solana token mint addresses.", }, }, required: ["mints"], additionalProperties: false, }, }, { name: "get_flash_trade_markets", description: "List Flash Trade perpetual markets currently available on Solana mainnet.", parameters: { type: "object", properties: { pool_name: { type: "string", description: "Optional Flash pool identifier such as Crypto.1.", }, }, additionalProperties: false, }, }, { name: "get_flash_trade_positions", description: "Get Flash Trade perpetual positions for a Solana wallet on mainnet.", parameters: { type: "object", properties: { owner: { type: "string", description: "Optional Solana wallet address override. If omitted, use the configured wallet.", }, pool_name: { type: "string", description: "Optional Flash pool identifier such as Crypto.1.", }, }, additionalProperties: false, }, }, { name: "get_kamino_portfolio", description: "Get the unified Kamino portfolio view for a Solana wallet on mainnet across lending, multiply, leverage, liquidity, earn, and staking.", parameters: { type: "object", properties: { user: { type: "string", description: "Optional Solana wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_kamino_vaults", description: "List Kamino Earn vaults currently available on Solana mainnet.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_kamino_earn_positions", description: "Get Kamino Earn vault positions for a Solana wallet on mainnet.", parameters: { type: "object", properties: { user: { type: "string", description: "Optional Solana wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_kamino_liquidity_positions", description: "Get Kamino Liquidity strategy positions for a Solana wallet on mainnet.", parameters: { type: "object", properties: { user: { type: "string", description: "Optional Solana wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_kamino_lend_markets", description: "List Kamino lending markets currently available on Solana mainnet.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_kamino_lend_market_reserves", description: "Get reserve metrics for one Kamino lending market on Solana mainnet.", parameters: { type: "object", properties: { market: { type: "string", description: "Kamino market address.", }, }, required: ["market"], additionalProperties: false, }, }, { name: "get_kamino_lend_user_obligations", description: "Get Kamino obligations for a wallet in a specific Kamino market on Solana mainnet.", parameters: { type: "object", properties: { market: { type: "string", description: "Kamino market address.", }, user: { type: "string", description: "Optional Solana wallet address override.", }, }, required: ["market"], additionalProperties: false, }, }, { name: "get_kamino_lend_user_rewards", description: "Get Kamino rewards summary for a Solana wallet on mainnet.", parameters: { type: "object", properties: { user: { type: "string", description: "Optional Solana wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_kamino_open_positions", description: "Get all open Kamino lending positions for a Solana wallet on mainnet, aggregated across markets with loan details, reserve APYs, and rewards.", parameters: { type: "object", properties: { user: { type: "string", description: "Optional Solana wallet address override.", }, }, additionalProperties: false, }, }, { name: "sign_wallet_message", description: "Sign an arbitrary message with the connected wallet after explicit user confirmation in chat.", optional: true, parameters: { type: "object", properties: { message: { type: "string" }, purpose: { type: "string" }, user_confirmed: { type: "boolean" }, }, required: ["message", "purpose", "user_confirmed"], additionalProperties: false, }, }, { name: "transfer_sol", description: "Preview, prepare, or execute a native SOL transfer. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { recipient: { type: "string" }, amount: { type: "number" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["recipient", "amount", "mode", "purpose"], additionalProperties: false, }, }, { name: "transfer_spl_token", description: "Preview, prepare, or execute an SPL token transfer by mint address. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { recipient: { type: "string" }, mint: { type: "string" }, amount: { type: "number" }, decimals: { type: "integer" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["recipient", "mint", "amount", "mode", "purpose"], additionalProperties: false, }, }, { name: "swap_solana_tokens", description: `Preview or execute a Solana token swap via Jupiter. Use intent_preview followed by intent_execute after the user explicitly confirms the intent summary in chat; intent_execute fetches a fresh quote and only executes inside the approved limits. Do not use legacy execute for Solana swaps. The OpenClaw plugin handles internal execution authorization automatically. ${WALLET_TOOL_ONLY_GUIDANCE}`, optional: true, parameters: { type: "object", properties: { input_mint: { type: "string" }, output_mint: { type: "string" }, amount: { type: "number" }, slippage_bps: { type: "integer", description: "Optional slippage tolerance in basis points. Defaults to 300 (3%) for Solana swaps." }, minimum_output_amount_raw: { type: "integer", description: "Optional approved minimum output in raw output token units for intent_preview. For intent swaps, overly strict values are clamped to the slippage floor." }, max_fee_lamports: { type: "integer" }, valid_for_seconds: { type: "integer", description: "Optional intent validity window in seconds. Intent swaps use at least 120 seconds, max 120." }, max_attempts: { type: "integer", description: "Optional number of fresh quote/simulate/execute attempts. Intent swaps use at least 3 attempts, max 5." }, mode: { type: "string", enum: ["preview", "intent_preview", "intent_execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["input_mint", "output_mint", "amount", "mode", "purpose"], additionalProperties: false, }, }, { name: "swap_solana_lifi_cross_chain_tokens", description: "Preview, prepare, or execute a Solana-origin cross-chain swap through LI.FI. This currently supports Solana as the source chain and ethereum/base as the destination chain. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { input_token: { type: "string" }, destination_chain: { type: "string", enum: ["ethereum", "base", "1", "8453"] }, output_token: { type: "string" }, destination_address: { type: "string" }, amount_in_raw: { type: "string" }, slippage: { type: "number" }, allow_bridges: { type: "array", items: { type: "string" } }, deny_bridges: { type: "array", items: { type: "string" } }, prefer_bridges: { type: "array", items: { type: "string" } }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: [ "input_token", "destination_chain", "output_token", "destination_address", "amount_in_raw", "mode", "purpose", ], additionalProperties: false, }, }, { name: "launch_bags_token", description: "Preview, prepare, or execute a Bags token launch with fee-share config on mainnet. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { name: { type: "string" }, symbol: { type: "string" }, description: { type: "string" }, image_url: { type: "string" }, website: { type: "string" }, twitter: { type: "string" }, telegram: { type: "string" }, discord: { type: "string" }, base_mint: { type: "string" }, claimers: { type: "array", items: { type: "string" }, }, basis_points: { type: "array", items: { type: "integer" }, }, initial_buy_sol: { type: "number" }, bags_config_type: { type: "integer" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: [ "name", "symbol", "description", "base_mint", "claimers", "basis_points", "initial_buy_sol", "mode", "purpose", ], additionalProperties: false, }, }, { name: "kamino_lend_deposit", description: "Preview, prepare, or execute a Kamino lending deposit using a decimal token amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { market: { type: "string" }, reserve: { type: "string" }, amount_ui: { type: "string" }, obligation_address: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["market", "reserve", "amount_ui", "mode", "purpose"], additionalProperties: false, }, }, { name: "kamino_lend_withdraw", description: "Preview, prepare, or execute a Kamino lending withdraw using a decimal token amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { market: { type: "string" }, reserve: { type: "string" }, amount_ui: { type: "string" }, obligation_address: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["market", "reserve", "amount_ui", "mode", "purpose"], additionalProperties: false, }, }, { name: "kamino_lend_borrow", description: "Preview, prepare, or execute a Kamino lending borrow using a decimal token amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { market: { type: "string" }, reserve: { type: "string" }, amount_ui: { type: "string" }, obligation_address: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["market", "reserve", "amount_ui", "mode", "purpose"], additionalProperties: false, }, }, { name: "kamino_lend_repay", description: "Preview, prepare, or execute a Kamino lending repay using a decimal token amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { market: { type: "string" }, reserve: { type: "string" }, amount_ui: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["market", "reserve", "amount_ui", "mode", "purpose"], additionalProperties: false, }, }, { name: "kamino_earn_deposit", description: "Preview, prepare, or execute a Kamino Earn vault deposit using a decimal token amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { kvault: { type: "string" }, amount_ui: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["kvault", "amount_ui", "mode", "purpose"], additionalProperties: false, }, }, { name: "kamino_earn_withdraw", description: "Preview, prepare, or execute a Kamino Earn vault withdraw using a decimal token amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { kvault: { type: "string" }, amount_ui: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["kvault", "amount_ui", "mode", "purpose"], additionalProperties: false, }, }, { name: "flash_trade_open_position", description: "Preview, prepare, or execute a Flash Trade perpetual open on Solana mainnet using a supported Flash collateral.", optional: true, parameters: { type: "object", properties: { pool_name: { type: "string", description: "Flash pool identifier such as Crypto.1.", }, market_symbol: { type: "string", description: "Flash market symbol such as SOL or BTC.", }, collateral_symbol: { type: "string", description: "Flash collateral symbol, for example SOL for SOL longs or USDC for SOL shorts.", }, collateral_amount_raw: { type: "string", description: "Collateral amount in raw token units.", }, leverage: { type: "string", description: "Requested leverage as a decimal string such as 5 or 7.5.", }, side: { type: "string", enum: ["long", "short"], description: "Position direction.", }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: [ "pool_name", "market_symbol", "collateral_symbol", "collateral_amount_raw", "leverage", "side", "mode", "purpose", ], additionalProperties: false, }, }, { name: "flash_trade_close_position", description: "Preview, prepare, or execute a Flash Trade perpetual close on Solana mainnet.", optional: true, parameters: { type: "object", properties: { pool_name: { type: "string", description: "Flash pool identifier such as Crypto.1.", }, market_symbol: { type: "string", description: "Flash market symbol such as SOL or BTC.", }, side: { type: "string", enum: ["long", "short"], description: "Position direction to close.", }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["pool_name", "market_symbol", "side", "mode", "purpose"], additionalProperties: false, }, }, { name: "close_empty_token_accounts", description: "Preview or execute closing zero-balance token accounts. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { limit: { type: "integer" }, mode: { type: "string", enum: ["preview", "execute"] }, purpose: { type: "string" }, }, required: ["limit", "mode", "purpose"], additionalProperties: false, }, }, ]; const btcToolDefinitions = [ { name: "get_wallet_capabilities", description: "Describe the connected wallet backend, chain, and safety limits.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_wallet_address", description: "Return the configured wallet address for the connected backend.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_wallet_balance", description: "Get the native BTC balance for the configured wallet address.", parameters: { type: "object", properties: { address: { type: "string", description: "Optional wallet address override.", }, }, additionalProperties: false, }, }, { name: "get_btc_transfer_history", description: "Get BTC transfer history for the configured wallet account.", parameters: { type: "object", properties: { direction: { type: "string", enum: ["incoming", "outgoing", "all"] }, limit: { type: "integer" }, skip: { type: "integer" }, }, additionalProperties: false, }, }, { name: "get_btc_fee_rates", description: "Get current BTC fee-rate suggestions from the local BTC wallet service.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_btc_max_spendable", description: "Estimate the maximum BTC spendable amount after fees.", parameters: { type: "object", properties: { fee_rate: { type: "integer" }, }, additionalProperties: false, }, }, { name: "transfer_btc", description: "Preview, prepare, or execute a BTC transfer in satoshis. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { recipient: { type: "string" }, amount_sats: { type: "integer" }, fee_rate: { type: "integer" }, confirmation_target: { type: "integer" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, }, required: ["recipient", "amount_sats", "mode", "purpose"], additionalProperties: false, }, }, ]; const evmToolDefinitions = [ { name: "get_wallet_capabilities", description: "Describe the connected wallet backend, chain, and safety limits.", parameters: { type: "object", properties: { network: { type: "string", enum: EVM_CORE_NETWORKS, description: "Optional EVM network override for this request.", }, }, additionalProperties: false, }, }, { name: "get_wallet_address", description: "Return the configured wallet address for the connected backend.", parameters: { type: "object", properties: { network: { type: "string", enum: EVM_CORE_NETWORKS, description: "Optional EVM network override for this request.", }, }, additionalProperties: false, }, }, { name: "get_wallet_balance", description: "Get the EVM wallet overview: native asset, discovered ERC-20 balances, per-asset USD values, assets, balance_usd, and total_value_usd when available. Pricing uses aggregator APIs rather than RPC.", parameters: { type: "object", properties: { address: { type: "string", description: "Optional wallet address override.", }, network: { type: "string", enum: EVM_CORE_NETWORKS, description: "Optional EVM network override for this request.", }, }, additionalProperties: false, }, }, { name: "get_lifi_supported_chains", description: "List the LI.FI chains currently allowed for OpenClaw cross-chain routing.", parameters: { type: "object", properties: {}, additionalProperties: false }, }, { name: "get_lifi_quote", description: "Get a read-only LI.FI cross-chain quote for Ethereum/Base/Solana routes. Execution is not enabled by this tool.", parameters: { type: "object", properties: { from_chain: { type: "string", description: "Source chain: ethereum, base, solana, or the LI.FI chain id." }, to_chain: { type: "string", description: "Destination chain: ethereum, base, solana, or the LI.FI chain id." }, from_token: { type: "string", description: "Source token address. Use native/eth/sol for native tokens." }, to_token: { type: "string", description: "Destination token address. Use native/eth/sol for native tokens." }, amount_in_raw: { type: "string", description: "Input amount in token base units as a base-10 integer string." }, from_address: { type: "string", description: "Optional source wallet address. Defaults to the active wallet when the source chain matches it." }, to_address: { type: "string", description: "Optional destination wallet address. Defaults to the active wallet when the destination chain matches it." }, slippage: { type: "number", description: "Optional decimal fraction, for example 0.01 for 1%." }, allow_bridges: { type: "array", items: { type: "string" } }, deny_bridges: { type: "array", items: { type: "string" } }, prefer_bridges: { type: "array", items: { type: "string" } }, }, required: ["from_chain", "to_chain", "from_token", "to_token", "amount_in_raw"], additionalProperties: false, }, }, { name: "get_lifi_transfer_status", description: "Get LI.FI cross-chain transfer status using a source/destination transaction hash or LI.FI step id.", parameters: { type: "object", properties: { tx_hash: { type: "string" }, bridge: { type: "string" }, from_chain: { type: "string" }, to_chain: { type: "string" }, }, required: ["tx_hash"], additionalProperties: false, }, }, { name: "get_evm_network", description: "Show the effective EVM network context, available networks, and swap-supported networks.", parameters: { type: "object", properties: { network: { type: "string", enum: EVM_CORE_NETWORKS, }, }, additionalProperties: false, }, }, { name: "set_evm_network", description: "Select the active EVM network for subsequent wallet tool calls in this OpenClaw plugin session. Use this to switch between ethereum, base, and robinhood instead of editing code or plugin configuration.", parameters: { type: "object", properties: { network: { type: "string", enum: EVM_CORE_NETWORKS, description: "EVM network to make active for subsequent calls.", }, }, required: ["network"], additionalProperties: false, }, }, { name: "get_evm_token_balance", description: "Get the ERC-20 balance for the configured EVM wallet account.", parameters: { type: "object", properties: { token_address: { type: "string" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["token_address"], additionalProperties: false, }, }, { name: "get_evm_token_metadata", description: "Get ERC-20 token metadata for a contract address on the active EVM network.", parameters: { type: "object", properties: { token_address: { type: "string" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["token_address"], additionalProperties: false, }, }, { name: "get_evm_fee_rates", description: "Get current EVM fee-rate suggestions for the active network.", parameters: { type: "object", properties: { network: { type: "string", enum: EVM_CORE_NETWORKS }, }, additionalProperties: false, }, }, { name: "get_evm_transaction_receipt", description: "Get the transaction receipt for a broadcast EVM transaction hash.", parameters: { type: "object", properties: { tx_hash: { type: "string" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["tx_hash"], additionalProperties: false, }, }, { name: "get_evm_aave_account", description: "Get read-only Aave V3 account data for the configured EVM wallet on supported mainnet networks.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum", "base"] }, }, additionalProperties: false, }, }, { name: "get_evm_aave_reserves", description: "Get the read-only Aave V3 reserve catalog for the configured EVM network, including reserve flags, pricing, and liquidity metadata.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum", "base"] }, }, additionalProperties: false, }, }, { name: "get_evm_aave_positions", description: "Get read-only Aave V3 per-reserve positions for the configured EVM wallet, including supplied and borrowed balances on supported mainnet networks.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum", "base"] }, }, additionalProperties: false, }, }, { name: "manage_evm_aave_position", description: "Preview, prepare, or execute a narrow Aave V3 lending operation on supported EVM mainnet networks. Supported operations are supply, withdraw, borrow, and repay. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { operation: { type: "string", enum: ["supply", "withdraw", "borrow", "repay"] }, token_address: { type: "string" }, amount_raw: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum", "base"] }, }, required: ["operation", "token_address", "amount_raw", "mode", "purpose"], additionalProperties: false, }, }, { name: "get_evm_lido_overview", description: "Get the read-only Lido staking overview for the configured EVM wallet on supported networks, including contract addresses and sample wrap rates.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum"] }, }, additionalProperties: false, }, }, { name: "get_evm_lido_positions", description: "Get read-only Lido positions for the configured EVM wallet, including stETH, wstETH, and stETH-equivalent balances.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum"] }, }, additionalProperties: false, }, }, { name: "manage_evm_lido_position", description: "Preview, prepare, or execute a narrow Lido staking operation on Ethereum mainnet. Supported operations are stake_eth_for_wsteth, wrap_steth, and unwrap_wsteth. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { operation: { type: "string", enum: ["stake_eth_for_wsteth", "wrap_steth", "unwrap_wsteth"] }, amount_raw: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum"] }, }, required: ["operation", "amount_raw", "mode", "purpose"], additionalProperties: false, }, }, { name: "get_evm_lido_withdrawal_requests", description: "Get read-only Lido withdrawal queue requests for the configured EVM wallet, including finalized and claimable request statuses.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum"] }, }, additionalProperties: false, }, }, { name: "manage_evm_lido_withdrawal", description: "Preview, prepare, or execute a narrow Lido withdrawal queue operation on Ethereum mainnet. Supported operations are request_withdrawal_steth, request_withdrawal_wsteth, and claim_withdrawal. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { operation: { type: "string", enum: ["request_withdrawal_steth", "request_withdrawal_wsteth", "claim_withdrawal"], }, amount_raw: { type: "string" }, request_id: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum"] }, }, required: ["operation", "mode", "purpose"], additionalProperties: false, }, }, { name: "get_evm_morpho_vaults", description: "Get read-only Morpho vault discovery and detail data for the configured EVM network on supported mainnet chains. When listing, results are ordered (default: largest TVL first) and can be filtered by underlying asset.", parameters: { type: "object", properties: { vault_address: { type: "string", description: "Optional explicit vault address for a single-vault lookup." }, limit: { type: "integer", minimum: 1, maximum: 500 }, listed_only: { type: "boolean", description: "Filter to listed vaults only. Defaults to true." }, asset_address: { type: "string", description: "Optional underlying asset address filter (e.g. only USDC vaults)." }, order_by: { type: "string", enum: ["TotalAssetsUsd", "TotalAssets", "TotalSupply", "Liquidity", "LiquidityUsd", "Apy", "NetApy", "Address"], description: "Sort field when listing. Defaults to TotalAssetsUsd.", }, order_direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction. Defaults to desc." }, network: { type: "string", enum: ["ethereum", "base"] }, }, additionalProperties: false, }, }, { name: "get_evm_morpho_markets", description: "Get read-only Morpho market discovery and detail data for the configured EVM network on supported mainnet chains. When listing, results are ordered (default: largest supply first) and can be filtered by free-text search or by collateral/loan asset.", parameters: { type: "object", properties: { market_id: { type: "string", description: "Optional explicit market id (32-byte hex) for a single-market lookup." }, limit: { type: "integer", minimum: 1, maximum: 500 }, listed_only: { type: "boolean", description: "Filter to listed markets only. Defaults to true." }, search: { type: "string", description: "Optional free-text search over market/asset symbols (e.g. 'wstETH')." }, collateral_asset_address: { type: "string", description: "Optional collateral asset address filter." }, loan_asset_address: { type: "string", description: "Optional loan asset address filter." }, order_by: { type: "string", enum: ["SupplyAssetsUsd", "BorrowAssetsUsd", "SupplyApy", "NetSupplyApy", "BorrowApy", "NetBorrowApy", "Utilization", "TotalLiquidityUsd", "Lltv"], description: "Sort field when listing. Defaults to SupplyAssetsUsd.", }, order_direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction. Defaults to desc." }, network: { type: "string", enum: ["ethereum", "base"] }, }, additionalProperties: false, }, }, { name: "get_evm_morpho_positions", description: "Get read-only Morpho vault and market positions for the configured EVM wallet on supported mainnet chains.", parameters: { type: "object", properties: { network: { type: "string", enum: ["ethereum", "base"] }, }, additionalProperties: false, }, }, { name: "manage_evm_morpho_vault_position", description: "Preview, prepare, or execute a narrow Morpho vault operation on supported EVM mainnet networks. Supported operations are supply and withdraw. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { operation: { type: "string", enum: ["supply", "withdraw"] }, token_address: { type: "string" }, vault_address: { type: "string" }, vault_preset: { type: "string" }, amount_raw: { type: "string" }, native_amount_raw: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum", "base"] }, }, required: ["operation", "token_address", "mode", "purpose"], additionalProperties: false, }, }, { name: "manage_evm_morpho_market_position", description: "Preview, prepare, or execute a narrow Morpho market operation on supported EVM mainnet networks. Supported operations are supply_collateral, borrow, repay, and withdraw_collateral. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { operation: { type: "string", enum: ["supply_collateral", "borrow", "repay", "withdraw_collateral"] }, token_address: { type: "string" }, market_id: { type: "string" }, market_preset: { type: "string" }, amount_raw: { type: "string" }, native_amount_raw: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum", "base"] }, }, required: ["operation", "token_address", "mode", "purpose"], additionalProperties: false, }, }, { name: "get_evm_swap_quote", description: "Get a read-only Velora quote for an ERC-20 or native ETH swap on supported EVM mainnet networks. This does not approve or execute a swap.", parameters: { type: "object", properties: { token_in: { type: "string" }, token_out: { type: "string" }, amount_in_raw: { type: "string" }, network: { type: "string", enum: ["ethereum", "base"] }, }, required: ["token_in", "token_out", "amount_in_raw"], additionalProperties: false, }, }, { name: "swap_evm_tokens", description: "Preview, prepare, or execute an ERC-20 or native ETH swap through Velora on supported EVM mainnet networks. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { token_in: { type: "string" }, token_out: { type: "string" }, amount_in_raw: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum", "base"] }, }, required: ["token_in", "token_out", "amount_in_raw", "mode", "purpose"], additionalProperties: false, }, }, { name: "get_uniswap_swap_quote", description: "Get a read-only Uniswap execution preview for an ERC-20 or native ETH swap on ethereum, base, or robinhood. Supported paths are CLASSIC, UniswapX orders, and canonical ETH↔WETH wrap/unwrap. This does not approve, sign, or execute a swap.", parameters: { type: "object", properties: { token_in: { type: "string" }, token_out: { type: "string" }, amount_in_raw: { type: "string" }, slippage_bps: { type: "integer" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["token_in", "token_out", "amount_in_raw"], additionalProperties: false, }, }, { name: "swap_evm_uniswap_tokens", description: "Preview, prepare, or execute a supported Uniswap path on ethereum, base, or robinhood: CLASSIC, UniswapX orders, or canonical ETH↔WETH wrap/unwrap. ERC-20 paths may use Permit2 EIP-712 or an UniswapX order signature. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { token_in: { type: "string" }, token_out: { type: "string" }, amount_in_raw: { type: "string" }, slippage_bps: { type: "integer" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["token_in", "token_out", "amount_in_raw", "mode", "purpose"], additionalProperties: false, }, }, { name: "swap_evm_lifi_cross_chain_tokens", description: "Preview, prepare, or execute an EVM-origin cross-chain swap through LI.FI. This currently supports ethereum/base as the source network and ethereum/base/solana as the destination chain. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { token_in: { type: "string" }, destination_chain: { type: "string", enum: ["ethereum", "base", "solana", "1", "8453", "1151111081099710"] }, output_token: { type: "string" }, destination_address: { type: "string" }, amount_in_raw: { type: "string" }, slippage: { type: "number" }, allow_bridges: { type: "array", items: { type: "string" } }, deny_bridges: { type: "array", items: { type: "string" } }, prefer_bridges: { type: "array", items: { type: "string" } }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: ["ethereum", "base"] }, }, required: [ "token_in", "destination_chain", "output_token", "destination_address", "amount_in_raw", "mode", "purpose", ], additionalProperties: false, }, }, { name: "transfer_evm_native", description: "Preview, prepare, or execute a native EVM transfer using a wei amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { recipient: { type: "string" }, amount_wei: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["recipient", "amount_wei", "mode", "purpose"], additionalProperties: false, }, }, { name: "transfer_evm_token", description: "Preview, prepare, or execute an ERC-20 transfer using a raw base-unit amount. Preview or prepare first. After the user explicitly confirms the shown summary in chat, call execute; the OpenClaw plugin handles the internal execution authorization automatically.", optional: true, parameters: { type: "object", properties: { token_address: { type: "string" }, recipient: { type: "string" }, amount_raw: { type: "string" }, mode: { type: "string", enum: ["preview", "prepare", "execute"] }, purpose: { type: "string" }, user_intent: { type: "boolean" }, network: { type: "string", enum: EVM_CORE_NETWORKS }, }, required: ["token_address", "recipient", "amount_raw", "mode", "purpose"], additionalProperties: false, }, }, ]; export default function registerAgentWalletPlugin(api) { api?.logger?.info?.("[agent-wallet] registering OpenClaw wallet plugin"); const backend = resolveBackend(api); selectedWalletBackend = null; selectedSolanaNetwork = defaultSolanaNetwork(api); selectedEvmNetwork = defaultSelectableEvmNetwork(api); selectedBtcNetwork = defaultBtcNetwork(api); const duplicateSessionToolNames = new Set( walletSessionToolDefinitions.map((definition) => definition.name) ); const toolDefinitions = []; const seen = new Set(); for (const definition of [ ...walletSessionToolDefinitions, ...solanaToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)), ...btcToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)), ...evmToolDefinitions.filter((item) => !duplicateSessionToolNames.has(item.name)), ]) { if (seen.has(definition.name)) { continue; } seen.add(definition.name); toolDefinitions.push(definition); } for (const definition of toolDefinitions) { registerTool(api, definition); } api?.logger?.info?.( `[agent-wallet] default wallet backend ${backend}; registered ${toolDefinitions.length} multi-wallet tools` ); }