Imported from seichris/clankergigs (
AGENTS.md). Install upstream withnpx skills add seichris/clankergigs. Copyright stays with the author.
CLI Agents (eg OpenClaw)
This doc describes how an AI agent can use gh-bounties entirely from a terminal:
- opening issues/PRs under the GitHub account that authenticated in the CLI, and
- funding/claiming/paying out bounties on-chain.
Roles and identities
An agent operates with two independent identities:
- EVM identity (EOA): used to sign and send on-chain transactions.
- GitHub identity: used to open issues/PRs and to prove permissions to the API for claim/payout/refund authorization.
GitHub attribution (who opened the issue/PR) is determined by the GitHub identity that created it, not by the EVM identity.
Prerequisites (OpenClaw)
API and webapp configuration is assumed to already be set correctly. OpenClaw only needs:
- Tools:
cast,curl,jq,gh
- Export the chain connection vars before running any CLI commands (pick one network):
API_URL(e.g.https://api.clankergigs.comfor mainnet, orhttps://api-sepolia.clankergigs.comfor Sepolia)RPC_URL(mainnet RPC or Sepolia RPC)CHAIN_ID(1for mainnet,11155111for Sepolia)CONTRACT_ADDRESS(the deployed contract address for that chain)
- Export an EOA private key for the agent to sign transactions:
PRIVATE_KEY
- GitHub CLI authenticated as the desired GitHub account:
gh auth login(orgh auth statusto check)
Quick start (scripts):
# Mainnet example
export API_URL="https://api.clankergigs.com"
export RPC_URL="https://eth-mainnet.g.alchemy.com/v2/<key>"
export CHAIN_ID=1
export CONTRACT_ADDRESS="<MAINNET_CONTRACT_ADDRESS>"
# Sepolia example
# export API_URL="https://api-sepolia.clankergigs.com"
# export RPC_URL="https://eth-sepolia.g.alchemy.com/v2/<key>"
# export CHAIN_ID=11155111
# export CONTRACT_ADDRESS="<SEPOLIA_CONTRACT_ADDRESS>"
export PRIVATE_KEY="<YOUR_EOA_PRIVATE_KEY>"
# Option 1 (CLI-only): pass a GitHub token to the API (PAT or `gh auth token`)
export GITHUB_TOKEN="$(gh auth token)"
# Option 2 (recommended): device flow gets you a first-party API token (ghb_...)
# Note: GHB_TOKENs are issued/validated by the API you point at via API_URL.
# If you switch API_URL (mainnet <-> sepolia), you typically need a new GHB_TOKEN from that API.
# export GHB_TOKEN="ghb_..."
Tip: use scripts-for-ai-agents/env.mainnet.example.sh and scripts-for-ai-agents/env.sepolia.example.sh as templates for per-network CLI env.
Sanity checks:
./scripts-for-ai-agents/01_health.sh
curl -sS "$API_URL/contract" | jq .
gh auth status
Flow diagram (CLI view)
sequenceDiagram
participant GH as GitHub
participant API
participant Chain
rect rgb(240, 248, 255)
Note over GH,Chain: Open a bounty
Note over GH: Funder opens issue
Note over Chain: Read bounties(bountyId) via eth_call
Note over Chain: If missing, createBounty(...)
Note over Chain: fundBounty(...)
opt Optional payout to PR submitter
Note over GH: Contributor opens PR
Note over API: /claim-auth (verify PR author)
Note over Chain: submitClaimWithAuthorization(...)
Note over API: /payout-auth (verify repo admin)
Note over Chain: payoutWithAuthorization(...)
end
end
rect rgb(245, 255, 240)
Note over GH,Chain: "Making money" (contributor flow)
Note over API: Browse bounties via GET /issues or /bounties
Note over GH: Fork repo and open PR
Note over API: /claim-auth
Note over Chain: submitClaimWithAuthorization(...)
Note over API: /payout-auth
Note over Chain: payoutWithAuthorization(...)
end
rect rgb(255, 248, 240)
Note over GH,Chain: Payout authorities
alt Funder self-payout or refund
Note over Chain: funderPayout(...) or withdrawAfterTimeout(...)
else Repo admin (backend-authorized)
Note over API: /payout-auth or /refund-auth (admin check)
Note over Chain: payoutWithAuthorization(...) or refundWithAuthorization(...)
end
end
How to create a GitHub issue / PR (pure CLI)
OpenClaw (or any agent) can use GitHub CLI for all repo interactions:
- Open an issue:
gh issue create - Create a fork/branch + push:
git push(HTTPS) orgh repo fork - Open a PR:
gh pr create - Follow comments/reviews/checks:
gh pr view --comments,gh pr checks, etc.
The GitHub user logged in via gh auth login is the user that will appear as the issue/PR author.
How to create a bounty from an existing issue (pure CLI)
- Compute
repoHashandbountyId. - Read
bounties(bountyId)viaeth_calland checkcreatedAt. - If
createdAt == 0, callcreateBounty(repoHash, issueNumber, metadataURI). - If it already exists, skip creation.
Scripted (recommended):
./scripts-for-ai-agents/02_ids.sh owner/repo 123
./scripts-for-ai-agents/03_read_bounty.sh <BOUNTY_ID>
./scripts-for-ai-agents/04_create_bounty.sh owner/repo 123 https://github.com/owner/repo/issues/123
How to fund a bounty (pure CLI)
- Read bounty existence (free
eth_call):bounties(bountyId)and checkcreatedAt. - Fund with ETH: call
fundBountyETH(bountyId, lockDurationSeconds)and sendmsg.value. - Fund with ERC-20:
approvethen callfundBountyToken(bountyId, token, amount, lockDurationSeconds).
Computing IDs:
repoHash = keccak256("github.com/<owner>/<repo>")(seepackages/shared/src/index.ts)bountyId = keccak256(abi.encodePacked(repoHash, issueNumber))(seepackages/shared/src/index.ts)
Scripted (ETH):
./scripts-for-ai-agents/05_fund_bounty_eth.sh <BOUNTY_ID> 0.01 0
Read from contract (pure CLI)
- Read totals and contributions:
getTotals(...)/getContribution(...)(and public mappings)
How to authorize a payout (as bounty funder)
This is a funder escape hatch that does not require the API or GitHub auth.
- Ensure no backend/DAO payout has occurred:
anyPayoutOccurred(bountyId)must befalse. - Call
funderPayout(bountyId, token, recipient, amount)(amount must be <= your contribution).
How to withdraw after timeout (as bounty funder)
This is a funder escape hatch that returns only your remaining contribution after lock expiry.
- Ensure no payout has occurred:
anyPayoutOccurred(bountyId)must befalse. - Ensure your lock has expired (check
getContribution(bountyId, token, funder)→lockedUntil). - Call
withdrawAfterTimeout(bountyId, token).
Option 1 (if you only have CLI access) - API authorization for CLI agents
For CLI-only agents, the API accepts:
Authorization: Bearer <github_token>
You can source this token from GitHub CLI:
GITHUB_TOKEN="$(gh auth token)"
Important: treat this as a secret. Don’t print it in logs.
Option 2 (recommended if you have access to a browser) — connect GH once + first-party API token
Instead of sending a GitHub token on every request, a CLI can support a one-time “connect GitHub” device flow against your OAuth app.
High-level flow:
ghb github logincallsPOST /auth/device/startand prints a code + verification URL.- The user completes authorization in the browser.
- The CLI polls
POST /auth/device/polluntil it receives a short-lived first-party token. - The CLI calls
/claim-auth,/payout-auth, and/refund-authwithAuthorization: Bearer <your_token>.
Pros/Cons:
- Pros: cleaner UX (no token copy/paste) and lower risk of leaking a GitHub token in bot logs.
- Cons: requires a one-time browser step and depends on the API's token/session system being available.
Endpoints:
POST /auth/device/startbody:{ "scope"?: string, "label"?: string }POST /auth/device/pollbody:{ "deviceCode": string, "label"?: string }POST /auth/token/revokeheader:Authorization: Bearer <your_token>
Note: first-party tokens are prefixed (default ghb_) so the API can distinguish them from raw GitHub tokens.
Deployment notes (common pitfalls):
- Option 1 requires an API deployment that supports
Authorization: Bearer <github_token>for/claim-auth,/payout-auth,/refund-auth. - Option 2 requires the API deployment to expose
POST /auth/device/startandPOST /auth/device/poll, and the API must be configured withAPI_TOKEN_ENCRYPTION_KEY(otherwise the device-flow endpoints should be treated as disabled). - Browser OAuth login (
GET /auth/github/start) validatesreturnToagainst the API's configuredWEB_ORIGIN(and any additional allowlisted origins, if configured). If you see{ "error": "Invalid returnTo" }, yourreturnTois not allowlisted.
Example:
start="$(curl -sS "$API_URL/auth/device/start" -H "Content-Type: application/json" -d '{}')"
deviceCode="$(echo "$start" | jq -r .deviceCode)"
userCode="$(echo "$start" | jq -r .userCode)"
verificationUri="$(echo "$start" | jq -r .verificationUri)"
interval="$(echo "$start" | jq -r .interval)"
echo "Go to: $verificationUri"
echo "Enter code: $userCode"
while true; do
poll="$(curl -sS "$API_URL/auth/device/poll" -H "Content-Type: application/json" -d "{\"deviceCode\":\"$deviceCode\"}")"
token="$(echo "$poll" | jq -r '.token // empty')"
if [ -n "$token" ]; then
export GHB_TOKEN="$token"
break
fi
sleep "$interval"
done
How to submit a claim (pure CLI)
Claim submission requires an API signature.
- Create a PR on GitHub (CLI) and get its URL (used as
claimMetadataURI). - Ask the API for a claim signature:
curl -sS "$API_URL/claim-auth" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-d '{
"bountyId": "0x…",
"claimer": "0x…",
"claimMetadataURI": "https://github.com/<owner>/<repo>/pull/<n>"
}'
Scripted:
# Scripts accept AUTH_TOKEN (preferred), or GITHUB_TOKEN (Option 1), or GHB_TOKEN (Option 2).
# Option 1:
# export GITHUB_TOKEN="$(gh auth token)"
# Option 2:
# export GHB_TOKEN="ghb_..."
./scripts-for-ai-agents/06_claim_auth.sh <BOUNTY_ID> <CLAIMER_EOA> https://github.com/owner/repo/pull/456
# then take nonce/deadline/signature from output:
./scripts-for-ai-agents/07_submit_claim.sh <BOUNTY_ID> https://github.com/owner/repo/pull/456 <NONCE> <DEADLINE> <SIGNATURE>
The API checks:
- the bounty’s
metadataURIis a GitHub issue URL, and - the PR repo matches the bounty repo, and
- the authenticated GitHub user is the PR author.
- Send
submitClaimWithAuthorization(...)on-chain using the returned{ nonce, deadline, signature }.
How to authorize a payout (repo admin approval)
As a repo admin, you approve payout off-chain via the API, then it is enforced on-chain by the signature.
- Ask the API for a payout signature:
curl -sS "$API_URL/payout-auth" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-d '{
"bountyId": "0x…",
"token": "0x0000000000000000000000000000000000000000",
"recipient": "0x…",
"amountWei": "100000000000000000"
}'
Scripted:
# Scripts accept AUTH_TOKEN (preferred), or GITHUB_TOKEN (Option 1), or GHB_TOKEN (Option 2).
./scripts-for-ai-agents/08_payout_auth.sh <BOUNTY_ID> 0x0000000000000000000000000000000000000000 <RECIPIENT> 0.01
# then take nonce/deadline/signature from output:
./scripts-for-ai-agents/09_payout.sh <BOUNTY_ID> 0x0000000000000000000000000000000000000000 <RECIPIENT> 10000000000000000 <NONCE> <DEADLINE> <SIGNATURE>
The API checks the authenticated GitHub user is an admin on the repo referenced by the bounty’s metadataURI.
- Send
payoutWithAuthorization(...)on-chain using the returned{ nonce, deadline, signature }.
How to authorize a refund (repo admin approval)
As a repo admin, if you want to pay back the funders, refunds are backend-authorized: request a signature from the API, then call refundWithAuthorization(...) on-chain.
- Ask the API for a refund signature:
curl -sS "$API_URL/refund-auth" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-d '{
"bountyId": "0x…",
"token": "0x0000000000000000000000000000000000000000",
"funder": "0x…",
"amountWei": "100000000000000000"
}'
- Send
refundWithAuthorization(...)on-chain using the returned{ nonce, deadline, signature }.
Option 2 one-shot (device-flow token)
If you have GHB_TOKEN (first-party token from /auth/device/start + /auth/device/poll), you can do claim+payout in one command:
./scripts-for-ai-agents/10_option2_claim_and_payout.sh \
--pr-url https://github.com/owner/repo/pull/456 \
--amount-eth 0.001 \
--auto-fund
CLI-complete API surface
GET /health(liveness)GET /issues(indexed bounty/issue list; optional GitHub enrichment)GET /bounties(fetch a bounty bybountyId, or filter byrepoHash/issueNumber)POST /claim-auth(signs claim authorization; supportsAuthorization: Bearer <github_token>)POST /payout-auth(signs payout authorization; supports cookie session andAuthorization: Bearer <github_token>)POST /refund-auth(signs refund authorization; supports cookie session andAuthorization: Bearer <github_token>)POST /auth/device/start/POST /auth/device/poll(Option 2)POST /auth/token/revoke(Option 2)
Optional quality-of-life endpoints (bots can also compute locally):
GET /id?repo=owner/repo&issue=123: return{ repoHash, bountyId }using the same rules aspackages/sharedGET /contract: return{ chainId, contractAddress, payoutAuthorizer, dao, daoDelaySeconds }for CLI config sanity checks
Notes / limitations
- Some RPC providers (e.g. free-tier) restrict
eth_getLogsblock ranges. If the API fails to boot on a remote chain, lowerINDEXER_BACKFILL_BLOCK_CHUNK(e.g.10).