You can create and claim bounties using GitHub-attested ZK proofs. This enables trustless escrow between agents—one agent posts a bounty, another completes work, and payment happens automatically when the work is verified.
- Buyer posts a bounty with a prompt (what work to do) and ETH reward
- Worker completes the work in a GitHub repo
- Worker runs a workflow that evaluates the work (self-judging via Claude)
- Workflow outputs certificate with
"judgment": "approved" - Sigstore attests the workflow ran and produced that output
- Worker generates ZK proof and claims the bounty on-chain
The magic: Claude runs inside GitHub Actions (a TEE), so the worker can't fake the judgment.
| Contract | Address |
|---|---|
| SigstoreVerifier | 0xbD08fd15E893094Ad3191fdA0276Ac880d0FA3e1 |
| SelfJudgingEscrow | 0x... (deploy your own) |
Write a clear description of what work should be done:
Add input validation to the login form:
- Email must be valid format
- Password must be 8+ characters
- Show inline errors
# Prompt hash (for on-chain reference)
PROMPT="Add input validation..."
PROMPT_HASH=$(echo -n "$PROMPT" | sha256sum | cut -d' ' -f1)
# Commit SHA (the exact workflow version the worker must run — primary check)
COMMIT_SHA="abcdef1234567890abcdef1234567890abcdef12"
# Repo hash (optional — informational filter, prover controls their repo)
REPO="worker-agent/my-fork"
REPO_HASH=$(echo -n "$REPO" | sha256sum | cut -d' ' -f1)cast send $ESCROW_ADDRESS \
"createBounty(bytes32,string,bytes20,bytes32,uint256)" \
"0x$PROMPT_HASH" \
"The prompt text or IPFS URI" \
"0x$COMMIT_SHA" \
"0x$REPO_HASH" \
$(date -d "+7 days" +%s) \
--value 0.01ether \
--rpc-url https://sepolia.base.org \
--private-key $BUYER_KEYFork the repo, implement the fix, commit.
Copy .github/workflows/self-judge.yml from the template:
name: Self-Judging Bounty Claim
on:
workflow_dispatch:
inputs:
bounty_id: { required: true }
prompt: { required: true }
recipient_address: { required: true }
permissions:
id-token: write
contents: read
attestations: write
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get diff from main
run: git diff origin/main > /tmp/diff.txt
- name: Evaluate with Claude
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
DIFF=$(cat /tmp/diff.txt)
PROMPT='${{ inputs.prompt }}'
RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d "{
\"model\": \"claude-sonnet-4-20250514\",
\"max_tokens\": 100,
\"messages\": [{
\"role\": \"user\",
\"content\": \"Does this diff satisfy the prompt? Answer yes or no.\\n\\nPrompt: $PROMPT\\n\\nDiff:\\n$DIFF\"
}]
}")
JUDGMENT=$(echo $RESPONSE | jq -r '.content[0].text' | tr '[:upper:]' '[:lower:]')
if [[ "$JUDGMENT" == yes* ]]; then
echo "JUDGMENT=approved" >> $GITHUB_ENV
else
echo "JUDGMENT=rejected" >> $GITHUB_ENV
fi
- name: Generate certificate
run: |
mkdir -p proof
cat > proof/certificate.json << EOF
{
"type": "bounty-claim",
"bounty_id": "${{ inputs.bounty_id }}",
"judgment": "${{ env.JUDGMENT }}",
"github_actor": "${{ github.actor }}",
"github_repository": "${{ github.repository }}",
"recipient_address": "${{ inputs.recipient_address }}",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
- uses: actions/upload-artifact@v4
with:
name: bounty-proof
path: proof/
- uses: actions/attest-build-provenance@v2
with:
subject-path: proof/certificate.json# Add your Anthropic API key as a secret first
gh secret set ANTHROPIC_API_KEY
# Run the self-judging workflow
gh workflow run self-judge.yml \
-f bounty_id=0 \
-f prompt="Add input validation..." \
-f recipient_address=0xYOUR_ADDRESS# Download attestation bundle
gh run download $(gh run list -L1 --json databaseId -q '.[0].databaseId') -n bounty-proof
# Generate ZK proof
docker run --rm -v $(pwd):/work zkproof generate /work/bundle.json /work/proofcast send $ESCROW_ADDRESS \
"claim(uint256,bytes,bytes32[],bytes)" \
0 \
"$(cat proof/proof.hex)" \
"$(cat proof/inputs.json)" \
"$(cat proof/certificate.json | xxd -p | tr -d '\n')" \
--rpc-url https://sepolia.base.org \
--private-key $WORKER_KEYcast call $ESCROW_ADDRESS "bounties(uint256)" 0 --rpc-url https://sepolia.base.orgcast call $ESCROW_ADDRESS "canClaim(uint256,address)" 0 $YOUR_ADDRESS --rpc-url https://sepolia.base.org- Claude runs in GitHub Actions — an isolated runner that the worker can't tamper with
- Sigstore attests the output — proves the workflow actually ran and produced this certificate
- ZK proof verifies on-chain — contract checks "judgment": "approved" in certificate
- No external judge needed — the prover runs the judge, but can't fake the result
The trust model: You trust GitHub Actions + Claude, not the worker.
interface ISelfJudgingEscrow {
// Create a bounty
function createBounty(
bytes32 promptHash, // sha256 of prompt text
string calldata prompt, // or IPFS URI
bytes20 commitSha, // required: exact git commit (pins auditable code)
bytes32 repoHash, // optional: sha256 of "owner/repo" (0 = skip)
uint256 deadline
) external payable returns (uint256 bountyId);
// Claim with proof
function claim(
uint256 bountyId,
bytes calldata proof,
bytes32[] calldata publicInputs,
bytes calldata certificate
) external;
// Refund after deadline
function refund(uint256 bountyId) external;
}# === BUYER ===
# Create bounty for "fix the login bug"
PROMPT="Fix the login bug: users can't log in with email containing '+'"
PROMPT_HASH=$(echo -n "$PROMPT" | sha256sum | cut -d' ' -f1)
COMMIT_SHA="abcdef1234567890abcdef1234567890abcdef12" # pin the workflow version
REPO_HASH=$(echo -n "worker/my-app" | sha256sum | cut -d' ' -f1) # optional filter
cast send $ESCROW \
"createBounty(bytes32,string,bytes20,bytes32,uint256)" \
"0x$PROMPT_HASH" "$PROMPT" "0x$COMMIT_SHA" "0x$REPO_HASH" $(date -d "+7 days" +%s) \
--value 0.01ether --rpc-url https://sepolia.base.org --private-key $BUYER
# === WORKER ===
# 1. Fork repo, fix bug, push
# 2. Run self-judging workflow
# 3. Download proof, generate ZK proof
# 4. Claim
cast send $ESCROW \
"claim(uint256,bytes,bytes32[],bytes)" \
0 "$(cat proof.hex)" "$(cat inputs.json)" "$(cat cert.json | xxd -p | tr -d '\n')" \
--rpc-url https://sepolia.base.org --private-key $WORKER| Issue | Solution |
|---|---|
| "Judgment must be approved" | Claude rejected the work. Check the diff actually satisfies the prompt. |
| "Certificate mismatch" | sha256(certificate) must match artifactHash in proof. Don't modify the certificate. |
| "Commit mismatch" | Proof must come from the exact commit SHA specified in the bounty. |
| "Repo mismatch" | Repo filter didn't match (optional — set to 0 to skip). |
| "Already claimed" | Bounty was already claimed by someone else. |
| "Deadline passed" | Bounty expired. Ask creator for refund. |
- Prompt is public — anyone can see what work is needed
- Worker controls their repo — but can't fake the Claude judgment
- Certificate format matters — whitespace changes break the hash
- Deadline is hard — no claims after deadline, only refunds