Thanks for your interest in contributing! Lendwise aggregates and compares lending rates across DeFi protocols, and every contribution — a protocol adapter, a bug report, a token icon, a docs fix — makes the data better for everyone.
| I want to... | Start here |
|---|---|
| Add a new lending protocol | Adding a protocol adapter — the highest-impact contribution |
| Report wrong data or a broken page | Bug report |
| Request a protocol integration | Protocol request |
| Suggest a feature | Feature request |
| Ask a question | GitHub Discussions |
| Make a first small PR | Your first PR in 15 minutes |
Prerequisites: Node.js 24, pnpm 11.
git clone https://github.com/lendwise-fi/lendwise
cd lendwise
pnpm install
cp .env.example .env.local # see below for which keys you actually need
pnpm dev # → http://localhost:3000Not every env var is required for local work:
| Variable | Needed for |
|---|---|
DATABASE_URL |
Anything that reads/writes APY history (Neon Postgres) |
THEGRAPH_API_KEY |
pnpm codegen and the Compound V3 adapter (free key) |
NEXT_PUBLIC_RPC_PROVIDER + NEXT_PUBLIC_RPC_API_KEY |
Browser RPC config; pnpm dev and pnpm build need both |
RPC_API_KEY (+ RPC_API_KEY_SECRET for Infura, or RPC_JWT_PRIVATE_KEY + RPC_JWT_KEY_ID for dRPC JWT) |
Server RPC config: server wallet reads and Blend need it. For keyless local Blend reads, set RPC_URL_STELLAR alone |
| Everything else | Optional locally — rate limiting and analytics no-op when unset |
Most adapter work only touches GraphQL APIs and subgraphs — you can build and test those
adapters with just THEGRAPH_API_KEY, no database needed. Blend reads Soroban RPC, so it
needs the server RPC config or RPC_URL_STELLAR.
Scripts run through pnpm <script> (they use tsconfig.scripts.json). Running
pnpm exec tsx directly on a script that reaches the server RPC config needs
--tsconfig tsconfig.scripts.json; otherwise server-only throws a misleading "Client
Component" error.
The project has no code-review bottleneck other than these — all enforced in CI:
pnpm codegen # regenerate GraphQL types (needed before typecheck/test)
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit (strict mode)
pnpm test # vitest
pnpm format # prettier --writeHouse rules:
- TypeScript strict, no
any. - Functional only — no classes.
Promise.allSettledwherever multiple sources are aggregated: one failure never blocks the others.- Rates are stored as APY. Convert APR before emitting:
(1 + APR/365)^365 - 1(helpers insrc/lib/utils.ts). - Never parse a
productId— it is an opaque key. Provider/chain/asset live as typed columns onproducts; resolve by JOIN. - Filter chains by
chain_id, never by chain name — names are inconsistent across adapters; only the numeric id is canonical.
This is the contribution Lendwise is designed around. An adapter is a small, self-contained module (~5 files) that transforms a protocol's data source — GraphQL API, subgraph, or RPC — into the Lendwise data model. No database migration, no pipeline changes.
The full guide lives at src/lib/protocols/README.md —
architecture, the YieldAdapter contract, conventions, and war stories. The short version:
-
Create
src/lib/protocols/{name}/{version}/with anindex.tsexporting an adapter built withdefineYieldAdapter().providercomes from{name}/common/config.ts(one constant per protocol family, shared across versions) — never a re-typed string literal:import { ACME_PROVIDER } from '../common/config' export const adapter = defineYieldAdapter({ id: 'acme_v2', name: 'Acme v2', provider: ACME_PROVIDER, version: 'v2', chains: { 1: { slug: CHAIN_SLUG_MAP[1] } }, getProducts: fetchAcmeProducts, // full market catalogue getApySpot: fetchAcmeApySpot, // one rate snapshot per product // getApyHistory: optional — reconcile falls back to donor hours without it })
-
Register it in both registries (the compiler enforces they move together):
src/config/protocols-meta.ts— spread in your adapter's own{version}/meta.tsfragment (client-safe display metadata)src/config/protocols-server.ts— server-only dynamic import
-
Prove it with the live test harness:
pnpm adapter:test acme_v2
It runs
getProducts+getApySpotfor real, validates every payload against the strict schema, checks the two calls enumerate the same productId set, and prints a human-review summary (markets per chain, APY min/median/max, TVL). Paste that summary in your PR — it's how we review adapters.
Look at src/lib/protocols/morpho/v1/ for a complete
reference implementation.
Good entry points that don't require understanding the whole pipeline:
- Token icons — add an SVG to
public/icons/tokens/{SYMBOL}.svgfor an asset currently falling back to CoinGecko. - Docs — fix a typo, clarify a setup step, improve an explanation in the README or in
src/lib/protocols/README.md. - Issues labeled
good first issue.
| Branch | Role | Deployed to |
|---|---|---|
testnet |
Default branch. Every pull request targets it. | testnet.lendwise.fi (Stellar testnet) |
mainnet |
Staging on mainnet, promoted from testnet. |
mainnet.lendwise.fi |
main |
Production, promoted from mainnet. |
lendwise.fi |
Changes flow testnet → mainnet → main, and maintainers do the promotions. main only
accepts pull requests with green CI; nobody pushes to it directly.
- Fork, create a branch from
testnet. Give it its own name: a pull request from a branch namedmain,mainnetortestnetis closed automatically, since our deployments attach settings to those names. - Commit using Conventional Commits style
(
fix:,feat:,chore:, ...) — that's what the history uses. - Make sure the quality bar passes locally, and run
pnpm formatbefore pushing: CI checks formatting. - Open the PR against
testnet— the template will walk you through the checklist. CI must be green.
Small, focused PRs get reviewed fast. If you're planning something large, open an issue or a Discussion first so we can align before you invest the time.
On a first contribution, CI waits for a maintainer to approve the run. Pull requests from forks get no automatic preview deployment: the tests are the proof, so say in the PR how you verified the change.
This project follows the Contributor Covenant. Be kind.
By contributing, you agree that your contributions will be licensed under the MIT License.