A fully decentralized token ecosystem that runs entirely on GitHub. No servers, no wallets, no gas fees — just forks, pull requests, and consensus.
Live Balance Explorer:
- Root ledger: https://gitledger.github.io/gitcoin/ — Balances, Transactions, Validators tabs
- Your fork:
https://{your-github-username}.github.io/gitcoin/— see Fork Explorer Setup below
| Blockchain concept | GitCoin equivalent |
|---|---|
| Full node | Fork of this repository |
| Transaction | Pull Request with UTXO file changes |
| Block | Merge commit on main |
| Hash chain | Git commit history |
| Validator / miner | Any GitHub account with a registered public key in validators/pubkeys.json |
| Consensus (TX) | Sender posts /approve on their own PR |
| Consensus (other) | ⌈1/3⌉ of all validators post /approve |
| Double-spend guard | Git merge conflict (two PRs can't delete the same file) |
| UTXO integrity | Merkle inclusion proof (O(log n) per input) verified against ledger.json |
| Total supply | 4,294,967,295 GTC (fixed, no inflation) |
| Minimum unit | 1 GTC (integer amounts only) |
1. Pull the latest main branch
2. Run create_transaction.py
- Lists your UTXOs with numbered shortcuts
- Fetches current Merkle root from ledger.json (GitHub Pages)
- Builds and signs the TX, computes a Merkle inclusion proof per input
- Writes output UTXO files and git rm's inputs automatically
3. git add utxo/ && git commit && git push to a new branch, open a PR
4. validate-tx.yml:
a. Verifies Ed25519 signature and UTXO ownership
b. Verifies Merkle inclusion proof matches the canonical root in ledger.json
c. Verifies output txids are cryptographically derived from inputs (chain integrity)
5. On success: tx-valid label attached + sender prompted to post /approve
6. Sender comments /approve on the PR
7. PR is ready to merge
8. update-pages.yml rebuilds ledger.json (with new Merkle root) and deploys explorer
Non-TX PRs (key registration, code changes) require ⌈1/3⌉ of all validators to post
/approvebefore merging.
Each TX PR must include:
| Field | Description |
|---|---|
MERKLE_ROOT |
SHA-256 Merkle root of the entire UTXO set at PR creation time |
MERKLE_PROOF |
JSON map of txid → proof path — one inclusion proof per input UTXO |
create_transaction.py generates these automatically by fetching ledger.json from GitHub Pages.
Why this matters:
- The validator only needs to walk O(log n) hashes per input instead of scanning all UTXOs
- If another TX is merged while your PR is open, the root changes and your proof becomes invalid — you must regenerate the TX. This is by design: it prevents double-spending the same UTXO across two concurrent PRs.
- Past transactions are unaffected — already-merged UTXOs on
mainare settled and do not require re-validation.
pip install cryptographyYou also need:
- A GitHub account
- Git installed locally
ghCLI (optional): https://cli.github.com
Click Fork at the top of this page. Your fork is your full node — it contains the entire ledger history.
git clone https://github.com/YOUR_USERNAME/gitcoin
cd gitcoin
git remote add upstream https://github.com/gitledger/gitcoin.gitupstream is required for the auto-PR feature — it tells the script which repo to open the PR against.
python3 .github/scripts/generate_keypair.pyOutput:
⚠️ PRIVATE KEY — Keep this secret, never commit it:
<your-private-key-base64url>
✅ PUBLIC KEY — Share this in your REGISTER_KEY PR:
<your-public-key-base64url>
Store your private key in a password manager. If you lose it, you lose access to your coins. Never commit it to any repository.
Before you can send GTC, your public key must be in validators/pubkeys.json.
Create a PR to this repo's main branch with:
Changed file — add your entry to validators/pubkeys.json:
{
"existing_user": "their_key",
"YOUR_GITHUB_USERNAME": "YOUR_PUBLIC_KEY_BASE64URL"
}PR title: register: YOUR_GITHUB_USERNAME
PR body: anything (no TX_VERSION needed — the workflow detects this is not a TX and requires ⌈1/3⌉ of all validators to approve).
Once merged, you are automatically added to the validator pool.
To get your own explorer at https://{your-username}.github.io/gitcoin/:
- Settings → Pages → Source: set to GitHub Actions
- Settings → Actions → General → Workflow permissions: set to Read and write permissions
Once configured, the explorer auto-deploys whenever utxo/ or validators/ changes on main.
To add a badge to your fork's README, replace the badge URL at the top of this file:
[](https://YOUR_USERNAME.github.io/gitcoin/)Visit the live balance explorer:
https://<owner>.github.io/gitcoin/
Or inspect the ledger directly:
git pull origin main
cat docs/ledger.json # includes merkle_root and utxo_txidsRun a full, independent audit of the entire ledger state from the repository root:
git pull origin main
python3 .github/scripts/validate_full.pyThis checks:
- Every
utxo/*.jsonfile is well-formed and internally consistent - No txid appears more than once (no double-spend)
- Total supply across all UTXOs matches the published ledger snapshot on GitHub Pages (
docs/ledger.jsonin the Pages site; on a fresh clone the checked-out file onmainmay still be a placeholder, sovalidate_full.pyfalls back to the remote copy) - Every UTXO owner has a registered Ed25519 public key
- The Merkle root recomputed from all txids matches that same published ledger snapshot
- Every TX commit in git history conserves value and uses the correct txid derivation formula
See VALIDATION.md for detailed documentation on both full-chain and per-TX validation.
git pull origin mainpython3 .github/scripts/create_transaction.pyThe script will:
- Show your UTXOs as a numbered list — enter
1or1,2instead of full txids - Fetch the current Merkle root from GitHub Pages
- Build and sign the TX, compute inclusion proofs
- Write output UTXO files and
git rminput files automatically
The script writes .git/GITCOIN_TX_MSG with the full TX body as the commit message. Use it directly:
git add utxo/
git commit -F .git/GITCOIN_TX_MSG
git push origin <new-branch-name>After git push, the script will ask:
Auto-create PR now? (requires GH_TOKEN env var) [Y/n]:
- With token: PR is created automatically — works from forks too.
- Without token: open the PR manually at
https://github.com/gitledger/gitcoin/compare
To enable auto-PR, create a GH_TOKEN.txt file in the repo root with your PAT
(create a PAT with repo scope at https://github.com/settings/tokens):
echo ghp_yourtoken > GH_TOKEN.txtGH_TOKEN.txt is listed in .gitignore and will never be committed.
validate-tx.yml runs automatically. If valid:
tx-validlabel is attached- A comment asks you to post
/approveon the PR
Simply comment /approve on your own PR and it is ready to merge.
If another TX merges while your PR is open, the Merkle root changes and validation will fail with "Merkle root mismatch". Rerun
create_transaction.pyon the latestmainto regenerate a fresh proof.
Anyone with a registered public key in validators/pubkeys.json is a validator.
- Generate your keypair:
python3 .github/scripts/generate_keypair.py - Open a PR adding
"YOUR_USERNAME": "YOUR_PUBLIC_KEY"tovalidators/pubkeys.json - Once merged, you are immediately in the validator pool
Scores in validators/registry.json are metadata only. Anyone not listed defaults to 100 points.
| Action | Points |
|---|---|
Comment /approve on a non-TX PR |
+20 |
| Submitting a valid TX that gets merged | +5 |
Inactivity penalty: −10 points per week with no /approve activity.
pip install cryptographyYou also need:
- A GitHub account
- Git installed locally
ghCLI (optional, for convenience): https://cli.github.com
Click Fork at the top of this page. Your fork is your full node — it contains the entire ledger history.
git clone https://github.com/YOUR_USERNAME/gitcoin
cd gitcoin
git remote add upstream https://github.com/gitledger/gitcoin.gitupstream is required for the auto-PR feature — it tells the script which repo to open the PR against.
python3 .github/scripts/generate_keypair.pyOutput:
⚠️ PRIVATE KEY — Keep this secret, never commit it:
<your-private-key-base64url>
✅ PUBLIC KEY — Share this in your REGISTER_KEY PR:
<your-public-key-base64url>
Store your private key in a password manager. If you lose it, you lose access to your coins. Never commit it to any repository.
Before you can send GTC, your public key must be in validators/pubkeys.json.
Create a PR to this repo's main branch with:
Changed file — add your entry to validators/pubkeys.json:
{
"existing_user": "their_key",
"YOUR_GITHUB_USERNAME": "YOUR_PUBLIC_KEY_BASE64URL"
}PR title: register: YOUR_GITHUB_USERNAME
PR body: anything (no TX_VERSION needed — the workflow detects this is not a TX and requires ⌈1/3⌉ of all validators to approve).
Once a maintainer merges the PR, you can start transacting — and you are automatically added to the validator pool.
Visit the live balance explorer:
https://<owner>.github.io/gitcoin/
Or inspect the ledger directly:
git pull origin main
cat docs/ledger.jsonOr scan your UTXOs manually:
grep -rl '"owner": "YOUR_USERNAME"' utxo/git pull origin maingrep -rl '"owner": "YOUR_USERNAME"' utxo/Note the txid values (the filenames without .json).
python3 .github/scripts/create_transaction.pyYou will be prompted for:
- Your GitHub username
- Your private key
- Recipient username
- Amount to send (GTC)
- UTXO txids to spend (comma-separated)
- Optional memo
The script outputs the exact PR body to copy and the file changes to make. It can also write the output UTXO files automatically.
The script writes .git/GITCOIN_TX_MSG with the full TX body as the commit message:
git add utxo/
git commit -F .git/GITCOIN_TX_MSG
git push origin <new-branch-name>After git push, the script asks:
Auto-create PR now? (requires GH_TOKEN env var) [Y/n]:
- With token: PR is created automatically — works from forks too.
- Without token: open manually at
https://github.com/gitledger/gitcoin/compare
To enable auto-PR, create a GH_TOKEN.txt file in the repo root with your PAT
(create a PAT with repo scope at https://github.com/settings/tokens):
echo ghp_yourtoken > GH_TOKEN.txtGH_TOKEN.txt is listed in .gitignore and will never be committed.
validate-tx.yml runs automatically. If valid:
tx-validlabel is attached- A Validator Vote Requested comment is posted listing selected validators and deadline
- Selected validators comment
/approve— anyone invalidators/pubkeys.jsonis eligible - When ⌈2/3⌉ approvals are reached, merge the PR
Anyone with a registered public key in validators/pubkeys.json is a validator.
- Generate your keypair:
python3 .github/scripts/generate_keypair.py - Open a PR adding
"YOUR_USERNAME": "YOUR_PUBLIC_KEY"tovalidators/pubkeys.json - Once merged, you are immediately in the validator pool
Scores in validators/registry.json are metadata only. Anyone not listed defaults to 100 points.
| Action | Points |
|---|---|
Comment /approve on a non-TX PR |
+20 |
| Submitting a valid TX that gets merged | +5 |
Inactivity penalty: −10 points per week with no /approve activity.
When a code change or key registration PR is opened:
- You receive a GitHub @mention in the review comment.
- Review the PR changes.
- Comment exactly
/approveto cast your vote.
⌈1/3⌉ of all registered validators must approve before the PR can be merged.
TX_VERSION: 1
FROM: alice
TO: bob
AMOUNT: 50
INPUT_TXIDS: a1b2c3d4e5f6...,d4e5f6a1b2c3...
OUTPUT_TO_TXID: f7a8b9c0d1e2...
OUTPUT_CHANGE_TXID: e3d4c5b6a7f8...
MEMO: payment for work
SIGNATURE: <base64url Ed25519 signature>
| Field | Required | Description |
|---|---|---|
TX_VERSION |
✅ | Must be 1 |
FROM |
✅ | Must match PR author's GitHub login |
TO |
✅ | Recipient GitHub username |
AMOUNT |
✅ | Integer GTC to send (minimum: 1 GTC) |
INPUT_TXIDS |
✅ | Comma-separated txids of UTXOs you are spending |
OUTPUT_TO_TXID |
✅ | txid of new UTXO file added for the recipient |
OUTPUT_CHANGE_TXID |
if change | txid of change UTXO returned to you |
MEMO |
optional | Free-text note |
SIGNATURE |
✅ | Ed25519 signature over the canonical message |
Conservation rule: sum(inputs) == AMOUNT + change. No GTC can be created or destroyed.
Note: All amounts are integers. The minimum transactable unit is 1 GTC. Decimal amounts are not supported.
TX_VERSION: REGISTER_KEY
USERNAME: alice
PUBLIC_KEY: <base64url Ed25519 public key>
PR must only modify validators/pubkeys.json by adding one new entry.
Each file in utxo/ represents one unspent coin:
{
"txid": "a1b2c3d4...",
"owner": "alice",
"amount": 100,
"unit": "GTC",
"created_at_block": "<merge commit SHA>",
"created_at_height": 42
}The filename must match the txid field: utxo/<txid>.json.
Computing a txid:
import hashlib
txid = hashlib.sha256(f"{owner}{amount}{created_at_block}".encode()).hexdigest()Follow these steps once when deploying a new GitCoin instance.
Create a new public GitHub repository. Do not enable Branch Protection yet.
python3 .github/scripts/generate_keypair.pyimport hashlib
owner = "YOUR_GITHUB_USERNAME"
amount = 4294967295
block_hash = "0" * 64
txid = hashlib.sha256(f"{owner}{amount}{block_hash}".encode()).hexdigest()
print(txid)genesis/genesis.json — replace REPLACE_WITH_FOUNDER_USERNAME and REPLACE_WITH_GENESIS_TXID.
validators/pubkeys.json — replace placeholders with your username and public key.
validators/registry.json — replace REPLACE_WITH_FOUNDER_USERNAME with your username.
Create utxo/<genesis_txid>.json:
{
"txid": "<your computed genesis txid>",
"owner": "YOUR_GITHUB_USERNAME",
"amount": 4294967295,
"unit": "GTC",
"created_at_block": "0000000000000000000000000000000000000000000000000000000000000000",
"created_at_height": 0
}git add .
git commit -m "genesis: initialize GitCoin ledger"
git push origin mainIn your repository Settings → Pages:
- Set Source to GitHub Actions
In your repository Settings → Actions → General:
- Ensure GitHub Actions can request the permissions needed for GitHub Pages deployment (Pages/OIDC), rather than repository contents writes
Both settings are required: Pages provides the deployment target, and the workflow deploys the generated
docs/ledger.jsonvia the GitHub Pages artifact flow rather than committing or pushing it back to the repository.
In Settings → Branches → Add rule for main:
- Require status checks to pass before merging
- Add required check:
validate-tx / validate - Add required check:
consensus-check / passed
- Add required check:
- Require branches to be up to date before merging
- Include administrators ← CRITICAL: do not skip this
- Allow auto-merge
- Allow force pushes — leave unchecked
- Allow deletions — leave unchecked
After enabling "Include administrators", even you cannot merge without going through consensus. This is intentional — it is the foundation of the system's trustlessness.
In your repository Issues → Labels, create:
tx-valid(color:#2ea043)tx-invalid(color:#f85149)tx-expired(color:#8b949e)
| Property | How it is enforced |
|---|---|
| No stored bot keys | All workflows use only ephemeral GITHUB_TOKEN (auto-issued per run, expires on completion) |
| No admin bypass | Branch Protection includes administrators |
| No double-spend | Git merge conflict blocks the second PR deleting the same UTXO file |
| No code injection from PR | pull_request_target runs main branch code; the PR head branch is never checked out or executed |
| No shell injection from PR body | PR body is parsed as plain text by Python, never interpolated into shell commands |
| Signature forgery | Ed25519 signatures are verified against the registered public key for each sender |
| Sybil validators | Public key registration required; key must be merged into main via consensus |
.github/
├── workflows/
│ ├── validate-tx.yml pull_request_target → verify TX sig; on success posts validator
│ │ vote comment inline (no separate assign-validators trigger needed)
│ │ non-TX PRs get immediate success on both required checks
│ ├── consensus-check.yml issue_comment → count /approve from pubkeys.json validators
│ ├── expire-tx.yml schedule (6h) → close PRs past 48h deadline
│ └── update-pages.yml push to main (utxo/** or validators/**) → rebuild + deploy Pages
└── scripts/
├── validate_tx.py Core validation logic (TRANSFER + REGISTER_KEY)
├── update_ledger.py UTXO scanner → ledger.json with balances, TX history, validators
├── generate_keypair.py User tool: generate Ed25519 keypair
└── create_transaction.py User tool: build, sign, write files, git rm inputs automatically
utxo/ One JSON file per unspent coin
validators/
pubkeys.json Ed25519 public keys per GitHub username (validator pool)
registry.json Optional scoring metadata (score, last_active)
genesis/
genesis.json Genesis block metadata
docs/
index.html Explorer UI: Balances / Transactions / Validators tabs
ledger.json Snapshot rebuilt after every merge to main
If GitHub ever becomes unavailable, the entire ledger history is preserved in every fork's git log. The same workflow logic can be migrated to:
- GitLab (GitLab CI/CD)
- Gitea / Forgejo (Gitea Actions)
- Radicle (decentralized git hosting)
The UTXO files and commit history are the canonical truth. No data lives outside the repository.