Found tracing the demo resolver path end to end.
The default is a dead host
crates/dpp-node/src/config.rs falls back to https://id.odal-node.io when
RESOLVER_BASE_URL is unset, and .env.example line 133 ships that value
uncommented, so a copy-paste .env inherits it.
id.odal-node.io is NXDOMAIN. Checked 2026-09-19 — the apex and docs.
resolve; id. does not.
Why it is worse than a bad default
build_carrier_url writes that base into every passports qr_code_url at
publish time, and the passport is then signed. So the dead URL is inside
the signature. The .env.example comment already says the consequence plainly:
"It cannot be corrected afterwards — publishing starts the ESPR retention
window and odal passport archive refuses inside it, so the labels would have
to be reissued."
A silent default that produces unfixable output is the combination worth
removing. Nothing prints a warning; the node boots, publishes, and the QR codes
scan to nothing.
The prose is already honest — .env.example says the default "is not wired
up yet". This issue is about the code default, which no one reads.
Three options
|
Behaviour |
Trade |
| A. No default |
RESOLVER_BASE_URL becomes required; the node refuses to boot without it |
Fail-closed and unambiguous. Breaking for anyone relying on the default — though what they are relying on is broken |
B. Default to http://localhost:8003 |
Matches RESOLVER_PORTs own default, so a local node works out of the box |
A wrong value becomes obviously wrong rather than subtly wrong. Helps the local demo directly |
C. Keep it, refuse under NODE_PROFILE=production |
Mirrors the existing trust gate, which already "refuses to boot on placeholder trust" |
Consistent with boot/trust.rs. Leaves the trap open in every non-production profile — which is where the demo runs |
Recommendation: B, and C on top if cheap. B removes the trap at its root and
makes the default useful: a freshly-booted local node produces carrier URLs
that actually resolve against its own resolver. A localhost URL that escapes to
production is self-evidently wrong at a glance; id.odal-node.io is not.
A alone is defensible and more purist, but it makes the quickstart longer for
every developer to protect against a mistake B prevents by construction.
Not done here
This changes a value that lands inside signatures, so it is a decision rather
than a fix to apply unilaterally. No code changed.
Related
The same trace found that CREDENTIAL_ISSUERS_SELF being unset makes
/credential/dpp/{id} serve the public body with a 200 — also silent, also
worth its own look. Not filed separately yet.
Found tracing the demo resolver path end to end.
The default is a dead host
crates/dpp-node/src/config.rsfalls back tohttps://id.odal-node.iowhenRESOLVER_BASE_URLis unset, and.env.exampleline 133 ships that valueuncommented, so a copy-paste
.envinherits it.id.odal-node.iois NXDOMAIN. Checked 2026-09-19 — the apex anddocs.resolve;
id.does not.Why it is worse than a bad default
build_carrier_urlwrites that base into every passportsqr_code_urlatpublish time, and the passport is then signed. So the dead URL is inside
the signature. The
.env.examplecomment already says the consequence plainly:A silent default that produces unfixable output is the combination worth
removing. Nothing prints a warning; the node boots, publishes, and the QR codes
scan to nothing.
The prose is already honest —
.env.examplesays the default "is not wiredup yet". This issue is about the code default, which no one reads.
Three options
RESOLVER_BASE_URLbecomes required; the node refuses to boot without ithttp://localhost:8003RESOLVER_PORTs own default, so a local node works out of the boxNODE_PROFILE=productionboot/trust.rs. Leaves the trap open in every non-production profile — which is where the demo runsRecommendation: B, and C on top if cheap. B removes the trap at its root and
makes the default useful: a freshly-booted local node produces carrier URLs
that actually resolve against its own resolver. A
localhostURL that escapes toproduction is self-evidently wrong at a glance;
id.odal-node.iois not.A alone is defensible and more purist, but it makes the quickstart longer for
every developer to protect against a mistake B prevents by construction.
Not done here
This changes a value that lands inside signatures, so it is a decision rather
than a fix to apply unilaterally. No code changed.
Related
The same trace found that
CREDENTIAL_ISSUERS_SELFbeing unset makes/credential/dpp/{id}serve the public body with a 200 — also silent, alsoworth its own look. Not filed separately yet.