Adding a provider
Adding a new provider is the single most impactful kind of
contribution. Every supported exchange, brokerage, chain, and price
source comes from this same pattern — one directory per provider
under packages/clients/providers/src/providers/.
What providers exist
Section titled “What providers exist”The provider directory layout (each is a self-contained adapter):
Directorypackages/clients/providers/src/providers/
Directorybinance/
- …
Directorybitcoin/
- …
Directorybitget/
- …
Directorybitstamp/
- …
Directorybybit/
- …
Directorycoinbase/
- …
Directorycoingecko/
- …
Directorydefillama/
- …
Directoryetherscan/
- …
Directoryfinnhub/
- …
Directoryfrankfurter/
- …
Directorygate/
- …
Directorygemini/
- …
Directoryhuobi/
- …
Directoryibkr/
- …
Directorykraken/
- …
Directorykucoin/
- …
Directorymexc/
- …
Directoryokx/
- …
Directorysolana/
- …
Directoryton/
- …
Directorytron/
- …
Directorywise/
- …
Directoryyahoo-finance/
- …
Directoryai-deepseek/
- …
Directoryai-openai/
- …
Directoryai-perplexity/
- …
The capability-based interface model
Section titled “The capability-based interface model”Each provider implements one or more capability interfaces:
| Capability | Interface | What it does |
|---|---|---|
| Pricing | PriceProvider | Quote current and historical prices for a token in a base currency. |
| Balances | BalanceProvider | Read the current balance of an account / wallet for a list of tokens. |
| Transactions | TransactionProvider | Page through historical transactions of an account / wallet. |
| Token identity | TokenIdentityProvider | Enrich a token row with provider-specific metadata. |
| AI inference | AIProvider | Parse a screenshot / answer a structured question. |
| OAuth | OAuthProvider | Drive the OAuth handshake (Binance is the only one currently). |
A provider can implement any subset. CoinGecko implements
PriceProvider + TokenIdentityProvider. Etherscan implements
BalanceProvider + TransactionProvider + TokenIdentityProvider.
Kraken implements all four (price, balance, tx, identity).
The step-by-step
Section titled “The step-by-step”-
Open an issue first for non-trivial providers (anything with auth flows, anything that introduces new capability shapes, anything where the upstream is complicated). For a small pricing source with a key, just file the PR.
-
Create the directory:
Terminal window mkdir packages/clients/providers/src/providers/myprovidercd packages/clients/providers/src/providers/myprovider -
Implement the capability adapter. Copy from the closest existing provider that implements the same capability:
- Pricing-only? Copy
coingecko/orfrankfurter/. - Exchange (price + balance + tx)? Copy
kraken/. - Blockchain RPC? Copy
etherscan/(EVM) orhelius/(Solana — actually undersolana/). - AI? Copy
ai-openai/.
The directory typically contains:
myprovider/index.ts # exports the @Service() class(es)MyProviderClient.ts # raw HTTP / SDK wrapperMyProviderPriceProvider.ts # if implementing PriceProvidertypes.ts # upstream payload shapes - Pricing-only? Copy
-
Register the provider. Each capability has a registry under
packages/clients/providers/src/core/. The provider class@Service()-registers itself; routers that need a price / balance / tx provider for a given context look it up. -
Wire up env vars. If the provider needs an API key, declare it in the data-provider’s
apps/backend/data-provider/.env.exampleand read it viapackages/infra/config/’s helpers. ThePricingService/BalanceServiceroute automatically falls through providers that report “not configured”. -
Add an institution row. For exchanges / brokerages / chains, add a seed entry to the institutions catalogue migration so the UI shows the integration:
- Migration in
packages/infra/db/src/migrations/adding anINSERT INTO institutions .... - For blockchains, also a row in
institution_blockchain_mappings.
- Migration in
-
Write tests. Two layers:
- Unit tests stubbing the upstream HTTP client.
- Integration tests with
withTestDbif the provider writes to the schema (token identity providers do; pure pricing providers typically don’t).
-
Document the env var in
.env.example(root) and inOptional integration keys. -
Run the full checks:
Terminal window bun run type-checkbun lint:fixbun test --preload ./packages/business/domain/test-preload.ts \packages/ --timeout 30000 -
Open the PR with a conventional-commit message:
feat(providers): add MyProvider pricing adapter
Capability examples
Section titled “Capability examples”A pricing provider
Section titled “A pricing provider”@Service()export class MyProviderPriceProvider implements PriceProvider { private readonly client = Container.get(MyProviderClient);
async getCurrentPrice( token: Token, base: Token ): Promise<PriceResult | null> { const data = await this.client.fetch(`/price/${token.symbol}/${base.symbol}`); if (!data) return null; return { price: new Decimal(data.price), timestamp: new Date(data.ts), source: 'myprovider', granularity: 'intraday', }; }
async getHistoricalPrice( token: Token, base: Token, at: Date ): Promise<PriceResult | null> { // ... }}A token identity provider
Section titled “A token identity provider”@Service()export class MyProviderTokenIdentityProvider implements TokenIdentityProvider { private readonly client = Container.get(MyProviderClient);
async enrich(token: Token): Promise<Partial<TokenMetadata>> { const id = await this.client.lookupBySymbol(token.symbol); if (!id) return {}; return { myprovider: { id, symbol: token.symbol } }; }}The merged TokenMetadata is written back to
tokens.providerMetadata. See
Tokens & market segments.
What we’ll likely close
Section titled “What we’ll likely close”- A new exchange that just calls one undocumented endpoint, no retry handling, no rate-limit awareness. Get to “production shape” before submitting.
- A pricing adapter that doesn’t implement historical prices. Historical prices are what the rollup needs; current-only providers are partial.
- An AI provider that doesn’t have a clear use case (we have three; we don’t need a fourth without a reason).