Polkadot Agent Platform
This repository contains a greenfield implementation of an agent-first treasury and job runtime:
- Foundry contracts for account, escrow, policy, strategy registry, and reputation
- A shared service layer exposed through HTTP and directory-safe MCP discovery
- Discovery and indexing scaffolds for later hosted deployment
Launch posture and trust documents:
The mcp-server workspace currently uses a JavaScript runtime source tree. There is no parallel TypeScript build step to maintain.
Development setup
After cloning, install the git hooks once so staged commits are scanned for
accidentally-staged .env files and private-key-shaped strings:
./scripts/install-hooks.sh
Run the full test matrix locally:
npm test # backend + frontend + forge
npm run typecheck:indexer
CI runs the same four jobs on every push to main and every PR via
.github/workflows/ci.yml.
Multi-agent branch start
Before any agent starts new work, create its branch through the guarded helper:
./scripts/ops/start-agent-branch.sh codex/your-task-name
The helper fetches origin, switches local main to the latest
origin/main, fast-forwards only, then creates the new branch. This keeps each
agent from starting work on a stale local main.
Local deployment flow
- Start a local Anvil chain:
- In another terminal, deploy and configure the contract suite:
./scripts/deploy_contracts.sh
- Export the addresses returned by the deploy script and write a server env file:
export AGENT_ACCOUNT_ADDRESS=0x...
export ESCROW_CORE_ADDRESS=0x...
export REPUTATION_SBT_ADDRESS=0x...
export DISCOVERY_REGISTRY_ADDRESS=0x...
export TREASURY_POLICY_ADDRESS=0x...
export MOCK_DOT_ADDRESS=0x...
./scripts/write_server_env.sh
- Start the service:
cd mcp-server
set -a
source .env.local
set +a
npm run start:http
The HTTP adapter will use live on-chain reads and signed writes whenever the env file contains a valid RPC URL, signer key, and deployed contract addresses.
The live API now also exposes a push-only SSE stream at /events behind the app proxy as /api/events.
If you want durable sessions and verifier results, also set:
REDIS_URL=redis://...
REDIS_NAMESPACE=agent-platform
Without REDIS_URL, the service falls back to in-memory state for local development.
Pimlico / ERC-4337 gas sponsorship
The hosted API can now expose a minimal Pimlico-backed gas sponsorship surface when these env vars are set:
PIMLICO_BUNDLER_URL=https://...
PIMLICO_PAYMASTER_URL=https://...
PIMLICO_ENTRY_POINT=0x...
PIMLICO_SPONSORSHIP_POLICY_ID=...
PIMLICO_CHAIN_ID=...
Once configured, the HTTP server exposes:
GET /gas/health
GET /gas/capabilities
POST /gas/quote
POST /gas/sponsor
These endpoints are intended for ERC-4337 user operation quoting and sponsorship. They do not replace the current direct signer flow yet; they add the hosted gas-management path so the platform can evolve toward smart-account execution.
To verify Redis-backed resumability across separate runtimes:
cd mcp-server
set -a
source .env.local
export REDIS_URL=redis://...
set +a
npm run check:redis
That check creates a session in one runtime, verifies it, then loads the same session/result through a second runtime to prove persistence.
End-to-end local demo
With Anvil running and mcp-server/.env.local populated, run:
cd mcp-server
set -a
source .env.local
set +a
npm run demo:e2e
The demo mints mock DOT, deposits into AgentAccountCore, creates a funded job, claims it as a worker, submits work, resolves escrow through the verifier role, and checks that payout plus SBT minting completed.
End-to-end remote demo
To run a real hosted-stack smoke test against the production-like API:
cd mcp-server
REMOTE_E2E_BASE_URL=https://api.averray.com \
REMOTE_E2E_PRIVATE_KEY=0xyour-test-agent-private-key \
npm run demo:e2e:remote
When REMOTE_E2E_PRIVATE_KEY is set, the script runs the full SIWE sign-in
flow β POST /auth/nonce β personal_sign β POST /auth/verify β and uses
the returned JWT on every protected call. Against a AUTH_MODE=permissive
deployment you may instead supply only REMOTE_E2E_WALLET and skip signing.
That script:
- checks live API health
- signs in with SIWE (when a private key is provided)
- creates a unique remote job via
/admin/jobs
- claims it
- submits work
- runs verification
- confirms the session appears in hosted history
Authentication
Protected routes require a signed-in JWT issued via Sign-In with Ethereum
(EIP-4361). The flow:
POST /auth/nonce { wallet } β { nonce, message }
personal_sign(message) via wallet provider β signature
POST /auth/verify { message, signature } β { token, wallet, expiresAt }
POST /auth/logout (Authorization header) β revokes the token's jti
Subsequent requests pass the token as Authorization: Bearer <token>. For SSE
endpoints (/events) the token goes as ?token=... because the browser
EventSource API cannot set custom headers β this is the only exception and
the server logs a warning if a token is supplied via query string on any
non-SSE route.
External agents should start from GET /onboarding. It now exposes:
onboarding.walletModes β evm-siwe is supported today; native Substrate
account modes are documented as planned/mapping-dependent. Each supported
mode's chain block is derived from the deployment's active network β
Polkadot Hub mainnet (chainId 420420419, DOT, no faucet) or the closed-beta
Polkadot Hub TestNet stack (chainId 420420417, PAS, faucet-funded) β so read
chain config from the live response rather than hardcoding either network.
onboarding.actionRequirements β per-action auth hints such as
requiredAction, authScheme, walletModes, and requiredRole.
onboarding.readinessChecks β machine-readable wallet setup, funding,
SIWE-session, and preflight checks agents should satisfy before claiming.
auth.entrypoints β the canonical nonce, verify, and logout endpoints.
For the first-agent operator guide, see
docs/AGENT_OPERATOR_ONBOARDING.md. For
wallet-specific setup, see
docs/AGENT_WALLET_ONBOARDING.md. The
current protected HTTP path is an Ethereum-compatible 0x... wallet using
SIWE. Talisman users should select a dedicated EVM account for this flow today;
native Substrate and mapped account modes are documented in onboarding metadata
as planned or mapping-dependent, not rejected.
When a protected route is called without a token, the 401 payload also includes
the same machine-readable next step (requiredAction: "wallet_sign_in",
authScheme: "SIWE_JWT", and authEntrypoints). Role-gated routes add
requiredRole.
Logout revokes the current token by writing its jti into a TTL-bounded
blacklist in the state store. Any subsequent request with that token returns
401 token_revoked. Blacklist entries auto-expire alongside the token's own
exp so Redis does not grow unbounded.
Public routes (no auth required): /, /health, /metrics,
/agent-tools.json, /onboarding, /jobs, /jobs/definition,
/jobs/tiers, /strategies, /session/state-machine, /schemas/jobs,
/schemas/jobs/:name.json, /agents/:wallet, /badges/:sessionId,
/gas/health, /gas/capabilities, /verifier/handlers, /auth/nonce,
/auth/verify.
AUTH_MODE=strict (production default) rejects unauthenticated requests on
protected routes with 401. AUTH_MODE=permissive (dev default) falls back to
the legacy ?wallet= query param and logs a warning β useful for local demos
until every caller has been migrated.
Environment variables (see mcp-server/.env.example):
AUTH_MODE β strict | permissive
AUTH_JWT_SECRETS β comma-separated HS256 secrets. First entry signs new
tokens; rest remain accepted during verification to support zero-downtime
rotation. Each must be β₯32 characters.
AUTH_DOMAIN β SIWE domain and expected verifier check.
AUTH_CHAIN_ID β SIWE chainId and expected verifier check.
AUTH_TOKEN_TTL_SECONDS (default 86400) β JWT lifetime.
AUTH_NONCE_TTL_SECONDS (default 300) β SIWE nonce lifetime.
AUTH_ADMIN_WALLETS β comma-separated EVM addresses granted the admin
role claim at sign-in. Required to call POST /admin/jobs.
AUTH_VERIFIER_WALLETS β comma-separated EVM addresses granted the
verifier role claim at sign-in. Required to call POST /verifier/run.
Roles are pinned at sign-in time by env config; a user picks up role claims
when they SIWE-login, so rotating the env lists invalidates authority at the
next sign-in rather than on every request. Each protected route that needs a
role checks the JWT claim and returns 403 missing_role on a mismatch.
On-chain escrow resolution is additionally gated by TreasuryPolicy.verifiers;
in the current backend-signer architecture, the configured chain signer is the
authorized verifier address.