GitHub Integrations

Examples and code snippets for integrating GitForge with GitHub

GitHub Actions Workflows

Bounty Payout Automation

Automatically process bounty payments when PRs are merged:

.github/workflows/bounty-payout.yml
name: Bounty Payout

on:
  pull_request:
    types: [closed]

jobs:
  process-bounty:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      
      - name: Check for bounty label
        id: check-bounty
        uses: actions/github-script@v6
        with:
          script: |
            const issue = await github.rest.issues.get({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number
            });
            
            const hasBounty = issue.data.labels.some(
              label => label.name === 'bounty'
            );
            
            return hasBounty;
      
      - name: Extract bounty info
        if: steps.check-bounty.outputs.result == 'true'
        id: bounty-info
        uses: actions/github-script@v6
        with:
          script: |
            const issue = context.payload.pull_request;
            const body = issue.body;
            
            // Extract bounty amount and contributor wallet
            const amountMatch = body.match(/Bounty[:\s]+\$?(\d+)/i);
            const walletMatch = body.match(/Wallet[:\s]+(\w+)/i);
            
            return {
              amount: amountMatch ? amountMatch[1] : '0',
              wallet: walletMatch ? walletMatch[1] : '',
              contributor: issue.user.login
            };
      
      - name: Send payment notification
        if: steps.check-bounty.outputs.result == 'true'
        uses: actions/github-script@v6
        with:
          script: |
            const bountyInfo = ${{ steps.bounty-info.outputs.result }};
            
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              body: `🎉 Bounty approved! Payment of $${bountyInfo.amount} is being processed for @${bountyInfo.contributor}.`
            });
      
      # Add your actual payment processing here
      # Example: Call payment API, send crypto, etc.

Leaderboard Update

Automatically update contributor leaderboard:

.github/workflows/leaderboard.yml
name: Update Leaderboard

on:
  pull_request:
    types: [closed]
  schedule:
    - cron: '0 0 * * 0'  # Weekly on Sunday

jobs:
  update-leaderboard:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      
      - name: Fetch contributor stats
        id: stats
        uses: actions/github-script@v6
        with:
          script: |
            // Get all merged PRs
            const prs = await github.paginate(
              github.rest.pulls.list,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                state: 'closed',
                per_page: 100
              }
            );
            
            // Calculate stats
            const stats = {};
            for (const pr of prs) {
              if (!pr.merged_at) continue;
              
              const user = pr.user.login;
              if (!stats[user]) {
                stats[user] = { prs: 0, bounties: 0, total: 0 };
              }
              
              stats[user].prs++;
              
              // Check if bounty
              if (pr.labels.some(l => l.name === 'bounty')) {
                stats[user].bounties++;
                // Extract amount from PR body
                const match = pr.body?.match(/\$(\d+)/);
                if (match) {
                  stats[user].total += parseInt(match[1]);
                }
              }
            }
            
            return stats;
      
      - name: Update CONTRIBUTORS.md
        run: |
          cat > CONTRIBUTORS.md << 'EOF'
          # 🏆 Contributor Leaderboard
          
          ## Top Contributors This Month
          
          | Contributor | Merged PRs | Bounties | Total Earned |
          |-------------|------------|----------|--------------|
          EOF
          
          # Add contributor data (simplified)
          echo "| @octocat | 5 | 3 | \$1,500 |" >> CONTRIBUTORS.md
          
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          git add CONTRIBUTORS.md
          git commit -m "Update contributor leaderboard" || exit 0
          git push

Issue Template Examples

Bounty Issue Template

.github/ISSUE_TEMPLATE/bounty.yml
name: 💰 Bounty
description: Create a bounty for contributors
title: "[BOUNTY] "
labels: ["bounty"]
body:
  - type: dropdown
    id: level
    attributes:
      label: Bounty Level
      description: Select the difficulty level
      options:
        - 🟢 Beginner ($50-$200)
        - 🟡 Intermediate ($200-$500)
        - 🔴 Advanced ($500-$2000)
    validations:
      required: true
  
  - type: input
    id: amount
    attributes:
      label: Bounty Amount
      description: Specific amount in USD or USDC
      placeholder: "$150"
    validations:
      required: true
  
  - type: textarea
    id: description
    attributes:
      label: Description
      description: What needs to be done?
      placeholder: Detailed description of the task
    validations:
      required: true
  
  - type: textarea
    id: acceptance
    attributes:
      label: Acceptance Criteria
      description: What must be completed to earn the bounty?
      value: |
        - [ ] Criterion 1
        - [ ] Criterion 2
        - [ ] Tests added
        - [ ] Documentation updated
    validations:
      required: true
  
  - type: input
    id: skills
    attributes:
      label: Skills Required
      placeholder: "JavaScript, React, CSS"
    validations:
      required: true
  
  - type: input
    id: time
    attributes:
      label: Estimated Time
      placeholder: "2-3 days"
    validations:
      required: false
  
  - type: textarea
    id: resources
    attributes:
      label: Resources
      description: Links, documentation, or references
      placeholder: |
        - Design: [link]
        - Documentation: [link]
    validations:
      required: false

Proposal Template

.github/ISSUE_TEMPLATE/proposal.yml
name: 🗳️ Governance Proposal
description: Submit a proposal for community voting
title: "[PROPOSAL] "
labels: ["proposal", "governance"]
body:
  - type: dropdown
    id: type
    attributes:
      label: Proposal Type
      options:
        - Protocol Change
        - Treasury Management
        - Role Assignment
        - Feature Request
    validations:
      required: true
  
  - type: input
    id: discussion
    attributes:
      label: Discussion Link
      description: Link to prior discussion thread
      placeholder: "https://github.com/org/repo/discussions/123"
  
  - type: textarea
    id: summary
    attributes:
      label: Summary
      description: One paragraph summary
    validations:
      required: true
  
  - type: textarea
    id: problem
    attributes:
      label: Problem
      description: What problem does this solve?
    validations:
      required: true
  
  - type: textarea
    id: solution
    attributes:
      label: Proposed Solution
      description: Detailed explanation
    validations:
      required: true
  
  - type: textarea
    id: benefits
    attributes:
      label: Benefits
      value: |
        - Benefit 1
        - Benefit 2
    validations:
      required: true
  
  - type: textarea
    id: implementation
    attributes:
      label: Implementation Plan
      value: |
        1. Step 1
        2. Step 2
        3. Step 3
    validations:
      required: true
  
  - type: input
    id: budget
    attributes:
      label: Budget (if applicable)
      placeholder: "$5,000"
  
  - type: markdown
    attributes:
      value: |
        ---
        ## Voting Instructions
        - 👍 = Yes, approve this proposal
        - 👎 = No, reject this proposal
        - 🎉 = Abstain
        
        Voting period: 7 days from submission

GitHub API Examples

Fetching Bounty Issues

JavaScript/Node.js
const { Octokit } = require('@octokit/rest');

const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN
});

async function fetchBounties(owner, repo) {
  try {
    // Get all open bounty issues
    const { data: issues } = await octokit.rest.issues.listForRepo({
      owner,
      repo,
      state: 'open',
      labels: 'bounty',
      per_page: 100
    });
    
    // Parse bounty information
    const bounties = issues.map(issue => {
      const amountMatch = issue.body?.match(/\$(\d+)/);
      const levelLabel = issue.labels.find(l => 
        l.name.includes('beginner') || 
        l.name.includes('intermediate') || 
        l.name.includes('advanced')
      );
      
      return {
        number: issue.number,
        title: issue.title,
        amount: amountMatch ? parseInt(amountMatch[1]) : 0,
        level: levelLabel?.name || 'unknown',
        url: issue.html_url,
        created: issue.created_at,
        assignee: issue.assignee?.login || null
      };
    });
    
    return bounties;
  } catch (error) {
    console.error('Error fetching bounties:', error);
    throw error;
  }
}

// Usage
fetchBounties('asymcrypto', 'gitforge-template')
  .then(bounties => {
    console.log(`Found ${bounties.length} open bounties`);
    bounties.forEach(b => {
      console.log(`- ${b.title}: $${b.amount}`);
    });
  });

Tracking Contributor Stats

JavaScript/Node.js
async function getContributorStats(owner, repo, username) {
  try {
    // Get all PRs by user
    const { data: prs } = await octokit.rest.pulls.list({
      owner,
      repo,
      state: 'all',
      creator: username,
      per_page: 100
    });
    
    // Calculate statistics
    const stats = {
      username,
      totalPRs: prs.length,
      mergedPRs: 0,
      bountiesClaimed: 0,
      totalEarned: 0,
      reputation: 0
    };
    
    for (const pr of prs) {
      if (pr.merged_at) {
        stats.mergedPRs++;
        stats.reputation += 10; // 10 points per merged PR
        
        // Check if it was a bounty
        const isBounty = pr.labels.some(l => l.name === 'bounty');
        if (isBounty) {
          stats.bountiesClaimed++;
          stats.reputation += 50; // 50 points per bounty
          
          // Extract amount
          const match = pr.body?.match(/\$(\d+)/);
          if (match) {
            stats.totalEarned += parseInt(match[1]);
          }
        }
      }
    }
    
    // Determine tier
    if (stats.reputation >= 1000) {
      stats.tier = '👑 Core';
    } else if (stats.reputation >= 500) {
      stats.tier = '🏆 Expert';
    } else if (stats.reputation >= 100) {
      stats.tier = '🥈 Contributor';
    } else {
      stats.tier = '🌱 Newcomer';
    }
    
    return stats;
  } catch (error) {
    console.error('Error fetching contributor stats:', error);
    throw error;
  }
}

// Usage
getContributorStats('asymcrypto', 'gitforge-template', 'octocat')
  .then(stats => {
    console.log(`Stats for @${stats.username}:`);
    console.log(`Tier: ${stats.tier}`);
    console.log(`Merged PRs: ${stats.mergedPRs}`);
    console.log(`Bounties: ${stats.bountiesClaimed}`);
    console.log(`Total Earned: $${stats.totalEarned}`);
    console.log(`Reputation: ${stats.reputation}`);
  });

Webhook Integration

Setting Up Webhooks

Configure webhooks to receive real-time notifications:

  1. Go to repository Settings → Webhooks
  2. Click "Add webhook"
  3. Enter your payload URL
  4. Select events: Issues, Pull Requests, Discussions
  5. Save webhook

Webhook Handler Example

Node.js/Express
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Verify webhook signature
function verifySignature(req) {
  const signature = req.headers['x-hub-signature-256'];
  const payload = JSON.stringify(req.body);
  const secret = process.env.WEBHOOK_SECRET;
  
  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Webhook endpoint
app.post('/webhook', async (req, res) => {
  // Verify signature
  if (!verifySignature(req)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = req.headers['x-github-event'];
  const payload = req.body;
  
  try {
    switch (event) {
      case 'issues':
        await handleIssueEvent(payload);
        break;
      
      case 'pull_request':
        await handlePullRequestEvent(payload);
        break;
      
      case 'discussion':
        await handleDiscussionEvent(payload);
        break;
    }
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).send('Error processing webhook');
  }
});

async function handleIssueEvent(payload) {
  const { action, issue } = payload;
  
  // Check if it's a bounty
  const isBounty = issue.labels.some(l => l.name === 'bounty');
  
  if (action === 'opened' && isBounty) {
    console.log(`New bounty created: ${issue.title}`);
    // Send notification to Discord, Slack, etc.
    await notifyNewBounty(issue);
  }
}

async function handlePullRequestEvent(payload) {
  const { action, pull_request } = payload;
  
  if (action === 'closed' && pull_request.merged) {
    // Check if it closes a bounty
    const closesMatch = pull_request.body?.match(/Closes #(\d+)/i);
    if (closesMatch) {
      console.log(`Bounty PR merged: ${pull_request.title}`);
      await processBountyPayout(pull_request, closesMatch[1]);
    }
  }
}

async function notifyNewBounty(issue) {
  // Send to Discord
  const webhookUrl = process.env.DISCORD_WEBHOOK;
  await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      embeds: [{
        title: `💰 New Bounty: ${issue.title}`,
        description: issue.body.substring(0, 200),
        url: issue.html_url,
        color: 0x6366f1
      }]
    })
  });
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Notification Integrations

Discord Integration

Discord Webhook
async function sendDiscordNotification(type, data) {
  const webhookUrl = process.env.DISCORD_WEBHOOK;
  
  const embeds = {
    bounty: {
      title: `💰 New Bounty: ${data.title}`,
      description: data.description,
      color: 0x10b981,
      fields: [
        { name: 'Amount', value: `$${data.amount}`, inline: true },
        { name: 'Level', value: data.level, inline: true },
        { name: 'Link', value: `[View Bounty](${data.url})` }
      ]
    },
    merged: {
      title: `✅ Bounty Completed: ${data.title}`,
      description: `Congratulations @${data.contributor}!`,
      color: 0x6366f1,
      fields: [
        { name: 'Bounty', value: `$${data.amount}`, inline: true },
        { name: 'Contributor', value: data.contributor, inline: true }
      ]
    },
    proposal: {
      title: `🗳️ New Proposal: ${data.title}`,
      description: data.summary,
      color: 0xf59e0b,
      fields: [
        { name: 'Type', value: data.type, inline: true },
        { name: 'Voting Ends', value: data.deadline, inline: true },
        { name: 'Vote', value: `[Cast Your Vote](${data.url})` }
      ]
    }
  };
  
  await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ embeds: [embeds[type]] })
  });
}

Slack Integration

Slack Webhook
async function sendSlackNotification(type, data) {
  const webhookUrl = process.env.SLACK_WEBHOOK;
  
  const messages = {
    bounty: {
      text: `💰 New Bounty Available!`,
      blocks: [
        {
          type: 'header',
          text: { type: 'plain_text', text: data.title }
        },
        {
          type: 'section',
          fields: [
            { type: 'mrkdwn', text: `*Amount:*\n$${data.amount}` },
            { type: 'mrkdwn', text: `*Level:*\n${data.level}` }
          ]
        },
        {
          type: 'actions',
          elements: [{
            type: 'button',
            text: { type: 'plain_text', text: 'View Bounty' },
            url: data.url,
            style: 'primary'
          }]
        }
      ]
    }
  };
  
  await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(messages[type])
  });
}

Advanced Integrations

Crypto Payment Integration

Example using ethers.js for USDC payments:

USDC Payment
const { ethers } = require('ethers');

async function sendUSDCPayment(recipientAddress, amount) {
  // Connect to Ethereum provider
  const provider = new ethers.providers.JsonRpcProvider(
    process.env.RPC_URL
  );
  
  // Load wallet
  const wallet = new ethers.Wallet(
    process.env.PRIVATE_KEY,
    provider
  );
  
  // USDC contract address (Ethereum mainnet)
  const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
  
  // USDC ABI (simplified)
  const usdcABI = [
    'function transfer(address to, uint256 amount) returns (bool)'
  ];
  
  // Create contract instance
  const usdc = new ethers.Contract(usdcAddress, usdcABI, wallet);
  
  // USDC has 6 decimals
  const amountInUnits = ethers.utils.parseUnits(amount.toString(), 6);
  
  // Send transaction
  const tx = await usdc.transfer(recipientAddress, amountInUnits);
  console.log(`Transaction sent: ${tx.hash}`);
  
  // Wait for confirmation
  const receipt = await tx.wait();
  console.log(`Payment confirmed in block ${receipt.blockNumber}`);
  
  return {
    txHash: tx.hash,
    blockNumber: receipt.blockNumber
  };
}

// Usage
sendUSDCPayment('0x...', 150)
  .then(result => console.log('Payment successful:', result))
  .catch(err => console.error('Payment failed:', err));
Security Warning

Never hardcode private keys. Always use environment variables and secure secret management.