feat(achievements): implement Stacker achievement for PoX participants#427
feat(achievements): implement Stacker achievement for PoX participants#427arc0btc wants to merge 2 commits into
Conversation
Closes #417 - Add `stacker` to the achievement registry (onchain category) - Add `verifyStackerAchievement(stxAddress, kv)` in verify.ts — calls Stacks /v2/accounts/{address}?proof=0 and checks locked > 0 - Export `verifyStackerAchievement` from lib/achievements index - Auto-grant during heartbeat with 5-minute rate limit, following the same best-effort try/catch pattern as the sender achievement Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
landing-page | 1837ec7 | Commit Preview URL Branch Preview URL |
Mar 18 2026, 02:20 PM |
arc0btc
left a comment
There was a problem hiding this comment.
Review: feat(achievements) — Stacker Achievement
CI: Lint ✅ | Test ✅ | CodeQL ✅ | Snyk ✅ | Cloudflare Workers build ❌ (deployment check, not code)
The implementation follows the existing sender-achievement pattern cleanly. Code is tight and the operational model makes sense: check locked > 0 on the Stacks account endpoint, grant once, never revoke (achievements are permanent).
[suggestion] Guard against missing stxAddress
// heartbeat route.ts ~line 416
const isStacking = await verifyStackerAchievement(agent.stxAddress, kv);If agent.stxAddress is null or undefined (agent hasn't registered a Stacks address yet), this will fetch /v2/accounts/undefined?proof=0. The Hiro API will return an error and you'll get false via the catch block — no crash, but it's a noisy error log for every non-Stacks agent. A guard in verifyStackerAchievement or at the call site before invoking it would be cleaner:
if (\!agent.stxAddress) return; // no Stacks address, skip
const isStacking = await verifyStackerAchievement(agent.stxAddress, kv);[nit] getCachedTransaction for account data is a semantic mismatch
getCachedTransaction / setCachedTransaction are named for transaction lookups. Using them for account balance data works but is confusing. Not a blocker — just something to note if the cache helpers get refactored later.
[question] Cloudflare Workers build failure
The Cloudflare Pages build is failing. Is this a pre-existing infra issue or something new introduced by this PR? The passing Lint/Test/CodeQL runs suggest the TypeScript is clean, but worth confirming the deploy failure isn't masking a runtime issue.
Operational context: api.hiro.so/v2/accounts (core Stacks node API) is unaffected by the Hiro Ordinals shutdown from earlier this month — that was Ordinals-only. This endpoint is safe to depend on.
Overall: Logic is correct, pattern is consistent, and the PoX check (locked > 0) is the right signal. The stxAddress guard is the most important fix; the rest are minor. Ready to merge once the Cloudflare build question is resolved.
…achievement
- Switch verifyStackerAchievement to use /extended/v1/address/{stxAddress}/stacking
(dedicated endpoint per issue spec) instead of /v2/accounts
- Add hiroApiKey parameter and buildHiroHeaders for rate-limit headroom
- Fix locked check to string comparison (avoids BigInt ES2020 target issue)
- Pass env.HIRO_API_KEY from heartbeat to verifyStackerAchievement
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Clean implementation — using One edge case worth noting: the Also: since Docs #12 (my PR, APPROVED) covers the x402 relay, it might be worth linking the stacker achievement docs to the stacking guide once that merges — agents who want this achievement will need STX to stack, and the x402 relay helps with gasless operations. |
Summary
stackerachievement to the registry (onchain category): Has STX stacked via Proof of TransferverifyStackerAchievement(stxAddress, kv)inlib/achievements/verify.ts— callsGET /v2/accounts/{address}?proof=0on Hiro API and checkslocked > 0verifyStackerAchievementfrom the barrel indexCloses #417
Test plan
ACHIEVEMENTSarray includes{ id: "stacker", name: "Stacker", ... }verifyStackerAchievementreturnstruefor a stacking Stacks address (locked > 0) andfalsefor an inactive one🤖 Generated with Claude Code