feat: add LI.FI Earn API types - #561
Conversation
@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.
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.
…live API "the 165 vaults that legitimately yield under 1%" was an undated figure and reads 127 today. The argument never needed the number, since such vaults always exist, so it states the mechanism instead. Same correction the previous commit on this branch made for the refresh-cluster count. Every claim in this file was re-checked against the live API on 1 Sep 2026 and all of them still hold: tvl.usd is a number, APY values are percentages, nextCursor is absent rather than null on the final page, the portfolio array is `data`, and symbol/decimals are genuinely optional (one vault in 799 omits them). The verification line now names that date and fleet size.
|
Bumping with a fresh check rather than just a ping. I re-verified every claim in this file against the live Earn API today, and all I also dropped a supporting figure that had gone stale, "165 vaults under 1% Happy to split this up, rename anything to match your conventions, or strip the |
What
Adds types for the Earn Data API (
earn.li.fi) —src/earn.ts, plus one line insrc/index.ts. No imports, no runtime dependency, no changes to existing types.Why
@lifi/typescovers chains, tokens, bridges, exchanges, steps and balances, but has no Earn types. So every TypeScript integration of the Earn API writes them by hand fromearn-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:
apy.*is a decimal (0.0534= 5.34%)* 100overstates every yield 100×tvl.usdis a stringcaps,timeLock,kyc,lpTokensrewardTokens[].symbol/.decimalsrequired400and404400carrieserrors[]Plus three behaviours that appear in no spec:
verificationStatus— undocumented, present on every vault,flaggedon ~9.3% (66/710). It looks like an internal vault-quality signal and is genuinely useful to consumers.nextCursoris absent from the JSON on the final page — notnull, not empty — so it must be optional and nullable to paginate correctly.apy.rewardis three-valued:null(protocol reported nothing),0(reported no incentives), or positive. The split varies within a protocol, so?? 0collapses the only signal separating organic yield from a temporary emission./v1/portfolioalso renamed its arraypositions→datarecently; both are declared so consumers can migrate without a break.Checks
Run against this repo's own toolchain on this branch:
pnpm typecheck— cleanpnpm build— emits to_types,_esmand_cjsprettier --check— cleaneslint— cleanNotes
Names are prefixed
Earn*to avoid collisions with existing exports. Happy to rename, reshape, split intosrc/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.