Canopy is a Model Context Protocol (MCP) server that issues short-lived, verifiable capabilities linking an off-chain policy decision to a specific contract call. Each capability binds a txIntent to a callHash, so only compliant transactions reach the chain. It is not production ready. Do not look at me if it doesn't work, go make it better. I'm just here for the vibes.
- Smart-contract developers that need policy-guarded calls
- Backend engineers building relayers or wallets with pre-flight checks
- Security teams verifying off-chain compliance before execution
- Smart whitelisting without storing info on chain
- For compliance bro's, it's a good way to check KYC/ID etc without storing on chain.
- Express API exposing
GET /health/ping,POST /policy/evaluate,POST /capability/issue, andPOST /proof/verify - Loads policy logic from
POLICY_WASM_PATHand signs capabilities withISSUER_ECDSA_PRIVATE_KEY
- Shared TypeScript library providing the
TxIntenttype andcallHashhelper - Used by both the server and on-chain contracts
- Solidity
CanopyVerifierLib.sol, ERC-2771 forwarder, and venue examples - Built and tested with Foundry
examples.httprequest collection- Documentation and configuration samples
- Short-lived
EIP-712capabilities bound to call hashes - Pluggable policy evaluation via OPA Wasm or custom logic
- On-chain verification library and compliant forwarder
- MCP tooling for issuance and proof verification
- Node.js 20+ (
.nvmrcprovided) pnpmv10 (corepack enable&&corepack prepare pnpm@10.0.0 --activate)- Optional: Foundry for contract builds (
curl -L https://foundry.paradigm.xyz | bash && foundryup) - CLI tools:
curl,jq
pnpm install
pnpm -w -F @canopy/attest build
pnpm -w -F mcp-server dev- Build
txIntent(chainId, subject, target, value, selector, args, policyId). POST /policy/evaluate→{ decision, artifacts: { callHash, expiry, nonce, capabilitySig } }.- Attach proof to the on-chain call; the destination/validator verifies or pre-flights via
/proof/verify.
callHash = keccak256(abi.encode(
chainid, target, subject, selector, value, keccak256(args)
))Domain: { name:"Canopy", version:"1", chainId, verifyingContract: verifier }
Type: CompliantCall(subject, verifier, target, value, argsHash, policyId, expiry, nonce)
- Short TTL (e.g., 60s), single-use nonce, issuer allowlist
- Normalize inputs: checksummed
0xaddresses, 4-byte selector, raw args (no selector)
| Tool | Description | HTTP Endpoint |
|---|---|---|
health |
ping the server | GET /health/ping |
policy |
evaluate policy & issue capability | POST /policy/evaluate |
capability |
issue capability directly | POST /capability/issue |
proof |
verify a capability proof | POST /proof/verify |
POST /policy/evaluate HTTP/1.1
Content-Type: application/json
{
"txIntent": {
"chainId": 1,
"subject": "0x0000000000000000000000000000000000000001",
"target": "0x0000000000000000000000000000000000000002",
"value": "0x0",
"selector": "0xabcdef01",
"args": "0x",
"policyId": "0x42"
}
}{
"decision": "allow",
"artifacts": {
"callHash": "0x...",
"expiry": 0,
"nonce": "0x1",
"capabilitySig": "0x..."
}
}- Additional capability schemas like
EIP-3074 - Persistent storage for issued nonces
- More MCP tools for contract introspection
MIT