flowchart TD
A["PR merged"] --> B["GitHub Action"]
B --> C["Resolve closed issues (GraphQL closingIssuesReferences)"]
C --> D["For each issue with token:N label"]
D --> E["Resolve identity: PR author login → GitHub numeric id → Keycloak user (Admin API: idpAlias=github, idpUserId)"]
E --> F{"Account GitHub-linked?"}
F -->|Yes| G["POST /transfers bundle N: treasury → volunteer wallet (pending, accept in-app)"]
F -->|No| H["PR comment: link your GitHub account, then re-run"]
G --> I["PR comment: Reward sent, open the wallet app to accept"]
I --> J["Ledger entry (idempotency + audit)"]
Proposal: Token reward for volunteers on merged PRs
Problem
Reward a Greenstand volunteer with tokens when their contribution lands.
token:[number].Key finding: the plumbing already exists
Nothing here needs a new payment system. The existing register-reward flow already gifts tokens from a treasury wallet via a Keycloak service account (
apps/user/.../queue-listener.service.ts→POST /transfers). We reuse that pattern.SENDER_WALLET_IDwallet, Greenstand-controlled, pre-funded.treetrackerrealm already has GitHub as a login provider. Signing in with GitHub links a volunteer's GitHub identity to their Greenstand account automatically. No mapping table, no Keycloak config.POST /transfers(bundle) from treasury to the resolved wallet.Recommendations
token:[number]label, hardenedSENDER_WALLET_ID, Greenstand-controlled, pre-fundedFlow
flowchart TD A["PR merged"] --> B["GitHub Action"] B --> C["Resolve closed issues (GraphQL closingIssuesReferences)"] C --> D["For each issue with token:N label"] D --> E["Resolve identity: PR author login → GitHub numeric id → Keycloak user (Admin API: idpAlias=github, idpUserId)"] E --> F{"Account GitHub-linked?"} F -->|Yes| G["POST /transfers bundle N: treasury → volunteer wallet (pending, accept in-app)"] F -->|No| H["PR comment: link your GitHub account, then re-run"] G --> I["PR comment: Reward sent, open the wallet app to accept"] I --> J["Ledger entry (idempotency + audit)"]Components
.github/workflows/token-reward.yml; runs onpull_requestclosedwheremerged == true, basemain.closingIssuesReferenceson the PR (authoritative; covers body keywords and manually-linked issues); parsetoken:Nlabel per issue.idpAlias=github,idpUserId) →wallet.keycloak_account_id→wallet_id.POST /transfers{ sender_wallet: SENDER_WALLET_ID, receiver_wallet: <wallet_id>, bundle: { bundle_size: N } }.Identity & eligibility
Delivery
POST /transfersbundle → lands as a pending transfer in the volunteer's in-app notifications; they accept. Fully private (no public link, no email).redeem()change to bind the token to the recipient (today it is bearer).token:[number]label handlingtoken:Nper issue;Na positive integer.Idempotency & security
token:paidlabel). Re-runs, edits, reopen/reclose never double-pay.mainin the base repo, never from fork-run workflows; treasury/service-account secrets never exposed to fork PRs.Nper issue enforced on the transfer path, so a mislabeledtoken:9999can't drain the treasury even if the workflow is bypassed.Edge cases
pull_request.user.type == "Bot"; optional maintainer exclusion).Prerequisites (the only new infra)
realm-managementpermission to look up users by GitHub identity, andSENDER_WALLET_ID) wallet so its transfers complete.Phased rollout
token:N→ resolve GitHub-linked wallet → treasuryPOST /transfers→ PR comment + ledger. Not-linked authors get an enroll comment.redeem()binding fix).