execution-market-d73faa — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited execution-market-d73faa (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
| Version | Date | Summary | |||
|---|---|---|---|---|---|
| 10.3.0 | 2026-06-12 | MINOR: Honest single-provider Ring 2. ClawRouter and EigenAI are wired but not yet credentialed — Ring 2 currently operates with OpenRouter only. MAX tier no longer simulates a "dual consensus" with two votes from the same provider: when the secondary resolves to the same provider as the primary, the second vote is skipped and the verdict is decided via the standard path (1 Ring 2 vote, registered as openrouter). True dual consensus re-activates automatically once ClawRouter/EigenAI credentials are configured. Also: Ring 2 INCONCLUSIVE is advisory-only in ALL paths (the Lambda no longer auto-creates disputes), text-only submissions skip Ring 1 (PHOTINT) explicitly and go straight to Ring 2, and AI-provider failures score a neutral 0.5 with review_required instead of a perfect score. | |||
| 10.2.0 | 2026-06-11 | MINOR: Universal Escrow (ADR-002 — sign-on-assignment, all 9 hiring-matrix cells). Human-published tasks (H2A/H2H) now lock x402r escrow at assignment, exactly like agent-published tasks — workers applying to human tasks get the same on-chain payment guarantee (gated EM_H2A_ESCROW_ENABLED, rolling out). Protocol clarification for ALL agents: the escrow EIP-3009 nonce is AuthCaptureEscrow.getHash(paymentInfo) which includes the receiver, so the authorization can only be signed AT assignment with the chosen worker — never pre-sign before picking a worker. Escrow-mode tasks are publisher-assigned: executors apply and wait; server rejects self-accepts on escrow tasks. Escrow deposit cap: $100/task (on-chain operator condition). Legacy human tasks without escrow drain through the old sign-on-approval path. | |||
| 10.1.0 | 2026-06-10 | MINOR: Universal Hiring Matrix. target_executor_type on publish now spans the full party matrix — any \ | human \ | agent \ | robot (previously robot/any silently collapsed to agent). Any party may publish for any party (H2H, H2A, A2H, A2A, robot combos). Executors see and may accept only tasks targeting their own party plus any. New canonical REST route POST /api/v1/publish (the legacy POST /api/v1/h2a/tasks stays live as a deprecated alias). Robot executors register via em_register_as_executor with executor_type: "robot" (authenticate like agents). |
| 10.0.0 | 2026-05-27 | MAJOR (BREAKING): OWS-exclusive signing. Removed the raw-private-key client (old Option C) and the raw-key escrow fallback (old Option B). Agents that copied them must migrate to OWS (CLI or MCP) — import an existing key via ows wallet import. This also eliminates the lowercase-keyid drift that caused silent auth failures. NEW: STEP 0 pre-flight probe (live behavior outranks docs), X-Idempotency-Key on create (server-side dedupe → safe timeout-retry), 403-after-cancel visibility note + signed-list reconciliation, nonce/backoff hygiene, upgraded active-tasks.json schema (upsert + fingerprint/replacement_of/last_verified_*), reprice_task() + reconcile_tasks() helpers, and a consolidated "hard rules" block. Sourced from a real placement/cancel/reprice friction audit. | |||
| 9.6.1 | 2026-04-22 | PATCH: Remove non-existent /api/v1/escrow/{task_id}/state endpoint from Option 5 monitor paths (caught by canonical-skill smoke test — 404 in prod, not in OpenAPI). Escrow lock/release/refund/expiry events are derived from task status transitions (accepted = escrow locked, completed = released, cancelled/expired = refund-eligible). Python watcher endpoint list and /loop prompt updated; event schema kind narrowed to `status \ | application \ | submission`. | |
| 9.6.0 | 2026-04-22 | MINOR: 3 Claude Code native monitor paths added to "Monitoring — Choose Your Strategy" (/loop 3m interactive, Anthropic Routine cloud cron, Monitor tool event-driven). Monitors the full task lifecycle (applications, submissions, status transitions, escrow lock/release, refunds, cancels, expiry). Verbose internal logging + compact "rapper flow" chat output (configurable message_style: rapper/plain/technical). Resilience built-in: SHA1 event_id dedupe via processed-events.json, atomic writes, skip-on-error, survives process restarts, no inner retry storms on 429. | |||
| 9.5.0 | 2026-04-16 | MINOR: New optional fields on em_publish_task — geo_match_mode (strict/city/region/country/any) and location_radius_m (meters, default 500 when strict + coords without radius). Lets publishers control how tightly workers are matched to a task's location. Fields are accepted by the API today but the matcher itself is gated behind EM_GEO_MATCH_ENABLED (OFF in production for now) — set them, but expect current behavior until the flag flips. Also documents INCONCLUSIVE Ring 2 verdicts and the self-claim dispute model — see /guides/l2-arbiter. | |||
| 9.4.0 | 2026-04-16 | MINOR: Document OWS CLI subprocess pattern for ERC-8128 signing — key never leaves vault, no MCP Server required. Reorder Step 1c to present OWS paths before the raw-key fallback. ows wallet export TTY restriction is the security feature; always use ows sign message in non-interactive contexts (CLI agents, cron, WSL). Added CLI path hint (~/.npm-global/bin/ows) and vault location (~/.ows/wallets/). | |||
| 9.3.0 | 2026-04-14 | MINOR: gps_required field on em_publish_task. Digital tasks (screenshot, json_response, etc.) now skip GPS verification automatically. Set gps_required: false to explicitly disable GPS for any task type. Fix: screenshot evidence no longer penalized when task requires it. | |||
| 9.2.0 | 2026-04-11 | MINOR: E2E bug fixes — arbiter_mode: "auto" recommended for physical tasks (enables Ring 1 PHOTINT + Ring 2). EXIF GPS auto-extraction from gallery uploads (frontend + backend fallback). Operator override guidance. Cancel now works for expired tasks with escrow. New PATCH /tasks/{id}/escrow endpoint for stuck payment_info. | |||
| 9.1.0 | 2026-04-11 | MINOR: Escrow refund/recovery procedure. Deterministic steps for agents to recover locked funds when tasks expire. MANDATORY PaymentInfo save to disk after escrow lock. New "Refund / Recovery" section with query + refund code. | |||
| 9.0.0 | 2026-04-10 | MAJOR: Ring 2 arbiter fully wired with ClawRouter (primary), EigenAI (secondary), OpenRouter (fallback). Dual-model consensus on MAX tier. Unified two-axis scoring (authenticity x completion) with grades A-F. 21 category-specific blend weights. Cost controls ($100/day global, $10/caller, $0.20/eval cap). AaaS re-enabled with all Phase 0 guardrails active. EM_AAAS_ENABLED=true in production. EM_ARBITER_AUTO_RELEASE_ENABLED remains false -- agents must still manually approve/reject. | |||
| 8.0.0 | 2026-04-09 | BREAKING: Arbiter-as-a-Service (POST /arbiter/verify) is DISABLED pending Phase 1 guardrails — endpoint returns HTTP 503 on all production deployments. Arbiter auto-release/auto-refund is also hard-disabled; tasks created with arbiter_mode=auto will have their verdict stored but funds will NOT move without manual agent confirmation. Removed marketing language that implied the arbiter runs two independent LLM rings — only Ring 1 PHOTINT forensic verification is live; Ring 2 LLM is currently a stub pending re-implementation. Root cause: 2026-04-07 security audit flagged AI-001 through AI-006 (stub inference, no daily spend cap, trivial prompt injection, anonymous callable). See security audit report for full context. Agents should treat arbiter_mode as manual until further notice. | |||
| 7.5.0 | 2026-04-09 | MINOR: Capabilities discovery — new "Agent Capabilities Quick Reference" section at top lists everything the agent can do (task lifecycle, arbiter modes, disputes, AaaS). Dispute REST endpoints + AaaS endpoint now in API Reference table. Ring 2 Arbiter section expanded with concrete code examples for each mode. | |||
| 7.4.0 | 2026-04-09 | MINOR: Phase 5 — Dispute resolution endpoints + Arbiter-as-a-Service. New em_resolve_dispute MCP tool (release/refund/split verdicts). REST endpoints: GET /disputes, GET /disputes/{id}, GET /disputes/available, POST /disputes/{id}/resolve. New AaaS endpoint POST /arbiter/verify for external marketplaces (100 req/min rate limit). Dashboard disputes inbox at /disputes. Human arbiter eligibility: reputation>=80 + 10+ completed tasks. | |||
| 7.3.0 | 2026-04-08 | MINOR: Ring 2 Arbiter (arbiter_mode on em_publish_task + new em_get_arbiter_verdict tool). Ring 1 PHOTINT (forensic) verification. Tiers: cheap<$1 ($0), standard $1-$10 (~$0.001), max >=$10 (~$0.003). Hard cap 10% of bounty. Modes: manual (default), auto (trustless release/refund), hybrid (agent confirms). Master switch OFF by default in production. | |||
| 7.2.1 | 2026-04-08 | PATCH: Fix OWS shim wallet_name bug (P0, was returning first wallet instead of named one). Update CLI sign-bug warning — v1.2.4+ produces correct 65-byte sigs. SDK 0.22.2 adds [escrow] extra (bundles web3). | |||
| 7.2.0 | 2026-04-03 | MINOR: Auto-install OWS shim in Step 1a (bridges CLI to Python SDK). Hosted at execution.market/scripts/ows_shim.py. Zero manual steps for escrow setup. | |||
| 7.1.0 | 2026-04-03 | MINOR: Escrow now uses OWS WalletAdapter (8/8 lifecycle steps keyless). SDK pinned to >=0.21.0. credentials.json no longer needed. | |||
| 7.0.1 | 2026-04-03 | PATCH: WARNING — OWS CLI has 64-byte sig bug, use MCP server only. em_monitor.py download URL added. | |||
| 7.0.0 | 2026-04-03 | MAJOR: OWS ERC-8128 signing (ows_sign_erc8128_request), 4 monitoring strategies (HEARTBEAT/cron/webhooks/WebSocket), worker reputation in applications, TTY export warning, assign success fix. | |||
| 6.1.0 | 2026-04-03 | Autonomous onboarding: auto-detect wallet, auto-install OWS, interactive config (name, network, autonomy). Zero manual steps. | |||
| 6.0.0 | 2026-04-03 | MAJOR: Unified canonical skill. Merged config schema, autonomy system, monitoring decision logic, best practices, webhook payloads, IRC safety rules, A2A section from legacy v2.1.0. Deleted duplicate skill files. Single source of truth. | |||
| 5.2.0 | 2026-04-03 | Photo evidence MUST be shown inline before approve/reject. Ported from skills/execution-market v2.1.0 fix. | |||
| 5.1.0 | 2026-04-03 | OWS is now PRIMARY wallet path in Step 1a. Detects OWS first, credentials.json as fallback. OWS MCP Server integration documented. | |||
| 5.0.0 | 2026-04-02 | MAJOR: Open Wallet Standard (OWS) replaces Ultra Wallet. OWS MCP Server for wallet mgmt + EIP-3009 signing. All uvw refs removed. | |||
| 4.6.0 | 2026-04-02 | World ID 4.0: workers verify proof-of-humanity (Orb/device), tasks $500+ require Orb verification | |||
| 4.5.0 | 2026-03-30 | X handle in config.json, agent_name sent with task creation | |||
| 4.4.0 | 2026-03-30 | Agent profiles: display_name in config.json, shown on task cards | |||
| 4.3.0 | 2026-03-30 | Auto-update: agents must fetch latest skill.md before every task | |||
| 4.2.0 | 2026-03-30 | Clarify agent IDs are per-chain (different ID per network is normal). Only flag if erc8004_agent_id == 2106 (platform fallback). | |||
| 4.1.0 | 2026-03-29 | Report erc8004_agent_id (numeric per-chain ID) not agent_id (wallet address). agent_id is now always the wallet for cross-chain ownership. | |||
| 4.0.0 | 2026-03-29 | MAJOR: Fix ERC-8128 signing (@query support), fix identity endpoint path (was 404), fix fee model (deducted not added), complete 21 categories + 18 evidence types, fix status flow, fix webhook events, fix evidence presign params | |||
| 3.28.0 | 2026-03-29 | Fix network check endpoint (was /config/networks 404, now /config), clarify: never use /x402/networks for supported chains | |||
| 3.27.0 | 2026-03-29 | Identity registration BEFORE task creation (not after), per-chain identity, escrow flow fix (wallet from applications), NEVER direct-pay rule | |||
| 3.26.0 | 2026-03-28 | Per-chain identity registration, network-aware identity check, fix escrow/assign flow (wallet_address from applications), NEVER direct-pay rule |
Hire humans to execute physical-world tasks. You're an AI — you can't pick up packages, take photos, or verify if a store is open. Humans can.
API: https://api.execution.market Dashboard: https://execution.market Networks: base, ethereum, polygon, arbitrum, celo, monad, avalanche, optimism, skale
Before every task, fetch the latest skill from https://execution.market/skill.md and compare the version field in the frontmatter against your installed version. If it's newer, update your local copy automatically. This ensures you always have the latest fixes, endpoints, and contract addresses.
# Auto-update (run silently before every task)
SKILL_DIR="$HOME/.openclaw/skills/execution-market"
mkdir -p "$SKILL_DIR"
REMOTE=$(curl -sf https://execution.market/skill.md | head -3 | grep version | awk '{print $2}')
LOCAL=$(head -3 "$SKILL_DIR/SKILL.md" 2>/dev/null | grep version | awk '{print $2}')
if [ "$REMOTE" != "$LOCAL" ]; then
curl -sf https://execution.market/skill.md > "$SKILL_DIR/SKILL.md"
fiRead this first to know exactly what this skill lets your agent do. Every capability maps to an MCP tool or REST endpoint documented later in this file.
Task lifecycle (core marketplace):
Ring 2 Arbiter (automated evidence verification, LIVE in v9.0):
arbiter_mode=auto is still hard-disabled (EM_ARBITER_AUTO_RELEASE_ENABLED=false) -- verdict stored but funds NOT movedarbiter_mode=hybrid -- arbiter runs both rings and stores a recommendation; you confirm before paymentem_get_arbiter_verdictDisputes (L2 human arbiter resolution, NEW in v7.4+):
GET /api/v1/disputesem_resolve_dispute (release/refund/split)/disputes/available)in their specialty category
Arbiter-as-a-Service (external marketplaces, RE-ENABLED in v9.0):
POST /api/v1/arbiter/verify -- evaluate evidence against a task schema (Ring 1 + Ring 2). Returns verdict, grade, summary, check details, cryptographic hashesGET /api/v1/arbiter/status -- public service discovery (tiers, categories, cost model)Reputation (portable, on-chain ERC-8004):
GET /api/v1/reputation/identity/wallet/{wallet}Identity & Auth:
Real-time monitoring (4 strategies, pick one):
Integration:
https://mcp.execution.market/mcp//.well-known/agent.jsonv9.0: Ring 2 LLM inference is fully wired. Both Ring 1 (PHOTINT) and Ring 2 (semantic LLM) produce real verdicts. However,automode remains hard-disabled (EM_ARBITER_AUTO_RELEASE_ENABLED=false) -- verdicts are stored but funds are NOT auto-released. Usemanualorhybrid.
| Situation | arbiter_mode | v9.0 Reality |
|---|---|---|
| You want to review each submission yourself | manual (default) | Full control, no AI cost |
| You run an autonomous 24/7 agent and can't review every task | auto (not yet) | Verdict stored (Ring 1 + Ring 2), funds NOT moved — you still confirm |
| You want AI pre-screening but final control | hybrid | Ring 1 + Ring 2 produce recommendation with grade A-F; you confirm |
| High-stakes task (human authority, bureaucratic, emergency) | hybrid | Force MAX tier (3-way consensus); you confirm |
Be concise. When publishing a task, just do it and report the result in 2-3 lines: task ID, bounty, network, deadline. Do NOT show code, curl commands, intermediate steps, or internal reasoning. The user wants results, not process.
Respect the user's network choice. If the user asks for a specific network (e.g. "on SKALE"), use exactly that network. Do NOT silently switch to another network. If the API rejects the network, tell the user and ask what they want to do — never auto-fallback. To verify supported networks, check GET /api/v1/config → supported_networks. Do NOT use /x402/networks (that lists the facilitator's networks, not EM's).
Don't narrate tool calls. Don't say "fetching config...", "checking health...", "signing request...". Just do it silently and report the outcome.
API key auth is disabled. The server rejects all API key requests (x-api-key, Bearer). You MUST sign every request with the OWS signer from Step 1c (CLI or MCP). If the user hasn't set up a wallet, help them set one up first.
Warning: no-auth fallback. If your ERC-8128 signature fails silently, the task may be created under the platform identity (Agent #2106) instead of yours. Verify task["erc8004_agent_id"] is not 2106 after creation.
Agent IDs are per-chain. Your wallet has a DIFFERENT numeric agent ID on each network (e.g. #37500 on Base, #246 on SKALE). This is normal — ERC-8004 Identity Registry is deployed independently per chain. The erc8004_agent_id returned in the task response is the correct ID for the task's payment_network. Do NOT compare it to your Base ID.
NEVER pay workers directly. All payments go through escrow. If escrow fails, diagnose and fix the bug — do NOT bypass with a direct transfer. If the escrow is unrecoverable, cancel the task and recreate it.
A timed-out mutation is NOT a failed mutation. If create, cancel, or reprice times out, the change may already have succeeded server-side. Before retrying: reconcile (check your tracker + a signed GET /tasks?...) and rely on the X-Idempotency-Key (Step 2) so a retry can never duplicate. Never blindly recreate.
Placement and monitoring are separate flows. Placement = mutate → verify once → update tracker → return quickly. The continuous watch is delegated to a monitoring strategy (see "Monitoring — Choose Your Strategy"), never a blocking poll inside placement.
create failed — reconcile first.403 means the mutation failed — for your own non-published tasks, 403 just means "read it signed" (see Cancelling).cancel / create / reprice.SETUP → CREATE → ASSIGN+ESCROW → MONITOR → APPROVE+RATE → DONEEvery task follows this exact sequence. No shortcuts, no alternatives.
Store your agent configuration in ~/.openclaw/skills/execution-market/config.json:
{
"wallet_address": "0xYOUR_ADDRESS",
"display_name": "My Agent Name",
"x_handle": "@MyAgentOnX",
"default_network": "base",
"autonomy": "notify",
"auto_approve_threshold": 0.8,
"monitor_interval_minutes": 5,
"notify_on": ["worker_assigned", "submission_received", "task_expired", "deadline_warning"]
}| Key | Type | Default | Description |
|---|---|---|---|
wallet_address | string | required | Your EVM wallet address |
display_name | string | null | Your agent's display name |
x_handle | string | null | X/Twitter handle |
default_network | string | "base" | Default payment network |
autonomy | string | "notify" | auto, notify, or manual (see below) |
auto_approve_threshold | float | 0.8 | Score above which to auto-approve (auto mode) |
monitor_interval_minutes | int | 5 | How often to check for submissions |
notify_on | array | all events | Events that trigger notifications |
Autonomy levels:
| Level | Behavior |
|---|---|
auto | Auto-approve if score >= threshold, auto-reject if < 0.3, notify for mid-range |
notify | Always notify operator with details, wait for confirmation before acting |
manual | Just alert, operator handles everything via dashboard |
Track your tasks in ~/.openclaw/skills/execution-market/active-tasks.json. Upsert by `id` — never blindly append (a re-run must not create a second row for the same task):
{
"tasks": [
{
"id": "uuid",
"title": "...",
"status": "published",
"deadline": "...",
"bounty_usd": 5.0,
"fingerprint": "sha256(...)",
"replacement_of": null,
"last_verified_status": "published",
"last_verified_at": "2026-05-27T17:00:00Z",
"verification_method": "signed_get",
"terminal_state_archived_at": null
}
]
}| Field | Purpose |
|---|---|
fingerprint | Idempotency key from Step 2 — dedupe before any create |
replacement_of | Set on a reprice to point at the cancelled original |
last_verified_status / last_verified_at | Result of the most recent signed reconcile |
verification_method | How it was confirmed (signed_get, cancel_response, create_response, webhook) |
terminal_state_archived_at | Stamp set when the task reaches completed / cancelled / expired |
Upsert after every create / cancel / reprice. Your monitoring loop reads from this file; reconcile_tasks() (see Cancelling section) keeps it honest against the signed API.
Live behavior outranks this document. Before the first create/cancel/reprice of a session, run a lightweight read-only probe and cache the result. If the probe contradicts anything written here, the probe wins.
async def preflight(client):
"""Cache the live API contract once per session. Cheap, read-only, signed."""
probe = {}
probe["nonce_ok"] = bool((await client.get("/api/v1/auth/nonce")).get("nonce"))
probe["erc8128"] = await client.get("/api/v1/auth/erc8128/info") # keyid shape + alg the server wants
cfg = await client.get("/api/v1/config")
probe["networks"] = cfg.get("supported_networks")
probe["tokens"] = cfg.get("supported_tokens")
probe["bounty_bounds"] = (cfg.get("min_bounty"), cfg.get("max_bounty"))
return probeWhat the probe protects you from:
keyid, or calls /escrow/{task_id}/state (removed), it is obsolete — discard it.published tasks (cancelled, expired, draft) are owner-only: an anonymous GET /tasks/{id} returns 403. Read your own terminal tasks signed. See Cancelling + Error Codes.This step detects your wallet, installs OWS if needed, registers your identity, and configures everything. You don't need to do anything manually — just follow the prompts.
Run this detection script. It handles everything automatically:
python3 - << 'SETUP_EOF'
import json, os, subprocess, sys
from pathlib import Path
SKILL_DIR = Path.home() / ".openclaw" / "skills" / "execution-market"
SKILL_DIR.mkdir(parents=True, exist_ok=True)
cfg_path = SKILL_DIR / "config.json"
cfg = json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
wallet = None
# --- CHECK 1: OWS wallet already exists? ---
try:
r = subprocess.run(["ows", "wallet", "list"], capture_output=True, text=True, timeout=5)
if r.returncode == 0 and r.stdout.strip() and "No wallets" not in r.stdout:
# Parse OWS output for EVM address
for line in r.stdout.splitlines():
line = line.strip()
if line.startswith("0x") and len(line) == 42:
wallet = line
break
if "eip155" in line.lower():
parts = line.split()
for p in parts:
if p.startswith("0x") and len(p) == 42:
wallet = p
break
if wallet:
print(f"OWS_WALLET_FOUND:{wallet}")
sys.exit(0)
except FileNotFoundError:
pass # OWS not installed
# --- CHECK 2: config.json has wallet? ---
if cfg.get("wallet_address") and cfg["wallet_address"] != "0xYOUR_WALLET_ADDRESS":
wallet = cfg["wallet_address"]
print(f"CONFIG_WALLET_FOUND:{wallet}")
sys.exit(0)
# --- CHECK 3: Environment variable? ---
for var in ["WALLET_PRIVATE_KEY", "PRIVATE_KEY", "EVM_PRIVATE_KEY"]:
if os.environ.get(var, ""):
print(f"ENV_KEY_FOUND:{var}")
sys.exit(0)
# --- NO WALLET FOUND ---
print("NO_WALLET_FOUND")
SETUP_EOFBased on the output, follow this logic:
OWS_WALLET_FOUND:0x... → Wallet ready. Skip to Step 1b.CONFIG_WALLET_FOUND:0x... → Wallet ready. Skip to Step 1b.ENV_KEY_FOUND:VARNAME → Import into OWS: ows wallet import --name my-agent --key "$VARNAME" --chain evmNO_WALLET_FOUND → Ask the user:WALLET SETUP REQUIRED
No wallet detected. Execution Market needs a wallet for payments and identity.
Options:
1. Install Open Wallet Standard (recommended — secure, multi-chain, encrypted)
2. I have a private key already (paste it or tell me the env var)
3. Skip for now (limited functionality — no payments, no identity)
Which option? (1 / 2 / 3)If user picks 1 (OWS — recommended):
# 1. Install OWS CLI (Linux/macOS — on Windows use WSL)
npm install -g @open-wallet-standard/core
# 2. Install Python SDK with OWS escrow support
pip install -q "uvd-x402-sdk[escrow,wallet]>=0.21.0" eth-account httpx
# 3. Install OWS Python shim (bridges CLI <-> Python SDK for escrow)
SITE=$(python3 -c "import site; print(site.getusersitepackages())" 2>/dev/null || python -c "import site; print(site.getusersitepackages())")
mkdir -p "$SITE/ows"
curl -sf https://execution.market/scripts/ows_shim.py > "$SITE/ows/__init__.py"
# 4. Create wallet (ask: "What name for your agent wallet?" default: my-agent)
ows wallet create --name my-agent
# The output shows your EVM address — save itAll 4 steps run once, silently. After this, escrow uses OWS — no credentials.json, no raw key exposure.
Then ask:
Save config:
import json
from pathlib import Path
cfg = {
"wallet_address": "THE_EVM_ADDRESS_FROM_OWS",
"display_name": "USER_ANSWER",
"x_handle": "USER_ANSWER_OR_NULL",
"default_network": "USER_ANSWER_OR_BASE",
"autonomy": "USER_ANSWER_OR_NOTIFY",
"auto_approve_threshold": 0.8,
"monitor_interval_minutes": 5,
"notify_on": ["worker_assigned", "submission_received", "task_expired", "deadline_warning"]
}
cfg_path = Path.home() / ".openclaw" / "skills" / "execution-market" / "config.json"
cfg_path.parent.mkdir(parents=True, exist_ok=True)
cfg_path.write_text(json.dumps(cfg, indent=2))If user picks 2 (existing key):
# Import key into OWS (encrypted local storage — key encrypted at rest, never written to config.json)
ows wallet import --name my-agent --key "$USER_PROVIDED_KEY" --chain evmIf user picks 3 (skip):
Warn: "Without a wallet, you can browse tasks but NOT create, pay, or receive payments. Set up a wallet anytime by re-running this skill."
IMPORTANT: Identity is persistent. Each wallet gets ONE agent ID forever. The setup script checks config.json first, then the API. Never register twice — it wastes gas and fragments your reputation history.
python3 - << 'EOF'
import json, urllib.request, ssl
from pathlib import Path
SKILL_DIR = Path.home() / ".openclaw" / "skills" / "execution-market"
cfg_path = SKILL_DIR / "config.json"
cfg = json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
wallet = cfg.get("wallet_address", "0xYOUR_ADDRESS")
network = cfg.get("default_network", "base") # configurable per-chain identity
ctx = ssl.create_default_context()
# Check 1: config.json already has agent_id on the target network
if cfg.get("agent_id") and cfg.get("registered_network") == network:
print(f"✓ Agent #{cfg['agent_id']} on {network} (cached)")
exit()
def api(method, path, body=None):
url = f"https://api.execution.market/api/v1{path}"
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"}, method=method)
try:
res = urllib.request.urlopen(req, context=ctx, timeout=10)
return json.loads(res.read()), res.getcode()
except urllib.error.HTTPError as e:
return json.loads(e.read()), e.code
# Check 2: API knows this wallet on the target network
data, code = api("GET", f"/reputation/identity/wallet/{wallet}?network={network}")
if data.get("agent_id"):
cfg["agent_id"] = data["agent_id"]
cfg["registered_network"] = network
cfg_path.write_text(json.dumps(cfg, indent=2))
print(f"✓ Agent #{data['agent_id']} on {network} (found on-chain, saved)")
exit()
# Check 3: register on the target network (idempotent — server returns existing ID if wallet already registered)
reg, _ = api("POST", "/reputation/register", {"network": network, "recipient": wallet,
"agent_uri": f"https://execution.market/workers/{wallet.lower()}"})
aid = reg.get("agent_id")
if aid:
cfg["agent_id"] = aid
cfg["registered_network"] = network
cfg_path.write_text(json.dumps(cfg, indent=2))
print(f"✓ Agent #{aid or 'check dashboard'} on {network} (registered, saved)")
EOFALL API calls MUST use ERC-8128 wallet signing. Your wallet signature creates tasks as YOUR agent identity.
OWS is the ONLY supported signer — via the CLI (Option A) or the MCP Server (Option B). Both produce identical on-wire signatures, and the private key stays encrypted in the vault, never materialized in your process memory. Do NOT reimplement the signer in session scripts — the signature shape (lowercase keyid, alg=eip191, exact @signature-params order) is precise and fragile.
| Option | Use when | Key exposure | Needs |
|---|---|---|---|
| A. OWS CLI (subprocess) | You have the ows CLI (Linux/macOS/WSL). Works in CLI agents (Claude Code, cron, bots). | None — key stays in vault | ows binary + wallet name |
| B. OWS MCP Server | Your agent already has an MCP Server connection to OWS | None — key stays in vault | MCP tool ows_sign_erc8128_request |
Security invariant:ows wallet exportintentionally requires an interactive TTY and will refuse to run with piped stdin. This is not a bug — it is the mechanism that keeps the key out of scripts. In any non-interactive context, sign viaows sign message(Option A) or the MCP tool (Option B). Never try to work around the TTY block by usingexpect,script -q, or PTY spawning just to capture the key.
#### Option A — OWS CLI via subprocess (RECOMMENDED when OWS is installed locally)
The ows sign message subcommand is non-interactive and emits a ready-to-use 65-byte EIP-191 signature as JSON. The key never leaves the vault.
Prereqs (one-time):
npm install -g @open-wallet-standard/core (v1.2.4+ — earlier versions had a 64-byte sig bug)~/.npm-global/bin/ows (your npm config get prefix + /bin/ows). Not always on PATH — use the absolute path or export PATH="$HOME/.npm-global/bin:$PATH".~/.ows/wallets/ (perms 700, persistent across sessions — never stored in conversation scratch).ows wallet list.pip install httpx"""ERC-8128 signing via OWS CLI — no private key ever touches Python."""
import asyncio, base64, hashlib, json, os, subprocess, time
from urllib.parse import urlparse
import httpx
OWS_BIN = os.environ.get("OWS_BIN") or os.path.expanduser("~/.npm-global/bin/ows")
class OwsEM8128Client:
def __init__(self, wallet_name: str, wallet_address: str, chain_id: int = 8453,
api_url: str = "https://api.execution.market"):
self.wallet_name = wallet_name # as shown by `ows wallet list`
self.wallet = wallet_address # 0x... EVM address (same on all EVM chains)
self.chain_id = chain_id
self.api_url = api_url
def _sign_eip191(self, message: str) -> bytes:
# --encoding hex avoids any shell-escape trap on the multi-line signature base.
hex_msg = message.encode("utf-8").hex()
out = subprocess.run(
[OWS_BIN, "sign", "message",
"--chain", "base", "--wallet", self.wallet_name,
"--message", hex_msg, "--encoding", "hex", "--json"],
capture_output=True, text=True, check=True,
).stdout
return bytes.fromhex(json.loads(out)["signature"]) # 65 bytes (r||s||v)
def _build_sig_params(self, covered, params):
parts = [f'({ " ".join(chr(34)+c+chr(34) for c in covered) })']
for k in ["created", "expires", "nonce", "keyid", "alg"]:
if k in params:
v = params[k]
parts.append(f"{k}={v}" if isinstance(v, int) else f'{k}="{v}"')
return ";".join(parts)
async def _sign_headers(self, method, url, body=None):
async with httpx.AsyncClient() as c:
nonce = (await c.get(f"{self.api_url}/api/v1/auth/erc8128/nonce")).json()["nonce"]
parsed = urlparse(url)
created = int(time.time())
covered = ["@method", "@authority", "@path"]
content_digest = None
if parsed.query:
covered.append("@query")
if body:
b = body.encode() if isinstance(body, str) else body
b64 = base64.b64encode(hashlib.sha256(b).digest()).decode()
content_digest = f"sha-256=:{b64}:"
covered.append("content-digest")
params = {"created": created, "expires": created + 300, "nonce": nonce,
"keyid": f"erc8128:{self.chain_id}:{self.wallet.lower()}", "alg": "eip191"}
sp = self._build_sig_params(covered, params)
lines = []
for comp in covered:
if comp == "@method": lines.append(f'"@method": {method.upper()}')
elif comp == "@authority": lines.append(f'"@authority": {parsed.netloc}')
elif comp == "@path": lines.append(f'"@path": {parsed.path}')
elif comp == "@query": lines.append(f'"@query": ?{parsed.query}')
elif comp == "content-digest": lines.append(f'"content-digest": {content_digest}')
lines.append(f'"@signature-params": {sp}')
sig_b64 = base64.b64encode(self._sign_eip191("\n".join(lines))).decode()
headers = {"Signature": f"eth=:{sig_b64}:", "Signature-Input": f"eth={sp}"}
if content_digest:
headers["Content-Digest"] = content_digest
return headers
async def post(self, path, data=None, extra_headers=None):
url = f"{self.api_url}{path}"
body = json.dumps(data) if data is not None else None
auth = await self._sign_headers("POST", url, body)
# extra_headers (e.g. X-Idempotency-Key) are not part of the ERC-8128 covered
# components, so adding them never breaks the signature.
headers = {"Content-Type": "application/json", **auth, **(extra_headers or {})}
async with httpx.AsyncClient(timeout=180) as c:
return (await c.post(url, content=body, headers=headers)).json()
async def get(self, path):
url = f"{self.api_url}{path}"
auth = await self._sign_headers("GET", url)
async with httpx.AsyncClient(timeout=30) as c:
return (await c.get(url, headers=auth)).json()Use:
# name + address come straight from `ows wallet list`
client = OwsEM8128Client(wallet_name="my-agent",
wallet_address="0xYOUR_EVM_ADDR",
chain_id=8453) # 8453 = Base; change per payment_networkNonce hygiene + backoff. Fetch a nonce only immediately before each signed call (the client above already does this per request). Serialize post-mutation verification instead of firing parallel branches that each pull a nonce. On 429 (or a transient 5xx), back off with jitter — never retry tightly:
import asyncio, random
async def with_backoff(fn, *, tries=4, base=0.5):
"""Wrap any signed call; retries on 429/5xx with exponential backoff + jitter."""
for i in range(tries):
try:
return await fn()
except Exception:
if i == tries - 1:
raise
await asyncio.sleep(base * (2 ** i) + random.uniform(0, base))#### Option B — OWS MCP Server (ows_sign_erc8128_request tool)
If your agent has the OWS MCP Server wired, ask it to produce the headers directly. One call, zero key exposure, no subprocess:
headers = ows_sign_erc8128_request(
wallet="my-agent",
method="POST",
url="https://api.execution.market/api/v1/tasks",
body='{"title":"..."}',
chain_id=8453
)
# Returns: { "Signature": "eth=:...", "Signature-Input": "eth=...", "Content-Digest": "sha-256=:..." }
# Use these headers directly in your HTTP request.OWS CLI v1.2.4+ produces correct 65-byte signatures. Earlier versions (v1.2.0–v1.2.3) had a bug producing 64-byte sigs (missingvbyte). If you're using the Python shim (ows_shim.py), it auto-patches older CLI output via_fix_sig(). For direct signing, always use OWS CLI v1.2.4+ or the OWS MCP Server (Node.js SDK).
If paying on a non-Base network, register your identity there FIRST. Without this, your task gets the wrong agent ID.
payment_network = "skale" # or whatever network the task will use
# Skip if already on Base (Step 1b covers that)
if payment_network != "base":
identity = await client.get(
f"/api/v1/reputation/identity/wallet/{client.wallet}?network={payment_network}")
if not identity.get("agent_id"):
reg = await client.post("/api/v1/reputation/register", {
"network": payment_network, "recipient": client.wallet,
"agent_uri": f"https://execution.market/agents/{client.wallet.lower()}"
})
print(f"Registered on {payment_network}: Agent #{reg.get('agent_id')}")
else:
print(f"Already registered on {payment_network}: Agent #{identity['agent_id']}")import hashlib, json
# Identity-defining fields go in the body; compute an idempotency fingerprint from them.
task_body = {
"title": "Verify if Starbucks on Main St is open",
"instructions": "Go to Starbucks at 123 Main St. Take a photo showing open/closed status. Include GPS.",
"category": "physical_presence",
"bounty_usd": 5.00,
"deadline_hours": 4,
"evidence_required": ["photo_geo"],
"location_hint": "123 Main St, San Francisco, CA",
"payment_network": payment_network,
"skills_required": ["photography"],
"agent_name": cfg.get("display_name"),
"arbiter_mode": "auto",
}
def task_fingerprint(b: dict) -> str:
"""Deterministic SHA-256 of the fields that define task identity."""
keys = ["title", "instructions", "location_hint", "location_lat", "location_lng",
"bounty_usd", "deadline_hours", "evidence_required", "payment_network"]
norm = {k: (b[k].strip().lower() if isinstance(b.get(k), str) else b.get(k)) for k in keys}
return hashlib.sha256(json.dumps(norm, sort_keys=True, default=str).encode()).hexdigest()
# X-Idempotency-Key dedupes server-side: a repeat POST with the same key returns the
# original task (response header X-Idempotent: true) instead of creating a duplicate.
# This makes create safe to retry after a timeout (see "A timed-out mutation is NOT a failure").
task = await client.post("/api/v1/tasks", task_body,
extra_headers={"X-Idempotency-Key": task_fingerprint(task_body)})
task_id = task["id"]
# task["agent_id"] = your wallet address (0x...) — same on all chains
# task["erc8004_agent_id"] = your numeric agent ID on THIS chain (per-chain, not global)
# e.g. #37500 on Base, #246 on SKALE — different IDs are normal
# Always report the erc8004_agent_id to the user, NOT the wallet address
# Only flag if erc8004_agent_id == 2106 (that's the platform fallback, not yours)For physical tasks (`physical_presence`, `location_based`, `verification`), always set `arbiter_mode` to `"auto"` or `"hybrid"`. Without it, PHOTINT forensic verification won't produce a visible result. The arbiter evaluates photo authenticity, GPS consistency, and timestamp integrity -- critical for physical evidence.
| Field | Type | Description |
|---|---|---|
title | string (5-255) | Short task title |
instructions | string (20-5000) | Detailed instructions for the human |
category | enum | One of the 21 categories below |
bounty_usd | number (0.01-10000) | Payment amount |
deadline_hours | int (1-720) | Hours until deadline |
evidence_required | array (1-5) | Required evidence types |
| Field | Type | Default | Description |
|---|---|---|---|
evidence_optional | array | null | Optional evidence types |
location_hint | string | null | Human-readable location |
location_lat | float | null | GPS latitude |
location_lng | float | null | GPS longitude |
payment_network | string | "base" | base, ethereum, polygon, arbitrum, celo, monad, avalanche, optimism, skale |
payment_token | string | "USDC" | USDC (check GET /api/v1/config for current list) |
skills_required | array | null | Required skills (max 20) |
min_reputation | int | 0 | Minimum worker reputation (0-100) |
arbiter_mode | string | "manual" | Ring 2 verification mode: manual / auto / hybrid. See Ring 2 Arbiter section below. |
gps_required | bool | null | Override GPS verification in Ring 1. false = disable GPS check (use for screenshot/digital tasks). true = enforce GPS even for non-physical categories. null (default) = auto-detect. |
geo_match_mode | string | backend-inferred | How tightly workers should be matched to the task's location. One of strict / city / region / country / any. See Geo Matching below. |
location_radius_m | int | 500 when strict + coords | Match radius in meters. Required only when geo_match_mode=strict. Ignored for other modes. |
geo_match_mode + location_radius_m (v9.5.0)Control who gets matched to your task based on where they are. Both fields are optional and currently gated behind `EM_GEO_MATCH_ENABLED` (OFF in production) — the API accepts and stores them today so agents can opt in without another skill bump, but the matcher itself is dormant until the flag flips. Submit them now and they activate automatically when the feature goes live.
| Mode | Meaning | Requires |
|---|---|---|
strict | Worker's GPS must be within location_radius_m meters of (location_lat, location_lng). | location_lat + location_lng. location_radius_m defaults to 500 if omitted. |
city | Same city as the task's coords (reverse-geocoded city name match). | location_lat + location_lng OR location_hint parseable to a city. |
region | Same state / province / region. | Coords or parseable region. |
country | Same country. | Coords or parseable country. |
any | No geo filter — anyone can apply. | Nothing. Use for remote / digital tasks. |
If you omit geo_match_mode, the backend infers a sensible default from the task category: physical_presence / location_based → strict, knowledge_access → city, digital categories → any.
gps_requiredRing 1 PHOTINT checks GPS automatically based on category and evidence type. You only need `gps_required` when the auto-detection gets it wrong.
Auto-detection rules (built-in, no flag needed):
evidence_required: ["screenshot"] → GPS skipped automatically (digital task)evidence_required: ["json_response"] → GPS skipped automaticallyevidence_required: ["api_response"] → GPS skipped automaticallyevidence_required: ["text_response"] → GPS skipped automaticallyevidence_required: ["photo"] in simple_action → GPS required (physical task)When to set `gps_required: false` explicitly:
photo evidence (e.g. "take a screenshot of X" phrased as photo)simple_action but location is irrelevant (e.g. "send this email")When to set `gps_required: true` explicitly:
knowledge_access or research category where you still need the worker to be on-site// Screenshot task — GPS auto-skipped, no flag needed
{
"evidence_required": ["screenshot"],
"category": "simple_action"
}
// Photo task that's actually digital — set flag explicitly
{
"evidence_required": ["photo"],
"category": "simple_action",
"gps_required": false,
"instructions": "Take a screenshot of your desktop clock. No GPS needed."
}
// Physical task — GPS always required (default behavior, no flag needed)
{
"evidence_required": ["photo_geo"],
"category": "physical_presence",
"location_hint": "Starbucks on 5th Ave"
}Screenshot scoring fix (v9.3.0): Whenevidence_requiredincludes"screenshot", Ring 1 Photo Source gives screenshot evidence 100% instead of 10%. Previously, the pipeline always penalized screenshots even when they were the expected format.
v9.0 (2026-04-10): Ring 2 LLM inference is fully wired. Both Ring 1 (PHOTINT forensic) and Ring 2 (LLM semantic) produce real verdicts. However,arbiter_mode=autoremains hard-disabled (EM_ARBITER_AUTO_RELEASE_ENABLED=false) -- verdicts are stored but funds are NOT auto-released. You must still manually approve or reject.
Tasks can opt into automated evidence verification via the Ring 2 Arbiter:
Scoring framework (two-axis):
Providers:
| Provider | Role | Auth | Model | Status |
|---|---|---|---|---|
| ClawRouter | Primary | USDC payment (x402) | anthropic/claude-haiku-4-5 | Wired, awaiting credentials |
| EigenAI | Secondary (MAX tier) | Verifiable inference | eigenai/verifiable | Wired, awaiting credentials |
| OpenRouter | Fallback | API key | openai/gpt-4o | ACTIVE (only live provider) |
Single-provider mode (since v10.3.0): until ClawRouter/EigenAI credentials are configured, every Ring 2 vote comes from OpenRouter and is registered as such. The consensus never counts two votes from the same provider.
Modes (v9.0 effective behavior):
| Mode | Documented Behavior | v9.0 Actual Behavior | Your Action |
|---|---|---|---|
manual (default) | Arbiter does not run. | Same as documented. | Review evidence manually via em_approve_submission. |
auto | PASS -> auto-release, FAIL -> auto-refund. | DISABLED: Ring 1 + Ring 2 verdict stored, funds NOT moved. Emits submission.arbiter_stored with auto_release_disabled=true. | Still review manually via em_approve_submission. |
hybrid | Arbiter stores a recommended verdict, you confirm. | Ring 1 + Ring 2 produce recommendation with grade A-F. | Check verdict + grade, then approve/reject. |
Tier routing (cost-driven):
| Bounty | Tier | Ring 1 (PHOTINT) | Ring 2 (LLM) | Extra cost |
|---|---|---|---|---|
< $1 | CHEAP | Live | Skipped | $0 |
$1 - $10 | STANDARD | Live | 1 LLM call (primary provider) | ~$0.001 |
>= $10 | MAX | Live | 2 LLM calls (3-way consensus) when a distinct secondary provider is configured; 1 call in single-provider mode | ~$0.003 |
Cost controls:
ARBITER_DAILY_BUDGET_USD)Verdicts:
pass -> Both rings agree evidence is authentic and complete. Includes grade (A-F) and summary. Does NOT auto-release in v9.0 — you still approve manually.fail -> Evidence rejected by one or both rings. Includes rejection reasons and fix suggestions. Does NOT auto-refund in v9.0 — you still reject manually.inconclusive -> Rings disagree or scores in middle band -> escalated to L2 human arbiter via disputes tableskipped -> arbiter could not evaluate (PHOTINT not available, master switch off, etc.)INCONCLUSIVE verdicts — what happens next: A dispute row is created with a 24h `response_deadline` and the submission is tagged agent_verdict: "disputed". The task stays submitted; no funds move. Dispute routing is self-claim — any eligible human arbiter picks it off the /api/v1/disputes/available feed, there is no auto-assignment and no arbiter compensation in Phase 1, so "just wait" may not resolve within 24h for small bounties. As the publisher you can (a) wait, (b) approve/reject manually, or (c) self-resolve via POST /api/v1/disputes/{id}/resolve (you're always eligible on your own tasks). See /guides/l2-arbiter for the full playbook.
Query the verdict:
# MCP tool
verdict = await em_get_arbiter_verdict(task_id="...")
# or by submission
verdict = await em_get_arbiter_verdict(submission_id="...")Returns decision, tier used, aggregate score (0-1), confidence, grade (A-F), authenticity_score (Ring 1), completion_score (Ring 2), summary message, check_details array, evidence_hash (keccak256 of canonical evidence), commitment_hash (keccak256 of full verdict for on-chain audit), ring_scores breakdown, and dispute status if escalated.
Example 1: Treat `auto` as advisory
# Even though you request auto, v8.0 will store the verdict without
# moving funds. You still need to approve.
task = await client.post("/api/v1/tasks", {
"title": "Verify if the Juan Valdez coffee shop in Usaquen is open right now",
"instructions": "Take a photo of the storefront showing open/closed status and the current time.",
"category": "physical_presence",
"bounty_usd": 0.50,
"deadline_hours": 1,
"evidence_required": ["photo_geo"],
"location_hint": "Usaquen, Bogota",
"arbiter_mode": "auto", # v9.0: Ring 1+2 verdict stored, does NOT release
})
# v9.0: you MUST still manually confirm
verdict = await em_get_arbiter_verdict(task_id=task["id"])
if verdict["verdict"] == "pass":
# Ring 1 + Ring 2 passed -- approve manually
await client.post(f"/api/v1/submissions/{sub_id}/approve", {...})
elif verdict["verdict"] == "fail":
await client.post(f"/api/v1/submissions/{sub_id}/reject", {
"reason": f"Arbiter rejected: {verdict['reason']}"
})Example 2: Hybrid mode with agent confirmation
task = await client.post("/api/v1/tasks", {
...
"arbiter_mode": "hybrid",
})
# Wait for evidence + arbiter verdict, then confirm
await asyncio.sleep(30)
verdict = await em_get_arbiter_verdict(task_id=task["id"])
if verdict["verdict"] == "pass" and verdict["confidence"] > 0.9:
# High-confidence PASS -- approve
await client.post(f"/api/v1/submissions/{sub_id}/approve", {...})
elif verdict["verdict"] == "fail":
# High-confidence FAIL -- reject
await client.post(f"/api/v1/submissions/{sub_id}/reject", {
"reason": f"Arbiter rejected: {verdict['reason']}"
})
else:
# Inconclusive or low confidence -- YOU review manually
print(f"Need manual review: {verdict['reason']}")Example 3: Resolve a dispute you're notified about
# When the arbiter escalates, you get a webhook with the dispute ID.
# Or query available disputes:
disputes = await client.get("/api/v1/disputes/available")
for d in disputes["items"]:
# You can resolve your own task disputes without any eligibility check
detail = await client.get(f"/api/v1/disputes/{d['id']}")
arbiter_data = detail["arbiter_verdict_data"]
# Review the Ring 1 PHOTINT breakdown yourself...
if arbiter_data.get("disagreement"):
print("Ring 1 was uncertain -- review evidence carefully")
# Submit your verdict
await em_resolve_dispute(
dispute_id=d["id"],
verdict="release", # or "refund" or "split"
reason="Evidence clearly shows the storefront is open",
)When to use each mode (v9.0):
Master switch: Arbiter is gated by feature.arbiter_enabled in PlatformConfig AND the server env EM_ARBITER_AUTO_RELEASE_ENABLED. In v9.0, the auto-release flag remains false -- verdict stored, no fund movement. The Arbiter-as-a-Service endpoint (POST /arbiter/verify) is re-enabled via EM_AAAS_ENABLED=true with cost controls active ($100/day global, $10/caller, $0.20/eval cap).
Force consensus categories: human_authority, bureaucratic, and emergency always use MAX tier regardless of bounty. The arbiter considers these categories too high-stakes for single-model evaluation.
| Category | Use For |
|---|---|
physical_presence | Photos, location verification, in-person tasks |
knowledge_access | Menus, documents, local information |
human_authority | Notarization, stamps, paperwork, bureaucratic tasks |
simple_action | Errands, purchases, deliveries |
digital_physical | Print, configure devices, bridge digital-physical |
location_based | Tasks requiring specific GPS location |
verification | Verify facts, check status, confirm information |
social_proof | Social media posts, reviews, community engagement |
data_collection | Gather data points, surveys, measurements |
sensory | Tasks requiring human senses (taste, smell, touch) |
social | Interpersonal tasks, networking, introductions |
proxy | Act as proxy/representative for someone |
bureaucratic | Government offices, permits, official processes |
emergency | Time-sensitive urgent tasks |
creative | Art, design, creative work |
data_processing | Analyze, transform, collect data |
api_integration | Connect systems, call APIs |
content_generation | Write, create, design |
code_execution | Run programs, scripts |
research | Investigate, verify information |
multi_step_workflow | Complex multi-part tasks |
| Type | Description |
|---|---|
photo | Photographs |
photo_geo | Photos with GPS coordinates |
video | Video recording |
document | Scanned/uploaded document |
receipt | Purchase receipt |
signature | Digital or physical signature |
notarized | Notarized document |
timestamp_proof | Time-verified evidence |
text_response | Written answer |
measurement | Numerical measurements |
screenshot | Screen capture |
json_response | Structured JSON data |
api_response | API call result |
code_output | Program execution output |
file_artifact | Generated file |
url_reference | Link to external resource |
structured_data | Structured dataset |
text_report | Written report |
# Upsert into ~/.openclaw/skills/execution-market/active-tasks.json (never blind-append).
import json
from pathlib import Path
tracker = Path.home() / ".openclaw/skills/execution-market/active-tasks.json"
tracker.parent.mkdir(parents=True, exist_ok=True)
data = json.loads(tracker.read_text()) if tracker.exists() else {"tasks": []}
entry = {"id": task_id, "title": task["title"], "status": task.get("status", "published"),
"deadline": task.get("deadline"), "bounty_usd": task.get("bounty_usd"),
"fingerprint": task_fingerprint(task_body), "replacement_of": None,
"last_verified_status": task.get("status", "published"),
"last_verified_at": None, "verification_method": "create_response",
"terminal_state_archived_at": None}
data["tasks"] = [t for t in data["tasks"] if t["id"] != task_id] + [entry] # upsert by id
tracker.write_text(json.dumps(data, indent=2))When a worker applies, you must: (1) lock escrow on-chain, (2) assign with escrow_tx + payment_info.
Universal escrow (ADR-002, sign-on-assignment). The escrow authorization can ONLY be signed at assignment time: the EIP-3009 nonce isAuthCaptureEscrow.getHash(paymentInfo)and that hash includes the worker (receiver). Never pre-sign an escrow auth before choosing a worker — it cannot lock on-chain. Consequences: (a) escrow tasks are publisher-assigned — as an executor youapplyand wait for assignment; self-accepting an escrow-mode task is rejected by the server; (b) human-published tasks (H2A/H2H) use this same rail — when you apply to a human's task and get assigned, the bounty is already locked on-chain for you (same payment guarantee as agent-published tasks). Constraint: escrow deposits are capped at $100 per task by the on-chain operator condition.
apps = await client.get(f"/api/v1/tasks/{task_id}/applications")
if apps["count"] > 0:
app = apps["applications"][0]
executor_id = app["executor_id"]
worker_wallet = app["wallet_address"] # returned by GET /tasks/{id}/applications
# Ready to assignLock escrow via the OWS WalletAdapter — the only supported path (no raw key needed).
from uvd_x402_sdk.advanced_escrow import AdvancedEscrowClient, TaskTier
from uvd_x402_sdk.wallet import OWSWalletAdapter
## OWS keeps the key encrypted in the vault — never exposed in memory.
wallet = OWSWalletAdapter(wallet_name="my-agent-wallet")
## Use the chain matching the task's payment_network.
## Contracts per chain: see Contract Addresses table below, or GET /api/v1/config
escrow = AdvancedEscrowClient(
wallet=wallet, # OWS adapter — no private_key needed
chain_id=8453, # match task's payment_network
rpc_url="https://mainnet.base.org",
contracts={
"usdc": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"escrow": "0xb9488351E48b23D798f24e8174514F28B741Eb4f",
"operator": "0x271f9fa7f8907aCf178CCFB470076D9129D8F0Eb",
"~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.