Every merged pull request gets paid, instantly and without a middleman.
Live app · See it work · How it works · Why Arc · Quick start · Trust model · Run locally
Open source runs the world, and the people who build it mostly work for free. Bounty platforms try to fix that, but each one puts a person or a server in the middle to check the merge, decide who wrote it and send the money.
MergePay removes the middle. Anyone escrows USDC on a GitHub issue. When a pull request that fixes it is merged, GitHub itself signs a token saying who merged what, and a contract on Arc verifies GitHub's RSA signature on-chain before paying the author. The whole trip from merge to paid takes about 10 seconds.
This happened on Arc mainnet with a real GitHub token. Issue #1 was funded and claimed, PR #2 merged at 07:56:18, and this comment appeared 10 seconds later:
- Funding tx:
0x7ad3…8d98 - Award tx, with GitHub's signature verified on-chain:
0x5c78…c103 - Payout tx, delivered when the wallet was linked:
0x24bb…4eb0
More bounties are open right now in the app, including two on tastemaker.
- Fund any public issue with USDC from any wallet. It sits in the contract, and each funder can take their share back if nothing merges before the deadline.
/claiman issue so only one contributor's PR can be paid. A claim with no activity for 7 days is released automatically.- Paid seconds after the merge, with a comment on the PR linking the transaction.
- One-click wallet link. Connect a wallet, press Link, and GitHub signs the proof. Contributors never need USDC for gas.
- Built for coding agents. If a bot account opens the PR, the human it's assigned to gets paid. Every setup step has a "Copy for agent" prompt for Claude Code or Codex.
- A personal dashboard after signing in with GitHub, showing USDC earned, claims, a payout pipeline and a month of merged work.
- A live activity feed read from contract events, not from a database anyone can edit.
sequenceDiagram
autonumber
actor Sponsor
actor Dev as Contributor
participant GH as GitHub
participant WF as Pinned workflow
participant R as Relayer
participant C as MergePay on Arc
Sponsor->>C: fund(repo, issue) with USDC
Dev->>GH: comment /claim on the issue
GH->>WF: assign the claimant
Dev->>GH: open PR "Fixes #N"
GH->>WF: PR merged (pull_request_target)
WF->>GH: request OIDC token, aud = contract:issue:claimant
GH-->>WF: RS256-signed JWT
WF->>R: hand over the token
R->>C: award(token), relayer only pays gas
C->>C: verify GitHub's signature with modexp, check every claim
C-->>Dev: USDC to the linked wallet
WF->>GH: comment on the PR with the tx link
- Fund.
fund(repoId, issue, repoName, workflowRef, expiry, relayerFee)escrows native USDC. Bounties are keyed by GitHub's numericrepository_id, which survives renames.workflowRefpins the exact workflow version allowed to award the bounty. - Claim.
claims.ymlhandles/claimand/unclaim, allows one claim per person per repo, warns PRs from non-claimants, and releases claims idle for 7 days. A claim is simply the issue's assignee, so maintainers can also assign by hand. - Award. On merge,
award.ymlrequests a GitHub OIDC token only for the issue's claimant.MergePay.solchecks the RS256 signature against GitHub's registered key, then checksiss,exp,event_name,repository_id, the pinnedjob_workflow_ref, and an audience that binds this contract, the issue and the recipient. - Link. A contributor proves "GitHub user #id controls wallet 0x…" through a signed token from a
workflow_dispatchrun in a repo they own. The contract requiresactor_id == repository_owner_id, so nobody else's repo can speak for them, and a neweriatso an old link can't be replayed. The app does this in one click.
The relayer (app/src/worker.ts) runs on a Cloudflare Worker at mergepay.codeswithroh.workers.dev, which is the URL installed workflows post to. It splits the JWT, simulates the call and pays gas. It can't change who gets paid, because every field that matters is inside GitHub's signature. Anyone can run one.
| Arc property | What it gives MergePay |
|---|---|
| USDC is the gas token | The bounty, the relayer fee and the payout are one asset. A contributor with an empty wallet still gets paid. |
| Sub-second finality | The "paid" comment on the PR is a final receipt, not a pending transaction. |
modexp and sha256 precompiles |
Verifying GitHub's 2048-bit RSA signature costs about 660,000 gas, roughly 1.3 cents, so every payout can be checked. |
| Fees priced in dollars | Sponsors set a flat relayer fee in USDC, 3 cents by default, and never think about gas. |
- Open the Fund tab and paste an issue URL.
- If the repo doesn't have MergePay yet, press Add to repo or Copy for agent. It's one workflow file:
# .github/workflows/mergepay.yml, full version in examples/mergepay.yml jobs: award: uses: codeswithroh/mergepay/.github/workflows/award.yml@v1 claims: uses: codeswithroh/mergepay/.github/workflows/claims.yml@v1
- Escrow USDC. Contributors comment
/claim, you merge as usual, and payouts happen on their own. - Public repos, from Nov 2, 2026: GitHub blocks
pull_request_targetunless the repo has an Actions event policy that allows it (details). Allow it formergepay.ymlonce, under Settings → Actions → Policies, or with:gh api -X POST repos/OWNER/REPO/actions/policies --input - <<'JSON' {"name":"MergePay payouts","enforcement":"active", "conditions":{"workflow_path":{"include":[".github/workflows/mergepay.yml"],"exclude":[]}}, "rules":[{"type":"restrict_action_events","parameters":{"allowed_events":["pull_request_target","issue_comment","schedule","workflow_dispatch"]}}]} JSON
- Find a bounty on the Bounties tab and comment
/claimon the issue. - Open a PR that says
Fixes #N. If a coding agent opens it from a bot account, assign the PR to yourself. - Link a wallet once, before or after the merge. Awards wait in the contract for 180 days.
- GitHub's signing keys. The keys registered at deploy are in the constructor calldata, and anyone can check them against GitHub's JWKS. A new key has to be announced with
proposeSigningKeyand waitsKEY_DELAY(3 days) before anyone can activate it. While a key change is pending, funders can refund open bounties early. Revoking a key is immediate, since it can't move funds. - Maintainers' merge decisions. Whoever can merge decides whose fix is accepted, as in any open source project. The pinned workflow keeps how the recipient is chosen public and fixed.
- Nothing else. The relayer only pays gas. The website's GitHub sign-in only controls who sees a dashboard, and the one-click link only presses buttons on GitHub for you. GitHub does the vouching.
- Claim parsing.
JsonClaims.solmatches the literal"key":". Quotes inside a JSON string are always escaped, so the pattern can't match inside a value, and values containing\are rejected. A test covers a branch name that tries to smuggle a secondaud. - Algorithm confusion. Only
RS256is accepted, with RSA moduli and e = 65537. pull_request_target. The award workflow never checks out PR code, and PR text only reaches the job throughenv:.- Agents. The payout address never comes from PR text, so a prompt-injected agent can't redirect money.
- Unclaimed awards. After 180 days without a linked wallet,
returnUnclaimedsends the payout back to funders, split by what each put in. - Tests. 30 Foundry tests cover forged, tampered, replayed and mis-addressed tokens, the key time-lock, claims and refunds.
- Contracts. Solidity 0.8.30 and Foundry, with hand-written Base64url, JSON-claim and RSA-PKCS#1 libraries on Arc's precompiles.
- Workflows. Reusable GitHub Actions workflows using GitHub's OIDC tokens.
- App and relayer. One Cloudflare Worker relays proofs, indexes events into KV and handles GitHub sign-in. mergepay.fun is served by Vercel, which proxies
/apiand/authto that Worker. The frontend is plain HTML and viem with no build step.
contracts/ MergePay.sol, Base64Url / JsonClaims / RsaSha256 libs, 30 tests, deploy scripts
.github/ reusable award.yml, claims.yml and link.yml workflows
examples/ the files maintainers and contributors copy
app/ Cloudflare Worker: relayer API, event indexer, GitHub sign-in, web app
brand/ logo
docs/ screenshots
# contracts
cd contracts && forge test -vv # 30 tests
node scripts/gen-fixtures.mjs # regenerate the signed test tokens
# local end-to-end: anvil + worker + GitHub-shaped tokens through the relayer
anvil --chain-id 5042 &
cd app && npm i && node scripts/e2e-local.mjs # deploys, prints the next command
npx wrangler dev --var CONTRACT:0x... --var RPC_URL:http://127.0.0.1:8545
CONTRACT=0x... node scripts/e2e-local.mjs
# mainnet
cd contracts && node scripts/fetch-jwks.mjs
OWNER=0x... forge script script/Deploy.s.sol --tc Deploy --rpc-url arc --broadcast --account <keystore>
cd ../app && npx wrangler secret put RELAYER_KEY && npx wrangler deploy- On-chain verification of GitHub OIDC tokens on Arc mainnet
- Claims with
/claim, a 7-day inactivity release, and returns after 180 days - Payouts for PRs opened by coding-agent bots
- Sign in with GitHub and one-click wallet linking
- Time-locked multisig as contract owner
- Embeddable README badge showing a repo's open bounties (#3, funded)
- Passkey wallets created at link time, so contributors never see a seed phrase
- GitLab support, which issues the same kind of signed CI tokens
MergePay pays for its own issues. Pick a funded one in the app, comment /claim, and open a PR that says Fixes #N. See CONTRIBUTING.md for the rest.






