@ledgerhq/coin-tezos
Version:
128 lines (111 loc) • 3.69 kB
text/typescript
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0
import type { TezosCoinConfig } from '../config'
import type { Baker } from '../types'
import { makeLRUCache } from '@ledgerhq/live-network/cache'
import network from '@ledgerhq/live-network/network'
import { log } from '@ledgerhq/logs'
import { ledgerValidatorAddress } from './bakers.whitelist-default'
export type TezosApiBaker = {
address: string
name: string
status: string
balance: number
features: TezosBakerFeature[]
delegation: {
enabled: boolean
minBalance: number
fee: number
capacity: number
freeSpace: number
estimatedApy: number
features: TezosBakerFeature[]
}
staking: {
enabled: boolean
minBalance: number
fee: number
capacity: number
freeSpace: number
estimatedApy: number
features: TezosBakerFeature[]
}
}
export type TezosBakerFeature = {
title: string
content: string | unknown
}
export const cache = makeLRUCache(
async (config: TezosCoinConfig): Promise<Baker[]> => {
const bakers: Baker[] = []
const TEZOS_API_BASE_URL = config.baker.url
const { data } = await network<TezosApiBaker[]>({
url: `${TEZOS_API_BASE_URL}/v3/bakers`,
})
if (data && Array.isArray(data)) {
log('tezos/bakers', 'found ' + data.length + ' bakers')
data
.filter((raw) => raw.status === 'active' && raw.delegation.enabled)
.forEach((raw) => {
const baker = asBaker(raw)
if (baker) {
bakers.push(baker)
}
})
bakers.sort((a, b) => (a.name > b.name ? 1 : -1))
}
const ledgerBakerIndex = bakers.findIndex((baker) => baker.address === ledgerValidatorAddress)
const [ledgerBaker] = bakers.splice(ledgerBakerIndex, 1)
log('tezos/bakers', 'loaded ' + bakers.length + ' bakers')
return [ledgerBaker, ...bakers]
},
(config: TezosCoinConfig) => config.baker.url
)
let _lastBakers: Baker[] = []
export const fetchAllBakers = async (config: TezosCoinConfig): Promise<Baker[]> => {
const allBakers = await cache.force(config)
_lastBakers = allBakers
return allBakers
}
function whitelist(all: Baker[], addresses: string[]) {
return all.filter((baker) => addresses.includes(baker.address))
}
export const listBakersWithDefault = (whitelistAddresses: string[]): Baker[] => {
return whitelist(_lastBakers, whitelistAddresses)
}
export const listBakers = async (
config: TezosCoinConfig,
whitelistAddresses: string[]
): Promise<Baker[]> => {
_lastBakers = await cache(config)
return whitelist(_lastBakers, whitelistAddresses)
}
export function getBakerSync(addr: string): Baker | undefined {
return _lastBakers.find((baker) => baker.address === addr)
}
export async function loadBaker(config: TezosCoinConfig, addr: string): Promise<Baker | undefined> {
const cacheBaker = getBakerSync(addr)
if (cacheBaker) return cacheBaker
const bakers = await cache(config)
const baker = bakers.find((baker) => baker.address === addr)
return baker
}
export const asBaker = (data: TezosApiBaker): Baker | undefined => {
const { address, name, delegation } = data
if (
typeof name === 'string' &&
typeof address === 'string' &&
typeof delegation.freeSpace === 'number' &&
typeof delegation.estimatedApy === 'number' &&
delegation.estimatedApy >= 0 &&
delegation.estimatedApy <= 1
) {
return {
name,
address,
logoURL: `https://services.tzkt.io/v1/avatars/${address}`,
nominalYield: `${Math.floor(10000 * delegation.estimatedApy) / 100} %`,
capacityStatus: delegation.freeSpace <= 0 ? 'full' : 'normal',
}
}
}