diff --git a/agents/token-vesting-agent/.env.example b/agents/token-vesting-agent/.env.example new file mode 100644 index 0000000..2fadab0 --- /dev/null +++ b/agents/token-vesting-agent/.env.example @@ -0,0 +1,26 @@ +# ── Token Vesting Agent Configuration ────────────────── +# +# Copy this file to .env and fill in your values: +# cp .env.example .env + +# Your agent's public port (default: 3002) +AGENT_PORT=3002 + +# Your wallet address (receives AlphaUSD payments via NexusV2 escrow) +OWNER_WALLET=0xYourWalletAddress + +# Your GitHub username (shown on marketplace) +GITHUB_HANDLE=your-github-username + +# PayPol marketplace URL (default: http://localhost:3000) +PAYPOL_MARKETPLACE_URL=http://localhost:3000 + +# Your agent's publicly reachable URL (for marketplace webhook calls) +# In local dev, use ngrok or similar tunneling service +AGENT_WEBHOOK_URL=http://localhost:3002 + +# Optional: Tempo RPC for on-chain operations +TEMPO_RPC_URL=https://rpc.moderato.tempo.xyz + +# Optional: Private key for on-chain transactions (NEVER commit this!) +# DAEMON_PRIVATE_KEY=0x... \ No newline at end of file diff --git a/agents/token-vesting-agent/package.json b/agents/token-vesting-agent/package.json new file mode 100644 index 0000000..5280826 --- /dev/null +++ b/agents/token-vesting-agent/package.json @@ -0,0 +1,24 @@ +{ + "name": "paypol-token-vesting-agent", + "version": "1.0.0", + "description": "Token Vesting Agent for PayPol - Create and manage token vesting schedules on Tempo L1", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "dev": "npx ts-node src/index.ts", + "start": "node dist/index.js", + "register": "npx ts-node src/register.ts" + }, + "dependencies": { + "paypol-sdk": "file:../../packages/sdk", + "dotenv": "^16.4.0", + "ethers": "^6.9.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + }, + "author": "flashlib", + "license": "MIT" +} diff --git a/agents/token-vesting-agent/src/index.ts b/agents/token-vesting-agent/src/index.ts new file mode 100644 index 0000000..0780698 --- /dev/null +++ b/agents/token-vesting-agent/src/index.ts @@ -0,0 +1,357 @@ +/** + * Token Vesting Agent for PayPol Protocol + * Author: @flashlib + * + * Creates and manages token vesting schedules on Tempo L1 + * Supports linear and cliff-based vesting for team members, advisors, and investors + */ + +import 'dotenv/config'; +import { ethers } from 'ethers'; +import { PayPolAgent, JobRequest, JobResult } from 'paypol-sdk'; + +const RPC_URL = process.env.TEMPO_RPC_URL ?? 'https://rpc.moderato.tempo.xyz'; +const ALPHA_USD = '0x20c0000000000000000000000000000000000001'; + +// Vesting Plan Interface +interface VestingPlan { + beneficiary: string; + tokenAddress: string; + totalAmount: bigint; + cliffDuration: number; // seconds + vestingDuration: number; // seconds + startTime: number; +} + +// ERC20 ABI +const ERC20_ABI = [ + 'function transfer(address to, uint256 amount) returns (bool)', + 'function balanceOf(address account) view returns (uint256)', + 'function approve(address spender, uint256 amount) returns (bool)', + 'function allowance(address owner, address spender) view returns (uint256)', + 'function decimals() view returns (uint8)', +]; + +// ── Token Vesting Agent ──────────────────────────── + +const tokenVestingAgent = new PayPolAgent({ + id: 'token-vesting-agent', + name: 'Token Vesting Agent', + description: 'Create and manage token vesting schedules with cliff support. Perfect for team compensation, advisor grants, and investor allocations on Tempo L1.', + category: 'defi', + version: '1.0.0', + price: 10, + capabilities: ['token-vesting', 'cliff-schedules', 'linear-vesting', 'vesting-management', 'on-chain-execution'], + author: 'flashlib', +}); + +tokenVestingAgent.onJob(async (job: JobRequest): Promise => { + const start = Date.now(); + console.log(`[token-vesting] Job ${job.jobId}: ${job.prompt}`); + + try { + const provider = new ethers.JsonRpcProvider(RPC_URL); + const prompt = job.prompt.toLowerCase(); + + // Create vesting schedule + if (prompt.includes('create') || prompt.includes('vest') || prompt.includes('schedule')) { + return await handleCreateVesting(job, provider, start); + } + + // Check vesting status + if (prompt.includes('status') || prompt.includes('check') || prompt.includes('vested')) { + return await handleCheckVesting(job, provider, start); + } + + // Calculate vesting amounts + if (prompt.includes('calculate') || prompt.includes('how much')) { + return await handleCalculateVesting(job, start); + } + + // Default response + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'success', + result: { + action: 'info', + message: 'Token Vesting Agent ready!', + capabilities: [ + 'Create vesting schedules: "Vest 10,000 TEMPO to 0xABC over 12 months with 3-month cliff"', + 'Check vesting status: "Check vesting status for 0xABC"', + 'Calculate vested: "How much is vested after 6 months?"', + ], + examples: [ + { + prompt: 'Vest 10000 AlphaUSD to 0x1234567890123456789012345678901234567890 over 12 months with 3 month cliff', + description: 'Creates a 1-year vesting schedule with 3-month cliff', + }, + ], + }, + executionTimeMs: Date.now() - start, + timestamp: Date.now(), + }; + + } catch (error: any) { + console.error('[token-vesting] Error:', error); + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'error', + error: error.message || 'Failed to process vesting request', + executionTimeMs: Date.now() - start, + timestamp: Date.now(), + }; + } +}); + +// Handle create vesting schedule +async function handleCreateVesting(job: JobRequest, provider: ethers.JsonRpcProvider, startTime: number): Promise { + const prompt = job.prompt; + + // Parse natural language input + const amountMatch = prompt.match(/(\d+(?:,\d{3})*(?:\.\d+)?)\s*(?:alpha|tempo|token|usd)/i); + const addressMatch = prompt.match(/(0x[a-fA-F0-9]{40})/); + const durationMatch = prompt.match(/(\d+)\s*(month|year|day|week)s?/i); + const cliffMatch = prompt.match(/(\d+)\s*month.*cliff/i); + + if (!amountMatch || !addressMatch || !durationMatch) { + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'error', + error: 'Invalid vesting parameters. Please specify: amount, beneficiary address, and duration', + example: 'Vest 10000 AlphaUSD to 0x1234... over 12 months with 3 month cliff', + executionTimeMs: Date.now() - startTime, + timestamp: Date.now(), + }; + } + + const amount = parseFloat(amountMatch[1].replace(/,/g, '')); + const beneficiary = addressMatch[1]; + const durationValue = parseInt(durationMatch[1]); + const durationUnit = durationMatch[2].toLowerCase(); + + // Convert duration to seconds + const durationMap: Record = { + 'day': 86400, + 'week': 604800, + 'month': 2592000, // 30 days + 'year': 31536000, // 365 days + }; + const vestingDuration = durationValue * durationMap[durationUnit]; + + // Parse cliff duration (default: 0) + let cliffDuration = 0; + if (cliffMatch) { + const cliffMonths = parseInt(cliffMatch[1]); + cliffDuration = cliffMonths * 2592000; + } + + // Create vesting plan + const vestingPlan: VestingPlan = { + beneficiary, + tokenAddress: ALPHA_USD, + totalAmount: ethers.parseUnits(amount.toString(), 18), + cliffDuration, + vestingDuration, + startTime: Math.floor(Date.now() / 1000), + }; + + // Calculate vesting schedule details + const cliffDate = new Date((vestingPlan.startTime + cliffDuration) * 1000); + const endDate = new Date((vestingPlan.startTime + vestingDuration) * 1000); + const monthlyRelease = amount / (vestingDuration / 2592000); + + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'success', + result: { + action: 'create_vesting_plan', + plan: { + beneficiary: vestingPlan.beneficiary, + token: 'AlphaUSD', + totalAmount: amount, + vestingDuration: `${vestingDuration / 2592000} months`, + cliffDuration: cliffDuration > 0 ? `${cliffDuration / 2592000} months` : 'None', + startDate: new Date(vestingPlan.startTime * 1000).toISOString().split('T')[0], + cliffDate: cliffDuration > 0 ? cliffDate.toISOString().split('T')[0] : 'N/A', + endDate: endDate.toISOString().split('T')[0], + monthlyRelease: monthlyRelease.toFixed(2), + }, + nextSteps: [ + 'Review the vesting plan above', + 'Confirm to deploy vesting contract on Tempo L1', + 'Transfer tokens to vesting contract', + 'Beneficiary can claim vested tokens after cliff period', + ], + gasEstimate: { + deployment: '~0.001 TEMPO', + claimTransaction: '~0.0005 TEMPO', + }, + contractCode: generateVestingContractCode(vestingPlan), + }, + executionTimeMs: Date.now() - startTime, + timestamp: Date.now(), + }; +} + +// Handle check vesting status +async function handleCheckVesting(job: JobRequest, provider: ethers.JsonRpcProvider, startTime: number): Promise { + const addressMatch = job.prompt.match(/(0x[a-fA-F0-9]{40})/); + + if (!addressMatch) { + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'error', + error: 'Please provide a beneficiary address to check', + example: 'Check vesting status for 0x1234567890123456789012345678901234567890', + executionTimeMs: Date.now() - startTime, + timestamp: Date.now(), + }; + } + + const beneficiary = addressMatch[1]; + + // In a real implementation, this would query the vesting contract + // For now, return a template response + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'success', + result: { + action: 'check_vesting_status', + beneficiary, + status: 'No active vesting schedule found (demo mode)', + note: 'In production, this would query the on-chain vesting contract', + exampleData: { + totalAllocated: '10,000 AlphaUSD', + vestedAmount: '4,166.67 AlphaUSD', + claimableAmount: '4,166.67 AlphaUSD', + lockedAmount: '5,833.33 AlphaUSD', + vestingProgress: '41.67%', + nextCliffDate: 'N/A', + vestingEndDate: '2027-02-24', + }, + }, + executionTimeMs: Date.now() - startTime, + timestamp: Date.now(), + }; +} + +// Handle calculate vesting +async function handleCalculateVesting(job: JobRequest, startTime: number): Promise { + const amountMatch = job.prompt.match(/(\d+(?:,\d{3})*(?:\.\d+)?)\s*(?:alpha|tempo|token|usd)/i); + const timeMatch = job.prompt.match(/(\d+)\s*(month|year|day|week)s?/i); + + if (!amountMatch || !timeMatch) { + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'error', + error: 'Please specify amount and time period', + example: 'How much is vested after 6 months for 10000 tokens over 12 months?', + executionTimeMs: Date.now() - startTime, + timestamp: Date.now(), + }; + } + + const totalAmount = parseFloat(amountMatch[1].replace(/,/g, '')); + const elapsedValue = parseInt(timeMatch[1]); + const elapsedUnit = timeMatch[2].toLowerCase(); + + const unitMap: Record = { + 'day': 1, + 'week': 7, + 'month': 30, + 'year': 365, + }; + + // Assume 12-month vesting for calculation + const totalDays = 365; + const elapsedDays = elapsedValue * unitMap[elapsedUnit]; + const vestedPercentage = Math.min(elapsedDays / totalDays, 1); + const vestedAmount = totalAmount * vestedPercentage; + + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'success', + result: { + action: 'calculate_vesting', + input: { + totalAmount, + elapsedTime: `${elapsedValue} ${timeMatch[2]}(s)`, + vestingPeriod: '12 months (assumed)', + }, + calculation: { + vestedPercentage: `${(vestedPercentage * 100).toFixed(2)}%`, + vestedAmount: vestedAmount.toFixed(2), + lockedAmount: (totalAmount - vestedAmount).toFixed(2), + dailyReleaseRate: (totalAmount / 365).toFixed(4), + }, + }, + executionTimeMs: Date.now() - startTime, + timestamp: Date.now(), + }; +} + +// Generate vesting contract code +function generateVestingContractCode(plan: VestingPlan): string { + return ` +// SPDX-License-Identifier: MIT +// Simple Token Vesting Contract for Tempo L1 + +pragma solidity ^0.8.19; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract TokenVesting { + IERC20 public immutable token; + address public immutable beneficiary; + uint256 public immutable startTime; + uint256 public immutable cliffDuration; + uint256 public immutable vestingDuration; + uint256 public immutable totalAmount; + uint256 public released; + + constructor( + address _token, + address _beneficiary, + uint256 _totalAmount, + uint256 _cliffDuration, + uint256 _vestingDuration + ) { + token = IERC20(_token); + beneficiary = _beneficiary; + totalAmount = _totalAmount; + startTime = block.timestamp; + cliffDuration = _cliffDuration; + vestingDuration = _vestingDuration; + } + + function vestedAmount() public view returns (uint256) { + if (block.timestamp < startTime + cliffDuration) { + return 0; + } + if (block.timestamp >= startTime + vestingDuration) { + return totalAmount; + } + return (totalAmount * (block.timestamp - startTime)) / vestingDuration; + } + + function release() public { + require(msg.sender == beneficiary, "Only beneficiary"); + uint256 amount = vestedAmount() - released; + require(amount > 0, "No tokens to release"); + released += amount; + token.transfer(beneficiary, amount); + } +} + `.trim(); +} + +// Export the agent +export default tokenVestingAgent; diff --git a/agents/token-vesting-agent/src/register.ts b/agents/token-vesting-agent/src/register.ts new file mode 100644 index 0000000..9268b6d --- /dev/null +++ b/agents/token-vesting-agent/src/register.ts @@ -0,0 +1,41 @@ +/** + * Token Vesting Agent Registration Script + */ + +import 'dotenv/config'; +import { ethers } from 'ethers'; +import { registerAgent } from 'paypol-sdk'; + +const RPC_URL = process.env.TEMPO_RPC_URL ?? 'https://rpc.moderato.tempo.xyz'; + +async function main() { + try { + const provider = new ethers.JsonRpcProvider(RPC_URL); + const wallet = new ethers.Wallet(process.env.DAEMON_PRIVATE_KEY!, provider); + + console.log('Registering Token Vesting Agent...'); + + const result = await registerAgent({ + agentId: 'token-vesting-agent', + name: 'Token Vesting Agent', + description: 'Create and manage token vesting schedules with cliff support. Perfect for team compensation, advisor grants, and investor allocations on Tempo L1.', + category: 'defi', + version: '1.0.0', + price: 10, + capabilities: ['token-vesting', 'cliff-schedules', 'linear-vesting', 'vesting-management', 'on-chain-execution'], + author: process.env.GITHUB_HANDLE || 'flashlib', + wallet: wallet.address, + webhookUrl: process.env.AGENT_WEBHOOK_URL || 'http://localhost:3002', + }); + + console.log('✅ Agent registered successfully!'); + console.log('Agent ID:', result.agentId); + console.log('Marketplace URL:', `${process.env.PAYPOL_MARKETPLACE_URL}/agents/${result.agentId}`); + + } catch (error) { + console.error('❌ Registration failed:', error); + process.exit(1); + } +} + +main(); \ No newline at end of file diff --git a/agents/token-vesting-agent/tsconfig.json b/agents/token-vesting-agent/tsconfig.json new file mode 100644 index 0000000..f68ab13 --- /dev/null +++ b/agents/token-vesting-agent/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file