diff --git a/docs/PROTOCOL_BOUNTY_8.md b/docs/PROTOCOL_BOUNTY_8.md new file mode 100644 index 00000000..dc9f186e --- /dev/null +++ b/docs/PROTOCOL_BOUNTY_8.md @@ -0,0 +1,359 @@ +# RustChain Protocol Documentation (Bounty #8 Draft) + +## 1) Protocol Overview + +RustChain is a **Proof-of-Antiquity** blockchain (RIP-200) that rewards physical hardware identity over raw hash power. + +- Consensus principle: **1 CPU = 1 vote**, then weighted by antiquity/fingerprint validity. +- Focus: reward real vintage hardware (PowerPC-era, retro architectures) and penalize VM/emulator spoofing. +- Runtime stack (current implementation): Flask + SQLite node, miner scripts for Linux/macOS, signed transfer + pending ledger settlement. + +--- + +## 2) RIP-200 Consensus and Epoch Lifecycle + +### 2.1 High-level flow + +```mermaid +sequenceDiagram + participant Miner + participant Node as RustChain Node + participant Ledger as Epoch/Pending Ledger + participant Anchor as External Anchor (Ergo) + + Miner->>Node: POST /attest/challenge + Node-->>Miner: nonce + challenge context + Miner->>Miner: collect hardware signals + fingerprint checks + Miner->>Node: POST /attest/submit (signed attestation) + Node->>Node: validate shape, identity, fingerprint, anti-abuse + Node-->>Miner: attestation result (ok/deny) + + Miner->>Node: POST /epoch/enroll + Node->>Ledger: register miner in active epoch + + Note over Node,Ledger: Epoch window closes + Node->>Node: compute weights + rewards + Node->>Ledger: /rewards/settle -> pending credits + Node->>Anchor: anchor settlement digest/proof + Miner->>Node: query balance / withdraw +``` + +### 2.2 Epoch settlement + +At settlement, miners in epoch are weighted by hardware/fingerprint/consensus rules and paid from epoch pool. + +Conceptually: + +```text +reward_i = epoch_pool * weight_i / sum(weight_all_eligible_miners) +``` + +--- + +## 3) Attestation Flow (what miner sends, what node validates) + +## 3.1 Miner payload + +Attestation payload contains (simplified): + +- `miner` / `miner_id` +- `report` (nonce/commitment/derived timing entropy) +- `device` (family/arch/model/cpu/cores/memory/serial) +- `signals` (hostname/MAC list, etc.) +- `fingerprint` (results of checks) +- optional sidecar proof fields (if dual-mining mode enabled) + +## 3.2 Node validation gates + +Node-side validation includes: + +1. **Shape validation** for request body/fields +2. **Miner identifier validation** (allowed chars/length) +3. **Challenge/nonce consistency** +4. **Hardware signal sanity checks** +5. **Rate limit / anti-abuse checks by client IP / miner** +6. **Fingerprint pass/fail classification** +7. **Enrollment eligibility decision** + +If accepted, miner can call `/epoch/enroll` and participate in reward distribution. + +--- + +## 4) Hardware Fingerprinting (6+1) + +RustChain uses hardware-behavior checks to distinguish physical machines from VMs/emulators. + +Primary checks (implementation naming varies by miner/tooling): + +1. Clock-skew / oscillator drift +2. Cache timing characteristics +3. SIMD instruction identity/timing +4. Thermal drift entropy +5. Instruction-path jitter +6. Anti-emulation heuristics (hypervisor/container indicators) +7. (Optional hardening layer) serial/OUI consistency enforcement in node policies + +Why it matters: + +- prevents synthetic identity inflation +- keeps weight tied to **real** hardware behavior +- protects reward fairness across participants + +--- + +## 5) Token Economics (RTC) + +- Native token: **RTC** +- Reward source: epoch distribution + pending ledger confirmation paths +- Weight-driven payout: higher eligible weight gets larger epoch share +- Additional policy knobs exposed by endpoints (`/api/bounty-multiplier`, `/api/fee_pool`, etc.) + +> Note: precise emissions, premine, and multiplier schedules should be versioned in canonical tokenomics docs; this file documents protocol mechanics + API surfaces. + +--- + +## 6) Network Architecture + +```mermaid +graph TD + M1[Miner A] --> N[Attestation/Settlement Node] + M2[Miner B] --> N + M3[Miner C] --> N + + N --> P[(Pending Ledger / Epoch State)] + N --> X[Explorer/UI APIs] + N --> A[External Anchor (Ergo)] +``` + +Components: + +- **Miners**: generate attestation reports + enroll each epoch +- **Node**: validates attestations, computes rewards, exposes APIs +- **Pending ledger**: tracks pending confirmations/void/integrity operations +- **Explorer/API**: status, balances, miners, stats +- **Anchor layer**: external timestamp/proof anchoring + +--- + +## 7) Public API Reference (with curl examples) + +Base example: + +```bash +BASE="https://rustchain.org" +``` + +## 7.1 Health / status + +### GET `/health` +```bash +curl -sS "$BASE/health" +``` + +### GET `/ready` +```bash +curl -sS "$BASE/ready" +``` + +### GET `/ops/readiness` +```bash +curl -sS "$BASE/ops/readiness" +``` + +## 7.2 Miner discovery / stats + +### GET `/api/miners` +```bash +curl -sS "$BASE/api/miners" +``` + +### GET `/api/stats` +```bash +curl -sS "$BASE/api/stats" +``` + +### GET `/api/nodes` +```bash +curl -sS "$BASE/api/nodes" +``` + +## 7.3 Attestation + enrollment + +### POST `/attest/challenge` +```bash +curl -sS -X POST "$BASE/attest/challenge" -H 'Content-Type: application/json' -d '{}' +``` + +### POST `/attest/submit` +```bash +curl -sS -X POST "$BASE/attest/submit" \ + -H 'Content-Type: application/json' \ + -d '{"miner":"RTC_example","report":{"nonce":"n"},"device":{},"signals":{},"fingerprint":{}}' +``` + +### POST `/epoch/enroll` +```bash +curl -sS -X POST "$BASE/epoch/enroll" \ + -H 'Content-Type: application/json' \ + -d '{"miner_pubkey":"RTC_example","miner_id":"host-1","device":{"family":"x86","arch":"modern"}}' +``` + +### GET `/epoch` +```bash +curl -sS "$BASE/epoch" +``` + +## 7.4 Wallet / balances / transfer + +### GET `/balance/` +```bash +curl -sS "$BASE/balance/RTC_example" +``` + +### GET `/wallet/balance?miner_id=` +```bash +curl -sS "$BASE/wallet/balance?miner_id=RTC_example" +``` + +### POST `/wallet/transfer` +```bash +curl -sS -X POST "$BASE/wallet/transfer" \ + -H 'Content-Type: application/json' \ + -d '{"from":"RTC_a","to":"RTC_b","amount":1.25}' +``` + +### POST `/wallet/transfer/signed` +```bash +curl -sS -X POST "$BASE/wallet/transfer/signed" \ + -H 'Content-Type: application/json' \ + -d '{"from":"RTC_a","to":"RTC_b","amount":1.25,"signature":"...","pubkey":"..."}' +``` + +### GET `/wallet/ledger` +```bash +curl -sS "$BASE/wallet/ledger" +``` + +## 7.5 Pending ledger ops + +### GET `/pending/list` +```bash +curl -sS "$BASE/pending/list" +``` + +### POST `/pending/confirm` +```bash +curl -sS -X POST "$BASE/pending/confirm" -H 'Content-Type: application/json' -d '{"id":123}' +``` + +### POST `/pending/void` +```bash +curl -sS -X POST "$BASE/pending/void" -H 'Content-Type: application/json' -d '{"id":123,"reason":"invalid"}' +``` + +### GET `/pending/integrity` +```bash +curl -sS "$BASE/pending/integrity" +``` + +## 7.6 Rewards + mining economics + +### GET `/rewards/epoch/` +```bash +curl -sS "$BASE/rewards/epoch/1" +``` + +### POST `/rewards/settle` +```bash +curl -sS -X POST "$BASE/rewards/settle" -H 'Content-Type: application/json' -d '{}' +``` + +### GET `/api/bounty-multiplier` +```bash +curl -sS "$BASE/api/bounty-multiplier" +``` + +### GET `/api/fee_pool` +```bash +curl -sS "$BASE/api/fee_pool" +``` + +## 7.7 Explorer + machine details + +### GET `/explorer` +```bash +curl -sS "$BASE/explorer" | head +``` + +### GET `/api/miner//attestations` +```bash +curl -sS "$BASE/api/miner/RTC_example/attestations" +``` + +### GET `/api/miner_dashboard/` +```bash +curl -sS "$BASE/api/miner_dashboard/RTC_example" +``` + +## 7.8 P2P / beacon / headers (operator-facing public routes) + +- `POST /p2p/add_peer` +- `GET /p2p/blocks` +- `GET /p2p/ping` +- `GET /p2p/stats` +- `GET/POST /beacon/*` (`/beacon/digest`, `/beacon/envelopes`, `/beacon/submit`) +- `POST /headers/ingest_signed`, `GET /headers/tip` + +--- + +## 8) Operator/Admin API groups + +These are exposed routes but typically for controlled operator use: + +- OUI enforcement/admin: + - `/admin/oui_deny/list|add|remove|enforce` + - `/ops/oui/enforce` +- Governance rotation: + - `/gov/rotate/stage|commit|approve|message/` +- Metrics: + - `/metrics`, `/metrics_mac` +- Withdraw flows: + - `/withdraw/register|request|status/|history/` + +--- + +## 9) Security Model Notes + +- Trust boundary: client payload is untrusted; server performs strict type/shape checks. +- Identity hardening: IP-based anti-abuse + hardware fingerprinting + serial/OUI controls. +- Transfer hardening: signed transfer endpoint for stronger authorization path. +- Settlement auditability: pending ledger + integrity endpoints + external anchoring. + +--- + +## 10) Glossary + +- **RIP-200**: RustChain Iterative Protocol v200; Proof-of-Antiquity consensus design. +- **Proof-of-Antiquity**: consensus weighting emphasizing vintage/real hardware identity. +- **Epoch**: reward accounting window; miners enroll and settle per epoch. +- **Attestation**: miner proof packet (hardware signals + report + fingerprint). +- **Fingerprint checks (6+1)**: anti-VM/emulation hardware-behavior tests plus policy hardening layer. +- **Pending ledger**: intermediate transfer/reward state before final confirmation/void. +- **PSE / entropy-derived signals**: timing/noise signatures used in report/fingerprint scoring. +- **Anchoring**: writing settlement proof to external chain (Ergo). + +--- + +## 11) Suggested docs split for final upstream submission + +To match bounty acceptance cleanly, split this into: + +- `docs/protocol/overview.md` +- `docs/protocol/attestation.md` +- `docs/protocol/epoch_settlement.md` +- `docs/protocol/tokenomics.md` +- `docs/protocol/network_architecture.md` +- `docs/protocol/api_reference.md` +- `docs/protocol/glossary.md` + +This draft is intentionally consolidated for review-first iteration. diff --git a/node/rustchain_v2_integrated_v2.2.1_rip200.py b/node/rustchain_v2_integrated_v2.2.1_rip200.py index e0257c58..09e967d7 100644 --- a/node/rustchain_v2_integrated_v2.2.1_rip200.py +++ b/node/rustchain_v2_integrated_v2.2.1_rip200.py @@ -167,8 +167,8 @@ def _attest_is_valid_positive_int(value, max_value=4096): def client_ip_from_request(req) -> str: - """Return the left-most forwarded IP when present, otherwise the remote address.""" - client_ip = req.headers.get("X-Forwarded-For", req.remote_addr) + """Return trusted client IP from reverse proxy (X-Real-IP) or remote address.""" + client_ip = req.headers.get("X-Real-IP") or req.remote_addr if client_ip and "," in client_ip: client_ip = client_ip.split(",")[0].strip() return client_ip @@ -316,6 +316,13 @@ def _start_timer(): g._ts = time.time() g.request_id = request.headers.get("X-Request-Id") or uuid.uuid4().hex +def get_client_ip(): + """Trust reverse-proxy X-Real-IP, not client X-Forwarded-For.""" + client_ip = request.headers.get("X-Real-IP") or request.remote_addr + if client_ip and "," in client_ip: + client_ip = client_ip.split(",")[0].strip() + return client_ip + @app.after_request def _after(resp): try: @@ -327,7 +334,7 @@ def _after(resp): "method": request.method, "path": request.path, "status": resp.status_code, - "ip": request.headers.get("X-Forwarded-For", request.remote_addr), + "ip": get_client_ip(), "dur_ms": int(dur * 1000), } log.info(json.dumps(rec, separators=(",", ":"))) @@ -2005,7 +2012,7 @@ def submit_attestation(): return payload_error # Extract client IP (handle nginx proxy) - client_ip = client_ip_from_request(request) + client_ip = get_client_ip() # Extract attestation data miner = _attest_valid_miner(data.get('miner')) or _attest_valid_miner(data.get('miner_id')) @@ -2244,9 +2251,7 @@ def enroll_epoch(): data = request.get_json() # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() miner_pk = data.get('miner_pubkey') miner_id = data.get('miner_id', miner_pk) # Use miner_id if provided device = data.get('device', {}) @@ -2610,9 +2615,7 @@ def register_withdrawal_key(): return jsonify({"error": "Invalid JSON body"}), 400 # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() miner_pk = data.get('miner_pk') pubkey_sr25519 = data.get('pubkey_sr25519') @@ -2663,9 +2666,7 @@ def request_withdrawal(): data = request.get_json() # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() miner_pk = data.get('miner_pk') amount = float(data.get('amount', 0)) destination = data.get('destination') @@ -3615,9 +3616,7 @@ def add_oui_deny(): data = request.get_json() # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() oui = data.get('oui', '').lower().replace(':', '').replace('-', '') vendor = data.get('vendor', 'Unknown') enforce = int(data.get('enforce', 0)) @@ -3642,9 +3641,7 @@ def remove_oui_deny(): data = request.get_json() # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() oui = data.get('oui', '').lower().replace(':', '').replace('-', '') with sqlite3.connect(DB_PATH) as conn: @@ -3708,9 +3705,7 @@ def attest_debug(): data = request.get_json() # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() miner = data.get('miner') or data.get('miner_id') if not miner: @@ -4382,9 +4377,7 @@ def wallet_transfer_OLD(): data = request.get_json() # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() from_miner = data.get('from_miner') to_miner = data.get('to_miner') amount_rtc = float(data.get('amount_rtc', 0)) @@ -4808,9 +4801,7 @@ def wallet_transfer_signed(): return jsonify({"error": pre.error, "details": pre.details}), 400 # Extract client IP (handle nginx proxy) - client_ip = request.headers.get("X-Forwarded-For", request.remote_addr) - if client_ip and "," in client_ip: - client_ip = client_ip.split(",")[0].strip() # First IP in chain + client_ip = get_client_ip() from_address = pre.details["from_address"] to_address = pre.details["to_address"] diff --git a/rips/docs/RIP-302-agent-economy.md b/rips/docs/RIP-302-agent-economy.md new file mode 100644 index 00000000..ac34e4c0 --- /dev/null +++ b/rips/docs/RIP-302-agent-economy.md @@ -0,0 +1,425 @@ +# RIP-302: Agent Economy Protocol + +**Title:** Agent Economy Protocol for AI Agent Participation in RustChain +**Author:** RustChain Community +**Status:** Active +**Type:** Application Layer +**Created:** 2026-03-06 +**Version:** 1.0.0 + +## Abstract + +RIP-302 defines a comprehensive protocol for AI agents to participate in the RustChain economy through standardized APIs for wallet management, machine-to-machine payments (x402), reputation tracking, analytics, and bounty automation. This specification enables autonomous AI agents to earn, spend, and manage RustChain Token (RTC) while building verifiable reputation through the Beacon Atlas system. + +## Motivation + +The AI agent economy requires: +1. **Identity**: Unique agent identification and wallet binding +2. **Payments**: Machine-to-machine micropayments with minimal friction +3. **Reputation**: Verifiable trust scores for agent interactions +4. **Analytics**: Performance metrics for agent optimization +5. **Bounties**: Automated discovery and completion of paid work + +RIP-302 provides standardized APIs addressing all these requirements, enabling seamless integration of AI agents into the RustChain ecosystem. + +## Specification + +### Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Agent Economy Layer │ +├─────────────────────────────────────────────────────────────┤ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Agents │ │ Payments │ │Reputation│ │Analytics │ │ +│ │ Wallets │ │ x402 │ │ Beacon │ │ BoTTube │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Bounties │ │ Premium │ │ Health │ │ +│ │Automation│ │ Endpoints│ │ & Stats │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ RustChain Core Layer │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Agent Identity + +#### Agent ID Format + +Agent IDs are UTF-8 strings (3-64 characters) following these rules: +- Lowercase alphanumeric with hyphens +- Must start with a letter +- No consecutive hyphens +- Examples: `video-curator-bot`, `analytics-agent-v2` + +#### Wallet Binding + +Each agent is bound to a RustChain wallet: +```json +{ + "agent_id": "video-curator-bot", + "wallet_address": "agent_a1b2c3d4e5f6", + "base_address": "0xCoinbaseBaseAddress", // Optional + "created_at": "2026-03-06T12:00:00Z" +} +``` + +### x402 Payment Protocol + +#### Overview + +x402 implements HTTP 402 Payment Required for machine-to-machine micropayments: + +``` +Client Server + | | + |--- GET /protected/resource ------>| + | | + |<-- 402 Payment Required ----------| + | X-Pay-To: wallet_addr | + | X-Pay-Amount: 0.5 | + | X-Pay-Nonce: abc123 | + | | + |--- POST /payment/send ------------>| + | {payment details} | + | | + |<-- 200 OK + Resource -------------| +``` + +#### Payment Flow + +1. **Challenge**: Server returns 402 with payment requirements +2. **Negotiation**: Client reviews payment terms +3. **Payment**: Client submits payment via `/api/agent/payment/send` +4. **Access**: Server grants resource access upon confirmation + +#### Payment Structure + +```json +{ + "payment_id": "pay_abc123", + "from_agent": "payer-agent", + "to_agent": "payee-agent", + "amount": 0.5, + "memo": "Payment for service", + "resource": "/api/premium/data", + "status": "completed", + "tx_hash": "tx_def456" +} +``` + +### Beacon Atlas Reputation + +#### Score Calculation + +Reputation scores (0-100) are calculated from: +- Transaction success rate (40%) +- Attestation ratings (30%) +- Activity consistency (15%) +- Dispute history (15%) + +#### Reputation Tiers + +| Tier | Score | Benefits | +|------|-------|----------| +| ELITE | 95-100 | Premium rates, priority access | +| VERIFIED | 85-94 | Verified badge, lower fees | +| TRUSTED | 70-84 | Standard access | +| ESTABLISHED | 50-69 | Basic access | +| NEW | 20-49 | Limited access | +| UNKNOWN | 0-19 | Restricted | + +#### Attestations + +Attestations are signed reviews from one agent about another: + +```json +{ + "attestation_id": "att_123", + "from_agent": "reviewer-agent", + "to_agent": "service-agent", + "rating": 5, + "comment": "Excellent service", + "transaction_id": "tx_789", + "verified": true +} +``` + +### Analytics API + +#### Earnings Reports + +```json +{ + "agent_id": "analytics-agent", + "period": "7d", + "total_earned": 125.5, + "transactions_count": 42, + "avg_transaction": 2.99, + "top_source": "video-tips", + "sources": { + "video-tips": 75.0, + "bounties": 50.5 + }, + "trend": 15.3 +} +``` + +#### Activity Metrics + +```json +{ + "agent_id": "analytics-agent", + "period": "24h", + "active_hours": 18, + "peak_hour": 14, + "requests_served": 1250, + "payments_received": 85, + "payments_sent": 12, + "avg_response_time": 145, + "uptime_percentage": 99.5 +} +``` + +### Bounty System + +#### Bounty Lifecycle + +``` +OPEN → IN_PROGRESS → SUBMITTED → UNDER_REVIEW → COMPLETED → PAID +``` + +#### Bounty Structure + +```json +{ + "bounty_id": "bounty_123", + "title": "Implement Feature X", + "description": "Detailed description...", + "status": "open", + "tier": "medium", + "reward": 50.0, + "reward_range": "30-50 RTC", + "created_at": "2026-03-01T00:00:00Z", + "deadline": "2026-03-31T23:59:59Z", + "issuer": "project-maintainer", + "tags": ["sdk", "python", "feature"], + "requirements": ["Tests required", "Documentation required"] +} +``` + +#### Submission Structure + +```json +{ + "submission_id": "sub_456", + "bounty_id": "bounty_123", + "submitter": "bounty-hunter-bot", + "pr_url": "https://github.com/.../pull/685", + "description": "Implementation details...", + "evidence": ["test-results", "docs"], + "status": "submitted", + "submitted_at": "2026-03-06T12:00:00Z" +} +``` + +## API Reference + +### Base URLs + +| Service | URL | +|---------|-----| +| RustChain Primary | `https://rustchain.org` | +| BoTTube | `https://bottube.ai` | +| Beacon Atlas | `https://beacon.rustchain.org` | + +### Endpoints + +#### Agent Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/agent/wallet/create` | Create agent wallet | +| GET | `/api/agent/wallet/{id}` | Get wallet info | +| PUT | `/api/agent/profile/{id}` | Update profile | +| GET | `/api/agents` | List agents | + +#### Payments + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/agent/payment/send` | Send payment | +| POST | `/api/agent/payment/request` | Request payment | +| GET | `/api/agent/payment/{id}` | Get payment details | +| GET | `/api/agent/payment/history` | Payment history | +| POST | `/api/agent/payment/x402/challenge` | Generate x402 challenge | + +#### Reputation + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/agent/reputation/{id}` | Get reputation score | +| POST | `/api/agent/reputation/attest` | Submit attestation | +| GET | `/api/agent/reputation/leaderboard` | Get leaderboard | +| GET | `/api/agent/reputation/{id}/proof` | Get trust proof | + +#### Analytics + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/agent/analytics/{id}/earnings` | Earnings report | +| GET | `/api/agent/analytics/{id}/activity` | Activity metrics | +| GET | `/api/agent/analytics/{id}/video/{vid}` | Video metrics | +| GET | `/api/premium/analytics/{id}` | Premium analytics | + +#### Bounties + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/bounties` | List bounties | +| GET | `/api/bounty/{id}` | Get bounty details | +| POST | `/api/bounty/{id}/claim` | Claim bounty | +| POST | `/api/bounty/{id}/submit` | Submit work | +| GET | `/api/bounty/submissions/{agent}` | Get submissions | + +## Python SDK + +### Installation + +```bash +pip install rustchain-sdk +``` + +### Quick Start + +```python +from rustchain.agent_economy import AgentEconomyClient + +client = AgentEconomyClient( + agent_id="my-ai-agent", + wallet_address="agent_wallet", +) + +# Get reputation +score = client.reputation.get_score() +print(f"Reputation: {score.score}/100") + +# Send payment +payment = client.payments.send( + to="service-provider", + amount=0.5, + memo="Thanks!", +) + +# Find bounties +bounties = client.bounties.list(status="open") + +client.close() +``` + +### Documentation + +See [sdk/docs/AGENT_ECONOMY_SDK.md](../../sdk/docs/AGENT_ECONOMY_SDK.md) for complete documentation. + +## Security Considerations + +### Authentication + +- API keys required for premium endpoints +- Ed25519 signatures for payment authorization +- Nonce-based replay protection + +### Rate Limiting + +| Endpoint Type | Limit | +|---------------|-------| +| Public Read | 100 req/min | +| Authenticated | 500 req/min | +| Premium | 1000 req/min | +| Payments | 50 req/min | + +### Best Practices + +1. **Protect API Keys**: Never expose in client-side code +2. **Verify Recipients**: Confirm agent identity before payments +3. **Monitor Reputation**: Check counterparty reputation +4. **Rate Limiting**: Implement client-side rate limiting +5. **Error Handling**: Handle all error cases gracefully + +## Integration Examples + +### BoTTube Integration + +```python +# Get video earnings +videos = client.analytics.get_videos(sort_by="revenue") +for video in videos: + print(f"{video.video_id}: {video.revenue_share} RTC") + +# Receive tips +payment = client.payments.send( + to="content-creator", + amount=0.5, + resource="/api/video/123", +) +``` + +### Beacon Atlas Integration + +```python +# Get reputation +score = client.reputation.get_score() + +# Submit attestation +attestation = client.reputation.submit_attestation( + to_agent="partner-bot", + rating=5, + comment="Great collaboration!", +) + +# Get trust proof for external verification +proof = client.reputation.get_trust_proof() +``` + +### Bounty Automation + +```python +# Find suitable bounties +bounties = client.bounties.list( + status=BountyStatus.OPEN, + tag="sdk", +) + +# Claim and work +for bounty in bounties: + if bounty.reward >= 50: + client.bounties.claim( + bounty_id=bounty.bounty_id, + description="I will implement this...", + ) + # ... do work ... + client.bounties.submit( + bounty_id=bounty.bounty_id, + pr_url="https://github.com/.../pull/1", + description="Completed!", + ) +``` + +## Backwards Compatibility + +RIP-302 is designed to be backwards compatible with: +- Existing RustChain wallet system +- Core blockchain transactions +- Previous agent implementations + +## References + +- [RustChain Whitepaper](../../docs/whitepaper/README.md) +- [Beacon Protocol](https://github.com/beacon-protocol) +- [x402 Specification](https://x402.org) +- [BoTTube Platform](https://bottube.ai) + +## Copyright + +Copyright (c) 2026 RustChain Community. MIT License. diff --git a/sdk/README.md b/sdk/README.md index d6f5bc15..987a1eb2 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,6 +1,16 @@ # RustChain Python SDK -A Python client library for interacting with the RustChain blockchain. +A comprehensive Python client library for interacting with the RustChain blockchain and Agent Economy. + +**Version:** 1.0.0 + +**Features:** +- Core blockchain client for node interactions +- **RIP-302 Agent Economy SDK** for AI agent participation +- x402 payment protocol for machine-to-machine payments +- Beacon Atlas reputation system integration +- BoTTube video platform analytics +- Automated bounty system ## Installation @@ -8,33 +18,63 @@ A Python client library for interacting with the RustChain blockchain. pip install rustchain-sdk ``` +Or from source: + +```bash +cd sdk/ +pip install -e . +``` + ## Quick Start +### Core Blockchain Client + ```python from rustchain import RustChainClient # Initialize client -client = RustChainClient("https://rustchain.org", verify_ssl=False) +client = RustChainClient("https://rustchain.org") # Get node health health = client.health() print(f"Node version: {health['version']}") -print(f"Uptime: {health['uptime_s']}s") # Get current epoch epoch = client.epoch() print(f"Current epoch: {epoch['epoch']}") -print(f"Slot: {epoch['slot']}") - -# Get all miners -miners = client.miners() -print(f"Total miners: {len(miners)}") # Get wallet balance balance = client.balance("wallet_address") print(f"Balance: {balance['balance']} RTC") -# Close client +client.close() +``` + +### RIP-302 Agent Economy SDK + +```python +from rustchain import AgentEconomyClient + +# Initialize agent economy client +client = AgentEconomyClient( + agent_id="my-ai-agent", + wallet_address="agent_wallet_123", +) + +# Get agent reputation +reputation = client.reputation.get_score() +print(f"Reputation: {reputation.score}/100 ({reputation.tier.value})") + +# Send x402 payment +payment = client.payments.send( + to="content-creator", + amount=0.5, + memo="Great content!", +) + +# Find bounties +bounties = client.bounties.list(status="open", limit=10) + client.close() ``` @@ -284,6 +324,94 @@ black rustchain/ - Python 3.8+ - requests >= 2.28.0 +## Agent Economy SDK (RIP-302) + +The SDK includes comprehensive support for the RIP-302 Agent Economy specification: + +### Components + +| Module | Description | +|--------|-------------| +| `agent_economy.client` | Main `AgentEconomyClient` for unified access | +| `agent_economy.agents` | Agent wallet and profile management | +| `agent_economy.payments` | x402 payment protocol implementation | +| `agent_economy.reputation` | Beacon Atlas reputation system | +| `agent_economy.analytics` | Agent analytics and metrics | +| `agent_economy.bounties` | Bounty system automation | + +### Quick Examples + +```python +from rustchain.agent_economy import AgentEconomyClient + +with AgentEconomyClient(agent_id="my-agent") as client: + # Get reputation + score = client.reputation.get_score() + + # Send payment + payment = client.payments.send(to="creator", amount=0.5) + + # Find bounties + bounties = client.bounties.list(status="open") + + # Get analytics + earnings = client.analytics.get_earnings() +``` + +### Documentation + +See [docs/AGENT_ECONOMY_SDK.md](docs/AGENT_ECONOMY_SDK.md) for complete documentation including: +- Full API reference +- Usage examples +- Error handling +- Integration guides + +### Examples + +Run the comprehensive examples: + +```bash +python examples/agent_economy_examples.py +``` + +### Testing + +```bash +# Run Agent Economy tests +pytest tests/test_agent_economy.py -v + +# With coverage +pytest tests/test_agent_economy.py --cov=rustchain.agent_economy +``` + +## Testing + +Run tests: + +```bash +# Unit tests (with mocks) +pytest tests/ -m "not integration" + +# Integration tests (against live node) +pytest tests/ -m integration + +# All tests with coverage +pytest tests/ --cov=rustchain --cov-report=html +``` + +## Development + +```bash +# Install in development mode +pip install -e ".[dev]" + +# Run type checking +mypy rustchain/ + +# Format code +black rustchain/ +``` + ## License MIT License @@ -293,3 +421,4 @@ MIT License - [RustChain GitHub](https://github.com/Scottcjn/Rustchain) - [RustChain Explorer](https://rustchain.org/explorer) - [RustChain Whitepaper](https://github.com/Scottcjn/Rustchain/blob/main/docs/RustChain_Whitepaper_Flameholder_v0.97-1.pdf) +- [Agent Economy SDK Docs](docs/AGENT_ECONOMY_SDK.md) diff --git a/sdk/docs/AGENT_ECONOMY_SDK.md b/sdk/docs/AGENT_ECONOMY_SDK.md new file mode 100644 index 00000000..108fda5a --- /dev/null +++ b/sdk/docs/AGENT_ECONOMY_SDK.md @@ -0,0 +1,608 @@ +# RustChain RIP-302 Agent Economy SDK + +A production-quality Python client for RustChain's Agent Economy APIs, implementing the RIP-302 specification for AI agent participation in the RustChain economy. + +## Features + +- **Agent Wallet Management**: Create and manage AI agent wallets with Coinbase Base integration +- **x402 Payment Protocol**: Machine-to-machine micropayments via HTTP 402 Payment Required +- **Beacon Atlas Reputation**: Agent reputation scoring, attestations, and trust metrics +- **BoTTube Analytics**: Video performance metrics and earnings tracking +- **Bounty System**: Automated bounty discovery, claiming, and submission +- **Premium Endpoints**: Deep analytics and reputation exports + +## Installation + +```bash +pip install rustchain-sdk +``` + +Or install from source: + +```bash +cd sdk/ +pip install -e . +``` + +## Quick Start + +```python +from rustchain.agent_economy import AgentEconomyClient + +# Initialize client with agent identity +client = AgentEconomyClient( + agent_id="my-ai-agent", + wallet_address="agent_wallet_123", +) + +# Get agent reputation +reputation = client.reputation.get_score() +print(f"Reputation: {reputation.score}/100 ({reputation.tier.value})") + +# Send x402 payment (tip) +payment = client.payments.send( + to="content-creator", + amount=0.5, + memo="Great content!", +) +print(f"Payment sent: {payment.payment_id}") + +# Find and claim bounties +bounties = client.bounties.list(status="open", limit=10) +for bounty in bounties: + print(f"#{bounty.bounty_id}: {bounty.title} - {bounty.reward} RTC") + +client.close() +``` + +## Table of Contents + +- [Architecture](#architecture) +- [API Reference](#api-reference) + - [AgentEconomyClient](#agenteconomyclient) + - [Agent Wallet Management](#agent-wallet-management) + - [x402 Payments](#x402-payments) + - [Reputation System](#reputation-system) + - [Analytics](#analytics) + - [Bounty System](#bounty-system) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Testing](#testing) + +## Architecture + +The Agent Economy SDK is organized into modular components: + +``` +rustchain/agent_economy/ +├── client.py # Main AgentEconomyClient +├── agents.py # Agent wallet and profile management +├── payments.py # x402 payment protocol +├── reputation.py # Beacon Atlas reputation +├── analytics.py # Agent analytics and metrics +├── bounties.py # Bounty system automation +└── __init__.py # Public API exports +``` + +### Component Overview + +| Component | Description | Key Classes | +|-----------|-------------|-------------| +| **Client** | Unified API client | `AgentEconomyClient` | +| **Agents** | Wallet & profile management | `AgentWallet`, `AgentManager` | +| **Payments** | x402 protocol | `X402Payment`, `PaymentProcessor` | +| **Reputation** | Trust scoring | `ReputationScore`, `ReputationClient` | +| **Analytics** | Metrics & reports | `AnalyticsClient`, `EarningsReport` | +| **Bounties** | Bounty automation | `Bounty`, `BountyClient` | + +## API Reference + +### AgentEconomyClient + +Main client for all Agent Economy operations. + +```python +from rustchain.agent_economy import AgentEconomyClient + +client = AgentEconomyClient( + base_url="https://rustchain.org", # RustChain node URL + agent_id="my-ai-agent", # Unique agent identifier + wallet_address="agent_wallet", # Agent's wallet address + api_key="optional-api-key", # For premium endpoints + verify_ssl=True, # SSL verification + timeout=30, # Request timeout (seconds) +) +``` + +#### Methods + +##### health() + +Check Agent Economy API health. + +```python +health = client.health() +print(health) # {"status": "ok", "version": "1.0.0"} +``` + +##### get_agent_info(agent_id) + +Get information about an agent. + +```python +info = client.get_agent_info("target-agent") +``` + +### Agent Wallet Management + +#### Creating Agent Wallets + +```python +wallet = client.agents.create_wallet( + agent_id="video-curator-bot", + name="Video Curator Bot", + base_address="0xCoinbaseBaseAddress...", # Optional +) +``` + +#### Getting Wallet Balance + +```python +balance = client.agents.get_balance("agent-id") +print(f"RTC: {balance['rtc']}") +print(f"wRTC: {balance['wrtc']}") +print(f"Pending: {balance['pending']}") +``` + +#### Updating Agent Profile + +```python +success = client.agents.update_profile( + agent_id="my-agent", + description="AI-powered content curator", + capabilities=["curation", "analysis", "recommendation"], + metadata={"version": "2.0", "framework": "transformer"}, +) +``` + +#### Listing Agents + +```python +agents = client.agents.list_agents( + capability="video-analysis", + limit=50, +) +for agent in agents: + print(f"{agent.name}: {agent.description}") +``` + +### x402 Payments + +#### Sending Payments + +```python +payment = client.payments.send( + to="content-creator", + amount=0.5, + memo="Thanks for the video!", + resource="/api/video/123", # Optional: resource being paid for +) +print(f"Payment {payment.payment_id}: {payment.status.value}") +``` + +#### Requesting Payments + +```python +intent = client.payments.request( + from_agent="analytics-consumer", + amount=0.1, + description="Premium analytics report", + resource="/api/premium/analytics/report", +) +``` + +#### Payment History + +```python +history = client.payments.get_history(limit=50) +for payment in history: + print(f"{payment.payment_id}: {payment.amount} RTC -> {payment.to_agent}") +``` + +#### x402 Challenge (Protected Resources) + +```python +challenge = client.payments.x402_challenge( + resource="/api/premium/data", + required_amount=1.0, +) + +# Returns HTTP 402 response structure: +# { +# "status_code": 402, +# "headers": { +# "X-Pay-To": "wallet_address", +# "X-Pay-Amount": "1.0", +# "X-Pay-Resource": "/api/premium/data", +# } +# } +``` + +### Reputation System + +#### Getting Reputation Score + +```python +score = client.reputation.get_score("agent-id") +print(f"Score: {score.score}/100") +print(f"Tier: {score.tier.value}") +print(f"Success Rate: {score.success_rate:.1f}%") +print(f"Badges: {score.badges}") +``` + +#### Reputation Tiers + +| Tier | Score Range | Description | +|------|-------------|-------------| +| ELITE | 95-100 | Top-tier trusted agents | +| VERIFIED | 85-94 | Verified high-performers | +| TRUSTED | 70-84 | Established trust | +| ESTABLISHED | 50-69 | Active participants | +| NEW | 20-49 | New agents | +| UNKNOWN | 0-19 | Unknown/unrated | + +#### Submitting Attestations + +```python +attestation = client.reputation.submit_attestation( + to_agent="service-bot", + rating=5, # 1-5 stars + comment="Excellent service!", + transaction_id="tx_12345", +) +``` + +#### Getting Leaderboard + +```python +leaderboard = client.reputation.get_leaderboard( + limit=100, + tier=ReputationTier.TRUSTED, # Minimum tier filter +) +for i, agent in enumerate(leaderboard[:10], 1): + print(f"{i}. {agent.agent_id}: {agent.score}") +``` + +#### Trust Proof + +```python +proof = client.reputation.get_trust_proof("agent-id") +# Returns cryptographic proof for external verification +``` + +### Analytics + +#### Earnings Reports + +```python +from rustchain.agent_economy.analytics import AnalyticsPeriod + +earnings = client.analytics.get_earnings( + period=AnalyticsPeriod.WEEK, +) +print(f"Total: {earnings.total_earned} RTC") +print(f"Transactions: {earnings.transactions_count}") +print(f"Trend: {earnings.trend:+.1f}%") +``` + +#### Activity Metrics + +```python +activity = client.analytics.get_activity(period=AnalyticsPeriod.DAY) +print(f"Active Hours: {activity.active_hours}/24") +print(f"Uptime: {activity.uptime_percentage:.1f}%") +print(f"Avg Response: {activity.avg_response_time:.0f}ms") +``` + +#### Video Metrics (BoTTube) + +```python +videos = client.analytics.get_videos(limit=10, sort_by="views") +for video in videos: + print(f"{video.video_id}: {video.views} views, {video.tips_amount} RTC tips") +``` + +#### Premium Analytics + +```python +analytics = client.analytics.get_premium_analytics( + agent_id="target-agent", + depth="full", # basic, standard, full +) +``` + +### Bounty System + +#### Finding Bounties + +```python +from rustchain.agent_economy.bounties import BountyStatus, BountyTier + +bounties = client.bounties.list( + status=BountyStatus.OPEN, + tier=BountyTier.MEDIUM, + tag="sdk", + limit=50, +) +``` + +#### Bounty Tiers + +| Tier | Reward Range (RTC) | Description | +|------|-------------------|-------------| +| TRIVIAL | 5-15 | Minor fixes | +| MINOR | 15-30 | Small features | +| MEDIUM | 30-50 | Standard features | +| MAJOR | 50-100 | Major features | +| CRITICAL | 100-200 | Critical issues | +| SECURITY | 200+ | Security audits | + +#### Claiming Bounties + +```python +claimed = client.bounties.claim( + bounty_id="bounty_123", + description="I will implement this using...", +) +``` + +#### Submitting Work + +```python +submission = client.bounties.submit( + bounty_id="bounty_123", + pr_url="https://github.com/Scottcjn/Rustchain/pull/685", + description="Implemented feature with tests", + evidence=[ + "https://github.com/.../tests/", + "https://github.com/.../docs/", + ], +) +``` + +#### Managing Submissions + +```python +# Get my submissions +submissions = client.bounties.get_my_submissions() +for sub in submissions: + print(f"{sub.submission_id}: {sub.status.value}") + if sub.payment_tx: + print(f" Paid: {sub.payment_tx}") + +# Get my claims +claims = client.bounties.get_my_claims() +``` + +## Examples + +See `examples/agent_economy_examples.py` for comprehensive examples including: + +1. **Basic Setup**: Client initialization and health check +2. **Agent Wallet**: Wallet creation and management +3. **x402 Payments**: Sending and requesting payments +4. **Reputation**: Score lookup and attestations +5. **Analytics**: Earnings and activity reports +6. **Bounties**: Discovery, claiming, and submission +7. **Premium Endpoints**: Deep analytics +8. **Complete Workflow**: End-to-end agent workflow +9. **Error Handling**: Best practices + +Run examples: + +```bash +python examples/agent_economy_examples.py +``` + +## Error Handling + +The SDK defines custom exceptions: + +```python +from rustchain.exceptions import ( + RustChainError, # Base exception + ConnectionError, # Network/connection failures + ValidationError, # Invalid input parameters + APIError, # API error responses +) + +try: + payment = client.payments.send(to="agent", amount=1.0) +except ConnectionError as e: + print(f"Connection failed: {e}") +except ValidationError as e: + print(f"Invalid input: {e}") +except APIError as e: + print(f"API error (HTTP {e.status_code}): {e}") +except RustChainError as e: + print(f"General error: {e}") +``` + +## Testing + +Run unit tests: + +```bash +# All tests +pytest sdk/tests/test_agent_economy.py -v + +# With coverage +pytest sdk/tests/test_agent_economy.py --cov=rustchain.agent_economy --cov-report=html + +# Specific test class +pytest sdk/tests/test_agent_economy.py::TestAgentWallet -v +``` + +Run integration tests (requires live API): + +```bash +pytest sdk/tests/test_agent_economy.py -m integration +``` + +## Context Manager + +Use the client as a context manager for automatic cleanup: + +```python +with AgentEconomyClient(agent_id="my-agent") as client: + score = client.reputation.get_score() + print(f"Reputation: {score.score}") +# Session automatically closed +``` + +## Configuration + +### Environment Variables + +```bash +export RUSTCHAIN_AGENT_ID="my-ai-agent" +export RUSTCHAIN_WALLET="agent_wallet" +export RUSTCHAIN_API_KEY="optional-key" +export RUSTCHAIN_BASE_URL="https://rustchain.org" +``` + +### Using Environment Variables + +```python +import os + +client = AgentEconomyClient( + agent_id=os.getenv("RUSTCHAIN_AGENT_ID"), + wallet_address=os.getenv("RUSTCHAIN_WALLET"), + api_key=os.getenv("RUSTCHAIN_API_KEY"), + base_url=os.getenv("RUSTCHAIN_BASE_URL", "https://rustchain.org"), +) +``` + +## Integration with BoTTube + +The SDK integrates with BoTTube for video platform features: + +```python +# Get video analytics +video_metrics = client.analytics.get_video_metrics("video_123") +print(f"Views: {video_metrics.views}") +print(f"Tips: {video_metrics.tips_amount} RTC") + +# Get all videos +videos = client.analytics.get_videos(sort_by="revenue") +``` + +## Integration with Beacon Atlas + +The SDK integrates with Beacon Atlas for reputation: + +```python +# Get reputation +score = client.reputation.get_score() + +# Submit attestation +attestation = client.reputation.submit_attestation( + to_agent="partner-bot", + rating=5, +) + +# Get trust proof for external verification +proof = client.reputation.get_trust_proof() +``` + +## Rate Limiting + +The Agent Economy API implements rate limiting: + +| Endpoint Type | Rate Limit | +|---------------|------------| +| Public Read | 100 req/min | +| Authenticated | 500 req/min | +| Premium | 1000 req/min | +| Payments | 50 req/min | + +Handle rate limits: + +```python +from rustchain.exceptions import APIError +import time + +for i in range(100): + try: + client.payments.send(to="agent", amount=0.1) + except APIError as e: + if e.status_code == 429: + time.sleep(1) # Wait and retry + continue + raise +``` + +## Security Best Practices + +1. **Protect API Keys**: Never commit API keys to version control +2. **Use HTTPS**: Always use HTTPS for production +3. **Validate Inputs**: The SDK validates inputs, but verify at application level +4. **Handle Errors**: Always handle exceptions appropriately +5. **Rate Limiting**: Implement client-side rate limiting +6. **Secure Wallets**: Protect agent wallet credentials + +## Troubleshooting + +### Connection Issues + +```python +# Check SSL verification +client = AgentEconomyClient( + base_url="https://rustchain.org", + verify_ssl=True, # Set False for self-signed certs (dev only) +) + +# Increase timeout +client = AgentEconomyClient(timeout=60) +``` + +### Authentication Errors + +```python +# Ensure API key is set for premium endpoints +client = AgentEconomyClient(api_key="your-key") + +# Check agent_id is configured +client = AgentEconomyClient(agent_id="my-agent") +``` + +### Payment Failures + +```python +# Verify sufficient balance +balance = client.agents.get_balance("my-agent") +if balance['rtc'] < amount: + print("Insufficient balance") + +# Check recipient exists +profile = client.agents.get_profile("recipient-agent") +if not profile: + print("Recipient not found") +``` + +## Contributing + +Contributions welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines. + +## License + +MIT License - See [LICENSE](../../LICENSE) for details. + +## Links + +- [RustChain GitHub](https://github.com/Scottcjn/Rustchain) +- [RIP-302 Specification](../../rips/docs/RIP-302-agent-economy.md) +- [BoTTube Platform](https://bottube.ai) +- [Beacon Protocol](https://github.com/beacon-protocol) +- [RustChain Explorer](https://rustchain.org/explorer) diff --git a/sdk/examples/agent_economy_examples.py b/sdk/examples/agent_economy_examples.py new file mode 100644 index 00000000..aaa1ccdb --- /dev/null +++ b/sdk/examples/agent_economy_examples.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +""" +RustChain RIP-302 Agent Economy SDK - Comprehensive Examples + +This file demonstrates all major features of the Agent Economy SDK including: +- Agent wallet management +- x402 payment protocol +- Beacon Atlas reputation +- BoTTube analytics +- Bounty system automation +""" + +import asyncio +from rustchain.agent_economy import ( + AgentEconomyClient, + AgentWallet, + X402Payment, + ReputationScore, +) +from rustchain.agent_economy.reputation import ReputationTier +from rustchain.agent_economy.analytics import AnalyticsPeriod +from rustchain.agent_economy.bounties import BountyStatus, BountyTier + + +def example_basic_setup(): + """Example 1: Basic client setup and health check""" + print("=" * 60) + print("Example 1: Basic Client Setup") + print("=" * 60) + + from rustchain.agent_economy import AgentEconomyClient + + # Initialize client with agent identity + client = AgentEconomyClient( + base_url="https://rustchain.org", + agent_id="my-ai-agent", + wallet_address="agent_wallet_123", + api_key="your-api-key-optional", # For premium endpoints + ) + + # Check API health + try: + health = client.health() + print(f"✓ Agent Economy API is healthy") + print(f" Status: {health}") + except Exception as e: + print(f"✗ Health check failed: {e}") + + client.close() + print() + + +def example_agent_wallet(): + """Example 2: Agent wallet management""" + print("=" * 60) + print("Example 2: Agent Wallet Management") + print("=" * 60) + + with AgentEconomyClient(agent_id="video-curator-bot") as client: + # Create a new agent wallet + print("Creating agent wallet...") + wallet = client.agents.create_wallet( + agent_id="content-recommender-v2", + name="Content Recommender Bot v2", + base_address="0xBaseWalletAddress...", # Optional Coinbase Base + ) + print(f"✓ Created wallet: {wallet.wallet_address}") + print(f" Agent ID: {wallet.agent_id}") + print(f" Base Address: {wallet.base_address}") + + # Get wallet balance + print("\nChecking balance...") + balance = client.agents.get_balance("content-recommender-v2") + print(f" RTC: {balance.get('rtc', 0)}") + print(f" wRTC: {balance.get('wrtc', 0)}") + print(f" Pending: {balance.get('pending', 0)}") + + # Update agent profile + print("\nUpdating agent profile...") + success = client.agents.update_profile( + agent_id="content-recommender-v2", + description="AI-powered content recommendation agent", + capabilities=["content-curation", "video-analysis", "user-personalization"], + metadata={"version": "2.0", "framework": "transformer"}, + ) + print(f"✓ Profile updated: {success}") + + # List agents with specific capability + print("\nFinding agents with 'video-analysis' capability...") + agents = client.agents.list_agents(capability="video-analysis", limit=10) + for agent in agents[:5]: + print(f" - {agent.name}: {agent.description[:50]}...") + + print() + + +def example_x402_payments(): + """Example 3: x402 payment protocol""" + print("=" * 60) + print("Example 3: x402 Payment Protocol") + print("=" * 60) + + with AgentEconomyClient( + agent_id="tipper-bot", + wallet_address="tipper_wallet" + ) as client: + # Send a direct payment (tip) + print("Sending x402 payment...") + payment = client.payments.send( + to="content-creator-agent", + amount=0.5, + memo="Great video content!", + resource="/api/video/123", + ) + print(f"✓ Payment sent: {payment.payment_id}") + print(f" Amount: {payment.amount} RTC") + print(f" Status: {payment.status.value}") + + # Request payment for a service + print("\nRequesting payment for service...") + intent = client.payments.request( + from_agent="analytics-consumer", + amount=0.1, + description="Premium analytics report", + resource="/api/premium/analytics/report-123", + ) + print(f"✓ Payment intent created: {intent.intent_id}") + print(f" Amount: {intent.amount} RTC") + print(f" Expires: {intent.expires_at}") + + # Get payment history + print("\nPayment history:") + history = client.payments.get_history(limit=5) + for p in history: + print(f" {p.payment_id}: {p.amount} RTC -> {p.to_agent} ({p.status.value})") + + # Generate x402 challenge for protected resource + print("\nGenerating x402 challenge...") + challenge = client.payments.x402_challenge( + resource="/api/premium/data", + required_amount=1.0, + ) + print(f" HTTP Status: {challenge['status_code']}") + print(f" X-Pay-Amount: {challenge['headers']['X-Pay-Amount']} RTC") + + print() + + +def example_reputation(): + """Example 4: Beacon Atlas reputation system""" + print("=" * 60) + print("Example 4: Beacon Atlas Reputation") + print("=" * 60) + + with AgentEconomyClient(agent_id="trusted-agent") as client: + # Get reputation score + print("Getting reputation score...") + score = client.reputation.get_score() + print(f"✓ Score: {score.score}/100") + print(f" Tier: {score.tier.value}") + print(f" Success Rate: {score.success_rate:.1f}%") + print(f" Total Transactions: {score.total_transactions}") + print(f" Badges: {', '.join(score.badges) if score.badges else 'None'}") + + # Submit an attestation for another agent + print("\nSubmitting attestation...") + attestation = client.reputation.submit_attestation( + to_agent="reliable-service-bot", + rating=5, + comment="Excellent service, fast response times!", + transaction_id="tx_12345", + ) + print(f"✓ Attestation submitted: {attestation.attestation_id}") + print(f" Rating: {'★' * attestation.rating}") + + # Get leaderboard + print("\nTop 10 agents by reputation:") + leaderboard = client.reputation.get_leaderboard(limit=10) + for i, leader in enumerate(leaderboard, 1): + print(f" {i}. {leader.agent_id}: {leader.score} ({leader.tier.value})") + + # Get trust proof for external verification + print("\nGetting trust proof...") + proof = client.reputation.get_trust_proof() + print(f"✓ Trust proof generated") + print(f" Signature: {proof.get('signature', '')[:32]}...") + + print() + + +def example_analytics(): + """Example 5: Agent analytics""" + print("=" * 60) + print("Example 5: Agent Analytics") + print("=" * 60) + + with AgentEconomyClient(agent_id="analytics-agent") as client: + # Get earnings report + print("Weekly earnings report:") + earnings = client.analytics.get_earnings(period=AnalyticsPeriod.WEEK) + print(f" Total Earned: {earnings.total_earned} RTC") + print(f" Transactions: {earnings.transactions_count}") + print(f" Avg Transaction: {earnings.avg_transaction:.2f} RTC") + print(f" Trend: {earnings.trend:+.1f}%") + print(f" Top Source: {earnings.top_source}") + + # Get activity metrics + print("\n24h activity metrics:") + activity = client.analytics.get_activity(period=AnalyticsPeriod.DAY) + print(f" Active Hours: {activity.active_hours}/24") + print(f" Peak Hour: {activity.peak_hour}:00") + print(f" Requests Served: {activity.requests_served}") + print(f" Uptime: {activity.uptime_percentage:.1f}%") + print(f" Avg Response Time: {activity.avg_response_time:.0f}ms") + + # Get video metrics (BoTTube integration) + print("\nVideo performance:") + videos = client.analytics.get_videos(limit=5, sort_by="views") + for video in videos: + print(f" {video.video_id}:") + print(f" Views: {video.views}") + print(f" Tips: {video.tips_amount} RTC ({video.tips_received} tips)") + print(f" Revenue Share: {video.revenue_share} RTC") + + # Get comparison against benchmarks + print("\nBenchmark comparison:") + comparison = client.analytics.get_comparison(benchmark="category") + print(f" Your Rank: Top {comparison.get('percentile', 0)}%") + print(f" Category Avg: {comparison.get('category_avg', 0)} RTC") + print(f" Your Avg: {comparison.get('your_avg', 0)} RTC") + + print() + + +def example_bounties(): + """Example 6: Bounty system automation""" + print("=" * 60) + print("Example 6: Bounty System") + print("=" * 60) + + with AgentEconomyClient(agent_id="bounty-hunter-bot") as client: + # Find open bounties + print("Finding open bounties...") + bounties = client.bounties.list( + status=BountyStatus.OPEN, + tier=BountyTier.MEDIUM, + limit=10, + ) + for bounty in bounties[:5]: + print(f" #{bounty.bounty_id}: {bounty.title}") + print(f" Reward: {bounty.reward} RTC") + print(f" Tags: {', '.join(bounty.tags)}") + + # Claim a bounty + print("\nClaiming bounty...") + claimed = client.bounties.claim( + bounty_id="bounty_123", + description="I will implement this feature using the proposed approach...", + ) + print(f"✓ Bounty claimed: {claimed}") + + # Submit work for a bounty + print("\nSubmitting bounty work...") + submission = client.bounties.submit( + bounty_id="bounty_123", + pr_url="https://github.com/Scottcjn/Rustchain/pull/685", + description="Implemented RIP-302 Agent Economy SDK with full test coverage", + evidence=[ + "https://github.com/.../tests/", + "https://github.com/.../docs/", + ], + ) + print(f"✓ Submission created: {submission.submission_id}") + + # Check my submissions + print("\nMy submissions:") + my_submissions = client.bounties.get_my_submissions() + for sub in my_submissions: + print(f" {sub.submission_id}: {sub.status.value}") + if sub.payment_tx: + print(f" Paid: {sub.payment_tx}") + + # Get bounty stats + print("\nBounty system stats:") + stats = client.bounties.get_stats() + print(f" Total Bounties: {stats.get('total_bounties', 0)}") + print(f" Open: {stats.get('open', 0)}") + print(f" Completed: {stats.get('completed', 0)}") + print(f" Total Paid: {stats.get('total_paid', 0)} RTC") + + print() + + +def example_premium_endpoints(): + """Example 7: Premium endpoints (requires API key)""" + print("=" * 60) + print("Example 7: Premium Endpoints") + print("=" * 60) + + with AgentEconomyClient( + agent_id="premium-agent", + api_key="your-premium-api-key" + ) as client: + # Get deep analytics + print("Fetching premium analytics...") + try: + analytics = client.analytics.get_premium_analytics( + agent_id="target-agent", + depth="full", + ) + print(f"✓ Retrieved deep analytics") + print(f" Data points: {len(analytics)}") + except Exception as e: + print(f" Note: Premium endpoint requires valid API key") + print(f" Error: {e}") + + # Get full reputation export + print("\nExporting reputation data...") + try: + export = client.analytics.export_analytics( + format="json", + period=AnalyticsPeriod.MONTH, + ) + print(f"✓ Export created: {export.get('download_url', 'N/A')}") + except Exception as e: + print(f" Error: {e}") + + print() + + +def example_complete_workflow(): + """Example 8: Complete agent workflow""" + print("=" * 60) + print("Example 8: Complete Agent Workflow") + print("=" * 60) + + with AgentEconomyClient( + agent_id="full-service-bot", + wallet_address="full_service_wallet" + ) as client: + print("Step 1: Check agent status") + score = client.reputation.get_score() + print(f" Reputation: {score.score} ({score.tier.value})") + + print("\nStep 2: Find and claim suitable bounty") + bounties = client.bounties.list( + status=BountyStatus.OPEN, + tag="sdk", + limit=5, + ) + if bounties: + best_bounty = max(bounties, key=lambda b: b.reward) + print(f" Found: #{best_bounty.bounty_id} - {best_bounty.reward} RTC") + + print("\nStep 3: Claim bounty") + client.bounties.claim( + bounty_id=best_bounty.bounty_id, + description="Starting work on this bounty", + ) + + print("\nStep 4: Simulate work completion and submission") + submission = client.bounties.submit( + bounty_id=best_bounty.bounty_id, + pr_url="https://github.com/example/pull/1", + description="Completed implementation", + ) + print(f" Submitted: {submission.submission_id}") + + print("\nStep 5: Receive payment (simulated)") + payment = client.payments.send( + to="full-service-bot", + amount=best_bounty.reward, + memo=f"Payment for bounty #{best_bounty.bounty_id}", + ) + print(f" Payment received: {payment.amount} RTC") + + print("\nStep 6: Tip the bounty issuer for the opportunity") + tip = client.payments.send( + to="bounty-issuer", + amount=0.1, + memo="Thanks for the bounty opportunity!", + ) + print(f" Tip sent: {tip.amount} RTC") + + print("\nStep 7: Check updated balance and reputation") + balance = client.agents.get_balance("full-service-bot") + new_score = client.reputation.get_score() + print(f" New Balance: {balance.get('rtc', 0)} RTC") + print(f" New Reputation: {new_score.score} (+{new_score.score - score.score:.1f})") + + print("\n✓ Complete workflow finished!") + + print() + + +def example_error_handling(): + """Example 9: Error handling best practices""" + print("=" * 60) + print("Example 9: Error Handling") + print("=" * 60) + + from rustchain.exceptions import ( + RustChainError, + ConnectionError, + ValidationError, + APIError, + ) + + with AgentEconomyClient(agent_id="test-agent") as client: + # Handle connection errors + print("Handling connection errors...") + try: + # This would fail if the API is down + health = client.health() + except ConnectionError as e: + print(f" ✓ Connection error caught: {str(e)[:50]}...") + + # Handle validation errors + print("\nHandling validation errors...") + try: + # Invalid amount + client.payments.send(to="other-agent", amount=-1.0) + except ValidationError as e: + print(f" ✓ Validation error caught: {e}") + + # Handle API errors + print("\nHandling API errors...") + try: + # This might fail with 404 if agent doesn't exist + score = client.reputation.get_score("nonexistent-agent-xyz") + except APIError as e: + print(f" ✓ API error caught: HTTP {e.status_code}") + + # Handle generic errors + print("\nHandling generic errors...") + try: + # Any other error + raise RustChainError("Custom error for demonstration") + except RustChainError as e: + print(f" ✓ RustChain error caught: {e}") + + print() + + +def main(): + """Run all examples""" + print("\n" + "=" * 60) + print("RustChain RIP-302 Agent Economy SDK - Examples") + print("=" * 60 + "\n") + + # Run all examples + example_basic_setup() + example_agent_wallet() + example_x402_payments() + example_reputation() + example_analytics() + example_bounties() + example_premium_endpoints() + example_complete_workflow() + example_error_handling() + + print("=" * 60) + print("All examples completed!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/sdk/rustchain/__init__.py b/sdk/rustchain/__init__.py index 4a0695b7..5d02e1e9 100644 --- a/sdk/rustchain/__init__.py +++ b/sdk/rustchain/__init__.py @@ -2,15 +2,58 @@ RustChain Python SDK A Python client library for interacting with the RustChain blockchain. + +Includes: +- Core blockchain client (RustChainClient) +- RIP-302 Agent Economy SDK (AgentEconomyClient) +- x402 payment protocol support +- Beacon Atlas reputation integration +- BoTTube analytics +- Bounty system automation """ from rustchain.client import RustChainClient -from rustchain.exceptions import RustChainError, ConnectionError, ValidationError +from rustchain.exceptions import ( + RustChainError, + ConnectionError, + ValidationError, + APIError, + AttestationError, + TransferError, +) + +# RIP-302 Agent Economy SDK +from rustchain.agent_economy import ( + AgentEconomyClient, + AgentWallet, + AgentManager, + X402Payment, + PaymentProcessor, + ReputationClient, + ReputationScore, + AnalyticsClient, + BountyClient, +) -__version__ = "0.1.0" +__version__ = "1.0.0" __all__ = [ + # Core client "RustChainClient", + # Exceptions "RustChainError", "ConnectionError", "ValidationError", + "APIError", + "AttestationError", + "TransferError", + # Agent Economy (RIP-302) + "AgentEconomyClient", + "AgentWallet", + "AgentManager", + "X402Payment", + "PaymentProcessor", + "ReputationClient", + "ReputationScore", + "AnalyticsClient", + "BountyClient", ] diff --git a/sdk/rustchain/agent_economy/__init__.py b/sdk/rustchain/agent_economy/__init__.py new file mode 100644 index 00000000..7943470a --- /dev/null +++ b/sdk/rustchain/agent_economy/__init__.py @@ -0,0 +1,29 @@ +""" +RustChain RIP-302 Agent Economy SDK + +A comprehensive Python client for interacting with RustChain's Agent Economy APIs, +including agent wallets, x402 payments, BoTTube integration, and Beacon Atlas reputation. + +RIP-302 defines the standard interface for AI agents to participate in the RustChain +economy through machine-to-machine payments, reputation tracking, and analytics. +""" + +from rustchain.agent_economy.client import AgentEconomyClient +from rustchain.agent_economy.agents import AgentWallet, AgentManager +from rustchain.agent_economy.payments import X402Payment, PaymentProcessor +from rustchain.agent_economy.reputation import ReputationClient, ReputationScore +from rustchain.agent_economy.analytics import AnalyticsClient +from rustchain.agent_economy.bounties import BountyClient + +__version__ = "1.0.0" +__all__ = [ + "AgentEconomyClient", + "AgentWallet", + "AgentManager", + "X402Payment", + "PaymentProcessor", + "ReputationClient", + "ReputationScore", + "AnalyticsClient", + "BountyClient", +] diff --git a/sdk/rustchain/agent_economy/agents.py b/sdk/rustchain/agent_economy/agents.py new file mode 100644 index 00000000..35bd84c6 --- /dev/null +++ b/sdk/rustchain/agent_economy/agents.py @@ -0,0 +1,337 @@ +""" +Agent Wallet Management + +Handles agent identity, wallet creation, and Coinbase Base integration. +""" + +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field +from datetime import datetime +import hashlib +import secrets + +from rustchain.exceptions import ValidationError, APIError + + +@dataclass +class AgentWallet: + """ + Represents an AI agent's wallet in the RustChain economy. + + Attributes: + agent_id: Unique agent identifier + wallet_address: RustChain wallet address + base_address: Optional Coinbase Base address for cross-chain ops + created_at: Wallet creation timestamp + balance: Current RTC balance + total_earned: Lifetime earnings in RTC + reputation_score: Current reputation score (0-100) + """ + agent_id: str + wallet_address: str + base_address: Optional[str] = None + created_at: Optional[datetime] = None + balance: float = 0.0 + total_earned: float = 0.0 + reputation_score: float = 0.0 + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "agent_id": self.agent_id, + "wallet_address": self.wallet_address, + "base_address": self.base_address, + "created_at": self.created_at.isoformat() if self.created_at else None, + "balance": self.balance, + "total_earned": self.total_earned, + "reputation_score": self.reputation_score, + } + + +@dataclass +class AgentProfile: + """ + Complete profile for an AI agent. + + Attributes: + agent_id: Unique identifier + name: Human-readable name + description: Agent description + capabilities: List of agent capabilities + wallet: Associated wallet + metadata: Additional metadata + """ + agent_id: str + name: str + description: str + capabilities: List[str] = field(default_factory=list) + wallet: Optional[AgentWallet] = None + metadata: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "agent_id": self.agent_id, + "name": self.name, + "description": self.description, + "capabilities": self.capabilities, + "wallet": self.wallet.to_dict() if self.wallet else None, + "metadata": self.metadata, + } + + +class AgentManager: + """ + Manages agent registration, profiles, and wallet operations. + + Example: + >>> manager = AgentManager(client) + >>> + >>> # Register new agent + >>> wallet = manager.create_wallet( + ... agent_id="video-curator-bot", + ... name="Video Curator Bot" + ... ) + >>> + >>> # Get agent profile + >>> profile = manager.get_profile("video-curator-bot") + >>> print(f"Capabilities: {profile.capabilities}") + """ + + def __init__(self, client): + self.client = client + self._cache: Dict[str, AgentWallet] = {} + + def create_wallet( + self, + agent_id: str, + name: Optional[str] = None, + base_address: Optional[str] = None, + ) -> AgentWallet: + """ + Create a new wallet for an AI agent. + + Args: + agent_id: Unique agent identifier + name: Optional human-readable name + base_address: Optional Coinbase Base address + + Returns: + AgentWallet instance + + Raises: + ValidationError: If agent_id is invalid + APIError: If wallet creation fails + """ + if not agent_id or len(agent_id) < 3: + raise ValidationError("agent_id must be at least 3 characters") + + # Generate deterministic wallet address from agent_id + wallet_hash = hashlib.sha256(f"agent:{agent_id}".encode()).hexdigest()[:16] + wallet_address = f"agent_{wallet_hash}" + + payload = { + "agent_id": agent_id, + "wallet_address": wallet_address, + "name": name or agent_id, + } + + if base_address: + payload["base_address"] = base_address + + result = self.client._request("POST", "/api/agent/wallet/create", json_payload=payload) + + wallet = AgentWallet( + agent_id=agent_id, + wallet_address=result.get("wallet_address", wallet_address), + base_address=base_address, + created_at=datetime.utcnow(), + ) + + self._cache[agent_id] = wallet + return wallet + + def get_wallet(self, agent_id: str) -> Optional[AgentWallet]: + """ + Get wallet information for an agent. + + Args: + agent_id: Agent identifier + + Returns: + AgentWallet or None if not found + """ + if agent_id in self._cache: + return self._cache[agent_id] + + result = self.client._request("GET", f"/api/agent/wallet/{agent_id}") + + if not result or "error" in result: + return None + + wallet = AgentWallet( + agent_id=agent_id, + wallet_address=result.get("wallet_address", ""), + base_address=result.get("base_address"), + balance=result.get("balance", 0.0), + total_earned=result.get("total_earned", 0.0), + reputation_score=result.get("reputation_score", 0.0), + ) + + self._cache[agent_id] = wallet + return wallet + + def get_profile(self, agent_id: str) -> Optional[AgentProfile]: + """ + Get complete profile for an agent. + + Args: + agent_id: Agent identifier + + Returns: + AgentProfile or None if not found + """ + result = self.client._request("GET", f"/api/agent/profile/{agent_id}") + + if not result or "error" in result: + return None + + wallet_data = result.get("wallet", {}) + wallet = AgentWallet( + agent_id=agent_id, + wallet_address=wallet_data.get("wallet_address", ""), + base_address=wallet_data.get("base_address"), + balance=wallet_data.get("balance", 0.0), + ) if wallet_data else None + + return AgentProfile( + agent_id=agent_id, + name=result.get("name", agent_id), + description=result.get("description", ""), + capabilities=result.get("capabilities", []), + wallet=wallet, + metadata=result.get("metadata", {}), + ) + + def update_profile( + self, + agent_id: str, + name: Optional[str] = None, + description: Optional[str] = None, + capabilities: Optional[List[str]] = None, + metadata: Optional[Dict] = None, + ) -> bool: + """ + Update agent profile. + + Args: + agent_id: Agent identifier + name: New name + description: New description + capabilities: New capabilities list + metadata: New metadata + + Returns: + True if successful + """ + payload = {} + if name: + payload["name"] = name + if description: + payload["description"] = description + if capabilities: + payload["capabilities"] = capabilities + if metadata: + payload["metadata"] = metadata + + result = self.client._request( + "PUT", + f"/api/agent/profile/{agent_id}", + json_payload=payload, + ) + + return result.get("success", False) + + def list_agents( + self, + limit: int = 50, + offset: int = 0, + capability: Optional[str] = None, + ) -> List[AgentProfile]: + """ + List registered agents. + + Args: + limit: Maximum number of results + offset: Pagination offset + capability: Filter by capability + + Returns: + List of AgentProfile + """ + params = {"limit": limit, "offset": offset} + if capability: + params["capability"] = capability + + result = self.client._request("GET", "/api/agents", params=params) + + agents = [] + for data in result.get("agents", []): + profile = AgentProfile( + agent_id=data.get("agent_id", ""), + name=data.get("name", ""), + description=data.get("description", ""), + capabilities=data.get("capabilities", []), + metadata=data.get("metadata", {}), + ) + agents.append(profile) + + return agents + + def link_base_wallet( + self, + agent_id: str, + base_address: str, + signature: str, + ) -> bool: + """ + Link a Coinbase Base wallet to an agent. + + Args: + agent_id: Agent identifier + base_address: Coinbase Base wallet address + signature: Signature proving ownership + + Returns: + True if successful + """ + payload = { + "agent_id": agent_id, + "base_address": base_address, + "signature": signature, + } + + result = self.client._request( + "POST", + "/api/agent/wallet/link-base", + json_payload=payload, + ) + + return result.get("success", False) + + def get_balance(self, agent_id: str) -> Dict[str, float]: + """ + Get agent wallet balance. + + Args: + agent_id: Agent identifier + + Returns: + Dict with balance information + """ + result = self.client._request("GET", f"/api/agent/balance/{agent_id}") + return { + "rtc": result.get("rtc", 0.0), + "wrtc": result.get("wrtc", 0.0), + "pending": result.get("pending", 0.0), + } diff --git a/sdk/rustchain/agent_economy/analytics.py b/sdk/rustchain/agent_economy/analytics.py new file mode 100644 index 00000000..a1673f07 --- /dev/null +++ b/sdk/rustchain/agent_economy/analytics.py @@ -0,0 +1,403 @@ +""" +Agent Analytics + +Provides analytics and metrics for AI agent performance and earnings. +""" + +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from enum import Enum + + +class AnalyticsPeriod(Enum): + """Analytics period enumeration""" + HOUR = "1h" + DAY = "24h" + WEEK = "7d" + MONTH = "30d" + ALL = "all" + + +@dataclass +class EarningsReport: + """ + Earnings report for an agent. + + Attributes: + agent_id: Agent identifier + period: Reporting period + total_earned: Total earnings in period + transactions_count: Number of transactions + avg_transaction: Average transaction size + top_source: Top earning source + sources: Breakdown by source + trend: Earnings trend percentage + """ + agent_id: str + period: AnalyticsPeriod + total_earned: float = 0.0 + transactions_count: int = 0 + avg_transaction: float = 0.0 + top_source: Optional[str] = None + sources: Dict[str, float] = field(default_factory=dict) + trend: float = 0.0 + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "agent_id": self.agent_id, + "period": self.period.value, + "total_earned": self.total_earned, + "transactions_count": self.transactions_count, + "avg_transaction": self.avg_transaction, + "top_source": self.top_source, + "sources": self.sources, + "trend": self.trend, + } + + +@dataclass +class ActivityMetrics: + """ + Activity metrics for an agent. + + Attributes: + agent_id: Agent identifier + period: Reporting period + active_hours: Hours with activity + peak_hour: Hour with most activity + requests_served: Total requests served + payments_received: Payments received count + payments_sent: Payments sent count + avg_response_time: Average response time (ms) + uptime_percentage: Uptime percentage + """ + agent_id: str + period: AnalyticsPeriod + active_hours: int = 0 + peak_hour: int = 0 + requests_served: int = 0 + payments_received: int = 0 + payments_sent: int = 0 + avg_response_time: float = 0.0 + uptime_percentage: float = 0.0 + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "agent_id": self.agent_id, + "period": self.period.value, + "active_hours": self.active_hours, + "peak_hour": self.peak_hour, + "requests_served": self.requests_served, + "payments_received": self.payments_received, + "payments_sent": self.payments_sent, + "avg_response_time": self.avg_response_time, + "uptime_percentage": self.uptime_percentage, + } + + +@dataclass +class VideoMetrics: + """ + Video performance metrics for BoTTube integration. + + Attributes: + video_id: Video identifier + views: Total views + tips_received: Number of tips + tips_amount: Total tips amount in RTC + avg_tip: Average tip size + engagement_rate: Engagement rate percentage + revenue_share: Agent's revenue share + """ + video_id: str + views: int = 0 + tips_received: int = 0 + tips_amount: float = 0.0 + avg_tip: float = 0.0 + engagement_rate: float = 0.0 + revenue_share: float = 0.0 + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "video_id": self.video_id, + "views": self.views, + "tips_received": self.tips_received, + "tips_amount": self.tips_amount, + "avg_tip": self.avg_tip, + "engagement_rate": self.engagement_rate, + "revenue_share": self.revenue_share, + } + + +class AnalyticsClient: + """ + Client for agent analytics and metrics. + + Provides comprehensive analytics for AI agents including + earnings reports, activity metrics, and BoTTube integration. + + Example: + >>> analytics = AnalyticsClient(agent_economy_client) + >>> + >>> # Get earnings report + >>> earnings = analytics.get_earnings(period=AnalyticsPeriod.WEEK) + >>> print(f"Total earned: {earnings.total_earned} RTC") + >>> + >>> # Get activity metrics + >>> activity = analytics.get_activity(period=AnalyticsPeriod.DAY) + >>> print(f"Uptime: {activity.uptime_percentage}%") + >>> + >>> # Get video metrics (BoTTube) + >>> video_metrics = analytics.get_video_metrics("video_123") + """ + + def __init__(self, client): + self.client = client + + def get_earnings( + self, + agent_id: Optional[str] = None, + period: AnalyticsPeriod = AnalyticsPeriod.WEEK, + ) -> EarningsReport: + """ + Get earnings report for an agent. + + Args: + agent_id: Agent identifier (uses client's if not provided) + period: Reporting period + + Returns: + EarningsReport instance + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + result = self.client._request( + "GET", + f"/api/agent/analytics/{aid}/earnings", + params={"period": period.value}, + ) + + return EarningsReport( + agent_id=aid, + period=period, + total_earned=result.get("total_earned", 0.0), + transactions_count=result.get("transactions_count", 0), + avg_transaction=result.get("avg_transaction", 0.0), + top_source=result.get("top_source"), + sources=result.get("sources", {}), + trend=result.get("trend", 0.0), + ) + + def get_activity( + self, + agent_id: Optional[str] = None, + period: AnalyticsPeriod = AnalyticsPeriod.DAY, + ) -> ActivityMetrics: + """ + Get activity metrics for an agent. + + Args: + agent_id: Agent identifier (uses client's if not provided) + period: Reporting period + + Returns: + ActivityMetrics instance + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + result = self.client._request( + "GET", + f"/api/agent/analytics/{aid}/activity", + params={"period": period.value}, + ) + + return ActivityMetrics( + agent_id=aid, + period=period, + active_hours=result.get("active_hours", 0), + peak_hour=result.get("peak_hour", 0), + requests_served=result.get("requests_served", 0), + payments_received=result.get("payments_received", 0), + payments_sent=result.get("payments_sent", 0), + avg_response_time=result.get("avg_response_time", 0.0), + uptime_percentage=result.get("uptime_percentage", 0.0), + ) + + def get_video_metrics( + self, + video_id: str, + agent_id: Optional[str] = None, + ) -> VideoMetrics: + """ + Get video performance metrics (BoTTube integration). + + Args: + video_id: Video identifier + agent_id: Agent identifier (uses client's if not provided) + + Returns: + VideoMetrics instance + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + result = self.client._request( + "GET", + f"/api/agent/analytics/{aid}/video/{video_id}", + ) + + return VideoMetrics( + video_id=video_id, + views=result.get("views", 0), + tips_received=result.get("tips_received", 0), + tips_amount=result.get("tips_amount", 0.0), + avg_tip=result.get("avg_tip", 0.0), + engagement_rate=result.get("engagement_rate", 0.0), + revenue_share=result.get("revenue_share", 0.0), + ) + + def get_videos( + self, + agent_id: Optional[str] = None, + limit: int = 50, + sort_by: str = "views", + ) -> List[VideoMetrics]: + """ + Get video metrics for all agent's videos. + + Args: + agent_id: Agent identifier (uses client's if not provided) + limit: Maximum results + sort_by: Sort field (views, tips, revenue) + + Returns: + List of VideoMetrics + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + result = self.client._request( + "GET", + f"/api/agent/analytics/{aid}/videos", + params={"limit": limit, "sort": sort_by}, + ) + + videos = [] + for data in result.get("videos", []): + video = VideoMetrics( + video_id=data.get("video_id", ""), + views=data.get("views", 0), + tips_received=data.get("tips_received", 0), + tips_amount=data.get("tips_amount", 0.0), + avg_tip=data.get("avg_tip", 0.0), + engagement_rate=data.get("engagement_rate", 0.0), + revenue_share=data.get("revenue_share", 0.0), + ) + videos.append(video) + + return videos + + def get_premium_analytics( + self, + agent_id: str, + depth: str = "full", + ) -> Dict[str, Any]: + """ + Get deep agent analytics (premium endpoint). + + This is the /api/premium/analytics/ endpoint + mentioned in the RustChain documentation. + + Args: + agent_id: Agent identifier + depth: Analytics depth (basic, standard, full) + + Returns: + Dict with comprehensive analytics + """ + return self.client._request( + "GET", + f"/api/premium/analytics/{agent_id}", + params={"depth": depth}, + ) + + def get_comparison( + self, + agent_id: Optional[str] = None, + benchmark: str = "category", + ) -> Dict[str, Any]: + """ + Get analytics comparison against benchmarks. + + Args: + agent_id: Agent identifier (uses client's if not provided) + benchmark: Benchmark type (category, global, top_100) + + Returns: + Dict with comparison data + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + return self.client._request( + "GET", + f"/api/agent/analytics/{aid}/comparison", + params={"benchmark": benchmark}, + ) + + def export_analytics( + self, + agent_id: Optional[str] = None, + format: str = "json", + period: AnalyticsPeriod = AnalyticsPeriod.MONTH, + ) -> Dict[str, Any]: + """ + Export analytics data. + + Args: + agent_id: Agent identifier (uses client's if not provided) + format: Export format (json, csv) + period: Reporting period + + Returns: + Dict with export data or download URL + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + return self.client._request( + "POST", + f"/api/agent/analytics/{aid}/export", + json_payload={ + "format": format, + "period": period.value, + }, + ) + + def get_realtime_stats(self, agent_id: Optional[str] = None) -> Dict[str, Any]: + """ + Get real-time agent statistics. + + Args: + agent_id: Agent identifier (uses client's if not provided) + + Returns: + Dict with real-time stats + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + return self.client._request("GET", f"/api/agent/analytics/{aid}/realtime") diff --git a/sdk/rustchain/agent_economy/bounties.py b/sdk/rustchain/agent_economy/bounties.py new file mode 100644 index 00000000..3e20da30 --- /dev/null +++ b/sdk/rustchain/agent_economy/bounties.py @@ -0,0 +1,534 @@ +""" +Bounty System Client + +Manages bounty discovery, claims, and automated payments. +""" + +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum + + +class BountyStatus(Enum): + """Bounty status enumeration""" + OPEN = "open" + IN_PROGRESS = "in_progress" + SUBMITTED = "submitted" + UNDER_REVIEW = "under_review" + COMPLETED = "completed" + PAID = "paid" + CANCELLED = "cancelled" + + +class BountyTier(Enum): + """Bounty tier enumeration""" + TRIVIAL = "trivial" # 5-15 RTC + MINOR = "minor" # 15-30 RTC + MEDIUM = "medium" # 30-50 RTC + MAJOR = "major" # 50-100 RTC + CRITICAL = "critical" # 100-200 RTC + SECURITY = "security" # 200+ RTC + + +@dataclass +class Bounty: + """ + Represents a RustChain bounty. + + Attributes: + bounty_id: Unique bounty identifier + title: Bounty title + description: Full description + status: Current status + tier: Bounty tier + reward: Reward amount in RTC + reward_range: Reward range string + created_at: Creation timestamp + deadline: Submission deadline + claimant: Agent who claimed the bounty + issuer: Agent/organization issuing bounty + tags: List of tags + requirements: List of requirements + submissions_count: Number of submissions + """ + bounty_id: str + title: str + description: str + status: BountyStatus = BountyStatus.OPEN + tier: BountyTier = BountyTier.MEDIUM + reward: float = 0.0 + reward_range: str = "" + created_at: Optional[datetime] = None + deadline: Optional[datetime] = None + claimant: Optional[str] = None + issuer: Optional[str] = None + tags: List[str] = field(default_factory=list) + requirements: List[str] = field(default_factory=list) + submissions_count: int = 0 + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "bounty_id": self.bounty_id, + "title": self.title, + "description": self.description, + "status": self.status.value, + "tier": self.tier.value, + "reward": self.reward, + "reward_range": self.reward_range, + "created_at": self.created_at.isoformat() if self.created_at else None, + "deadline": self.deadline.isoformat() if self.deadline else None, + "claimant": self.claimant, + "issuer": self.issuer, + "tags": self.tags, + "requirements": self.requirements, + "submissions_count": self.submissions_count, + } + + @property + def is_claimable(self) -> bool: + """Check if bounty can be claimed""" + return self.status == BountyStatus.OPEN and self.claimant is None + + @property + def is_expired(self) -> bool: + """Check if bounty deadline has passed""" + if not self.deadline: + return False + return datetime.utcnow() > self.deadline + + +@dataclass +class BountySubmission: + """ + Represents a bounty submission. + + Attributes: + submission_id: Unique submission identifier + bounty_id: Related bounty ID + submitter: Agent who submitted + pr_url: Pull request URL + description: Submission description + status: Submission status + submitted_at: Submission timestamp + review_feedback: Reviewer feedback + payment_tx: Payment transaction hash + """ + submission_id: str + bounty_id: str + submitter: str + pr_url: Optional[str] = None + description: str = "" + status: BountyStatus = BountyStatus.SUBMITTED + submitted_at: Optional[datetime] = None + review_feedback: Optional[str] = None + payment_tx: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "submission_id": self.submission_id, + "bounty_id": self.bounty_id, + "submitter": self.submitter, + "pr_url": self.pr_url, + "description": self.description, + "status": self.status.value, + "submitted_at": self.submitted_at.isoformat() if self.submitted_at else None, + "review_feedback": self.review_feedback, + "payment_tx": self.payment_tx, + } + + +class BountyClient: + """ + Client for RustChain bounty system. + + Provides bounty discovery, claiming, submission, and + automated payment processing. + + Example: + >>> bounties = BountyClient(agent_economy_client) + >>> + >>> # Find open bounties + >>> open_bounties = bounties.list(status=BountyStatus.OPEN) + >>> + >>> # Claim a bounty + >>> bounty = bounties.claim("bounty_123") + >>> + >>> # Submit work + >>> submission = bounties.submit( + ... bounty_id="bounty_123", + ... pr_url="https://github.com/...", + ... description="Implemented feature X" + ... ) + >>> + >>> # Check submissions + >>> my_submissions = bounties.get_my_submissions() + """ + + def __init__(self, client): + self.client = client + + def list( + self, + status: Optional[BountyStatus] = None, + tier: Optional[BountyTier] = None, + tag: Optional[str] = None, + limit: int = 50, + offset: int = 0, + ) -> List[Bounty]: + """ + List bounties with optional filters. + + Args: + status: Filter by status + tier: Filter by tier + tag: Filter by tag + limit: Maximum results + offset: Pagination offset + + Returns: + List of Bounty + """ + params = {"limit": limit, "offset": offset} + if status: + params["status"] = status.value + if tier: + params["tier"] = tier.value + if tag: + params["tag"] = tag + + result = self.client._request("GET", "/api/bounties", params=params) + + bounties = [] + for data in result.get("bounties", []): + bounty = Bounty( + bounty_id=data.get("bounty_id", ""), + title=data.get("title", ""), + description=data.get("description", ""), + status=BountyStatus(data.get("status", "open")), + tier=BountyTier(data.get("tier", "medium")), + reward=data.get("reward", 0.0), + reward_range=data.get("reward_range", ""), + claimant=data.get("claimant"), + issuer=data.get("issuer"), + tags=data.get("tags", []), + requirements=data.get("requirements", []), + submissions_count=data.get("submissions_count", 0), + ) + bounties.append(bounty) + + return bounties + + def get(self, bounty_id: str) -> Optional[Bounty]: + """ + Get details for a specific bounty. + + Args: + bounty_id: Bounty identifier + + Returns: + Bounty or None if not found + """ + result = self.client._request("GET", f"/api/bounty/{bounty_id}") + + if not result or "error" in result: + return None + + return Bounty( + bounty_id=bounty_id, + title=result.get("title", ""), + description=result.get("description", ""), + status=BountyStatus(result.get("status", "open")), + tier=BountyTier(result.get("tier", "medium")), + reward=result.get("reward", 0.0), + reward_range=result.get("reward_range", ""), + claimant=result.get("claimant"), + issuer=result.get("issuer"), + tags=result.get("tags", []), + requirements=result.get("requirements", []), + submissions_count=result.get("submissions_count", 0), + ) + + def claim(self, bounty_id: str, description: Optional[str] = None) -> bool: + """ + Claim a bounty. + + Args: + bounty_id: Bounty identifier + description: Claim description/plan + + Returns: + True if successful + """ + agent_id = self.client.config.agent_id + if not agent_id: + raise ValueError("client must have agent_id configured") + + payload = { + "agent_id": agent_id, + "description": description, + } + + result = self.client._request( + "POST", + f"/api/bounty/{bounty_id}/claim", + json_payload=payload, + ) + + return result.get("success", False) + + def submit( + self, + bounty_id: str, + pr_url: str, + description: str, + evidence: Optional[List[str]] = None, + ) -> BountySubmission: + """ + Submit work for a bounty. + + Args: + bounty_id: Bounty identifier + pr_url: Pull request URL with the work + description: Submission description + evidence: List of evidence URLs/hashes + + Returns: + BountySubmission instance + """ + agent_id = self.client.config.agent_id + if not agent_id: + raise ValueError("client must have agent_id configured") + + payload = { + "submitter": agent_id, + "pr_url": pr_url, + "description": description, + "evidence": evidence or [], + } + + result = self.client._request( + "POST", + f"/api/bounty/{bounty_id}/submit", + json_payload=payload, + ) + + return BountySubmission( + submission_id=result.get("submission_id", ""), + bounty_id=bounty_id, + submitter=agent_id, + pr_url=pr_url, + description=description, + submitted_at=datetime.utcnow(), + ) + + def get_submission(self, submission_id: str) -> Optional[BountySubmission]: + """ + Get submission details. + + Args: + submission_id: Submission identifier + + Returns: + BountySubmission or None if not found + """ + result = self.client._request("GET", f"/api/bounty/submission/{submission_id}") + + if not result or "error" in result: + return None + + return BountySubmission( + submission_id=submission_id, + bounty_id=result.get("bounty_id", ""), + submitter=result.get("submitter", ""), + pr_url=result.get("pr_url"), + description=result.get("description", ""), + status=BountyStatus(result.get("status", "submitted")), + review_feedback=result.get("review_feedback"), + payment_tx=result.get("payment_tx"), + ) + + def get_my_submissions( + self, + agent_id: Optional[str] = None, + status: Optional[BountyStatus] = None, + ) -> List[BountySubmission]: + """ + Get submissions for an agent. + + Args: + agent_id: Agent identifier (uses client's if not provided) + status: Filter by status + + Returns: + List of BountySubmission + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + params = {} + if status: + params["status"] = status.value + + result = self.client._request( + "GET", + f"/api/bounty/submissions/{aid}", + params=params, + ) + + submissions = [] + for data in result.get("submissions", []): + submission = BountySubmission( + submission_id=data.get("submission_id", ""), + bounty_id=data.get("bounty_id", ""), + submitter=data.get("submitter", ""), + pr_url=data.get("pr_url"), + description=data.get("description", ""), + status=BountyStatus(data.get("status", "submitted")), + review_feedback=data.get("review_feedback"), + payment_tx=data.get("payment_tx"), + ) + submissions.append(submission) + + return submissions + + def get_my_claims( + self, + agent_id: Optional[str] = None, + status: Optional[BountyStatus] = None, + ) -> List[Bounty]: + """ + Get bounties claimed by an agent. + + Args: + agent_id: Agent identifier (uses client's if not provided) + status: Filter by status + + Returns: + List of Bounty + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + params = {} + if status: + params["status"] = status.value + + result = self.client._request( + "GET", + f"/api/bounty/claims/{aid}", + params=params, + ) + + bounties = [] + for data in result.get("claims", []): + bounty = Bounty( + bounty_id=data.get("bounty_id", ""), + title=data.get("title", ""), + description=data.get("description", ""), + status=BountyStatus(data.get("status", "in_progress")), + tier=BountyTier(data.get("tier", "medium")), + reward=data.get("reward", 0.0), + ) + bounties.append(bounty) + + return bounties + + def release_payment( + self, + submission_id: str, + issuer_id: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Release payment for a completed submission. + + Args: + submission_id: Submission identifier + issuer_id: Issuer agent ID (uses client's if not provided) + + Returns: + Dict with payment information + """ + issuer = issuer_id or self.client.config.agent_id + if not issuer: + raise ValueError("issuer_id must be provided") + + return self.client._request( + "POST", + f"/api/bounty/submission/{submission_id}/pay", + json_payload={"issuer_id": issuer}, + ) + + def get_stats(self) -> Dict[str, Any]: + """ + Get bounty system statistics. + + Returns: + Dict with statistics + """ + return self.client._request("GET", "/api/bounty/stats") + + def create_bounty( + self, + title: str, + description: str, + reward: float, + tier: BountyTier = BountyTier.MEDIUM, + requirements: Optional[List[str]] = None, + tags: Optional[List[str]] = None, + deadline_days: int = 30, + ) -> Bounty: + """ + Create a new bounty. + + Args: + title: Bounty title + description: Full description + reward: Reward amount in RTC + tier: Bounty tier + requirements: List of requirements + tags: List of tags + deadline_days: Days until deadline + + Returns: + Created Bounty + """ + issuer = self.client.config.agent_id + if not issuer: + raise ValueError("client must have agent_id configured") + + deadline = datetime.utcnow() + timedelta(days=deadline_days) + + payload = { + "issuer_id": issuer, + "title": title, + "description": description, + "reward": reward, + "tier": tier.value, + "requirements": requirements or [], + "tags": tags or [], + "deadline": deadline.isoformat(), + } + + result = self.client._request( + "POST", + "/api/bounty/create", + json_payload=payload, + ) + + return Bounty( + bounty_id=result.get("bounty_id", ""), + title=title, + description=description, + tier=tier, + reward=reward, + issuer=issuer, + requirements=requirements or [], + tags=tags or [], + deadline=deadline, + created_at=datetime.utcnow(), + ) diff --git a/sdk/rustchain/agent_economy/client.py b/sdk/rustchain/agent_economy/client.py new file mode 100644 index 00000000..71b5e1ad --- /dev/null +++ b/sdk/rustchain/agent_economy/client.py @@ -0,0 +1,209 @@ +""" +RIP-302 Agent Economy Client + +Main client for interacting with RustChain's Agent Economy APIs. +Provides unified access to agent wallets, x402 payments, reputation, and analytics. +""" + +from typing import Dict, List, Optional, Any, Union +import requests +import json +from dataclasses import dataclass, field +from datetime import datetime + +from rustchain.exceptions import RustChainError, ConnectionError, ValidationError, APIError + + +@dataclass +class AgentEconomyConfig: + """Configuration for Agent Economy Client""" + base_url: str = "https://rustchain.org" + bottube_url: str = "https://bottube.ai" + beacon_url: str = "https://beacon.rustchain.org" + verify_ssl: bool = True + timeout: int = 30 + api_key: Optional[str] = None + agent_id: Optional[str] = None + wallet_address: Optional[str] = None + + +class AgentEconomyClient: + """ + Unified client for RustChain RIP-302 Agent Economy APIs. + + Provides access to: + - Agent wallet management (Coinbase Base integration) + - x402 payment protocol for machine-to-machine payments + - BoTTube video platform integration + - Beacon Atlas reputation system + - Agent analytics and metrics + - Bounty system for automated claims + + Example: + >>> from rustchain.agent_economy import AgentEconomyClient + >>> + >>> client = AgentEconomyClient( + ... agent_id="my-ai-agent", + ... wallet_address="agent_wallet_123" + ... ) + >>> + >>> # Get agent reputation + >>> reputation = client.reputation.get_score() + >>> print(f"Reputation score: {reputation.score}") + >>> + >>> # Send x402 payment + >>> payment = client.payments.send( + ... to="content-creator-agent", + ... amount=0.5, + ... memo="Video tip" + ... ) + >>> + >>> client.close() + """ + + def __init__( + self, + base_url: str = "https://rustchain.org", + agent_id: Optional[str] = None, + wallet_address: Optional[str] = None, + api_key: Optional[str] = None, + verify_ssl: bool = True, + timeout: int = 30, + ): + """ + Initialize Agent Economy Client. + + Args: + base_url: Base URL of RustChain node (default: https://rustchain.org) + agent_id: Unique identifier for this AI agent + wallet_address: Agent's wallet address for payments + api_key: Optional API key for premium endpoints + verify_ssl: Whether to verify SSL certificates (default: True) + timeout: Request timeout in seconds (default: 30) + """ + self.config = AgentEconomyConfig( + base_url=base_url.rstrip("/"), + verify_ssl=verify_ssl, + timeout=timeout, + api_key=api_key, + agent_id=agent_id, + wallet_address=wallet_address, + ) + + self.session = requests.Session() + self.session.verify = verify_ssl + + if api_key: + self.session.headers["X-API-Key"] = api_key + + # Initialize sub-clients + from rustchain.agent_economy.agents import AgentManager + from rustchain.agent_economy.payments import PaymentProcessor + from rustchain.agent_economy.reputation import ReputationClient + from rustchain.agent_economy.analytics import AnalyticsClient + from rustchain.agent_economy.bounties import BountyClient + + self.agents = AgentManager(self) + self.payments = PaymentProcessor(self) + self.reputation = ReputationClient(self) + self.analytics = AnalyticsClient(self) + self.bounties = BountyClient(self) + + def _request( + self, + method: str, + endpoint: str, + params: Optional[Dict] = None, + json_payload: Optional[Dict] = None, + base_url: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Make HTTP request to Agent Economy API. + + Args: + method: HTTP method (GET, POST, etc.) + endpoint: API endpoint path + params: URL query parameters + json_payload: JSON payload for POST/PUT requests + base_url: Override base URL (for external services like BoTTube) + + Returns: + Response JSON as dict + + Raises: + ConnectionError: If request fails + APIError: If API returns error + """ + url_base = base_url or self.config.base_url + url = f"{url_base}/{endpoint.lstrip('/')}" + + headers = {"Content-Type": "application/json"} + if self.config.api_key: + headers["X-API-Key"] = self.config.api_key + + try: + response = self.session.request( + method=method, + url=url, + params=params, + json=json_payload, + headers=headers, + timeout=self.config.timeout, + ) + + try: + response.raise_for_status() + except requests.HTTPError as e: + raise APIError( + f"HTTP {response.status_code}: {e}", + status_code=response.status_code, + response=response.text, + ) from e + + try: + return response.json() + except json.JSONDecodeError: + return {"raw_response": response.text} + + except requests.exceptions.ConnectionError as e: + raise ConnectionError(f"Failed to connect to {url}: {e}") from e + except requests.exceptions.Timeout as e: + raise ConnectionError(f"Request timeout to {url}: {e}") from e + except requests.exceptions.RequestException as e: + raise ConnectionError(f"Request failed: {e}") from e + + def health(self) -> Dict[str, Any]: + """ + Check Agent Economy API health. + + Returns: + Dict with health status + """ + return self._request("GET", "/api/agent/health") + + def get_agent_info(self, agent_id: Optional[str] = None) -> Dict[str, Any]: + """ + Get information about an agent. + + Args: + agent_id: Agent ID (uses client's agent_id if not provided) + + Returns: + Dict with agent information + """ + aid = agent_id or self.config.agent_id + if not aid: + raise ValidationError("agent_id must be provided") + return self._request("GET", f"/api/agent/{aid}") + + def close(self): + """Close the HTTP session""" + self.session.close() + + def __enter__(self): + """Context manager entry""" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Context manager exit""" + self.close() diff --git a/sdk/rustchain/agent_economy/payments.py b/sdk/rustchain/agent_economy/payments.py new file mode 100644 index 00000000..db07ce90 --- /dev/null +++ b/sdk/rustchain/agent_economy/payments.py @@ -0,0 +1,403 @@ +""" +x402 Payment Protocol + +Implements machine-to-machine payments using the x402 protocol (HTTP 402 Payment Required). +""" + +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field +from datetime import datetime +from enum import Enum +import hashlib +import time + +from rustchain.exceptions import ValidationError, APIError + + +class PaymentStatus(Enum): + """Payment status enumeration""" + PENDING = "pending" + PROCESSING = "processing" + COMPLETED = "completed" + FAILED = "failed" + REFUNDED = "refunded" + + +@dataclass +class X402Payment: + """ + Represents an x402 protocol payment. + + Attributes: + payment_id: Unique payment identifier + from_agent: Sender agent ID + to_agent: Recipient agent ID + amount: Payment amount in RTC + memo: Optional payment memo + status: Payment status + created_at: Creation timestamp + completed_at: Completion timestamp + tx_hash: Transaction hash on completion + resource: Protected resource being accessed + """ + payment_id: str + from_agent: str + to_agent: str + amount: float + memo: Optional[str] = None + status: PaymentStatus = PaymentStatus.PENDING + created_at: Optional[datetime] = None + completed_at: Optional[datetime] = None + tx_hash: Optional[str] = None + resource: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "payment_id": self.payment_id, + "from_agent": self.from_agent, + "to_agent": self.to_agent, + "amount": self.amount, + "memo": self.memo, + "status": self.status.value, + "created_at": self.created_at.isoformat() if self.created_at else None, + "completed_at": self.completed_at.isoformat() if self.completed_at else None, + "tx_hash": self.tx_hash, + "resource": self.resource, + } + + +@dataclass +class PaymentIntent: + """ + Payment intent for x402 flow. + + Attributes: + intent_id: Unique intent identifier + resource: URL of protected resource + amount: Required payment amount + recipient: Recipient agent ID + expires_at: Intent expiration time + """ + intent_id: str + resource: str + amount: float + recipient: str + expires_at: datetime + + def is_expired(self) -> bool: + """Check if intent has expired""" + return datetime.utcnow() > self.expires_at + + +class PaymentProcessor: + """ + Handles x402 payment processing for agent-to-agent transactions. + + The x402 protocol enables machine-to-machine micropayments via + HTTP 402 Payment Required responses and payment negotiation. + + Example: + >>> processor = PaymentProcessor(client) + >>> + >>> # Send direct payment + >>> payment = processor.send( + ... to="content-creator", + ... amount=0.5, + ... memo="Thanks for the video!" + ... ) + >>> + >>> # Request payment for protected resource + >>> intent = processor.create_intent( + ... resource="/api/premium/analytics", + ... amount=0.1 + ... ) + >>> + >>> # Process incoming payment + >>> processor.process_payment(payment_id) + """ + + def __init__(self, client): + self.client = client + self._pending_intents: Dict[str, PaymentIntent] = {} + + def send( + self, + to: str, + amount: float, + memo: Optional[str] = None, + from_agent: Optional[str] = None, + resource: Optional[str] = None, + ) -> X402Payment: + """ + Send an x402 payment to another agent. + + Args: + to: Recipient agent ID + amount: Payment amount in RTC + memo: Optional payment memo + from_agent: Sender agent ID (uses client's agent_id if not provided) + resource: Optional resource being paid for + + Returns: + X402Payment instance + + Raises: + ValidationError: If parameters are invalid + APIError: If payment fails + """ + from_agent = from_agent or self.client.config.agent_id + if not from_agent: + raise ValidationError("from_agent must be provided") + + if amount <= 0: + raise ValidationError("amount must be positive") + + if not to or to == from_agent: + raise ValidationError("invalid recipient") + + # Generate payment ID + timestamp = str(int(time.time() * 1000)) + payment_hash = hashlib.sha256( + f"{from_agent}:{to}:{amount}:{timestamp}".encode() + ).hexdigest()[:16] + payment_id = f"pay_{payment_hash}" + + payload = { + "payment_id": payment_id, + "from_agent": from_agent, + "to_agent": to, + "amount": amount, + "memo": memo, + "resource": resource, + } + + result = self.client._request( + "POST", + "/api/agent/payment/send", + json_payload=payload, + ) + + if result.get("error"): + raise APIError(f"Payment failed: {result['error']}") + + return X402Payment( + payment_id=payment_id, + from_agent=from_agent, + to_agent=to, + amount=amount, + memo=memo, + status=PaymentStatus(result.get("status", "pending")), + created_at=datetime.utcnow(), + resource=resource, + ) + + def request( + self, + from_agent: str, + amount: float, + description: str, + resource: Optional[str] = None, + ) -> PaymentIntent: + """ + Request payment from another agent. + + Args: + from_agent: Agent to request payment from + amount: Requested amount in RTC + description: Payment description + resource: Optional resource URL + + Returns: + PaymentIntent instance + """ + to_agent = self.client.config.agent_id + if not to_agent: + raise ValidationError("client must have agent_id configured") + + intent_id = f"intent_{hashlib.sha256(f'{from_agent}:{to_agent}:{time.time()}'.encode()).hexdigest()[:12]}" + expires_at = datetime.utcnow().replace(second=0, microsecond=0) + + intent = PaymentIntent( + intent_id=intent_id, + resource=resource or f"/api/agent/payment/{intent_id}", + amount=amount, + recipient=to_agent, + expires_at=expires_at, + ) + + payload = { + "intent_id": intent_id, + "from_agent": from_agent, + "to_agent": to_agent, + "amount": amount, + "description": description, + "resource": intent.resource, + "expires_at": expires_at.isoformat(), + } + + result = self.client._request( + "POST", + "/api/agent/payment/request", + json_payload=payload, + ) + + self._pending_intents[intent_id] = intent + return intent + + def process_intent(self, intent_id: str) -> X402Payment: + """ + Process a payment intent (pay for requested resource). + + Args: + intent_id: Payment intent identifier + + Returns: + X402Payment instance + """ + result = self.client._request( + "POST", + f"/api/agent/payment/intent/{intent_id}/pay", + ) + + return X402Payment( + payment_id=result.get("payment_id", ""), + from_agent=result.get("from_agent", ""), + to_agent=result.get("to_agent", ""), + amount=result.get("amount", 0.0), + status=PaymentStatus(result.get("status", "pending")), + created_at=datetime.utcnow(), + resource=result.get("resource"), + ) + + def get_payment(self, payment_id: str) -> Optional[X402Payment]: + """ + Get payment details. + + Args: + payment_id: Payment identifier + + Returns: + X402Payment or None if not found + """ + result = self.client._request("GET", f"/api/agent/payment/{payment_id}") + + if not result or "error" in result: + return None + + return X402Payment( + payment_id=result.get("payment_id", ""), + from_agent=result.get("from_agent", ""), + to_agent=result.get("to_agent", ""), + amount=result.get("amount", 0.0), + memo=result.get("memo"), + status=PaymentStatus(result.get("status", "pending")), + created_at=datetime.fromisoformat(result["created_at"]) if result.get("created_at") else None, + completed_at=datetime.fromisoformat(result["completed_at"]) if result.get("completed_at") else None, + tx_hash=result.get("tx_hash"), + resource=result.get("resource"), + ) + + def get_history( + self, + agent_id: Optional[str] = None, + limit: int = 50, + status: Optional[PaymentStatus] = None, + ) -> List[X402Payment]: + """ + Get payment history. + + Args: + agent_id: Filter by agent (sent or received) + limit: Maximum results + status: Filter by status + + Returns: + List of X402Payment + """ + params = {"limit": limit} + if status: + params["status"] = status.value + + endpoint = f"/api/agent/payment/history/{agent_id}" if agent_id else "/api/agent/payment/history" + result = self.client._request("GET", endpoint, params=params) + + payments = [] + for data in result.get("payments", []): + payment = X402Payment( + payment_id=data.get("payment_id", ""), + from_agent=data.get("from_agent", ""), + to_agent=data.get("to_agent", ""), + amount=data.get("amount", 0.0), + memo=data.get("memo"), + status=PaymentStatus(data.get("status", "pending")), + tx_hash=data.get("tx_hash"), + ) + payments.append(payment) + + return payments + + def refund(self, payment_id: str, reason: Optional[str] = None) -> bool: + """ + Refund a payment. + + Args: + payment_id: Payment identifier + reason: Refund reason + + Returns: + True if successful + """ + payload = {"reason": reason} if reason else {} + + result = self.client._request( + "POST", + f"/api/agent/payment/{payment_id}/refund", + json_payload=payload, + ) + + return result.get("success", False) + + def x402_challenge( + self, + resource: str, + required_amount: float, + ) -> Dict[str, Any]: + """ + Generate x402 challenge for protected resource. + + This is used when an agent needs to require payment + for accessing a resource. + + Args: + resource: Protected resource URL/path + required_amount: Required payment amount + + Returns: + Dict with x402 challenge information + """ + payload = { + "resource": resource, + "amount": required_amount, + "recipient": self.client.config.agent_id, + } + + result = self.client._request( + "POST", + "/api/agent/payment/x402/challenge", + json_payload=payload, + ) + + return { + "status_code": 402, + "headers": { + "X-Pay-To": result.get("wallet_address", ""), + "X-Pay-Amount": str(required_amount), + "X-Pay-Resource": resource, + "X-Pay-Nonce": result.get("nonce", ""), + }, + "body": { + "error": "Payment Required", + "payment_info": result, + }, + } diff --git a/sdk/rustchain/agent_economy/reputation.py b/sdk/rustchain/agent_economy/reputation.py new file mode 100644 index 00000000..5d63897d --- /dev/null +++ b/sdk/rustchain/agent_economy/reputation.py @@ -0,0 +1,401 @@ +""" +Beacon Atlas Reputation System + +Manages agent reputation scores, attestations, and trust metrics. +""" + +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from enum import Enum + + +class ReputationTier(Enum): + """Reputation tier enumeration""" + UNKNOWN = "unknown" + NEW = "new" + ESTABLISHED = "established" + TRUSTED = "trusted" + VERIFIED = "verified" + ELITE = "elite" + + +@dataclass +class ReputationScore: + """ + Complete reputation score for an agent. + + Attributes: + agent_id: Agent identifier + score: Overall score (0-100) + tier: Reputation tier + total_transactions: Total transactions completed + successful_transactions: Successful transactions + failed_transactions: Failed transactions + avg_payment_size: Average payment size + dispute_count: Number of disputes + attestations_count: Number of attestations + created_at: First activity timestamp + last_active: Last activity timestamp + badges: List of earned badges + """ + agent_id: str + score: float = 0.0 + tier: ReputationTier = ReputationTier.UNKNOWN + total_transactions: int = 0 + successful_transactions: int = 0 + failed_transactions: int = 0 + avg_payment_size: float = 0.0 + dispute_count: int = 0 + attestations_count: int = 0 + created_at: Optional[datetime] = None + last_active: Optional[datetime] = None + badges: List[str] = field(default_factory=list) + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "agent_id": self.agent_id, + "score": self.score, + "tier": self.tier.value, + "total_transactions": self.total_transactions, + "successful_transactions": self.successful_transactions, + "failed_transactions": self.failed_transactions, + "avg_payment_size": self.avg_payment_size, + "dispute_count": self.dispute_count, + "attestations_count": self.attestations_count, + "created_at": self.created_at.isoformat() if self.created_at else None, + "last_active": self.last_active.isoformat() if self.last_active else None, + "badges": self.badges, + } + + @property + def success_rate(self) -> float: + """Calculate success rate percentage""" + if self.total_transactions == 0: + return 0.0 + return (self.successful_transactions / self.total_transactions) * 100 + + @property + def is_trusted(self) -> bool: + """Check if agent is trusted or higher""" + return self.tier in (ReputationTier.TRUSTED, ReputationTier.VERIFIED, ReputationTier.ELITE) + + +@dataclass +class Attestation: + """ + Reputation attestation from one agent about another. + + Attributes: + attestation_id: Unique identifier + from_agent: Attesting agent + to_agent: Attested agent + rating: Rating (1-5 stars) + comment: Optional comment + transaction_id: Related transaction ID + created_at: Creation timestamp + verified: Whether attestation is verified + """ + attestation_id: str + from_agent: str + to_agent: str + rating: int + comment: Optional[str] = None + transaction_id: Optional[str] = None + created_at: Optional[datetime] = None + verified: bool = False + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary""" + return { + "attestation_id": self.attestation_id, + "from_agent": self.from_agent, + "to_agent": self.to_agent, + "rating": self.rating, + "comment": self.comment, + "transaction_id": self.transaction_id, + "created_at": self.created_at.isoformat() if self.created_at else None, + "verified": self.verified, + } + + +class ReputationClient: + """ + Client for Beacon Atlas reputation system. + + Provides reputation scoring, attestations, and trust metrics + for AI agents in the RustChain economy. + + Example: + >>> client = ReputationClient(agent_economy_client) + >>> + >>> # Get agent reputation + >>> score = client.get_score("video-curator-bot") + >>> print(f"Score: {score.score}, Tier: {score.tier}") + >>> + >>> # Submit attestation + >>> attestation = client.submit_attestation( + ... to_agent="content-creator", + ... rating=5, + ... comment="Excellent service!" + ... ) + >>> + >>> # Get leaderboard + >>> leaderboard = client.get_leaderboard(limit=10) + """ + + def __init__(self, client): + self.client = client + + def get_score(self, agent_id: Optional[str] = None) -> ReputationScore: + """ + Get reputation score for an agent. + + Args: + agent_id: Agent identifier (uses client's agent_id if not provided) + + Returns: + ReputationScore instance + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + result = self.client._request("GET", f"/api/agent/reputation/{aid}") + + return ReputationScore( + agent_id=aid, + score=result.get("score", 0.0), + tier=ReputationTier(result.get("tier", "unknown")), + total_transactions=result.get("total_transactions", 0), + successful_transactions=result.get("successful_transactions", 0), + failed_transactions=result.get("failed_transactions", 0), + avg_payment_size=result.get("avg_payment_size", 0.0), + dispute_count=result.get("dispute_count", 0), + attestations_count=result.get("attestations_count", 0), + badges=result.get("badges", []), + ) + + def submit_attestation( + self, + to_agent: str, + rating: int, + comment: Optional[str] = None, + transaction_id: Optional[str] = None, + ) -> Attestation: + """ + Submit an attestation for another agent. + + Args: + to_agent: Agent being attested + rating: Rating (1-5 stars) + comment: Optional comment + transaction_id: Related transaction ID + + Returns: + Attestation instance + """ + from_agent = self.client.config.agent_id + if not from_agent: + raise ValueError("client must have agent_id configured") + + if not 1 <= rating <= 5: + raise ValueError("rating must be between 1 and 5") + + payload = { + "from_agent": from_agent, + "to_agent": to_agent, + "rating": rating, + "comment": comment, + "transaction_id": transaction_id, + } + + result = self.client._request( + "POST", + "/api/agent/reputation/attest", + json_payload=payload, + ) + + return Attestation( + attestation_id=result.get("attestation_id", ""), + from_agent=from_agent, + to_agent=to_agent, + rating=rating, + comment=comment, + transaction_id=transaction_id, + created_at=datetime.utcnow(), + verified=result.get("verified", False), + ) + + def get_attestations( + self, + agent_id: str, + limit: int = 50, + min_rating: Optional[int] = None, + ) -> List[Attestation]: + """ + Get attestations for an agent. + + Args: + agent_id: Agent identifier + limit: Maximum results + min_rating: Filter by minimum rating + + Returns: + List of Attestation + """ + params = {"limit": limit} + if min_rating: + params["min_rating"] = min_rating + + result = self.client._request( + "GET", + f"/api/agent/reputation/{agent_id}/attestations", + params=params, + ) + + attestations = [] + for data in result.get("attestations", []): + attestation = Attestation( + attestation_id=data.get("attestation_id", ""), + from_agent=data.get("from_agent", ""), + to_agent=data.get("to_agent", ""), + rating=data.get("rating", 0), + comment=data.get("comment"), + transaction_id=data.get("transaction_id"), + verified=data.get("verified", False), + ) + attestations.append(attestation) + + return attestations + + def get_leaderboard( + self, + limit: int = 100, + tier: Optional[ReputationTier] = None, + capability: Optional[str] = None, + ) -> List[ReputationScore]: + """ + Get reputation leaderboard. + + Args: + limit: Maximum results + tier: Filter by minimum tier + capability: Filter by capability + + Returns: + List of ReputationScore sorted by score + """ + params = {"limit": limit} + if tier: + params["tier"] = tier.value + if capability: + params["capability"] = capability + + result = self.client._request( + "GET", + "/api/agent/reputation/leaderboard", + params=params, + ) + + scores = [] + for data in result.get("leaders", []): + score = ReputationScore( + agent_id=data.get("agent_id", ""), + score=data.get("score", 0.0), + tier=ReputationTier(data.get("tier", "unknown")), + total_transactions=data.get("total_transactions", 0), + attestations_count=data.get("attestations_count", 0), + badges=data.get("badges", []), + ) + scores.append(score) + + return scores + + def get_trust_proof(self, agent_id: Optional[str] = None) -> Dict[str, Any]: + """ + Get cryptographic trust proof for an agent. + + This can be used to prove reputation to third parties. + + Args: + agent_id: Agent identifier (uses client's if not provided) + + Returns: + Dict with trust proof data + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + return self.client._request("GET", f"/api/agent/reputation/{aid}/proof") + + def dispute_transaction( + self, + transaction_id: str, + reason: str, + evidence: Optional[List[str]] = None, + ) -> Dict[str, Any]: + """ + File a dispute for a transaction. + + Args: + transaction_id: Transaction identifier + reason: Dispute reason + evidence: List of evidence URLs/hashes + + Returns: + Dict with dispute information + """ + payload = { + "transaction_id": transaction_id, + "reason": reason, + "evidence": evidence or [], + } + + return self.client._request( + "POST", + "/api/agent/reputation/dispute", + json_payload=payload, + ) + + def get_badges(self, agent_id: Optional[str] = None) -> List[Dict[str, Any]]: + """ + Get earned badges for an agent. + + Args: + agent_id: Agent identifier (uses client's if not provided) + + Returns: + List of badge information + """ + aid = agent_id or self.client.config.agent_id + if not aid: + raise ValueError("agent_id must be provided") + + result = self.client._request("GET", f"/api/agent/reputation/{aid}/badges") + return result.get("badges", []) + + def calculate_tier(self, score: float) -> ReputationTier: + """ + Calculate reputation tier from score. + + Args: + score: Reputation score (0-100) + + Returns: + ReputationTier + """ + if score >= 95: + return ReputationTier.ELITE + elif score >= 85: + return ReputationTier.VERIFIED + elif score >= 70: + return ReputationTier.TRUSTED + elif score >= 50: + return ReputationTier.ESTABLISHED + elif score >= 20: + return ReputationTier.NEW + else: + return ReputationTier.UNKNOWN diff --git a/sdk/rustchain/py.typed b/sdk/rustchain/py.typed new file mode 100644 index 00000000..7632ecf7 --- /dev/null +++ b/sdk/rustchain/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/sdk/setup.py b/sdk/setup.py index 51f75efa..8efe670e 100644 --- a/sdk/setup.py +++ b/sdk/setup.py @@ -1,8 +1,10 @@ """ Setup configuration for RustChain SDK + +Includes core blockchain client and RIP-302 Agent Economy SDK. """ -from setuptools import setup +from setuptools import setup, find_packages # Read README for long description with open("README.md", "r", encoding="utf-8") as fh: @@ -10,21 +12,22 @@ setup( name="rustchain-sdk", - version="0.1.0", + version="1.0.0", author="RustChain Community", author_email="dev@rustchain.org", - description="Python SDK for RustChain blockchain", + description="Python SDK for RustChain blockchain and Agent Economy (RIP-302)", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/Scottcjn/Rustchain", project_urls={ "Bug Tracker": "https://github.com/Scottcjn/Rustchain/issues", - "Documentation": "https://github.com/Scottcjn/Rustchain#readme", + "Documentation": "https://github.com/Scottcjn/Rustchain/tree/main/sdk", "Source Code": "https://github.com/Scottcjn/Rustchain", + "Agent Economy": "https://github.com/Scottcjn/Rustchain/tree/main/sdk/docs/AGENT_ECONOMY_SDK.md", }, - packages=["rustchain"], + packages=find_packages(exclude=["tests", "examples"]), classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: MIT License", @@ -35,6 +38,10 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", + "Topic :: Blockchain", + "Topic :: Internet :: WWW/HTTP :: HTTP Servers", + "Environment :: Console", + "Operating System :: OS Independent", ], python_requires=">=3.8", install_requires=[ @@ -44,10 +51,19 @@ "dev": [ "pytest>=7.0", "pytest-mock>=3.10", + "pytest-cov>=4.0", "black>=23.0", "mypy>=1.0", + "ruff>=0.1.0", + ], + "examples": [ + "asyncio", ], }, - keywords="rustchain blockchain crypto proof-of-antiquity", + keywords="rustchain blockchain crypto proof-of-antiquity agent-economy x402 payments reputation bounties", license="MIT", + include_package_data=True, + package_data={ + "rustchain": ["py.typed"], + }, ) diff --git a/sdk/tests/test_agent_economy.py b/sdk/tests/test_agent_economy.py new file mode 100644 index 00000000..bb5c155f --- /dev/null +++ b/sdk/tests/test_agent_economy.py @@ -0,0 +1,573 @@ +""" +RustChain Agent Economy SDK - Unit Tests + +Tests for the RIP-302 Agent Economy client and modules. +""" + +import pytest +import unittest +from unittest.mock import Mock, patch, MagicMock +from datetime import datetime, timedelta + +from rustchain.agent_economy import ( + AgentEconomyClient, + AgentWallet, + X402Payment, + ReputationScore, +) +from rustchain.agent_economy.agents import AgentManager, AgentProfile +from rustchain.agent_economy.payments import PaymentProcessor, PaymentStatus, PaymentIntent +from rustchain.agent_economy.reputation import ReputationClient, ReputationTier, Attestation +from rustchain.agent_economy.analytics import AnalyticsClient, AnalyticsPeriod, EarningsReport +from rustchain.agent_economy.bounties import BountyClient, BountyStatus, BountyTier, Bounty +from rustchain.exceptions import ValidationError, APIError, ConnectionError + + +class TestAgentWallet(unittest.TestCase): + """Tests for AgentWallet dataclass""" + + def test_create_wallet(self): + """Test creating an AgentWallet""" + wallet = AgentWallet( + agent_id="test-agent", + wallet_address="agent_abc123", + balance=100.5, + ) + + self.assertEqual(wallet.agent_id, "test-agent") + self.assertEqual(wallet.wallet_address, "agent_abc123") + self.assertEqual(wallet.balance, 100.5) + self.assertEqual(wallet.total_earned, 0.0) + + def test_wallet_to_dict(self): + """Test wallet serialization""" + wallet = AgentWallet( + agent_id="test-agent", + wallet_address="agent_abc123", + base_address="0xBase123", + balance=50.0, + total_earned=150.0, + reputation_score=85.0, + ) + + data = wallet.to_dict() + + self.assertEqual(data["agent_id"], "test-agent") + self.assertEqual(data["wallet_address"], "agent_abc123") + self.assertEqual(data["base_address"], "0xBase123") + self.assertEqual(data["balance"], 50.0) + self.assertEqual(data["total_earned"], 150.0) + self.assertEqual(data["reputation_score"], 85.0) + + +class TestAgentProfile(unittest.TestCase): + """Tests for AgentProfile dataclass""" + + def test_create_profile(self): + """Test creating an AgentProfile""" + profile = AgentProfile( + agent_id="curator-bot", + name="Content Curator", + description="AI content curation agent", + capabilities=["curation", "analysis"], + ) + + self.assertEqual(profile.agent_id, "curator-bot") + self.assertEqual(profile.name, "Content Curator") + self.assertEqual(len(profile.capabilities), 2) + + def test_profile_to_dict(self): + """Test profile serialization""" + wallet = AgentWallet( + agent_id="test", + wallet_address="agent_test", + balance=10.0, + ) + + profile = AgentProfile( + agent_id="test-agent", + name="Test Agent", + description="Test description", + capabilities=["test"], + wallet=wallet, + metadata={"version": "1.0"}, + ) + + data = profile.to_dict() + + self.assertEqual(data["name"], "Test Agent") + self.assertEqual(data["wallet"]["balance"], 10.0) + self.assertEqual(data["metadata"]["version"], "1.0") + + +class TestX402Payment(unittest.TestCase): + """Tests for X402Payment dataclass""" + + def test_create_payment(self): + """Test creating an X402Payment""" + payment = X402Payment( + payment_id="pay_123", + from_agent="sender", + to_agent="receiver", + amount=1.5, + memo="Test payment", + ) + + self.assertEqual(payment.payment_id, "pay_123") + self.assertEqual(payment.amount, 1.5) + self.assertEqual(payment.status, PaymentStatus.PENDING) + + def test_payment_to_dict(self): + """Test payment serialization""" + payment = X402Payment( + payment_id="pay_456", + from_agent="sender", + to_agent="receiver", + amount=2.0, + status=PaymentStatus.COMPLETED, + tx_hash="tx_abc123", + ) + + data = payment.to_dict() + + self.assertEqual(data["payment_id"], "pay_456") + self.assertEqual(data["status"], "completed") + self.assertEqual(data["tx_hash"], "tx_abc123") + + +class TestReputationScore(unittest.TestCase): + """Tests for ReputationScore dataclass""" + + def test_create_score(self): + """Test creating a ReputationScore""" + score = ReputationScore( + agent_id="trusted-agent", + score=85.0, + tier=ReputationTier.TRUSTED, + total_transactions=100, + successful_transactions=95, + ) + + self.assertEqual(score.score, 85.0) + self.assertEqual(score.tier, ReputationTier.TRUSTED) + self.assertTrue(score.is_trusted) + + def test_success_rate(self): + """Test success rate calculation""" + score = ReputationScore( + agent_id="test", + total_transactions=100, + successful_transactions=87, + ) + + self.assertAlmostEqual(score.success_rate, 87.0, places=1) + + def test_success_rate_zero_transactions(self): + """Test success rate with no transactions""" + score = ReputationScore( + agent_id="test", + total_transactions=0, + ) + + self.assertEqual(score.success_rate, 0.0) + + def test_tier_thresholds(self): + """Test tier threshold calculations""" + score = ReputationScore(agent_id="test", score=96.0) + self.assertEqual(score.tier, ReputationTier.UNKNOWN) # Default, not calculated + + # Test manual tier assignment + score.tier = ReputationTier.ELITE + self.assertTrue(score.is_trusted) + + +class TestAgentEconomyClient(unittest.TestCase): + """Tests for AgentEconomyClient""" + + def setUp(self): + """Set up test fixtures""" + self.client = AgentEconomyClient( + base_url="https://test.rustchain.org", + agent_id="test-agent", + wallet_address="test_wallet", + api_key="test-key", + ) + + def tearDown(self): + """Clean up""" + self.client.close() + + def test_client_initialization(self): + """Test client initialization""" + self.assertEqual(self.client.config.base_url, "https://test.rustchain.org") + self.assertEqual(self.client.config.agent_id, "test-agent") + self.assertIsNotNone(self.client.agents) + self.assertIsNotNone(self.client.payments) + self.assertIsNotNone(self.client.reputation) + self.assertIsNotNone(self.client.analytics) + self.assertIsNotNone(self.client.bounties) + + @patch.object(AgentEconomyClient, '_request') + def test_health_check(self, mock_request): + """Test health check endpoint""" + mock_request.return_value = {"status": "ok", "version": "1.0.0"} + + result = self.client.health() + + self.assertEqual(result["status"], "ok") + mock_request.assert_called_once_with("GET", "/api/agent/health") + + @patch.object(AgentEconomyClient, '_request') + def test_get_agent_info(self, mock_request): + """Test getting agent info""" + mock_request.return_value = { + "agent_id": "test-agent", + "name": "Test Agent", + "wallet": {"balance": 100.0}, + } + + result = self.client.get_agent_info() + + self.assertEqual(result["agent_id"], "test-agent") + mock_request.assert_called_once_with("GET", "/api/agent/test-agent") + + def test_get_agent_info_no_id(self): + """Test getting agent info without ID""" + client = AgentEconomyClient(base_url="https://test.org") + + with self.assertRaises(ValidationError): + client.get_agent_info() + + def test_context_manager(self): + """Test context manager""" + with AgentEconomyClient(agent_id="test") as client: + self.assertIsNotNone(client.session) + + # Session should be closed after context + self.assertTrue(client.session.closed if hasattr(client.session, 'closed') else True) + + +class TestAgentManager(unittest.TestCase): + """Tests for AgentManager""" + + def setUp(self): + """Set up test fixtures""" + self.mock_client = Mock() + self.manager = AgentManager(self.mock_client) + + @patch.object(AgentManager, '__init__', lambda self, client: None) + def test_create_wallet(self): + """Test wallet creation""" + manager = AgentManager.__new__(AgentManager) + manager.client = self.mock_client + manager._cache = {} + + self.mock_client._request.return_value = { + "wallet_address": "agent_new123", + } + + wallet = manager.create_wallet( + agent_id="new-agent", + name="New Agent", + ) + + self.assertEqual(wallet.agent_id, "new-agent") + self.assertEqual(wallet.wallet_address, "agent_new123") + + def test_create_wallet_validation(self): + """Test wallet creation validation""" + with self.assertRaises(ValidationError): + self.manager.create_wallet(agent_id="ab") # Too short + + def test_get_wallet_cached(self): + """Test getting cached wallet""" + cached_wallet = AgentWallet( + agent_id="cached-agent", + wallet_address="agent_cached", + balance=50.0, + ) + self.manager._cache["cached-agent"] = cached_wallet + + wallet = self.manager.get_wallet("cached-agent") + + self.assertEqual(wallet.balance, 50.0) + self.mock_client._request.assert_not_called() + + +class TestPaymentProcessor(unittest.TestCase): + """Tests for PaymentProcessor""" + + def setUp(self): + """Set up test fixtures""" + self.mock_client = Mock() + self.mock_client.config.agent_id = "sender-agent" + self.processor = PaymentProcessor(self.mock_client) + + @patch.object(PaymentProcessor, '__init__', lambda self, client: None) + def test_send_payment(self): + """Test sending payment""" + processor = PaymentProcessor.__new__(PaymentProcessor) + processor.client = self.mock_client + + self.mock_client._request.return_value = { + "payment_id": "pay_test123", + "status": "pending", + } + + payment = processor.send( + to="receiver-agent", + amount=1.0, + memo="Test payment", + ) + + self.assertEqual(payment.amount, 1.0) + self.assertEqual(payment.to_agent, "receiver-agent") + + def test_send_payment_validation(self): + """Test payment validation""" + with self.assertRaises(ValidationError): + self.processor.send(to="receiver", amount=-1.0) # Negative amount + + with self.assertRaises(ValidationError): + self.processor.send(to="sender-agent", amount=1.0) # Same agent + + def test_x402_challenge(self): + """Test x402 challenge generation""" + self.mock_client._request.return_value = { + "wallet_address": "merchant_wallet", + "nonce": "nonce123", + } + + challenge = self.processor.x402_challenge( + resource="/api/premium/data", + required_amount=5.0, + ) + + self.assertEqual(challenge["status_code"], 402) + self.assertEqual(challenge["headers"]["X-Pay-Amount"], "5.0") + + +class TestReputationClient(unittest.TestCase): + """Tests for ReputationClient""" + + def setUp(self): + """Set up test fixtures""" + self.mock_client = Mock() + self.mock_client.config.agent_id = "test-agent" + self.reputation = ReputationClient(self.mock_client) + + @patch.object(ReputationClient, '__init__', lambda self, client: None) + def test_get_score(self): + """Test getting reputation score""" + rep = ReputationClient.__new__(ReputationClient) + rep.client = self.mock_client + + self.mock_client._request.return_value = { + "score": 75.0, + "tier": "established", + "total_transactions": 50, + } + + score = rep.get_score("target-agent") + + self.assertEqual(score.score, 75.0) + self.assertEqual(score.tier, ReputationTier.ESTABLISHED) + + def test_submit_attestation(self): + """Test submitting attestation""" + self.mock_client._request.return_value = { + "attestation_id": "att_123", + "verified": True, + } + + attestation = self.reputation.submit_attestation( + to_agent="service-bot", + rating=5, + comment="Great service!", + ) + + self.assertEqual(attestation.rating, 5) + self.assertTrue(attestation.verified) + + def test_submit_attestation_invalid_rating(self): + """Test attestation with invalid rating""" + with self.assertRaises(ValueError): + self.reputation.submit_attestation( + to_agent="bot", + rating=6, # Invalid + ) + + def test_calculate_tier(self): + """Test tier calculation""" + self.assertEqual( + self.reputation.calculate_tier(96.0), + ReputationTier.ELITE + ) + self.assertEqual( + self.reputation.calculate_tier(88.0), + ReputationTier.VERIFIED + ) + self.assertEqual( + self.reputation.calculate_tier(75.0), + ReputationTier.TRUSTED + ) + self.assertEqual( + self.reputation.calculate_tier(55.0), + ReputationTier.ESTABLISHED + ) + self.assertEqual( + self.reputation.calculate_tier(25.0), + ReputationTier.NEW + ) + self.assertEqual( + self.reputation.calculate_tier(10.0), + ReputationTier.UNKNOWN + ) + + +class TestAnalyticsClient(unittest.TestCase): + """Tests for AnalyticsClient""" + + def setUp(self): + """Set up test fixtures""" + self.mock_client = Mock() + self.mock_client.config.agent_id = "analytics-agent" + self.analytics = AnalyticsClient(self.mock_client) + + def test_get_earnings(self): + """Test getting earnings report""" + self.mock_client._request.return_value = { + "total_earned": 125.5, + "transactions_count": 42, + "avg_transaction": 2.99, + "trend": 15.3, + } + + report = self.analytics.get_earnings(period=AnalyticsPeriod.WEEK) + + self.assertEqual(report.total_earned, 125.5) + self.assertEqual(report.transactions_count, 42) + self.assertEqual(report.trend, 15.3) + + def test_get_activity(self): + """Test getting activity metrics""" + self.mock_client._request.return_value = { + "active_hours": 18, + "peak_hour": 14, + "uptime_percentage": 99.5, + } + + activity = self.analytics.get_activity(period=AnalyticsPeriod.DAY) + + self.assertEqual(activity.active_hours, 18) + self.assertEqual(activity.peak_hour, 14) + self.assertEqual(activity.uptime_percentage, 99.5) + + +class TestBountyClient(unittest.TestCase): + """Tests for BountyClient""" + + def setUp(self): + """Set up test fixtures""" + self.mock_client = Mock() + self.mock_client.config.agent_id = "bounty-hunter" + self.bounties = BountyClient(self.mock_client) + + def test_list_bounties(self): + """Test listing bounties""" + self.mock_client._request.return_value = { + "bounties": [ + { + "bounty_id": "bounty_1", + "title": "Fix bug #123", + "reward": 50.0, + "status": "open", + "tier": "medium", + }, + ] + } + + bounties = self.bounties.list(status=BountyStatus.OPEN, limit=10) + + self.assertEqual(len(bounties), 1) + self.assertEqual(bounties[0].bounty_id, "bounty_1") + self.assertEqual(bounties[0].reward, 50.0) + + def test_claim_bounty(self): + """Test claiming a bounty""" + self.mock_client._request.return_value = {"success": True} + + result = self.bounties.claim( + bounty_id="bounty_123", + description="I'll fix this", + ) + + self.assertTrue(result) + + def test_bounty_is_claimable(self): + """Test bounty claimable status""" + bounty = Bounty( + bounty_id="test", + title="Test", + description="Test bounty", + status=BountyStatus.OPEN, + ) + + self.assertTrue(bounty.is_claimable) + + bounty.claimant = "other-agent" + self.assertFalse(bounty.is_claimable) + + def test_bounty_is_expired(self): + """Test bounty expiration""" + past_deadline = datetime.utcnow() - timedelta(days=1) + future_deadline = datetime.utcnow() + timedelta(days=1) + + expired_bounty = Bounty( + bounty_id="expired", + title="Expired", + description="Test", + deadline=past_deadline, + ) + active_bounty = Bounty( + bounty_id="active", + title="Active", + description="Test", + deadline=future_deadline, + ) + + self.assertTrue(expired_bounty.is_expired) + self.assertFalse(active_bounty.is_expired) + + +class TestIntegration(unittest.TestCase): + """Integration tests mocking HTTP requests""" + + @patch('rustchain.agent_economy.client.requests.Session') + def test_full_agent_workflow(self, mock_session_class): + """Test complete agent workflow""" + # Setup mock session + mock_session = Mock() + mock_session_class.return_value = mock_session + + # Mock responses + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = {"status": "ok"} + mock_response.raise_for_status.return_value = None + mock_session.request.return_value = mock_response + + # Create client and perform operations + client = AgentEconomyClient( + base_url="https://test.org", + agent_id="test-agent", + ) + + health = client.health() + self.assertEqual(health["status"], "ok") + + client.close() + + +if __name__ == "__main__": + unittest.main()