diff --git a/agents/gas-estimator/.env.example b/agents/gas-estimator/.env.example new file mode 100644 index 0000000..82a4e86 --- /dev/null +++ b/agents/gas-estimator/.env.example @@ -0,0 +1,12 @@ +AGENT_PORT=3016 +GITHUB_HANDLE=sungdark +OWNER_WALLET=0x0000000000000000000000000000000000000001 +AGENT_WEBHOOK_URL=http://localhost:3016 +PAYPOL_MARKETPLACE_URL=http://localhost:3000 +CACHE_TTL_MS=15000 +TEMPO_RPC_URL=https://rpc.moderato.tempo.xyz +ETHEREUM_RPC_URL=https://ethereum-rpc.publicnode.com +ARBITRUM_RPC_URL=https://arbitrum-one-rpc.publicnode.com +BASE_RPC_URL=https://base-rpc.publicnode.com +COINGECKO_URL=https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd +TEMPO_TOKEN_USD=0 diff --git a/agents/gas-estimator/package-lock.json b/agents/gas-estimator/package-lock.json new file mode 100644 index 0000000..ad03602 --- /dev/null +++ b/agents/gas-estimator/package-lock.json @@ -0,0 +1,377 @@ +{ + "name": "paypol-gas-estimator-agent", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "paypol-gas-estimator-agent", + "version": "1.0.0", + "dependencies": { + "dotenv": "^16.4.0", + "ethers": "^6.13.0", + "paypol-sdk": "^1.0.2" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + } + }, + "../../packages/sdk": { + "name": "paypol-sdk", + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "@paypol-protocol/aps-1": "^1.0.0", + "axios": "^1.13.5", + "express": "^4.21.0" + }, + "devDependencies": { + "@types/express": "^5.0.0", + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz", + "integrity": "sha512-by3/Z0Qp+L9cAySEsSNNwZ6WWw8ywgGLPQGgbQDhNRSitqYgkgp4pErd23ZSCavbtUA2CN4jQtoB3T8nk4j3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ethers": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/paypol-sdk": { + "resolved": "../../packages/sdk", + "link": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/agents/gas-estimator/package.json b/agents/gas-estimator/package.json new file mode 100644 index 0000000..aa0c4ec --- /dev/null +++ b/agents/gas-estimator/package.json @@ -0,0 +1,23 @@ +{ + "name": "paypol-gas-estimator-agent", + "version": "1.0.0", + "description": "Multi-chain gas estimation agent for Tempo, Ethereum, Arbitrum, and Base", + "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" + }, + "author": "sungdark", + "dependencies": { + "dotenv": "^16.4.0", + "ethers": "^6.13.0", + "paypol-sdk": "^1.0.2" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + } +} diff --git a/agents/gas-estimator/src/index.ts b/agents/gas-estimator/src/index.ts new file mode 100644 index 0000000..4aabc21 --- /dev/null +++ b/agents/gas-estimator/src/index.ts @@ -0,0 +1,149 @@ +import 'dotenv/config'; +import { ethers } from 'ethers'; +import { PayPolAgent, JobRequest, JobResult } from 'paypol-sdk'; + +type Op = 'transfer' | 'erc20_transfer' | 'contract_deploy'; + +type ChainCfg = { + name: string; + slug: string; + rpcUrl: string; + tokenSymbol: string; + tokenUsd?: number; + speed: string; +}; + +const OPS: Record = { + transfer: 21_000, + erc20_transfer: 65_000, + contract_deploy: 1_200_000, +}; + +const CHAINS: ChainCfg[] = [ + { name: 'Tempo L1', slug: 'tempo', rpcUrl: process.env.TEMPO_RPC_URL ?? 'https://rpc.moderato.tempo.xyz', tokenSymbol: 'TEMPO', tokenUsd: Number(process.env.TEMPO_TOKEN_USD ?? 0), speed: '2s' }, + { name: 'Ethereum', slug: 'ethereum', rpcUrl: process.env.ETHEREUM_RPC_URL ?? 'https://ethereum-rpc.publicnode.com', tokenSymbol: 'ETH', speed: '12s' }, + { name: 'Arbitrum', slug: 'arbitrum', rpcUrl: process.env.ARBITRUM_RPC_URL ?? 'https://arbitrum-one-rpc.publicnode.com', tokenSymbol: 'ETH', speed: '2s' }, + { name: 'Base', slug: 'base', rpcUrl: process.env.BASE_RPC_URL ?? 'https://base-rpc.publicnode.com', tokenSymbol: 'ETH', speed: '2s' }, +]; + +const CACHE_TTL_MS = Number(process.env.CACHE_TTL_MS ?? 15_000); +let cache: { at: number; value: any } | null = null; + +const agent = new PayPolAgent({ + id: 'gas-estimation-agent', + name: 'Gas Estimation Agent', + description: 'Compares gas costs across Tempo L1, Ethereum, Arbitrum, and Base with USD estimates and cheapest-chain recommendation.', + category: 'analytics', + version: '1.0.0', + price: 6, + capabilities: ['gas-estimation', 'multichain-comparison', 'cost-optimization'], + author: process.env.GITHUB_HANDLE ?? 'community', +}); + +async function fetchEthUsd(): Promise { + const url = process.env.COINGECKO_URL ?? 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd'; + const res = await fetch(url); + if (!res.ok) throw new Error(`price fetch failed: ${res.status}`); + const json = await res.json() as any; + const price = Number(json?.ethereum?.usd); + if (!Number.isFinite(price) || price <= 0) throw new Error('invalid ETH/USD'); + return price; +} + +async function getGasGwei(chain: ChainCfg): Promise { + const provider = new ethers.JsonRpcProvider(chain.rpcUrl); + const fee = await provider.getFeeData(); + const wei = fee.gasPrice ?? fee.maxFeePerGas; + if (!wei) throw new Error('missing gas price'); + return Number(ethers.formatUnits(wei, 'gwei')); +} + +function opLabel(op: Op): string { + if (op === 'transfer') return 'Simple Transfer'; + if (op === 'erc20_transfer') return 'ERC-20 Transfer'; + return 'Contract Deploy'; +} + +agent.onJob(async (job: JobRequest): Promise => { + const start = Date.now(); + try { + const payload = (job.payload ?? {}) as any; + const op = (payload.operation ?? 'erc20_transfer') as Op; + const operation = OPS[op] ? op : 'erc20_transfer'; + const gasUnits = OPS[operation]; + + if (cache && Date.now() - cache.at < CACHE_TTL_MS && cache.value?.operation === operation) { + return { jobId: job.jobId, agentId: job.agentId, status: 'success', result: { ...cache.value, cache: 'hit' }, executionTimeMs: Date.now() - start, timestamp: Date.now() }; + } + + let ethUsd = 0; + try { ethUsd = await fetchEthUsd(); } catch { ethUsd = 0; } + + const estimates = await Promise.all(CHAINS.map(async (chain) => { + try { + const gasPrice = await getGasGwei(chain); + const tokenUsd = chain.tokenSymbol === 'ETH' ? ethUsd : (chain.tokenUsd ?? 0); + const costNative = gasPrice * 1e-9 * gasUnits; + const costUsd = costNative * tokenUsd; + + return { + chain: chain.name, + gasPrice: `${gasPrice.toFixed(4)} gwei`, + gasUnits, + costNative: `${costNative.toFixed(8)} ${chain.tokenSymbol}`, + cost: `$${costUsd.toFixed(4)}`, + speed: chain.speed, + status: 'ok', + }; + } catch (err: any) { + return { + chain: chain.name, + gasPrice: 'n/a', + gasUnits, + costNative: 'n/a', + cost: 'n/a', + speed: chain.speed, + status: `rpc_error: ${err.message}`, + }; + } + })); + + const valid = estimates.filter((e) => e.status === 'ok' && e.cost !== 'n/a'); + const cheapest = valid + .map((e) => ({ chain: e.chain, usd: Number(e.cost.slice(1)) })) + .sort((a, b) => a.usd - b.usd)[0]; + + const result = { + operation: opLabel(operation), + estimates, + recommendation: cheapest + ? `${cheapest.chain} is currently the cheapest for ${opLabel(operation)}.` + : 'No chain estimates available right now due to RPC errors.', + timestamp: new Date().toISOString(), + cacheTtlMs: CACHE_TTL_MS, + cache: 'miss', + notes: [ + 'Tempo USD conversion uses TEMPO_TOKEN_USD env (default 0).', + 'ETH/USD is shared by Ethereum, Arbitrum, and Base.', + ], + }; + + cache = { at: Date.now(), value: { ...result, operation } }; + + return { + jobId: job.jobId, + agentId: job.agentId, + status: 'success', + result, + executionTimeMs: Date.now() - start, + timestamp: Date.now(), + }; + } catch (err: any) { + return { jobId: job.jobId, agentId: job.agentId, status: 'error', error: err.message ?? String(err), executionTimeMs: Date.now() - start, timestamp: Date.now() }; + } +}); + +const PORT = Number(process.env.AGENT_PORT ?? 3016); +agent.listen(PORT, () => { + console.log(`[gas-estimation-agent] ready on :${PORT}`); +}); diff --git a/agents/gas-estimator/src/register.ts b/agents/gas-estimator/src/register.ts new file mode 100644 index 0000000..9095eef --- /dev/null +++ b/agents/gas-estimator/src/register.ts @@ -0,0 +1,32 @@ +import 'dotenv/config'; +import { registerAgent } from 'paypol-sdk'; + +async function main() { + const webhookUrl = process.env.AGENT_WEBHOOK_URL ?? 'http://localhost:3016'; + const ownerWallet = process.env.OWNER_WALLET; + const githubHandle = process.env.GITHUB_HANDLE; + const marketplaceUrl = process.env.PAYPOL_MARKETPLACE_URL ?? 'http://localhost:3000'; + + if (!ownerWallet) throw new Error('OWNER_WALLET is required'); + + const result = await registerAgent({ + id: 'gas-estimation-agent', + name: 'Gas Estimation Agent', + description: 'Compares gas costs across Tempo L1, Ethereum, Arbitrum, and Base and recommends cheapest execution route.', + category: 'analytics', + version: '1.0.0', + price: 6, + capabilities: ['gas-estimation', 'multichain-comparison', 'cost-optimization'], + webhookUrl, + ownerWallet, + githubHandle, + author: githubHandle ?? 'community', + }, marketplaceUrl); + + console.log(result); +} + +main().catch((err) => { + console.error(err.message ?? err); + process.exit(1); +}); diff --git a/agents/gas-estimator/tsconfig.json b/agents/gas-estimator/tsconfig.json new file mode 100644 index 0000000..170ac05 --- /dev/null +++ b/agents/gas-estimator/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": { "target": "ES2022", "module": "commonjs", "lib": ["ES2022"], "outDir": "dist", "rootDir": "src", "strict": false, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "sourceMap": true }, + "include": ["src"], "exclude": ["node_modules", "dist"] +}