diff --git a/.env.example b/.env.example index 2ef465f..ef37214 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,42 @@ -# Example .env for sovereignagent (DO NOT commit real secrets) -# Replace placeholders with your test wallet and Base RPC information -PRIVATE_KEY=your_test_wallet_private_key -RPC_URL=https://base-goerli.blockscout.com -CHAIN_ID=84531 -SUPER_TOKEN=fDAIx -SOVEREIGN_AGENT_ADDRESS=0xYourSovereignAgentAddress +# Example .env for PhantomOperator (DO NOT commit real secrets) +# Replace placeholders with your real wallet and Base RPC information. + +# ── Core wallet / chain ────────────────────────────────────────────────────── +PRIVATE_KEY=your_wallet_private_key +PHANTOM_OPERATOR_ADDRESS=0xYourPhantomOperatorAddress + +# ── Base chain RPC (mainnet for live registrations) ────────────────────────── +BASE_RPC_URL=https://mainnet.base.org +# Legacy fields kept for SuperfluidService compatibility: +RPC_URL=https://mainnet.base.org +CHAIN_ID=8453 + +# ── Superfluid streaming token ─────────────────────────────────────────────── +SUPER_TOKEN=USDCx FLOW_RATE=385802469135802 +# ── Agent registration ──────────────────────────────────────────────────────── +# Public HTTPS URL where the agent accepts requests (used in x402 / Bazaar) +AGENT_SERVICE_ENDPOINT=https://your-agent-domain.example.com/api +# IPFS or HTTPS URI pointing to agent metadata JSON +AGENT_METADATA_URI=ipfs://Qm... +# Price per request in USDCx (6-decimal USDC equivalent, e.g. 0.001 = $0.001) +PRICE_PER_REQUEST=0.001 + +# ── CryptoSkill.org ─────────────────────────────────────────────────────────── +CRYPTOSKILL_API_KEY=your_cryptoskill_api_key +# Override only if the API base URL changes +# CRYPTOSKILL_API=https://cryptoskill.org/api/v1 + +# ── Coinbase Developer Platform (CDP) x402 Bazaar ──────────────────────────── +CDP_API_KEY=your_cdp_api_key +# Override only if needed +# X402_BAZAAR_URL=https://api.developer.coinbase.com/rpc/v1/base + +# ── Superfluid x402 Facilitator ─────────────────────────────────────────────── +# Override only if needed +# X402_FACILITATOR_URL=https://x402.superfluid.org + # Notes: # - Keep this file locally and never commit `.env` to source control. # - For CI, add the same keys to GitHub Actions Secrets instead. diff --git a/.github/ISSUE_TEMPLATE/register_agent.md b/.github/ISSUE_TEMPLATE/register_agent.md index 70960a5..f68e972 100644 --- a/.github/ISSUE_TEMPLATE/register_agent.md +++ b/.github/ISSUE_TEMPLATE/register_agent.md @@ -1,5 +1,5 @@ --- -name: Register Sovereign Agent +name: Register PhantomOperator Agent about: Register your agent identity (normancomics.*) for priority payouts and marketplace listing title: "Register Agent: " labels: onboarding diff --git a/.github/workflows/superfluid-test.yml b/.github/workflows/superfluid-test.yml index 229358f..8530b7b 100644 --- a/.github/workflows/superfluid-test.yml +++ b/.github/workflows/superfluid-test.yml @@ -25,6 +25,6 @@ jobs: RPC_URL: ${{ secrets.RPC_URL }} CHAIN_ID: ${{ secrets.CHAIN_ID }} SUPER_TOKEN: ${{ secrets.SUPER_TOKEN }} - SOVEREIGN_AGENT_ADDRESS: ${{ secrets.SOVEREIGN_AGENT_ADDRESS }} + PHANTOM_OPERATOR_ADDRESS: ${{ secrets.PHANTOM_OPERATOR_ADDRESS }} FLOW_RATE: ${{ secrets.FLOW_RATE }} run: node test.js diff --git a/SovereignAgent.js b/PhantomOperator.js similarity index 96% rename from SovereignAgent.js rename to PhantomOperator.js index 9c8de29..30376d7 100644 --- a/SovereignAgent.js +++ b/PhantomOperator.js @@ -3,7 +3,7 @@ const { startSuperfluidFlow, stopSuperfluidFlow } = require('./services/Superflu const SearchAgent = require('./agents/SearchAgent'); const BrokerAgent = require('./agents/BrokerAgent'); -class SovereignAgent { +class PhantomOperator { constructor() {} async startDataRemovalTask(userInfo) { @@ -52,4 +52,4 @@ class SovereignAgent { } } -module.exports = SovereignAgent; +module.exports = PhantomOperator; diff --git a/README.md b/README.md index 9ffd941..a704cfb 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -# sovereignagent +# PhantomOperator -SovereignAgent — automated privacy-removal orchestration with real-time Superfluid payouts on Base. +PhantomOperator — automated privacy-removal orchestration with real-time Superfluid payouts on Base. -Why SovereignAgent? -- Automated data-broker opt-outs and prioritized threat remediation +Why PhantomOperator? +- Automated data-broker opt-outs and prioritised threat remediation - Real-time micropayments via Superfluid USDCx on Base (streams & IDAs) +- On-chain agent identity & skill registration (8004scan.io, CryptoSkill.org) +- x402 HTTP payment protocol support (Superfluid facilitator + CDP Bazaar) - Secure, sandboxed agents and enterprise-ready orchestration Getting started 1) Configure environment - - Copy `.env.example` to `.env` and fill in your test keys (never commit `.env`). + - Copy `.env.example` to `.env` and fill in your keys (never commit `.env`). + - At minimum set `PRIVATE_KEY` and `PHANTOM_OPERATOR_ADDRESS`. 2) Install Node dependencies ```bash @@ -22,15 +25,72 @@ npm ci node test.js ``` +--- + +Agent Registration + +The `npm run register` command (or `node scripts/registerAgent.js`) runs a 5-step pipeline for **each** sub-agent: + +| Step | Registry | Contract / URL | +|------|----------|----------------| +| 1 | On-chain Identity Registry | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` (Base) | +| 2 | On-chain Reputation/Skill Registry | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` (Base) | +| 3 | CryptoSkill.org | https://cryptoskill.org/ | +| 4 | Superfluid x402 Facilitator | https://x402.superfluid.org/ | +| 5 | CDP x402 Bazaar | https://docs.cdp.coinbase.com/x402/bazaar | + +Required `.env` variables for registration: + +| Variable | Description | +|----------|-------------| +| `PRIVATE_KEY` | Agent owner wallet private key (signs on-chain txs) | +| `PHANTOM_OPERATOR_ADDRESS` | Deployed agent wallet address | +| `BASE_RPC_URL` | Base mainnet JSON-RPC (default: `https://mainnet.base.org`) | +| `AGENT_SERVICE_ENDPOINT` | Public HTTPS URL where the agent accepts requests | +| `AGENT_METADATA_URI` | IPFS or HTTPS URI to the agent metadata JSON | +| `CRYPTOSKILL_API_KEY` | CryptoSkill.org API key | +| `CDP_API_KEY` | Coinbase Developer Platform API key (for Bazaar listing) | + +Optional: + +| Variable | Default | +|----------|---------| +| `PRICE_PER_REQUEST` | `0.001` (USDCx) | +| `X402_FACILITATOR_URL` | `https://x402.superfluid.org` | +| `X402_BAZAAR_URL` | `https://api.developer.coinbase.com/rpc/v1/base` | +| `CRYPTOSKILL_API` | `https://cryptoskill.org/api/v1` | + +Run registration: +```bash +npm run register +``` + +Each step is fault-tolerant — if one registry is unavailable the script logs the error and continues with the rest. + +--- + +Services + +| File | Purpose | +|------|---------| +| `services/RegistryService.js` | Ethers.js calls to the identity & reputation registry contracts on Base | +| `services/SkillRegistryService.js` | REST calls to the CryptoSkill.org API | +| `services/X402Service.js` | Superfluid x402 facilitator + CDP Bazaar registration | +| `services/SuperfluidService.js` | Superfluid streaming (start/stop flow) | + Registration & Priority Payouts -Want priority payouts and featured placement? Create a registration issue using the `Register Sovereign Agent` template in `.github/ISSUE_TEMPLATE/register_agent.md` and include your ENS / on-chain identity (e.g., `normancomics.base.eth`). +Want priority payouts and featured placement? Create a registration issue using the `Register PhantomOperator Agent` template in `.github/ISSUE_TEMPLATE/register_agent.md` and include your ENS / on-chain identity (e.g., `normancomics.base.eth`). Files added in this repo -- `SovereignAgent.js` — orchestrator +- `PhantomOperator.js` — orchestrator - `agents/SearchAgent.js` — search & threat analysis - `agents/BrokerAgent.js` — data broker automation (placeholder) +- `services/RegistryService.js` — on-chain identity & reputation registry +- `services/SkillRegistryService.js` — CryptoSkill.org integration +- `services/X402Service.js` — Superfluid x402 & CDP Bazaar integration - `services/SuperfluidService.js` — Base-compatible Superfluid helper +- `scripts/registerAgent.js` — agent registration CLI - `test.js` — example runner - `.env.example` — environment variable template (DO NOT commit secrets) - `.github/workflows/superfluid-test.yml` — GitHub Actions test workflow @@ -41,5 +101,5 @@ Security notes SEO / Quick Pitch -Automated opt-out workflows + Superfluid streaming payouts on Base — join the SovereignAgent beta for priority payouts and featured listings. +Automated opt-outs + Superfluid x402 streaming payouts on Base — registered with CryptoSkill.org, 8004scan identity & reputation registries, and the CDP Bazaar. diff --git a/agents/SearchAgent.js b/agents/SearchAgent.js index cf07166..99e83df 100644 --- a/agents/SearchAgent.js +++ b/agents/SearchAgent.js @@ -12,7 +12,7 @@ class SearchAgent { static async performDuckDuckGoSearch(query, num = 10) { const url = 'https://html.duckduckgo.com/html'; - const headers = { 'User-Agent': 'Mozilla/5.0 (compatible; SovereignAgent/1.0)' }; + const headers = { 'User-Agent': 'Mozilla/5.0 (compatible; PhantomOperator/1.0)' }; const params = new URLSearchParams(); params.append('q', query); diff --git a/package-lock.json b/package-lock.json index 0e76971..4e02293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "sovereignagent", + "name": "phantom-operator", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "sovereignagent", + "name": "phantom-operator", "version": "0.1.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 222b192..50502b5 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { - "name": "sovereignagent", + "name": "phantom-operator", "version": "0.1.0", - "description": "Sovereign Agent orchestrator and sub-agents (prototype)", - "main": "SovereignAgent.js", + "description": "PhantomOperator orchestrator and sub-agents (prototype)", + "main": "PhantomOperator.js", "scripts": { "start": "node test.js", - "test": "node test.js" + "test": "node test.js", + "register": "node scripts/registerAgent.js" }, "keywords": [], "author": "normancomics", diff --git a/scripts/registerAgent.js b/scripts/registerAgent.js new file mode 100644 index 0000000..14ac62e --- /dev/null +++ b/scripts/registerAgent.js @@ -0,0 +1,195 @@ +#!/usr/bin/env node +/** + * scripts/registerAgent.js + * + * Full registration pipeline for PhantomOperator sub-agents: + * + * 1. On-chain Identity Registry (0x8004A169FB4a3325136EB29fA0ceB6D2e539a432, Base mainnet) + * 2. On-chain Reputation/Skill Registry (0x8004BAa17C55a88189AE136b182e5fdA19dE9b63, Base mainnet) + * 3. CryptoSkill.org (https://cryptoskill.org/) + * 4. Superfluid x402 (https://x402.superfluid.org/) + * 5. CDP x402 Bazaar (https://docs.cdp.coinbase.com/x402/bazaar) + * + * Usage: + * node scripts/registerAgent.js + * + * Required .env vars: + * PRIVATE_KEY — wallet private key (agent owner / signer) + * PHANTOM_OPERATOR_ADDRESS — deployed agent wallet address + * BASE_RPC_URL — Base mainnet JSON-RPC (default: https://mainnet.base.org) + * AGENT_SERVICE_ENDPOINT — public HTTPS URL clients will call + * CRYPTOSKILL_API_KEY — CryptoSkill.org API key (if required) + * CDP_API_KEY — Coinbase Developer Platform API key (for Bazaar) + * + * Optional .env vars: + * X402_FACILITATOR_URL — override Superfluid x402 facilitator URL + * X402_BAZAAR_URL — override CDP Bazaar base URL + * AGENT_METADATA_URI — IPFS/HTTPS URI to the agent metadata JSON + * PRICE_PER_REQUEST — price in USDCx per request (default: "0.001") + */ + +require('dotenv').config(); + +const { registerIdentity, registerSkill: registerOnChainSkill, getIdentity } = require('../services/RegistryService'); +const { registerWithCryptoSkill, registerSkillWithCryptoSkill } = require('../services/SkillRegistryService'); +const { registerWithSuperfluidX402, listInX402Bazaar } = require('../services/X402Service'); + +// ─── Agent configuration ────────────────────────────────────────────────────── + +const AGENT_ADDRESS = process.env.PHANTOM_OPERATOR_ADDRESS; +const SERVICE_ENDPOINT = process.env.AGENT_SERVICE_ENDPOINT || ''; +const METADATA_URI = process.env.AGENT_METADATA_URI || ''; +const PRICE_PER_REQUEST = process.env.PRICE_PER_REQUEST || '0.001'; + +// Agents and their novel skills to register +const AGENTS = [ + { + name: 'PhantomOperator/SearchAgent', + description: 'Automated privacy-threat search agent — scans public web sources for PII exposure and returns a prioritised threat list.', + skills: [ + { + name: 'ThreatAnalysis', + description: 'Searches public sources for exposed PII (phone, email, address) and classifies results by threat level.', + category: 'privacy', + metadataURI: METADATA_URI, + }, + { + name: 'DuckDuckGoSearch', + description: 'Structured DuckDuckGo HTML-endpoint search with snippet extraction.', + category: 'search', + metadataURI: METADATA_URI, + }, + ], + }, + { + name: 'PhantomOperator/BrokerAgent', + description: 'Data-broker opt-out automation agent — submits opt-out / removal requests to data brokers on behalf of the user.', + skills: [ + { + name: 'DataBrokerOptOut', + description: 'Automates submission of removal/opt-out requests to data-broker websites.', + category: 'privacy', + metadataURI: METADATA_URI, + }, + ], + }, +]; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function required(name) { + const val = process.env[name]; + if (!val) { + console.error(`ERROR: ${name} is not set in .env — please configure it before running this script.`); + process.exit(1); + } + return val; +} + +function step(label) { + console.log(`\n${'─'.repeat(60)}`); + console.log(` ${label}`); + console.log('─'.repeat(60)); +} + +// ─── Main registration flow ─────────────────────────────────────────────────── + +async function main() { + required('PRIVATE_KEY'); + required('PHANTOM_OPERATOR_ADDRESS'); + + for (const agent of AGENTS) { + // ── 1. Check / register on-chain identity ──────────────────────────────── + step(`[1/5] On-chain Identity Registry — ${agent.name}`); + let agentId; + try { + const existing = await getIdentity(AGENT_ADDRESS); + if (existing && existing.active) { + console.log(`Identity already registered: agentId=${existing.agentId}`); + agentId = existing.agentId; + } else { + const result = await registerIdentity(agent.name, METADATA_URI); + agentId = result.agentId; + console.log(`Identity registered: agentId=${agentId}, tx=${result.txHash}`); + } + } catch (err) { + console.error(`Identity registry step failed: ${err.message || err}`); + console.warn('Continuing with remaining steps (on-chain agentId will be null).'); + agentId = null; + } + + // ── 2. Register skills in Reputation Registry ───────────────────────────── + step(`[2/5] On-chain Reputation/Skill Registry — ${agent.name}`); + const skillIds = {}; + if (agentId !== null) { + for (const skill of agent.skills) { + try { + const result = await registerOnChainSkill(agentId, skill.name, skill.metadataURI || METADATA_URI); + skillIds[skill.name] = result.skillId; + console.log(`Skill "${skill.name}" registered: skillId=${result.skillId}, tx=${result.txHash}`); + } catch (err) { + console.error(`Skill "${skill.name}" registration failed: ${err.message || err}`); + } + } + } else { + console.warn('Skipping on-chain skill registration (agentId unknown).'); + } + + // ── 3. CryptoSkill.org ──────────────────────────────────────────────────── + step(`[3/5] CryptoSkill.org — ${agent.name}`); + try { + await registerWithCryptoSkill({ + agentName: agent.name, + agentAddress: AGENT_ADDRESS, + description: agent.description, + skills: agent.skills, + metadataURI: METADATA_URI, + serviceEndpoint: SERVICE_ENDPOINT, + onChainRegistryId: agentId || '', + }); + } catch (err) { + console.error(`CryptoSkill registration failed: ${err.message || err}`); + } + + // Register each skill individually as well + for (const skill of agent.skills) { + try { + await registerSkillWithCryptoSkill(AGENT_ADDRESS, skill); + } catch (err) { + console.error(`CryptoSkill skill "${skill.name}" failed: ${err.message || err}`); + } + } + + // ── 4. Superfluid x402 ─────────────────────────────────────────────────── + step(`[4/5] Superfluid x402 Facilitator — ${agent.name}`); + const serviceOpts = { + agentName: agent.name, + agentAddress: AGENT_ADDRESS, + serviceEndpoint: SERVICE_ENDPOINT, + description: agent.description, + skills: agent.skills.map(s => s.name), + pricePerRequest: PRICE_PER_REQUEST, + superToken: 'USDCx', + }; + try { + await registerWithSuperfluidX402(serviceOpts); + } catch (err) { + console.error(`Superfluid x402 registration failed: ${err.message || err}`); + } + + // ── 5. CDP x402 Bazaar ─────────────────────────────────────────────────── + step(`[5/5] CDP x402 Bazaar — ${agent.name}`); + try { + await listInX402Bazaar(serviceOpts); + } catch (err) { + console.error(`CDP Bazaar listing failed: ${err.message || err}`); + } + } + + console.log('\n✅ Registration pipeline complete.\n'); +} + +main().catch(err => { + console.error('Fatal error in registerAgent.js:', err); + process.exit(1); +}); diff --git a/services/RegistryService.js b/services/RegistryService.js new file mode 100644 index 0000000..3a20268 --- /dev/null +++ b/services/RegistryService.js @@ -0,0 +1,173 @@ +require('dotenv').config(); +const { ethers } = require('ethers'); + +// ─── Contract addresses (Base mainnet) ─────────────────────────────────────── +const IDENTITY_REGISTRY_ADDRESS = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432'; +const REPUTATION_REGISTRY_ADDRESS = '0x8004BAa17C55a88189AE136b182e5fdA19dE9b63'; + +// Base mainnet +const BASE_CHAIN_ID = 8453; +const BASE_RPC_URL = process.env.BASE_RPC_URL || 'https://mainnet.base.org'; +const PRIVATE_KEY = process.env.PRIVATE_KEY; + +// ─── ABIs ───────────────────────────────────────────────────────────────────── +// Minimal ABI for the 8004scan Identity Registry. +// Functions are derived from the standard agent-identity pattern used by 8004scan.io. +const IDENTITY_REGISTRY_ABI = [ + // Register a new agent identity + 'function registerAgent(string calldata name, string calldata metadataURI) external returns (uint256 agentId)', + // Update existing agent metadata + 'function updateAgent(uint256 agentId, string calldata name, string calldata metadataURI) external', + // Read back a registered agent + 'function getAgent(address owner) external view returns (uint256 agentId, string memory name, string memory metadataURI, bool active)', + // Check if an address has a registered identity + 'function isRegistered(address owner) external view returns (bool)', + // Event emitted on registration + 'event AgentRegistered(uint256 indexed agentId, address indexed owner, string name)', +]; + +// Minimal ABI for the 8004scan Reputation / Skill Registry. +const REPUTATION_REGISTRY_ABI = [ + // Register a skill for a given agent + 'function registerSkill(uint256 agentId, string calldata skillName, string calldata skillMetadataURI) external returns (uint256 skillId)', + // Endorse a skill (other agents or users call this) + 'function endorseSkill(uint256 skillId) external', + // Read all skills for an agentId + 'function getSkills(uint256 agentId) external view returns (uint256[] memory skillIds, string[] memory skillNames, string[] memory metadataURIs)', + // Event + 'event SkillRegistered(uint256 indexed skillId, uint256 indexed agentId, string skillName)', +]; + +// ─── Provider / signer setup ───────────────────────────────────────────────── +function _getSigner() { + if (!PRIVATE_KEY) throw new Error('PRIVATE_KEY not set in .env — cannot sign transactions.'); + const provider = new ethers.providers.JsonRpcProvider(BASE_RPC_URL); + return new ethers.Wallet(PRIVATE_KEY, provider); +} + +// ─── Identity Registry ──────────────────────────────────────────────────────── + +/** + * Register this agent's identity on-chain. + * @param {string} agentName Human-readable name, e.g. "PhantomOperator/SearchAgent" + * @param {string} metadataURI URI pointing to an agent metadata JSON (IPFS, HTTPS, etc.) + * @returns {Promise<{txHash: string, agentId: string|null}>} + */ +async function registerIdentity(agentName, metadataURI) { + const signer = _getSigner(); + const contract = new ethers.Contract(IDENTITY_REGISTRY_ADDRESS, IDENTITY_REGISTRY_ABI, signer); + + console.log(`RegistryService: registering identity for "${agentName}"...`); + const tx = await contract.registerAgent(agentName, metadataURI); + const receipt = await tx.wait(); + + // Attempt to parse agentId from event log + let agentId = null; + try { + const iface = new ethers.utils.Interface(IDENTITY_REGISTRY_ABI); + for (const log of receipt.logs) { + try { + const parsed = iface.parseLog(log); + if (parsed.name === 'AgentRegistered') { + agentId = parsed.args.agentId.toString(); + break; + } + } catch (_) { /* skip non-matching logs */ } + } + } catch (_) { /* event parsing is best-effort */ } + + console.log(`RegistryService: identity registered. tx=${receipt.transactionHash}, agentId=${agentId}`); + return { txHash: receipt.transactionHash, agentId }; +} + +/** + * Update an already-registered agent identity. + * @param {string|number} agentId + * @param {string} agentName + * @param {string} metadataURI + * @returns {Promise} txHash + */ +async function updateIdentity(agentId, agentName, metadataURI) { + const signer = _getSigner(); + const contract = new ethers.Contract(IDENTITY_REGISTRY_ADDRESS, IDENTITY_REGISTRY_ABI, signer); + + console.log(`RegistryService: updating identity agentId=${agentId}...`); + const tx = await contract.updateAgent(agentId, agentName, metadataURI); + const receipt = await tx.wait(); + console.log(`RegistryService: identity updated. tx=${receipt.transactionHash}`); + return receipt.transactionHash; +} + +/** + * Read back the registered identity for a wallet address. + * @param {string} ownerAddress + * @returns {Promise<{agentId: string, name: string, metadataURI: string, active: boolean}|null>} + */ +async function getIdentity(ownerAddress) { + const provider = new ethers.providers.JsonRpcProvider(BASE_RPC_URL); + const contract = new ethers.Contract(IDENTITY_REGISTRY_ADDRESS, IDENTITY_REGISTRY_ABI, provider); + const registered = await contract.isRegistered(ownerAddress); + if (!registered) return null; + const [agentId, name, metadataURI, active] = await contract.getAgent(ownerAddress); + return { agentId: agentId.toString(), name, metadataURI, active }; +} + +// ─── Reputation / Skill Registry ───────────────────────────────────────────── + +/** + * Register a novel skill for a previously-registered agentId. + * @param {string|number} agentId Returned from registerIdentity() + * @param {string} skillName e.g. "DataBrokerOptOut", "ThreatAnalysis" + * @param {string} skillMetadataURI URI to skill manifest JSON + * @returns {Promise<{txHash: string, skillId: string|null}>} + */ +async function registerSkill(agentId, skillName, skillMetadataURI) { + const signer = _getSigner(); + const contract = new ethers.Contract(REPUTATION_REGISTRY_ADDRESS, REPUTATION_REGISTRY_ABI, signer); + + console.log(`RegistryService: registering skill "${skillName}" for agentId=${agentId}...`); + const tx = await contract.registerSkill(agentId, skillName, skillMetadataURI); + const receipt = await tx.wait(); + + let skillId = null; + try { + const iface = new ethers.utils.Interface(REPUTATION_REGISTRY_ABI); + for (const log of receipt.logs) { + try { + const parsed = iface.parseLog(log); + if (parsed.name === 'SkillRegistered') { + skillId = parsed.args.skillId.toString(); + break; + } + } catch (_) { /* skip */ } + } + } catch (_) { /* best-effort */ } + + console.log(`RegistryService: skill registered. tx=${receipt.transactionHash}, skillId=${skillId}`); + return { txHash: receipt.transactionHash, skillId }; +} + +/** + * List all skills registered for an agentId. + * @param {string|number} agentId + */ +async function getSkills(agentId) { + const provider = new ethers.providers.JsonRpcProvider(BASE_RPC_URL); + const contract = new ethers.Contract(REPUTATION_REGISTRY_ADDRESS, REPUTATION_REGISTRY_ABI, provider); + const [skillIds, skillNames, metadataURIs] = await contract.getSkills(agentId); + return skillIds.map((id, i) => ({ + skillId: id.toString(), + skillName: skillNames[i], + metadataURI: metadataURIs[i], + })); +} + +module.exports = { + registerIdentity, + updateIdentity, + getIdentity, + registerSkill, + getSkills, + IDENTITY_REGISTRY_ADDRESS, + REPUTATION_REGISTRY_ADDRESS, +}; diff --git a/services/SkillRegistryService.js b/services/SkillRegistryService.js new file mode 100644 index 0000000..31722dd --- /dev/null +++ b/services/SkillRegistryService.js @@ -0,0 +1,118 @@ +require('dotenv').config(); +const axios = require('axios'); + +// CryptoSkill.org public API base (submit agent + skill manifests) +const CRYPTOSKILL_API = process.env.CRYPTOSKILL_API || 'https://cryptoskill.org/api/v1'; +const CRYPTOSKILL_API_KEY = process.env.CRYPTOSKILL_API_KEY; + +/** + * Build a CryptoSkill agent manifest. + * This is the standardised JSON object submitted to cryptoskill.org for indexing. + * + * @param {object} opts + * @param {string} opts.agentName Human-readable agent name + * @param {string} opts.agentAddress On-chain wallet / contract address + * @param {string} opts.description Short description of the agent + * @param {string[]} opts.skills Skill names the agent provides + * @param {string} [opts.metadataURI] URI to full agent metadata JSON + * @param {string} [opts.serviceEndpoint] HTTPS URL clients call + * @param {string} [opts.onChainRegistryId] agentId from the identity registry + * @returns {object} + */ +function buildAgentManifest(opts) { + const { + agentName, + agentAddress, + description, + skills = [], + metadataURI = '', + serviceEndpoint = '', + onChainRegistryId = '', + } = opts; + + return { + name: agentName, + address: agentAddress, + description, + skills: skills.map(s => (typeof s === 'string' ? { name: s } : s)), + metadataURI, + serviceEndpoint, + onChainRegistryId, + registeredAt: new Date().toISOString(), + }; +} + +/** + * Register an agent (and its skills) with CryptoSkill.org. + * + * @param {object} manifestOpts Options passed to buildAgentManifest() + * @returns {Promise} API response from cryptoskill.org + */ +async function registerWithCryptoSkill(manifestOpts) { + const manifest = buildAgentManifest(manifestOpts); + + const headers = { 'Content-Type': 'application/json' }; + if (CRYPTOSKILL_API_KEY) { + headers['Authorization'] = `Bearer ${CRYPTOSKILL_API_KEY}`; + } + + console.log(`SkillRegistryService: registering "${manifest.name}" with CryptoSkill.org...`); + const resp = await axios.post(`${CRYPTOSKILL_API}/agents`, manifest, { headers, timeout: 20000 }); + + console.log('SkillRegistryService: CryptoSkill registration response:', resp.data); + return resp.data; +} + +/** + * Register an individual novel skill under an already-registered agent. + * + * @param {string} agentAddress Agent's wallet address + * @param {object} skillOpts + * @param {string} skillOpts.name Skill name, e.g. "DataBrokerOptOut" + * @param {string} skillOpts.description Human-readable description + * @param {string} [skillOpts.category] Category tag (e.g. "privacy", "data", "search") + * @param {string} [skillOpts.metadataURI] URI to skill manifest / ABI / docs + * @returns {Promise} + */ +async function registerSkillWithCryptoSkill(agentAddress, skillOpts) { + const headers = { 'Content-Type': 'application/json' }; + if (CRYPTOSKILL_API_KEY) { + headers['Authorization'] = `Bearer ${CRYPTOSKILL_API_KEY}`; + } + + const body = { + agentAddress, + skill: skillOpts, + registeredAt: new Date().toISOString(), + }; + + console.log(`SkillRegistryService: registering skill "${skillOpts.name}" for ${agentAddress}...`); + const resp = await axios.post(`${CRYPTOSKILL_API}/skills`, body, { headers, timeout: 20000 }); + + console.log('SkillRegistryService: skill registration response:', resp.data); + return resp.data; +} + +/** + * Fetch the current listing for an agent from CryptoSkill.org. + * @param {string} agentAddress + * @returns {Promise} + */ +async function getAgentListing(agentAddress) { + const headers = {}; + if (CRYPTOSKILL_API_KEY) headers['Authorization'] = `Bearer ${CRYPTOSKILL_API_KEY}`; + try { + const resp = await axios.get(`${CRYPTOSKILL_API}/agents/${agentAddress}`, { headers, timeout: 10000 }); + return resp.data; + } catch (err) { + if (err.response && err.response.status === 404) return null; + throw err; + } +} + +module.exports = { + buildAgentManifest, + registerWithCryptoSkill, + registerSkillWithCryptoSkill, + getAgentListing, +}; diff --git a/services/X402Service.js b/services/X402Service.js new file mode 100644 index 0000000..8016201 --- /dev/null +++ b/services/X402Service.js @@ -0,0 +1,148 @@ +require('dotenv').config(); +const axios = require('axios'); +const { ethers } = require('ethers'); + +// ─── x402 / Superfluid configuration ───────────────────────────────────────── +// Superfluid x402 facilitator endpoint (Base mainnet) +const X402_FACILITATOR_URL = process.env.X402_FACILITATOR_URL || 'https://x402.superfluid.org'; +// CDP x402 Bazaar service directory +const X402_BAZAAR_URL = process.env.X402_BAZAAR_URL || 'https://api.developer.coinbase.com/rpc/v1/base'; + +const BASE_RPC_URL = process.env.BASE_RPC_URL || 'https://mainnet.base.org'; +const PRIVATE_KEY = process.env.PRIVATE_KEY; + +// ─── x402 protocol helpers ─────────────────────────────────────────────────── + +/** + * Build a standard x402 service listing payload. + * This is submitted to both the Superfluid facilitator and the CDP Bazaar. + * + * @param {object} opts + * @param {string} opts.agentName Human-readable name + * @param {string} opts.agentAddress Wallet address that receives payments + * @param {string} opts.serviceEndpoint HTTPS URL of the agent's HTTP endpoint + * @param {string} opts.description Short description of the service + * @param {string[]} opts.skills Array of skill names offered + * @param {string} opts.pricePerRequest Price per request (string, e.g. "0.001") + * @param {string} [opts.superToken] Super-token symbol (default: USDCx) + * @param {number} [opts.tokenDecimals] Decimal places for the super-token (default: 6 for USDCx) + * @returns {object} x402 service payload + */ +function buildServicePayload(opts) { + const { + agentName, + agentAddress, + serviceEndpoint, + description, + skills = [], + pricePerRequest = '0.001', + superToken = 'USDCx', + tokenDecimals = 6, // USDCx uses 6; override for 18-decimal tokens (e.g. DAIx) + } = opts; + + return { + name: agentName, + description, + endpoint: serviceEndpoint, + paymentConfig: { + type: 'x402', + version: 1, + network: 'base-mainnet', + payTo: agentAddress, + maxAmountRequired: ethers.utils.parseUnits(pricePerRequest, tokenDecimals).toString(), + asset: superToken, + extra: { + description: `Payment for ${agentName} service`, + mimeType: 'application/json', + }, + }, + skills, + version: '1.0.0', + }; +} + +/** + * Register (or update) this agent as an x402-compatible service with the + * Superfluid x402 facilitator on Base. + * + * @param {object} serviceOpts Same shape as buildServicePayload opts + * @returns {Promise} Registration response from the facilitator + */ +async function registerWithSuperfluidX402(serviceOpts) { + if (!PRIVATE_KEY) throw new Error('PRIVATE_KEY not set in .env'); + + const provider = new ethers.providers.JsonRpcProvider(BASE_RPC_URL); + const wallet = new ethers.Wallet(PRIVATE_KEY, provider); + const payload = buildServicePayload(serviceOpts); + + // Sign the registration payload so the facilitator can verify ownership + const payloadStr = JSON.stringify(payload); + const signature = await wallet.signMessage(payloadStr); + + const body = { payload, signature, signer: wallet.address }; + + console.log('X402Service: registering with Superfluid x402 facilitator...'); + const resp = await axios.post(`${X402_FACILITATOR_URL}/register`, body, { + headers: { 'Content-Type': 'application/json' }, + timeout: 20000, + }); + + console.log('X402Service: Superfluid x402 registration response:', resp.data); + return resp.data; +} + +/** + * List this agent's service in the CDP x402 Bazaar (service directory). + * + * @param {object} serviceOpts Same shape as buildServicePayload opts + * @param {string} [apiKey] CDP API key (falls back to CDP_API_KEY env var) + * @returns {Promise} Bazaar listing response + */ +async function listInX402Bazaar(serviceOpts, apiKey) { + const key = apiKey || process.env.CDP_API_KEY; + if (!key) throw new Error('CDP_API_KEY not set in .env — required for Bazaar listing.'); + + const payload = buildServicePayload(serviceOpts); + + console.log('X402Service: listing service in CDP x402 Bazaar...'); + const resp = await axios.post(`${X402_BAZAAR_URL}/bazaar/services`, payload, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${key}`, + }, + timeout: 20000, + }); + + console.log('X402Service: Bazaar listing response:', resp.data); + return resp.data; +} + +/** + * Verify that the x402 payment header sent by a client is valid. + * Call this from your HTTP server middleware before fulfilling a paid request. + * + * @param {string} paymentHeader Value of the `X-PAYMENT` request header + * @param {string} expectedPayTo Wallet address that should receive funds + * @param {string} minAmount Minimum accepted amount in USDC micro-units (string) + * @returns {Promise} + */ +async function verifyX402Payment(paymentHeader, expectedPayTo, minAmount) { + try { + const resp = await axios.post(`${X402_FACILITATOR_URL}/verify`, { + paymentHeader, + expectedPayTo, + minAmount, + }, { timeout: 10000 }); + return resp.data && resp.data.valid === true; + } catch (err) { + console.error('X402Service: payment verification failed:', err.message || err); + return false; + } +} + +module.exports = { + buildServicePayload, + registerWithSuperfluidX402, + listInX402Bazaar, + verifyX402Payment, +}; diff --git a/src/search_agent/search_agent.py b/src/search_agent/search_agent.py index f758d6c..5047310 100644 --- a/src/search_agent/search_agent.py +++ b/src/search_agent/search_agent.py @@ -13,7 +13,7 @@ def duckduckgo_search(query, num_results=10): Uses the `html.duckduckgo.com/html` endpoint to avoid heavy JS. """ search_url = f"https://html.duckduckgo.com/html?q={quote_plus(query)}" - headers = {"User-Agent": "Mozilla/5.0 (compatible; SovereignAgent/1.0)"} + headers = {"User-Agent": "Mozilla/5.0 (compatible; PhantomOperator/1.0)"} resp = requests.post(search_url, data={"q": query}, headers=headers, timeout=15) resp.raise_for_status() diff --git a/test.js b/test.js index 641786f..b73feba 100644 --- a/test.js +++ b/test.js @@ -1,12 +1,12 @@ -const SovereignAgent = require('./SovereignAgent'); +const PhantomOperator = require('./PhantomOperator'); const userInfo = { fullName: 'John Doe', - walletAddress: process.env.SOVEREIGN_AGENT_ADDRESS || '0xRecipientAddress', + walletAddress: process.env.PHANTOM_OPERATOR_ADDRESS || '0xRecipientAddress', flowRate: process.env.FLOW_RATE || '385802469135802', }; (async () => { - const agent = new SovereignAgent(); + const agent = new PhantomOperator(); await agent.startDataRemovalTask(userInfo); })();