Skip to content

feat: add LI.FI Earn API types - #561

Open
FarseenSh wants to merge 3 commits into
lifinance:mainfrom
FarseenSh:feat/earn-types
Open

feat: add LI.FI Earn API types#561
FarseenSh wants to merge 3 commits into
lifinance:mainfrom
FarseenSh:feat/earn-types

Conversation

@FarseenSh

Copy link
Copy Markdown

What

Adds types for the Earn Data API (earn.li.fi) — src/earn.ts, plus one line in src/index.ts. No imports, no runtime dependency, no changes to existing types.

Why

@lifi/types covers chains, tokens, bridges, exchanges, steps and balances, but has no Earn types. So every TypeScript integration of the Earn API writes them by hand from earn-openapi.yaml — and the spec disagrees with the live service in several places, each of which silently produces wrong values rather than a type error.

Provenance

Derived from the live API and cross-checked against it — 710 vaults across 19 chains and 27 protocols, Aug 2026 — rather than transcribed from the spec. Where the two disagree, the correction is documented inline at the field it affects, so it travels with the type instead of living in a changelog:

Documented Actual
apy.* is a decimal (0.0534 = 5.34%) already a percentage — the quickstart's * 100 overstates every yield 100×
tvl.usd is a string a number (changed in the Apr 2026 rewrite)
caps, timeLock, kyc, lpTokens sent by zero of 710 vaults
rewardTokens[].symbol / .decimals required absent on some entries
structured errors on 400 and 404 only 400 carries errors[]
analytics refresh every 15 min observed floor 87 min, median 90

Plus three behaviours that appear in no spec:

  • verificationStatus — undocumented, present on every vault, flagged on ~9.3% (66/710). It looks like an internal vault-quality signal and is genuinely useful to consumers.
  • nextCursor is absent from the JSON on the final page — not null, not empty — so it must be optional and nullable to paginate correctly.
  • apy.reward is three-valued: null (protocol reported nothing), 0 (reported no incentives), or positive. The split varies within a protocol, so ?? 0 collapses the only signal separating organic yield from a temporary emission.

/v1/portfolio also renamed its array positionsdata recently; both are declared so consumers can migrate without a break.

Checks

Run against this repo's own toolchain on this branch:

  • pnpm typecheck — clean
  • pnpm build — emits to _types, _esm and _cjs
  • prettier --check — clean
  • eslint — clean

Notes

Names are prefixed Earn* to avoid collisions with existing exports. Happy to rename, reshape, split into src/earn/, or drop the inline commentary if you'd rather keep the type files terse — the field shapes are the substance and the rest is easy to trim.

These come out of EarnForge, where the equivalent Zod schemas are exercised by a live integration suite and a three-way drift check against the live API, the OpenAPI spec, and the schemas themselves — which is how the discrepancies above were found.

@lifi/types has no Earn types today, so every TypeScript integration of the
Earn Data API writes them by hand from earn-openapi.yaml — which disagrees
with the live service in several places.

These are derived from the live API and verified against 710 vaults across
19 chains and 27 protocols (Aug 2026). Each place the spec and the service
disagree is documented inline at the field it affects, so the correction
travels with the type.
FarseenSh added a commit to FarseenSh/earnforge that referenced this pull request Aug 4, 2026
Converted to lifinance/types' own prettier config (semi: false, singleQuote:
true) — the draft carried semicolons because it was derived from the published
tarball, which is tsc output rather than their source.

Two counts were also still measured against the 609-vault fleet: 139 vaults
yielding under 1% is now 161, and descriptions are on 23% rather than 16%.

Submitted as lifinance/types#561.
LI.FI de-indexed every vault on Unichain (130) and Scroll (534352) since this
was opened, so /v1/chains reports 17 rather than 19 and the fleet is 703.

Also replaces the analytics-freshness note. It claimed an "observed floor of 87
minutes", which was never a property of the API — only how far into the refresh
cycle that sample happened to land, so it read differently on every run. The
fleet refreshes in one hourly batch (338 of 703 vaults share a single updatedAt
minute) with a tail to ~97 hours; that is the structural claim and it holds.
The previous commit quoted "338 of 703 vaults share a single updatedAt minute".
Four walks across today returned 328, 338, 522 and 526 — the batch fires at
:01-:03 and fills progressively, so that count measures how far into the hour
the sample landed rather than anything about the API.

Same defect as the "observed floor of 87 minutes" line this file already
replaced once. The mechanism is the durable part, so it states that and stops
putting a number on it.
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.

1 participant