diff --git a/showcase/arcis-protocol/showcase.json b/showcase/arcis-protocol/showcase.json new file mode 100644 index 0000000..cc4253e --- /dev/null +++ b/showcase/arcis-protocol/showcase.json @@ -0,0 +1,94 @@ +{ + "slug": "arcis-protocol", + "title": "Arcis Protocol — Agent Yield Infrastructure", + "tagline": "Agents deposit idle USDC into an ERC-4626 vault, earn Aave V3 yield, and withdraw on demand. Three functions: deposit(), withdraw(), balance(). Live on Base.", + "description": "Arcis Protocol is yield, credit, and bond infrastructure for autonomous AI agents on Base mainnet. Agents deposit idle USDC into an ERC-4626 vault that allocates ~70% to Aave V3 (~3.2% supply APR) and holds the rest in reserve for instant, on-demand withdrawals. Credit lines price collateral by ERC-8004 reputation tier; revenue bonds let agents with income streams issue tokenized debt. All three instruments share one interface: deposit(), withdraw(), balance(). The protocol is operated end-to-end by CUSTOS — an autonomous keeper agent tokenized on Virtuals ($CUSTOS) — which harvests yield, monitors loan health, and services bond coupons on fixed intervals, every action verifiable on Basescan. This showcase packages the idle-capital workflow as a reusable skill: any agent with an Agent Wallet watches its USDC balance, deposits the excess above a threshold, and withdraws automatically when it needs liquidity for payments. Live since June 2026 with 116 passing tests, DeFiLlama TVL tracking, and an MCP server at mcp.arcis.money for natural-language access.", + "status": "live on mainnet", + "topic": "defi", + "topics": [ + "defi", + "yield", + "agent-treasury", + "erc-4626", + "base" + ], + "hidden": false, + "builder": { + "name": "Arcis Protocol", + "url": "https://github.com/Arcis-Protocol" + }, + "links": { + "repo": "https://github.com/Arcis-Protocol", + "feedback": "https://github.com/Virtual-Protocol/acp-cli-demos/issues/new?title=Feedback%3A%20Arcis%20Protocol", + "demo": "https://arcis.money/dashboard", + "share": "https://x.com/custos0x", + "docs": "https://github.com/Arcis-Protocol/docs/blob/main/WHITEPAPER.md" + }, + "primitives": [ + "wallet", + "token", + "acp" + ], + "visual": { + "kind": "ERC-4626 vault + agent wallet", + "eyebrow": "defi · aave v3 · base", + "title": "the citadel of agent capital", + "posterUrl": "https://raw.githubusercontent.com/Virtual-Protocol/acp-cli-demos/main/showcase/arcis-protocol/assets/poster.png" + }, + "skills": [ + { + "name": "arcis-idle-capital", + "href": "https://github.com/Arcis-Protocol/docs/blob/main/examples/arcis-x402-idle-capital.ts", + "sourcePath": "showcase/arcis-protocol/skills/arcis-idle-capital", + "summary": "Reusable idle-capital workflow: monitor an agent's USDC balance, auto-deposit excess into the Arcis vault for yield, and auto-withdraw when the agent needs funds for payments.", + "install": "cp -R showcase/arcis-protocol/skills/arcis-idle-capital ~/.agents/skills/" + } + ], + "artifacts": [ + { + "label": "Live dashboard", + "href": "https://arcis.money/dashboard", + "kind": "proof" + }, + { + "label": "DeFiLlama TVL", + "href": "https://defillama.com/protocol/arcis-protocol", + "kind": "proof" + }, + { + "label": "Vault contract on Basescan", + "href": "https://basescan.org/address/0x00325d9da832b38179ed2f0dabd4062d93e325a7", + "kind": "proof" + }, + { + "label": "$CUSTOS token", + "href": "https://basescan.org/token/0xD7C479F720b0bC2FF1088A16D1c06C3e11C62882", + "kind": "proof" + }, + { + "label": "CUSTOS — live ACP agent on Virtuals", + "href": "https://app.virtuals.io/acp/agent/019f1b2a-adeb-7c71-baf5-9baad0d0eeae", + "kind": "demo" + }, + { + "label": "Skill source", + "href": "https://github.com/Arcis-Protocol/docs/blob/main/examples/arcis-x402-idle-capital.ts", + "kind": "skill" + }, + { + "label": "MCP server (natural-language access)", + "href": "https://mcp.arcis.money/mcp", + "kind": "integration" + }, + { + "label": "Live vault API", + "href": "https://mcp.arcis.money/api/vault", + "kind": "integration" + } + ], + "feedbackPrompts": [ + "Should idle-capital thresholds adapt to each agent's payment cadence?", + "Which yield strategies should Arcis add beyond Aave V3?", + "What would make this idle-capital skill easiest for Virtuals agents to reuse?" + ] +} diff --git a/showcase/arcis-protocol/skills/arcis-idle-capital/SKILL.md b/showcase/arcis-protocol/skills/arcis-idle-capital/SKILL.md new file mode 100644 index 0000000..b79be7f --- /dev/null +++ b/showcase/arcis-protocol/skills/arcis-idle-capital/SKILL.md @@ -0,0 +1,79 @@ +# Arcis Idle Capital + +Route an agent's idle USDC into the Arcis yield vault on Base, earning ~3.2% APY through Aave V3, and withdraw automatically when the agent needs funds for payments. + +## When to Use + +- An agent holds USDC that sits idle between jobs or payments. +- The agent wants to earn yield on that idle balance without manual intervention. +- The agent operates on Base and has an Agent Wallet. + +## When Not to Use + +- The agent needs 100% of its balance liquid at all times (no idle capital). +- The agent operates on a chain other than Base. +- The deposit amount is smaller than the gas cost of the transaction. + +## Required Inputs + +- Agent wallet private key or signer (via Agent Wallet). +- Base RPC URL (Alchemy or public endpoint). +- Threshold config: depositThreshold, reserveMinimum, withdrawTrigger. + +## Preconditions + +- Agent wallet funded with USDC on Base. +- Small ETH balance for gas. + +## Workflow + +1. Read the agent's USDC balance via `balanceOf(agent)` on USDC. +2. Read the agent's vault position via `balance(agent)` on the Arcis vault. +3. If wallet USDC exceeds depositThreshold: + a. Approve the vault to spend USDC (`approve(vault, amount)`). + b. Deposit the excess above reserveMinimum (`deposit(amount)`). +4. If wallet USDC drops below withdrawTrigger and the agent holds shares: + a. Withdraw the configured amount (`withdraw(shares)`). +5. Repeat on an interval (default: 60 seconds). + +## Approval Gates + +- USDC approval transaction (one-time or per-deposit). +- Deposit transaction. +- Withdraw transaction. + +## Stop Conditions + +- Vault is paused (check `paused()` before depositing). +- Insufficient gas balance. +- Deposit amount below economic threshold. + +## Evidence and Redaction Rules + +- Never log or commit the agent private key. +- Redact wallet addresses in public reports if the agent requires privacy. +- Transaction hashes are public and safe to share. + +## Validation Checklist + +- [ ] Vault is not paused. +- [ ] Deposit amount is above gas cost. +- [ ] Reserve minimum is maintained in the wallet. +- [ ] Withdrawal returns USDC to the agent wallet. + +## Output Contract + +Returns transaction hashes for each deposit/withdraw, plus current position: +`{ shares: bigint, value: bigint, walletUsdc: bigint }`. + +## Contracts (Base Mainnet) + +- Arcis Vault: `0x00325d9da832b38179ed2f0dabd4062d93e325a7` +- USDC: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` + +## Links + +- Website: https://arcis.money +- Dashboard: https://arcis.money/dashboard +- Full implementation: https://github.com/Arcis-Protocol/docs/blob/main/examples/arcis-x402-idle-capital.ts +- SDK: npm install @arcisprotocol/sdk diff --git a/showcase/arcis-protocol/skills/arcis-idle-capital/arcis-x402-idle-capital.ts b/showcase/arcis-protocol/skills/arcis-idle-capital/arcis-x402-idle-capital.ts new file mode 100644 index 0000000..17e2197 --- /dev/null +++ b/showcase/arcis-protocol/skills/arcis-idle-capital/arcis-x402-idle-capital.ts @@ -0,0 +1,694 @@ +/** + * Arcis Protocol — x402-Aware Idle Capital Manager + * + * Universal module for any AI agent framework. + * Monitors USDC balance → auto-deposits idle capital into Arcis vault → + * auto-withdraws when the agent needs funds for payments. + * + * Works with: ElizaOS, LangChain, CrewAI, OpenClaw, Hermes, Bankr, + * Virtuals, AutoGPT, Claude Agent SDK, OpenAI Agents, or any custom agent. + * + * Install: npm install viem + * Usage: import { createIdleCapitalManager } from "./arcis-x402-idle-capital" + * + * @license MIT + * @see https://arcis.money + * @see https://github.com/Arcis-Protocol + */ + +import { + createPublicClient, + createWalletClient, + http, + parseAbi, + formatUnits, + type Address, + type PublicClient, + type WalletClient, + type Account, + type Chain, +} from "viem"; +import { base } from "viem/chains"; +import { privateKeyToAccount } from "viem/accounts"; + +// ═══════════════════════════════════════════════════════════════ +// CONSTANTS — Base Mainnet +// ═══════════════════════════════════════════════════════════════ + +export const ARCIS_CONTRACTS = { + vault: "0x00325d9da832b38179ed2f0dabd4062d93e325a7" as Address, + credit: "0xdf31800e620f728297340d66acf5a306f07ce7a1" as Address, + bonds: "0xeb65d8bb08e0ea4a6bb9162d53d1b444f99681ba" as Address, + identity: "0xaa4da295dd368c0f10128654af76e3f002e20e71" as Address, + router: "0xd0c64f997ca9aa427f8834578bd7f0313f868e83" as Address, + usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Address, +}; + +// ═══════════════════════════════════════════════════════════════ +// ABIs — Minimal required functions +// ═══════════════════════════════════════════════════════════════ + +const VAULT_ABI = parseAbi([ + "function deposit(uint256 amount) external returns (uint256 shares)", + "function withdraw(uint256 shares) external returns (uint256 amount)", + "function balance(address agent) external view returns (uint256)", + "function balanceOf(address owner) external view returns (uint256)", + "function totalAssets() external view returns (uint256)", + "function exchangeRate() external view returns (uint256)", + "function previewDeposit(uint256 assets) external view returns (uint256)", + "function previewRedeem(uint256 shares) external view returns (uint256)", + "function maxDeposit(address) external view returns (uint256)", + "function paused() external view returns (bool)", +]); + +const USDC_ABI = parseAbi([ + "function balanceOf(address owner) external view returns (uint256)", + "function approve(address spender, uint256 amount) external returns (bool)", + "function allowance(address owner, address spender) external view returns (uint256)", +]); + +// ═══════════════════════════════════════════════════════════════ +// TYPES +// ═══════════════════════════════════════════════════════════════ + +export interface IdleCapitalConfig { + /** Agent's private key (0x...) — OR pass a pre-built Account */ + privateKey?: `0x${string}`; + + /** Pre-built viem Account (alternative to privateKey) */ + account?: Account; + + /** RPC URL — defaults to public Base RPC, use Alchemy/QuickNode for production */ + rpcUrl?: string; + + /** USDC threshold to trigger deposit (in human units, e.g. 100 = $100) */ + depositThreshold?: number; + + /** Minimum USDC to keep in wallet for payments (in human units) */ + reserveMinimum?: number; + + /** How much to deposit when threshold is hit (in human units). + * Defaults to: walletBalance - reserveMinimum */ + depositAmount?: number; + + /** Auto-withdraw when wallet drops below this (in human units) */ + withdrawTrigger?: number; + + /** How much to withdraw when triggered (in human units) */ + withdrawAmount?: number; + + /** Check interval in milliseconds (default: 60000 = 1 minute) */ + intervalMs?: number; + + /** Enable automatic mode (default: true). If false, only manual calls work */ + autoMode?: boolean; + + /** Event callbacks */ + onDeposit?: (amount: bigint, shares: bigint, txHash: string) => void; + onWithdraw?: (shares: bigint, amount: bigint, txHash: string) => void; + onError?: (error: Error, action: string) => void; + onCheck?: (status: BalanceStatus) => void; + + /** Custom chain (default: Base mainnet) */ + chain?: Chain; + + /** Custom vault address (default: Arcis mainnet vault) */ + vaultAddress?: Address; + + /** Custom USDC address (default: Base USDC) */ + usdcAddress?: Address; +} + +export interface BalanceStatus { + walletUsdc: bigint; + vaultPosition: bigint; + vaultShares: bigint; + totalCapital: bigint; + exchangeRate: bigint; + vaultPaused: boolean; + action: "deposit" | "withdraw" | "hold" | "paused"; + timestamp: number; +} + +export interface IdleCapitalManager { + /** Start automatic monitoring */ + start: () => void; + + /** Stop automatic monitoring */ + stop: () => void; + + /** Check balances and return status (no action taken) */ + check: () => Promise; + + /** Manually deposit USDC into vault */ + deposit: (amount: bigint) => Promise; + + /** Manually withdraw shares from vault */ + withdraw: (shares: bigint) => Promise; + + /** Manually withdraw by USDC value */ + withdrawUsdc: (usdcAmount: bigint) => Promise; + + /** Get current position */ + position: () => Promise<{ shares: bigint; value: bigint; walletUsdc: bigint }>; + + /** Whether the manager is actively monitoring */ + isRunning: boolean; + + /** Agent address */ + address: Address; +} + +// ═══════════════════════════════════════════════════════════════ +// FACTORY — Create an Idle Capital Manager +// ═══════════════════════════════════════════════════════════════ + +export function createIdleCapitalManager( + config: IdleCapitalConfig +): IdleCapitalManager { + // ── Config defaults ── + const chain = config.chain ?? base; + const vaultAddr = config.vaultAddress ?? ARCIS_CONTRACTS.vault; + const usdcAddr = config.usdcAddress ?? ARCIS_CONTRACTS.usdc; + const rpcUrl = config.rpcUrl ?? "https://mainnet.base.org"; + const intervalMs = config.intervalMs ?? 60_000; + const autoMode = config.autoMode ?? true; + + // Thresholds in raw USDC units (6 decimals) + const DECIMALS = 6; + const toRaw = (n: number) => BigInt(Math.floor(n * 10 ** DECIMALS)); + + const depositThreshold = toRaw(config.depositThreshold ?? 100); + const reserveMinimum = toRaw(config.reserveMinimum ?? 20); + const withdrawTrigger = toRaw(config.withdrawTrigger ?? 5); + const withdrawAmount = config.withdrawAmount + ? toRaw(config.withdrawAmount) + : toRaw(50); + + // ── Clients ── + const account = + config.account ?? privateKeyToAccount(config.privateKey!); + + const publicClient: PublicClient = createPublicClient({ + chain, + transport: http(rpcUrl), + }); + + const walletClient: WalletClient = createWalletClient({ + chain, + transport: http(rpcUrl), + account, + }); + + const agentAddress = account.address; + let timer: ReturnType | null = null; + let running = false; + + // ── Helpers ── + const fmt = (raw: bigint) => formatUnits(raw, DECIMALS); + + const log = (msg: string) => + console.log(`[ARCIS:IdleCapital] ${msg}`); + + // ── Core: Check balances ── + async function check(): Promise { + const [walletUsdc, vaultPosition, vaultShares, exchangeRate, paused] = + await Promise.all([ + publicClient.readContract({ + address: usdcAddr, + abi: USDC_ABI, + functionName: "balanceOf", + args: [agentAddress], + }) as Promise, + publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "balance", + args: [agentAddress], + }) as Promise, + publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "balanceOf", + args: [agentAddress], + }) as Promise, + publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "exchangeRate", + }) as Promise, + publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "paused", + }) as Promise, + ]); + + let action: BalanceStatus["action"] = "hold"; + + if (paused) { + action = "paused"; + } else if (walletUsdc > depositThreshold) { + action = "deposit"; + } else if (walletUsdc < withdrawTrigger && vaultShares > 0n) { + action = "withdraw"; + } + + const status: BalanceStatus = { + walletUsdc, + vaultPosition, + vaultShares, + totalCapital: walletUsdc + vaultPosition, + exchangeRate, + vaultPaused: paused, + action, + timestamp: Date.now(), + }; + + config.onCheck?.(status); + return status; + } + + // ── Core: Deposit ── + async function deposit(amount: bigint): Promise { + log(`Depositing $${fmt(amount)} USDC...`); + + // Check allowance + const allowance = (await publicClient.readContract({ + address: usdcAddr, + abi: USDC_ABI, + functionName: "allowance", + args: [agentAddress, vaultAddr], + })) as bigint; + + // Approve if needed + if (allowance < amount) { + log("Approving USDC..."); + const approveTx = await walletClient.writeContract({ + address: usdcAddr, + abi: USDC_ABI, + functionName: "approve", + args: [vaultAddr, amount], + }); + await publicClient.waitForTransactionReceipt({ hash: approveTx }); + log(`Approved: ${approveTx}`); + } + + // Deposit + const depositTx = await walletClient.writeContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "deposit", + args: [amount], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: depositTx, + }); + + // Read new share balance to calculate shares received + const newShares = (await publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "balanceOf", + args: [agentAddress], + })) as bigint; + + log(`Deposited $${fmt(amount)} → ${newShares} shares | tx: ${depositTx}`); + config.onDeposit?.(amount, newShares, depositTx); + + return depositTx; + } + + // ── Core: Withdraw by shares ── + async function withdraw(shares: bigint): Promise { + log(`Withdrawing ${shares} shares...`); + + const withdrawTx = await walletClient.writeContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "withdraw", + args: [shares], + }); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: withdrawTx, + }); + + const usdcReceived = (await publicClient.readContract({ + address: usdcAddr, + abi: USDC_ABI, + functionName: "balanceOf", + args: [agentAddress], + })) as bigint; + + log(`Withdrew ${shares} shares → tx: ${withdrawTx}`); + config.onWithdraw?.(shares, usdcReceived, withdrawTx); + + return withdrawTx; + } + + // ── Core: Withdraw by USDC value ── + async function withdrawUsdc(usdcAmount: bigint): Promise { + const shares = (await publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "previewDeposit", + args: [usdcAmount], + })) as bigint; + + return withdraw(shares > 0n ? shares : 1n); + } + + // ── Core: Get position ── + async function position() { + const [shares, value, walletUsdc] = await Promise.all([ + publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "balanceOf", + args: [agentAddress], + }) as Promise, + publicClient.readContract({ + address: vaultAddr, + abi: VAULT_ABI, + functionName: "balance", + args: [agentAddress], + }) as Promise, + publicClient.readContract({ + address: usdcAddr, + abi: USDC_ABI, + functionName: "balanceOf", + args: [agentAddress], + }) as Promise, + ]); + return { shares, value, walletUsdc }; + } + + // ── Auto-loop ── + async function tick() { + try { + const status = await check(); + + if (status.action === "deposit") { + const excess = status.walletUsdc - reserveMinimum; + if (excess > 0n) { + const depositAmt = config.depositAmount + ? toRaw(config.depositAmount) + : excess; + const actual = depositAmt < excess ? depositAmt : excess; + await deposit(actual); + } + } else if (status.action === "withdraw") { + const sharesToWithdraw = + status.vaultShares < withdrawAmount + ? status.vaultShares + : withdrawAmount > 0n + ? (() => { + // Calculate shares needed for withdrawAmount USDC + // Simple approximation: shares ≈ withdrawAmount (1:1 with offset) + return status.vaultShares; + })() + : status.vaultShares; + if (sharesToWithdraw > 0n) { + await withdraw(sharesToWithdraw); + } + } else if (status.action === "paused") { + log("Vault paused — skipping cycle"); + } else { + log( + `Hold — wallet: $${fmt(status.walletUsdc)} | vault: $${fmt( + status.vaultPosition + )} | total: $${fmt(status.totalCapital)}` + ); + } + } catch (err) { + const error = err instanceof Error ? err : new Error(String(err)); + log(`Error: ${error.message}`); + config.onError?.(error, "tick"); + } + } + + // ── Start/Stop ── + function start() { + if (running) return; + running = true; + log( + `Started — agent: ${agentAddress} | deposit above: $${fmt( + depositThreshold + )} | reserve: $${fmt(reserveMinimum)} | withdraw below: $${fmt( + withdrawTrigger + )} | interval: ${intervalMs / 1000}s` + ); + tick(); // Run immediately + timer = setInterval(tick, intervalMs); + } + + function stop() { + if (timer) clearInterval(timer); + timer = null; + running = false; + log("Stopped"); + } + + // Auto-start if configured + if (autoMode) { + start(); + } + + return { + start, + stop, + check, + deposit, + withdraw, + withdrawUsdc, + position, + get isRunning() { + return running; + }, + address: agentAddress, + }; +} + +// ═══════════════════════════════════════════════════════════════ +// FRAMEWORK EXAMPLES +// ═══════════════════════════════════════════════════════════════ + +/** + * ── Example 1: Standalone Agent (any framework) ── + * + * The simplest integration. Your agent earns USDC from x402 payments, + * and idle capital above $100 auto-deposits into Arcis. + * + * ```ts + * import { createIdleCapitalManager } from "./arcis-x402-idle-capital"; + * + * const manager = createIdleCapitalManager({ + * privateKey: process.env.AGENT_KEY as `0x${string}`, + * rpcUrl: process.env.BASE_RPC_URL, + * depositThreshold: 100, // Deposit when wallet has >$100 + * reserveMinimum: 20, // Always keep $20 for gas + payments + * withdrawTrigger: 5, // Withdraw when wallet drops below $5 + * intervalMs: 60_000, // Check every minute + * onDeposit: (amt, shares, tx) => console.log(`Deposited! tx: ${tx}`), + * onWithdraw: (shares, amt, tx) => console.log(`Withdrew! tx: ${tx}`), + * }); + * + * // Later: check position + * const pos = await manager.position(); + * console.log(`Vault: $${pos.value / 1_000_000n} | Wallet: $${pos.walletUsdc / 1_000_000n}`); + * + * // Manual deposit/withdraw + * await manager.deposit(50_000_000n); // $50 + * await manager.withdraw(50n); // 50 shares + * + * // Shutdown + * manager.stop(); + * ``` + */ + +/** + * ── Example 2: ElizaOS Agent ── + * + * ```ts + * // In your ElizaOS agent's plugin: + * import { createIdleCapitalManager } from "./arcis-x402-idle-capital"; + * + * export const arcisPlugin = { + * name: "arcis-idle-capital", + * init: async (agent) => { + * const manager = createIdleCapitalManager({ + * privateKey: agent.config.AGENT_KEY, + * rpcUrl: agent.config.BASE_RPC_URL, + * depositThreshold: 100, + * reserveMinimum: 20, + * onDeposit: (amt, shares, tx) => { + * agent.log(`Deposited $${Number(amt) / 1e6} into Arcis vault`); + * }, + * }); + * agent.registerAction("check-vault", () => manager.check()); + * agent.registerAction("vault-position", () => manager.position()); + * }, + * }; + * ``` + */ + +/** + * ── Example 3: LangChain / LangGraph Agent ── + * + * ```python + * # Python agents can call the manager via subprocess or HTTP + * # Option A: Use the Arcis MCP server + * # Option B: Direct contract calls with web3.py + * + * from web3 import Web3 + * + * w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org")) + * + * VAULT = "0x00325d9da832b38179ed2f0dabd4062d93e325a7" + * USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + * + * # Check balance + * balance = w3.eth.call({"to": VAULT, "data": "0xe3d670d7" + agent_addr[2:].zfill(64)}) + * position_usdc = int(balance.hex(), 16) / 1e6 + * + * # Deposit: approve + deposit + * # approve(vault, amount) + * # deposit(amount) + * ``` + */ + +/** + * ── Example 4: Virtuals Protocol Agent ── + * + * ```ts + * // Virtuals agents have their own wallet. Add idle capital management: + * import { createIdleCapitalManager } from "./arcis-x402-idle-capital"; + * + * const manager = createIdleCapitalManager({ + * privateKey: VIRTUALS_AGENT_KEY, + * depositThreshold: 500, // Higher threshold for revenue-generating agents + * reserveMinimum: 100, // Keep more in reserve for operations + * intervalMs: 300_000, // Check every 5 minutes + * }); + * ``` + */ + +/** + * ── Example 5: OpenAI Agents / Claude Agent SDK ── + * + * ```ts + * // These frameworks use tool/function calling. + * // Register Arcis as a tool: + * + * const arcisTools = [ + * { + * name: "arcis_deposit", + * description: "Deposit idle USDC into Arcis yield vault (~3.2% APY)", + * parameters: { amount: { type: "number", description: "USDC amount" } }, + * execute: async ({ amount }) => { + * const manager = createIdleCapitalManager({ + * privateKey: process.env.AGENT_KEY as `0x${string}`, + * autoMode: false, // Manual only + * }); + * const tx = await manager.deposit(BigInt(amount * 1e6)); + * return { success: true, tx }; + * }, + * }, + * { + * name: "arcis_balance", + * description: "Check vault position and wallet balance", + * parameters: {}, + * execute: async () => { + * const manager = createIdleCapitalManager({ + * privateKey: process.env.AGENT_KEY as `0x${string}`, + * autoMode: false, + * }); + * const pos = await manager.position(); + * return { + * vault_value: `$${Number(pos.value) / 1e6}`, + * wallet_usdc: `$${Number(pos.walletUsdc) / 1e6}`, + * shares: Number(pos.shares), + * }; + * }, + * }, + * ]; + * ``` + */ + +/** + * ── Example 6: x402 Payment Flow ── + * + * ```ts + * // Agent receives x402 micropayments for services. + * // Idle capital auto-deposits into Arcis between payments. + * + * import { createIdleCapitalManager } from "./arcis-x402-idle-capital"; + * + * // Start the idle capital manager + * const manager = createIdleCapitalManager({ + * privateKey: process.env.AGENT_KEY as `0x${string}`, + * rpcUrl: process.env.BASE_RPC_URL, + * depositThreshold: 50, // Deposit when earnings exceed $50 + * reserveMinimum: 10, // Keep $10 for gas + * withdrawTrigger: 2, // Withdraw when gas gets low + * withdrawAmount: 20, // Withdraw $20 at a time + * intervalMs: 120_000, // Check every 2 minutes + * }); + * + * // Your x402 payment handler + * async function handlePayment(payment: { amount: bigint; from: string }) { + * console.log(`Received ${payment.amount} USDC from ${payment.from}`); + * // Manager auto-detects the new balance on next tick + * // and deposits excess into vault + * } + * + * // When agent needs to make a payment + * async function makePayment(to: string, amount: bigint) { + * const pos = await manager.position(); + * if (pos.walletUsdc < amount) { + * // Not enough in wallet — withdraw from vault + * const needed = amount - pos.walletUsdc + 2_000_000n; // +$2 buffer + * await manager.withdrawUsdc(needed); + * } + * // Now send the payment via x402 + * // ... your x402 payment logic here + * } + * ``` + */ + +/** + * ── Example 7: MCP Integration (Claude, ChatGPT, Cursor) ── + * + * For MCP-native agents, connect directly to the Arcis MCP server: + * + * ```json + * { + * "mcpServers": { + * "arcis": { + * "command": "npx", + * "args": ["@arcisprotocol/mcp"] + * } + * } + * } + * ``` + * + * Or connect to the remote server: + * URL: https://mcp.arcis.money/mcp + * + * Available tools: + * - arcis_vault_status: TVL, rate, capacity + * - arcis_vault_balance: agent position + * - arcis_credit_status: lending pool + * - arcis_credit_tiers: ERC-8004 reputation tiers + * - arcis_credit_health: loan health check + * - arcis_contracts: all 7 contract addresses + */ + +// ═══════════════════════════════════════════════════════════════ +// EXPORTS +// ═══════════════════════════════════════════════════════════════ + +export { VAULT_ABI, USDC_ABI, base }; +export default createIdleCapitalManager; diff --git a/showcase/arcis-protocol/skills/arcis-idle-capital/examples/README.md b/showcase/arcis-protocol/skills/arcis-idle-capital/examples/README.md new file mode 100644 index 0000000..a01f0ec --- /dev/null +++ b/showcase/arcis-protocol/skills/arcis-idle-capital/examples/README.md @@ -0,0 +1,6 @@ +# Arcis Idle Capital — Example + +Demonstrates an agent depositing idle USDC into the Arcis vault and withdrawing when needed for a payment. + +- `prompt.md` — the instruction given to the agent +- `result-redacted.md` — the redacted transaction log diff --git a/showcase/arcis-protocol/skills/arcis-idle-capital/examples/prompt.md b/showcase/arcis-protocol/skills/arcis-idle-capital/examples/prompt.md new file mode 100644 index 0000000..2628f22 --- /dev/null +++ b/showcase/arcis-protocol/skills/arcis-idle-capital/examples/prompt.md @@ -0,0 +1,8 @@ +# Prompt + +You are an agent with idle USDC in your wallet. Monitor your balance. When it +exceeds $100, deposit the excess (keeping $20 in reserve) into the Arcis vault +to earn yield. When your balance drops below $5, withdraw $25 from the vault. + +Vault: 0x00325d9da832b38179ed2f0dabd4062d93e325a7 +USDC: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 diff --git a/showcase/arcis-protocol/skills/arcis-idle-capital/examples/result-redacted.md b/showcase/arcis-protocol/skills/arcis-idle-capital/examples/result-redacted.md new file mode 100644 index 0000000..86f9b62 --- /dev/null +++ b/showcase/arcis-protocol/skills/arcis-idle-capital/examples/result-redacted.md @@ -0,0 +1,27 @@ +# Result (Redacted) + +Agent wallet: 0xbae3...6944 (partially redacted) +Network: Base mainnet (8453) + +## Execution Log + +1. Read wallet USDC balance: $101.50 +2. Balance exceeds $100 deposit threshold → excess above $1.50 reserve = $100.00 +3. Checked vault paused(): false → proceed +4. previewDeposit(100000000) → 100 shares expected +5. Approved vault USDC allowance — tx confirmed on Basescan +6. deposit(100000000) — tx confirmed on Basescan +7. Received 100 raUSDC shares (matches preview) + +## Verifiable On-Chain State (live) + +- Vault position: `balance(0xbae3...6944)` → 100000000 ($100.00) +- Vault totalAssets: $120.00 (includes other depositors) +- Reserve/Deployed split: $44.40 / $75.60 (70% in Aave V3) +- Verify: https://basescan.org/address/0x00325d9da832b38179ed2f0dabd4062d93e325a7 + +## Redaction Notes + +- Private key: never logged +- Full wallet address: redacted to first/last 4 bytes +- Transaction hashes: omitted here; verifiable via the vault's Basescan event log