Skip to content

fix(blockfrost): never-registered stake accounts and DReps are "inactive", not a 500/502 - #442

Open
QSchlegel wants to merge 1 commit into
preprodfrom
claude/daily-2026-11-13-blockfrost-404-probes
Open

QSchlegel wants to merge 1 commit into
preprodfrom
claude/daily-2026-11-13-blockfrost-404-probes

Conversation

@QSchlegel

@QSchlegel QSchlegel commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

What & why

Mesh's BlockfrostProvider.get rejects with a JSON string (parseHttpError → {"data":…,"headers":…,"status":404}), not an Error. fetchStakeAccountStatus and fetchDrepStatus only recognised err.status === 404 or an Error message, so Blockfrost's normal "never seen on chain" 404 was rethrown as a lookup failure. Reproduced against the real BlockfrostProvider with a local 404 server: the probe threw a string.

Impact on preprod today:

Path Before After
GET /api/v1/stakeAccountInfo, never-registered stake account (every fresh wallet) 500 200 {active:false, poolId:null}
GET /api/v1/drepInfo, unregistered DRep 500 200 {active:false}
MCP transaction_preview with certificates, unregistered stake credential 502 STAKE_LOOKUP_FAILED registration detected as needed
MCP vote draft, unregistered DRep 502 DREP_LOOKUP_FAILED vote-drep-unregistered ("register first")

Fix: both probes use the shape-tolerant isBlockfrostNotFound that the DRep explorer already had. It moved to src/utils/blockfrost-not-found.ts and is re-exported from drep-profile.ts, so the explorer's behaviour is unchanged. Non-404 failures (500, network) are still rethrown, so they still map to 500/502.

Hardening in the same route: stakeAccountInfo spliced the raw stakeAddress query into Blockfrost's /accounts/{stake} path, using the server's project key. Values like stake_test1…/../../addresses/…, …/history and …?page=2 reached other Blockfrost endpoints. Now the route only accepts a lowercase bech32 reward-address shape, for both key and script credentials. It takes the network from the prefix and answers 400 for anything else, before any Blockfrost call.

Tests

  • New src/__tests__/stakeAccountInfo.test.ts (13 tests) covers:
    • auth
    • network routing for key and script addresses
    • Mesh's string 404 → 200 inactive
    • string 500 → 500
    • 9 malformed inputs → 400, with no Blockfrost call
  • stakeAccountStatus.test.ts and drepStatus.test.ts cover:
    • the real Mesh rejection string (404 → inactive, 500 → rethrown)
    • one test against the real BlockfrostProvider and a local HTTP 404, which pins the shape to the library version
  • Before the fix: 10 of 22 tests in the touched stake suites fail, plus the 2 new drep cases. After: all pass.
  • npx tsc --noEmit: clean
  • node scripts/run-tests.mjs:
    • CJS: 186 suites / 2370 passed (2 skipped)
    • ESM: 13 suites / 140 passed
  • A separate review agent read the diff cold: no significant findings. From its notes, I fixed proxy-env robustness of the local-server test and added a script-address fixture.

Not verified / notes

  • I did not hit live Blockfrost. The 404 shape is verified against Mesh's real provider code path.
  • Behaviour change: the route now rejects all-uppercase bech32 and anything that isn't reward-address shaped. In-repo consumers (scripts/ci) send lowercase stake… addresses. The auth-negative CI check still gets its 401 because auth runs first.
  • The route doesn't verify the checksum: a well-shaped address with a bad checksum still gets Blockfrost's 400, which the route reports as a 500. That is left as is.
  • Left as is (pre-existing): the probes still treat any Error whose message contains "404" as not-found.

https://claude.ai/code/session_017rkSXDaEvnprpYppMe53rd


Generated by Claude Code

…ake and DRep probes

Mesh's BlockfrostProvider.get rejects with a JSON string
({"data":…,"status":404}), not an Error. fetchStakeAccountStatus and
fetchDrepStatus only recognised err.status / Error messages, so the normal
"never seen on chain" answer was rethrown as a lookup failure:

- GET /api/v1/stakeAccountInfo answered 500 for every never-registered
  stake account (a fresh wallet), and drepInfo 500 for an unregistered DRep.
- MCP transaction_preview with certificates failed 502 STAKE_LOOKUP_FAILED
  for exactly the wallets that need a registration, and a vote draft for an
  unregistered DRep failed 502 DREP_LOOKUP_FAILED instead of the
  "register first" refusal.

Both probes now use the shape-tolerant check the DRep explorer already had,
moved to src/utils/blockfrost-not-found.ts (re-exported from drep-profile).

stakeAccountInfo also spliced the raw query value into Blockfrost's
/accounts/{stake} path with the server key; it now accepts only a
lowercase bech32 reward address shape (key or script) and derives the
network from its prefix, answering 400 otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rkSXDaEvnprpYppMe53rd
@vercel

vercel Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
multisig Ready Ready Preview Nov 13, 2026 6:07am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — ba92b33c Deployed Nov 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants