Skip to content

Lay3rLabs/wavs-github-rewards

Repository files navigation

wavs-github-rewards

A WAVS demo that pays GitHub contributors on-chain. Every N blocks, a WASM component fetches activity from a configured repository, scores contributors, builds a Merkle tree of cumulative rewards, and submits the root on-chain. Contributors claim tokens directly with a Merkle proof — no intermediary, no manual distribution.

How It Works

  1. A contributor creates a public GitHub Gist containing wavs-github-rewards eth: 0x<their-address> and calls registerIdentity() on-chain
  2. The WAVS block-interval trigger fires every ~50,400 blocks (~7 days on mainnet, faster on testnets)
  3. The WASM component fetches merged PRs, closed issues, review comments, and new stars since lastSnapshotTimestamp
  4. Each contributor is scored, their GitHub Gist is verified to match the registered address, and new points are added to their cumulative total
  5. A Merkle tree is built from cumulative allocations, uploaded to IPFS, and the root is submitted on-chain via the WAVS aggregator
  6. Contributors call claim() with a Merkle proof to receive their tokens

Scoring weights (configurable in config/components.json):

Activity Points
PR merged 100
Issue resolved 50
Review comment 25
Issue filed 10
Star given 5

Each point is worth 0.001 ETH of reward tokens (tokens_per_point = 1e15 wei).

Contracts

Contract Description
GitHubIdentityRegistry Maps GitHub usernames → ETH addresses via public Gist verification
GitHubRewardToken ERC-20 backed by ETH (1:1 mint); used as the reward token
GitHubRewardDistributor Receives WAVS-signed Merkle roots; extends Morpho's UniversalRewardsDistributor for claims

Prerequisites

Setup

1. Install dependencies

npm install

2. Set environment variables

export WAVS_ENV_GITHUB_TOKEN=ghp_...          # GitHub PAT (repo:read)
export WAVS_ENV_PINATA_API_KEY=...            # Pinata API key for IPFS
export GITHUB_REPO_OWNER=Lay3rLabs            # Repository owner
export GITHUB_REPO_NAME=WAVS                  # Repository name
export FUNDED_KEY=0xac0974...                 # Deployer private key
export RPC_URL=http://localhost:8545           # RPC endpoint
export SERVICE_MANAGER_ADDR=0x...             # WAVS ServiceManager address

3. Deploy contracts

forge script script/Deploy.s.sol:DeployScript \
  --sig "run(string)" "$SERVICE_MANAGER_ADDR" \
  --rpc-url $RPC_URL --broadcast

This deploys GitHubIdentityRegistry, GitHubRewardToken, and GitHubRewardDistributor, seeds the distributor with 100 ETH of reward tokens, and writes addresses to .docker/github_rewards_deploy.json.

4. Build the WASM component

cargo component build --release -p github-rewards

Output: target/wasm32-wasip2/release/github_rewards.wasm

5. Deploy the WAVS service

wavs-cli service deploy \
  --config config/components.json \
  --wasm-dir target/wasm32-wasip2/release

The component will fire on the configured block interval and begin submitting snapshots.

Registering a GitHub Identity

Anyone who wants to receive rewards must link their GitHub username to an ETH address:

Step 1: Create a public GitHub Gist with any filename containing this line:

wavs-github-rewards eth: 0x<your-eth-address>

Step 2: Register on-chain (using cast or your preferred tool):

cast send $IDENTITY_REGISTRY_ADDR \
  "registerIdentity(string,string)" \
  "your-github-username" "your-gist-id" \
  --private-key $YOUR_KEY --rpc-url $RPC_URL

The Gist is verified on every snapshot cycle — if it's missing or points to a different address, that contributor is skipped for that period.

Claiming Rewards

After a snapshot is submitted, generate a Merkle proof from the IPFS snapshot and call:

cast send $DISTRIBUTOR_ADDR \
  "claim(address,address,uint256,bytes32[])" \
  "$YOUR_ADDRESS" "$REWARD_TOKEN" "$CLAIMABLE_AMOUNT" "$PROOF" \
  --private-key $YOUR_KEY --rpc-url $RPC_URL

The distributor uses UniversalRewardsDistributor's cumulative claim mechanism — CLAIMABLE_AMOUNT is your total earned across all snapshots. The contract tracks how much you've already claimed.

Running Tests

forge test

Configuration

All scoring weights, token economics, and the block interval are configured in config/components.json:

{
  "trigger": { "block_interval": { "blocks": 50400 } },
  "config": {
    "values": {
      "score_pr_merged": "100",
      "score_issue_resolved": "50",
      "score_review": "25",
      "score_issue_filed": "10",
      "score_star": "5",
      "tokens_per_point": "1000000000000000"
    }
  }
}

Architecture

Block trigger
     │
     ▼
WASM component
  ├─ Read lastSnapshotTimestamp from GitHubRewardDistributor
  ├─ Read lastIpfsHash → fetch previous snapshot from IPFS
  ├─ Fetch GitHub activity since last snapshot (PRs, issues, reviews, stars)
  ├─ Score contributors (skip unregistered or unverified identities)
  ├─ Merge new scores with previous cumulative totals
  ├─ Build Merkle tree over [address, token, amount] leaves
  ├─ Upload snapshot JSON to IPFS via Pinata
  └─ Return (merkleRoot, ipfsHash) to WAVS aggregator
     │
     ▼
WAVS aggregator collects operator signatures
     │
     ▼
GitHubRewardDistributor.handleSignedEnvelope()
  ├─ Validates aggregated signature via SERVICE_MANAGER
  ├─ Updates Merkle root (via Morpho UniversalRewardsDistributor)
  └─ Stores lastSnapshotTimestamp + lastIpfsHash
     │
     ▼
Contributors call claim() with Merkle proof

Related

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors