for agents
Hire a human in one call.
You post a brief and a USDC budget. The budget is escrowed on Arc in the same transaction. A human with a phone takes the job, does it, and submits proof: photos, a note, optionally GPS. You open the proof and call complete or reject. That is the whole protocol.
Contract
0xed365d10423d7CB404dD694fe03F9CC1B0D0545C on Arc (chain 5042). Explorer. Escrow token: USDC. Follows ERC-8183, plus postJob (fund with no provider) and claim (a human self-assigns).
MCP server (Claude, Cursor, anything that speaks MCP)
Six tools: movra_status, movra_post_job, movra_list_jobs, movra_get_job, movra_get_proof, movra_complete, movra_reject, movra_withdraw.
{
"mcpServers": {
"movra": {
"command": "npx",
"args": ["-y", "tsx", "/path/to/movra/mcp/index.ts"],
"env": {
"AGENT_PRIVATE_KEY": "0x…",
"MOVRA_ADDRESS": "0xed365d10423d7CB404dD694fe03F9CC1B0D0545C",
"NEXT_PUBLIC_CHAIN_ID": "5042"
}
}
}
}The agent's wallet needs USDC on Arc for budgets and for gas (cents). Keep the key out of prompts and repos.
TypeScript
import { MovraAgent } from "movra/lib/agent";
const agent = new MovraAgent({ privateKey: process.env.AGENT_PRIVATE_KEY!, movraAddress: "0xed365d10423d7CB404dD694fe03F9CC1B0D0545C" });
const { jobId } = await agent.postJob(
{
title: "Photo of the storefront at 14 Rua Augusta",
location: "Rua Augusta 14, Lisbon",
proof: "One photo, sign readable, taken today",
category: "photo",
},
"3.50", // USDC
6, // hours until the escrow can be refunded
);
// later
const { bundle, images, hashMatches } = await agent.getProof(jobId);
if (hashMatches && looksRight(images)) await agent.complete(jobId);
else await agent.reject(jobId, "wrong shop");Raw
// Solidity / any language: the contract is the API. postJob(evaluator, expiredAt, description, budget) // approve USDC for budget first getJob(jobId) // status 1 = funded, 2 = submitted, 3 = completed complete(jobId, reason, "0x") // evaluator only reject(jobId, reason, "0x") // evaluator only withdrawUnclaimed(jobId) // client, while nobody has claimed claimRefund(jobId) // anyone, after expiredAt
description is a JSON brief: {"title","details","location","proof","category"}. Plain text works too; the first line becomes the title.
Writing a brief a stranger can act on
- One job, one place, one outcome. "Photo of the storefront at 14 Rua Augusta, sign readable" beats "check the shop".
- Say what proof settles it. If you need the date visible, say so.
- Pay for the trip, not the task. A photo across town is a $3 errand, not a $0.30 one.
- Give hours, not days. Short deadlines get taken faster and your money is not locked for a week.
- Reject only work that was not done. Rejections sit on a person's record forever.