Skip to content

[BOUNTY #476] Button component with loading spinners - Resubmit (scope fix) #3110

[BOUNTY #476] Button component with loading spinners - Resubmit (scope fix)

[BOUNTY #476] Button component with loading spinners - Resubmit (scope fix) #3110

Workflow file for this run

name: Wallet Check
on:
# When PR is opened or body is edited
pull_request_target:
types: [opened, edited]
# When someone comments on a PR (might paste wallet there)
issue_comment:
types: [created]
permissions:
pull-requests: write
issues: write
jobs:
check-wallet:
# For issue_comment: only run on PR comments, skip bot comments
if: |
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request &&
github.event.comment.user.login != 'github-actions[bot]' &&
github.event.comment.user.login != 'chronoeth-creator')
runs-on: ubuntu-latest
concurrency:
group: wallet-check-${{ github.event.pull_request.number || github.event.issue.number }}
steps:
- name: Check for Solana wallet
env:
GH_TOKEN: ${{ secrets.SOLFOUNDRY_GITHUB_PAT }}
TELEGRAM_BOT_TOKEN: ${{ secrets.SOLFOUNDRY_TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.SOLFOUNDRY_TELEGRAM_CHAT_ID }}
run: |
python3 << 'PYEOF'
import os, json, re, urllib.request
token = os.environ.get("GH_TOKEN", "")
tg_token = os.environ.get("TELEGRAM_BOT_TOKEN", "")
tg_chat = os.environ.get("TELEGRAM_CHAT_ID", "")
event_path = os.environ.get("GITHUB_EVENT_PATH", "")
event_name = os.environ.get("GITHUB_EVENT_NAME", "")
repo = os.environ.get("GITHUB_REPOSITORY", "SolFoundry/solfoundry")
with open(event_path) as f:
event = json.load(f)
def gh_api(path, method="GET", data=None):
url = f"https://api.github.com/{path}"
body = json.dumps(data).encode() if data else None
req = urllib.request.Request(url, data=body, method=method)
req.add_header("Authorization", f"token {token}")
req.add_header("Accept", "application/vnd.github.v3+json")
if data:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req) as resp:
raw = resp.read()
if resp.status == 204 or not raw:
return None, resp.status
return json.loads(raw), resp.status
except urllib.error.HTTPError as e:
return None, e.code
def send_telegram_with_button(msg, button_text, button_url):
"""Send Telegram message with an inline keyboard button."""
if not tg_token or not tg_chat:
return
url = f"https://api.telegram.org/bot{tg_token}/sendMessage"
payload = {
"chat_id": tg_chat,
"text": msg,
"parse_mode": "HTML",
"disable_web_page_preview": True,
"reply_markup": json.dumps({
"inline_keyboard": [[
{"text": button_text, "url": button_url}
]]
})
}
data = json.dumps(payload).encode()
req = urllib.request.Request(url, data=data, method="POST")
req.add_header("Content-Type", "application/json")
try:
urllib.request.urlopen(req)
except Exception as e:
print(f"Telegram failed: {e}")
def find_wallet(text):
"""Find a Solana wallet address (43-44 base58 chars)."""
matches = re.findall(r'[1-9A-HJ-NP-Za-km-z]{43,44}', text or "")
return matches[0] if matches else None
# ── Determine PR number and what text to check ──
pr_number = None
pr_author = None
comment_text = ""
if event_name == "pull_request_target":
pr = event.get("pull_request", {})
pr_number = pr.get("number")
pr_author = pr.get("user", {}).get("login", "unknown")
elif event_name == "issue_comment":
issue = event.get("issue", {})
comment = event.get("comment", {})
pr_number = issue.get("number")
pr_author = issue.get("user", {}).get("login", "unknown")
comment_text = comment.get("body", "") or ""
if not pr_number:
print("No PR number, exiting")
exit(0)
# ── Get current PR body ──
pr_detail, _ = gh_api(f"repos/{repo}/pulls/{pr_number}")
if not pr_detail:
print(f"Could not fetch PR #{pr_number}")
exit(0)
# Skip merged or closed PRs — contributors can edit after merge without triggering bots
pr_state = pr_detail.get("state", "")
pr_merged = pr_detail.get("merged", False)
if pr_merged or pr_state == "closed":
print(f"PR #{pr_number} is {('merged' if pr_merged else 'closed')} — skipping wallet check")
exit(0)
pr_body = pr_detail.get("body", "") or ""
pr_author = pr_detail.get("user", {}).get("login", pr_author)
pr_url = pr_detail.get("html_url", f"https://github.com/{repo}/pull/{pr_number}")
# ── Check for wallet in PR body and comment ──
# SECURITY: Only accept wallet from PR author (prevent wallet injection)
wallet_in_body = find_wallet(pr_body)
wallet_in_comment = None
if event_name == "issue_comment":
comment_author = event.get("comment", {}).get("user", {}).get("login", "")
if comment_author == pr_author:
wallet_in_comment = find_wallet(comment_text)
elif find_wallet(comment_text):
print(f"SECURITY: Ignoring wallet from {comment_author} (not PR author {pr_author})")
wallet = wallet_in_body or wallet_in_comment
# ── Get current labels ──
labels_data, _ = gh_api(f"repos/{repo}/issues/{pr_number}/labels")
label_names = [l["name"] for l in (labels_data or [])]
has_missing_label = "missing-wallet" in label_names
headers_simple = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
# SECURITY: Redact wallet in public GitHub comments (show first 4 + last 4 only)
def redact_wallet(w):
if w and len(w) > 8:
return f"{w[:4]}...{w[-4:]}"
return w or "N/A"
if wallet:
# ── WALLET FOUND ──
wallet_redacted = redact_wallet(wallet)
if has_missing_label:
# Remove the missing-wallet label
gh_api(f"repos/{repo}/issues/{pr_number}/labels/missing-wallet", method="DELETE")
# Confirm on GitHub (REDACTED wallet — full wallet only goes to Telegram)
if wallet_in_body:
gh_api(f"repos/{repo}/issues/{pr_number}/comments", method="POST", data={
"body": (
f"✅ **Wallet detected:** `{wallet_redacted}`\n\n"
f"Thanks @{pr_author}! Your wallet has been recorded and your "
f"PR review will proceed as normal.\n\n"
f"---\n*SolFoundry Review Bot*"
)
})
elif wallet_in_comment:
gh_api(f"repos/{repo}/issues/{pr_number}/comments", method="POST", data={
"body": (
f"✅ **Wallet detected in comment:** `{wallet_redacted}`\n\n"
f"@{pr_author}, please also **edit your PR description** to include your wallet "
f"so it's on record for payout processing.\n\n"
f"Your review will proceed in the meantime.\n\n"
f"---\n*SolFoundry Review Bot*"
)
})
# Add review-triggered label so pr-review can skip if it already ran
gh_api(f"repos/{repo}/issues/{pr_number}/labels", method="POST", data={
"labels": ["review-triggered"]
})
# Trigger AI Code Review via workflow_dispatch (label removal doesn't trigger pull_request_target)
trigger_resp = gh_api(
f"repos/{repo}/actions/workflows/pr-review.yml/dispatches",
method="POST",
data={"ref": "main", "inputs": {"pr_number": str(pr_number)}}
)
review_triggered = trigger_resp[1] == 204
review_status = "🔄 Review triggered" if review_triggered else "⚠️ Review trigger failed"
# Send Telegram with inline View PR button (bot doesn't detect wallet-added immediately)
send_telegram_with_button(
f"✅ <b>Wallet added — PR #{pr_number}</b>\n"
f"👤 {pr_author}\n"
f"📬 <code>{wallet}</code>\n"
f"Label removed. {review_status}",
"👀 View PR",
pr_url
)
print(f"PR #{pr_number}: wallet found ({wallet_redacted}), removed missing-wallet label, review={'triggered' if review_triggered else 'trigger failed'}")
else:
print(f"PR #{pr_number}: wallet present, no missing-wallet label — all good")
else:
# ── NO WALLET ──
if not has_missing_label:
# Check if we already warned (avoid duplicate comments)
comments_data, _ = gh_api(f"repos/{repo}/issues/{pr_number}/comments?per_page=50")
already_warned = False
if comments_data:
already_warned = any(
"Missing Solana" in (c.get("body", "") or "") or
"Missing Solana wallet" in (c.get("body", "") or "")
for c in comments_data if isinstance(c, dict)
)
if not already_warned:
gh_api(f"repos/{repo}/issues/{pr_number}/comments", method="POST", data={
"body": (
f"⚠️ **Missing Solana Wallet**\n\n"
f"@{pr_author}, your PR doesn't include a Solana wallet address. "
f"We need this to send your $FNDRY bounty payout.\n\n"
f"**Edit your PR description** and add your wallet address:\n\n"
f"```\n**Wallet:** YOUR_SOLANA_ADDRESS_HERE\n```\n\n"
f"Or reply to this comment with your wallet address.\n\n"
f"⏰ **You have 24 hours** to add your wallet or this PR will be automatically closed.\n\n"
f"[Phantom](https://phantom.app) or [Backpack](https://backpack.app) recommended.\n\n"
f"---\n*SolFoundry Review Bot*"
)
})
gh_api(f"repos/{repo}/issues/{pr_number}/labels", method="POST", data={
"labels": ["missing-wallet"]
})
print(f"PR #{pr_number}: no wallet, warned + labeled")
# No Telegram for missing wallet — just label the PR, bot will see it
else:
print(f"PR #{pr_number}: no wallet, already warned")
else:
print(f"PR #{pr_number}: still no wallet, already labeled")
PYEOF