Repository navigation
1.0.0 is the first stable release: from here on the public API follows semver, as the README's
Stability and versioning
section defines. Breaking changes: deprecated exports removed, lenient MetricMetadata fields,
modified as unix seconds, no browser field, maxRetries defaulting to 2 and Node.js >= 22;
each has migration notes below.
It also contains 0.29.4–0.30.1. Of those, only 0.30.1 reached npm: 0.29.4, 0.29.5 and 0.30.0 were
never published and have no GitHub Release. If you are upgrading from 0.29.3 or earlier, read their
sections in the full
CHANGELOG too
(notably 0.30.0's BulkEntry['v'] type change).
Breaking changes
-
Removed the deprecated exports
MetricTierSchema,MetricTier,MetricDataTypeSchema,
MetricDataTypeandFetchFn(#23). None of them was used by the client; they were deprecated
with a promise of removal in 1.0. Migration:MetricTier/MetricTierSchema: useMetricMetadata['tier'], anumber(e.g.2). The
string enum ('free' | 'tier1' | …) never matched the API'stierfield.MetricDataType/MetricDataTypeSchema: define your own enum if you need these values
('average' | 'sum' | 'count' | 'percentage' | 'ratio'); no response was ever validated
against them.FetchFn: useGlassnodeFetch, the type of thefetchconfig option, ortypeof fetch
(whichFetchFnwas an alias of; a standardfetchstill fits the option).
-
MetricMetadata(fromgetMetricMetadata()/MetricMetadataSchema):refs,queriedand
parametersare now optional, in line with the lenient response policy (0.12.0) (#25). A response
that omits any of them now parses instead of rejecting the whole call; onlypathandtier
stay required. An absent field is reported as sent,undefined, with no{}default. Their
types become… | undefined, which breaks code that reads them directly (e.g.
meta.refs.docsorObject.keys(meta.parameters)) understrictNullChecks. The recorded API
responses still carry all three, so runtime values do not change today.Migration: read these fields with
?.and a fallback where you need one, e.g.
meta.refs?.docs,meta.queried?.a,meta.parameters?.a ?? [],
Object.entries(meta.parameters ?? {}). -
MetricMetadata.modifiedis now unix seconds (number | undefined), passed through as the
API sends it, like every other time field (timerange.min/max, the time series and bulkt,
callMetric()results). It used to be the one field converted to aDate. The schema is now
z.number().optional(); README "Timestamps", the schema comments and
examples/ex.metadata.validation.tsare updated. NotoDatehelper is added:
new Date(t * 1000)already covers every field (#22).-
Behaviour change:
modified: 0now passes through as0; it used to becomeundefined
("no modification time recorded"). Absent is stillundefined. -
Migration: convert explicitly where you need a
Date:const modified = meta.modified !== undefined ? new Date(meta.modified * 1000) : undefined;
To keep treating
0as "not recorded", usemeta.modified || undefined(or check for0)
before converting.
-
-
package.jsonno longer has a top-levelbrowserfield;unpkgandjsdelivrfields point at
dist/glassnode-api.umd.min.jsinstead (#37).exportsgets nobrowsercondition, so
exports-aware tools (Node, webpack 5, Vite, esbuild, Rollup, TypeScript) resolve exactly as
before: the tree-shakeable ESM build (import) or CommonJS (require). Both bundles still ship
at the same paths.- CDNs: the bare
https://unpkg.com/glassnode-apiURL now serves the UMD bundle. unpkg
ignoresbrowser, so it used to serve the CommonJSmain(dist/index.js), which fails in a
<script>tag. jsDelivr already served the UMD bundle throughbrowserand still does,
throughjsdelivr. Explicitdist/…min.jsURLs are unchanged. - Who is affected: tools that read
browserand ignoreexports, chiefly Browserify
(or a bundler withexportsresolution turned off). Browserify now bundlesdist/index.jsplus
zod's CommonJS build instead of the UMD bundle. It still works, with the same API, but the
output is about 6× larger: about 186 KB gzipped instead of 31 KB. - Migration: to keep the smaller bundle in Browserify, require the UMD file by path,
require('glassnode-api/dist/glassnode-api.umd.min.js'), or mapglassnode-apito it (e.g.
with abrowserfield in your ownpackage.json). Browserify ignoresexports, so the deep
path resolves;exports-aware tools cannot import it, and do not need to. In a page, load it
with a<script>tag from a CDN or your own copy. - webpack 4 is not affected: it could not parse the package before this change (the UMD bundle
uses?.) and still cannot (the ESM build targets ES2022). It needsnode_modules
transpiled either way.
- CDNs: the bare
-
maxRetriesnow defaults to2(was0) (#49). A429, a5xxor a transport failure
(network error, per-attempttimeout) is retried up to twice (up to 3 attempts), with the
existing backoff: exponential fromretryDelay(1 s), capped atmaxRetryDelay(30 s), full
jitter, aRetry-Afterhonoured. Every client call is aGET, so a retry repeats no side
effect. TheGlassnodeAPIconstructor applies the default (it depends onx402), exported as
DEFAULT_MAX_RETRIES(2) andDEFAULT_X402_MAX_RETRIES(0).GlassnodeConfigSchema
stays an object schema but no longer fills inmaxRetries:parse()leaves an unset value
undefined(it was0), and its output type is nownumber | undefined. Resolve it with
?? DEFAULT_MAX_RETRIES(orDEFAULT_X402_MAX_RETRIESwithx402).
onRequest/onResponsehook events reportmaxAttempts: 3by default.- Behaviour change: a call that used to fail at once on a transient error may now succeed,
or fail only after up to two waits; the error is the last attempt's.onRetryhooks and the
loggernow fire on those retries. - Worst-case latency:
timeoutis per attempt, so withtimeoutset a failing call can now
take about 3 ×timeoutplus the backoff. The backoff is at most about 3 s with the defaults
(jittered, up to 1 s then 2 s). ARetry-Afterwait replaces it, capped atmaxRetryDelay
(30 s by default, so up to about 60 s over two retries). For a deadline on the whole call,
retries included, passsignal: AbortSignal.timeout(ms)(see README
"Cancellation and per-call timeouts"),
or lowermaxRetries/maxRetryDelay. - x402 mode keeps
0: withx402: truethe default stays0. The fetch from
createX402Fetchnever lets a failure after a signed payment be retried (it raises
GlassnodePaymentError), but the client cannot tell whether a caller-supplied payment fetch
does the same, and a retry of a paid5xxor timeout through a bare x402 wrapper would sign a
new payment and could pay twice. WithcreateX402Fetchyou can opt in (maxRetries: 2): only
unpaid attempts are ever retried. - Opt out: pass
maxRetries: 0for the previous single-attempt behaviour.
- Behaviour change: a call that used to fail at once on a transient error may now succeed,
-
Node.js >= 22 is now required (
engines.nodeis>=22.0.0, was>=18.0.0) (#48). Node 18
reached end-of-life in April 2025 and Node 20 in April 2026; Node 22 is in maintenance until
April 2027.@types/nodemoves to the new floor (^22), so the compiler still rejects APIs newer
than the oldest supported Node. The CI floor jobcompat-node18becomescompat-node-floor
and runs on Node 22 (same steps: build, CJSrequiresmoke,scripts/smoke-timeout.mjs). No
code or output changes: thetsconfig*.jsontarget/libare unchanged (the Node builds'
ES2022 already fits Node 22, and their ESM output also reaches browsers through bundlers; the
browser bundles' own code stays ES2015, while their bundled Zod is ES2020), and the client keeps
its ownAbortSignal.any()stand-in for older browsers.Migration: upgrade to Node.js 22 or later. On Node 18 or 20, stay on 0.x: package managers
that enforceengines(e.g. withengine-strict) refuse to install 1.0, and it is not tested
there.
Docs
- README: a new "Stability and versioning" section states what semver covers from 1.0: the public
API, what a minor or a major release may change (including widened output types, rejected
inputs, Zod and optional peer majors), how new response fields are parsed, the TypeScript and
browser baselines, and what is not covered. The Features
list no longer calls retries opt-in: they are on by default since themaxRetrieschange above.
Build
tsconfig.browser.jsonusesmodule: ESNextandmoduleResolution: bundlerinstead of the
deprecatedmoduleResolution: node(node10), sobuild:browserno longer warns TS5107 and
keeps working on TypeScript 7 (#35). The browser bundles are byte-identical before and after.
Tooling
No change to the published package (#50).
- Dev dependencies, within their current majors and the
.npmrc7-dayminimum-release-age:
vitestand@vitest/coverage-v85.0.1,eslint10.11.0,prettier3.9.8,rollup4.63.4,
typescript-eslint8.70.1, the@x402/evm/@x402/fetchdev copies 2.27.0 andviem2.56.8.
TypeScript stays 6.0.3 (#39).examples/:@x402/*2.27.0,viem2.56.8 anddotenv16.6.1
(still 16.x; the 17/18 majors are not taken), locked withnpm install --before=<7 days ago>.
pnpm auditandnpm audit(inexamples/) report no vulnerabilities. - CI: every GitHub Action is pinned by full commit SHA with a
# vX.Y.Zcomment, and moved to its
latest major:actions/checkoutv7.0.1,actions/setup-nodev7.0.0,pnpm/action-setupv6.1.0,
actions/deploy-pagesv5.0.1 (configure-pagesv6.0.0 andupload-pages-artifactv5.0.0 were
already current). No input changes were needed: the workflows already setcache: 'pnpm'
explicitly (setup-node v5+ only auto-caches npm projects),pnpm/action-setupkeeps
standalone: truewith thenpm_config_ignore_scripts: 'false'bootstrap override, and
publish.ymlkeeps overwriting setup-node's.npmrc(v7 writes
_authToken=${NODE_AUTH_TOKEN}+registry=) before the OIDC publish. - CI hardening:
publish.yml'spublishjob no longer restores the pnpm cache (cache: 'pnpm'
dropped). It holdsid-token: writeandcontents: write, and setup-node's docs advise against
caching in privileged release jobs (cache poisoning). The other jobs keep their cache. - Release guards (#33). On 2026-09-29 the 0.29.5 and 0.30.0 publish runs were cancelled: GitHub
keeps one pending run per concurrency group, so a later merge's publish replaced them while they
waited for approval. No change to the published package.publish.ymlalways passes an explicit dist-tag tonpm publish:latest, or
backport-<major>.<minor>for a version lower than the currentlatest(e.g. a re-run of a
cancelled release), ornextfor a prerelease, solatestnever moves backwards. Such a
GitHub Release is not marked "Latest" (a prerelease is marked as one). The decision is in
scripts/release-plan.mjs: SemVer 2.0.0 comparison with prereleases, no new dependency.
release-state.shfails unless the tag islatest,nextorbackport-<n>.<n>.
package.jsonpublishConfigno longer setstag: "latest": the guard relied on--tag
overriding it, and a manual publish or an older npm would still have forcedlatest. npm's
default tag islatestanyway (npm publish --dry-runstill reportslatest, and honours
--tag backport-0.29).- The release job summary warns about every
CHANGELOG.mdversion between the last published
version and the one being released that is missing on npm. ci.ymlchecks the topCHANGELOG.mdheading (scripts/check-changelog-heading.mjs): into
mainit must be exactly## <package.json version>; intorelease/**,
## <x.y.z> (unreleased)is accepted too. A heading still marked "(unreleased)" cannot reach
main.- CONTRIBUTING.md "Releases" and the
publish.ymlheader explain the concurrency behaviour, the
recovery (re-run; an older version gets a non-latesttag) and "merge release PRs one at a
time". The scripts are tested intest/release-scripts.spec.ts,release-state.shwith a
fakenpmwhose arguments are asserted.