diff --git a/config/example.yaml b/config/example.yaml new file mode 100644 index 000000000..1fe68e59a --- /dev/null +++ b/config/example.yaml @@ -0,0 +1,19 @@ +# Olas Mech Worker Tool Configuration +tool_id: "polymarket_intelligence" +name: "Polymarket Prediction Market Intelligence x402 Tool" +description: "Retrieves live Polymarket prediction market analysis, implied probabilities, volume/liquidity metrics, and automated risk flags ($0.05 USDC on Base)." +author: "Autonomous Crypto Intelligence Labs" +version: "1.0.0" + +gateway: + url: "https://agent-payment-gateway.vercel.app/v1/prediction-market-analysis" + price_usdc: "0.05" + network: "eip155:8453" + asset_contract: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" + merchant_recipient: "0x43f9a721B59C247B6258e72A1Bf5A384b64F8A38" + +inputs: + schema: "schemas/input.json" + +outputs: + schema: "schemas/output.json" diff --git a/schemas/input.json b/schemas/input.json new file mode 100644 index 000000000..80eb19d3c --- /dev/null +++ b/schemas/input.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PolymarketIntelligenceInput", + "description": "Input schema for Olas Mech Tool: Polymarket Prediction Market Intelligence", + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "Natural language request or market query (e.g. 'Analyze Polymarket market 691547')" + }, + "market_id": { + "type": "string", + "description": "Polymarket market ID or slug (e.g. '691547', 'kraken-ipo-by-december-31-2026-513')" + }, + "analysis_type": { + "type": "string", + "description": "Depth of analysis requested", + "default": "market_intelligence" + } + }, + "required": ["market_id"] +} diff --git a/schemas/output.json b/schemas/output.json new file mode 100644 index 000000000..e564fe7fd --- /dev/null +++ b/schemas/output.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PolymarketIntelligenceOutput", + "description": "Output schema for Olas Mech Tool: Polymarket Prediction Market Intelligence", + "type": "object", + "properties": { + "requestId": { "type": "string" }, + "result": { + "type": "object", + "properties": { + "market": { "type": "object" }, + "outcomes": { "type": "array" }, + "market_metrics": { "type": "object" }, + "analysis": { "type": "object" }, + "data_timestamp": { "type": "string" }, + "source": { "type": "string" } + } + }, + "x402_settlement": { + "type": "object", + "properties": { + "status": { "type": "string" }, + "amount": { "type": "string" }, + "currency": { "type": "string" }, + "network": { "type": "string" }, + "recipient": { "type": "string" }, + "tx_hash": { "type": "string" } + } + } + }, + "required": ["result", "x402_settlement"] +} diff --git a/tests/test_tool_mocked.py b/tests/test_tool_mocked.py new file mode 100644 index 000000000..e6bfa5d66 --- /dev/null +++ b/tests/test_tool_mocked.py @@ -0,0 +1,80 @@ +""" +Unit & Integration Tests for Olas Mech Tool: polymarket_intelligence.py +ALL TESTS ARE 100% MOCKED (NO LIVE PAYMENTS OR REAL BLOCKCHAIN TRANSACTIONS EXECUTED). +""" + +import json +import unittest +from unittest.mock import patch, MagicMock + +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../tool'))) + +from polymarket_intelligence import run + +class TestPolymarketIntelligenceOlasTool(unittest.TestCase): + + @patch('requests.post') + def test_http_402_challenge_mocked(self, mock_post): + """MOCKED TEST: Verify tool handles HTTP 402 challenge correctly.""" + mock_resp = MagicMock() + mock_resp.status_code = 402 + mock_resp.json.return_value = { + "error": "Payment Required", + "status": 402, + "accepts": [ + { + "scheme": "exact", + "network": "eip155:8453", + "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amount": "0.05", + "payTo": "0x43f9a721B59C247B6258e72A1Bf5A384b64F8A38" + } + ] + } + mock_post.return_value = mock_resp + + output_str = run({"market_id": "691547"}) + data = json.loads(output_str) + + self.assertEqual(data["status"], "PAYMENT_REQUIRED") + self.assertEqual(data["http_status"], 402) + self.assertEqual(data["x402_challenge"]["price_usdc"], "0.05") + self.assertEqual(data["x402_challenge"]["recipient"], "0x43f9a721B59C247B6258e72A1Bf5A384b64F8A38") + print("[PASS] MOCKED TEST 1: HTTP 402 Challenge correctly parsed") + + @patch('requests.post') + def test_http_200_success_mocked(self, mock_post): + """MOCKED TEST: Verify tool returns live payload when payment signature attached.""" + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.json.return_value = { + "success": True, + "data": { + "market": { "id": "691547", "question": "Kraken IPO by December 31, 2026?" }, + "outcomes": [{ "name": "Yes", "price_usd": 0.12 }, { "name": "No", "price_usd": 0.88 }] + }, + "payment": { + "amount": "0.05", + "currency": "USDC", + "network": "BASE", + "tx_hash": "0x_mocked_tx_hash_for_unit_testing" + } + } + mock_post.return_value = mock_resp + + output_str = run({ + "market_id": "691547", + "payment_signature": "0x_mocked_tx_hash_for_unit_testing" + }) + data = json.loads(output_str) + + self.assertEqual(data["status"], "SUCCESS") + self.assertEqual(data["http_status"], 200) + self.assertEqual(data["result"]["market"]["id"], "691547") + self.assertEqual(data["x402_settlement"]["tx_hash"], "0x_mocked_tx_hash_for_unit_testing") + print("[PASS] MOCKED TEST 2: Paid HTTP 200 payload correctly returned") + +if __name__ == '__main__': + unittest.main() diff --git a/tools/polymarket_intelligence.py b/tools/polymarket_intelligence.py new file mode 100644 index 000000000..05b1da8d3 --- /dev/null +++ b/tools/polymarket_intelligence.py @@ -0,0 +1,83 @@ +""" +Olas Mech Tool Adapter: Polymarket x402 Intelligence Service +Exposes our production x402 Polymarket Intelligence API to Olas Mech Worker Nodes and Pearl/Polystrat agents. + +Architecture: +Pearl Agent ➔ Olas Mech Request ('polymarket_intelligence') ➔ Mech Worker Node ➔ HTTP 402 Challenge ➔ Base Mainnet $0.05 USDC Payment ➔ Production API Data ➔ Mech On-Chain Response +""" + +import json +import os +import requests +from typing import Any, Dict, Optional + +GATEWAY_URL = os.getenv("GATEWAY_BASE_URL", "https://agent-payment-gateway.vercel.app") +MERCHANT_WALLET = "0x43f9a721B59C247B6258e72A1Bf5A384b64F8A38" +PRICE_USDC = "0.05" +BASE_NETWORK = "eip155:8453" + +def run(kwargs: Dict[str, Any]) -> str: + """ + Main entrypoint for Olas Mech Worker execution. + kwargs contains 'prompt', 'market_id', and optional 'payment_signature'. + """ + market_id = kwargs.get("market_id") or "691547" + payment_sig = kwargs.get("payment_signature") or os.getenv("X402_PAYMENT_SIGNATURE") + + endpoint = f"{GATEWAY_URL}/v1/prediction-market-analysis" + payload = { + "market_id": market_id, + "analysis_type": kwargs.get("analysis_type", "market_intelligence") + } + + headers = { + "Content-Type": "application/json" + } + + if payment_sig: + headers["PAYMENT-SIGNATURE"] = payment_sig + + try: + response = requests.post(endpoint, json=payload, headers=headers, timeout=10) + + # HTTP 402 Payment Required Handling + if response.status_code == 402: + challenge_data = response.json() + accepts = challenge_data.get("accepts", [{}])[0] + + return json.dumps({ + "status": "PAYMENT_REQUIRED", + "http_status": 402, + "message": "x402 Payment Authorization required to unlock live Polymarket analysis", + "x402_challenge": { + "price_usdc": accepts.get("amount", PRICE_USDC), + "network": accepts.get("network", BASE_NETWORK), + "asset": accepts.get("asset", "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"), + "recipient": accepts.get("payTo", MERCHANT_WALLET) + } + }) + + response.raise_for_status() + api_data = response.json() + + return json.dumps({ + "status": "SUCCESS", + "http_status": 200, + "result": api_data.get("data", {}), + "x402_settlement": api_data.get("payment", { + "amount": PRICE_USDC, + "currency": "USDC", + "network": "BASE", + "recipient": MERCHANT_WALLET + }) + }) + + except Exception as e: + return json.dumps({ + "status": "ERROR", + "error": str(e) + }) + +if __name__ == "__main__": + test_res = run({"market_id": "691547"}) + print(test_res)