Skip to content

Commit effb335

Browse files
authored
feat(sdk): expose points and APY (#558)
* feat(sdk apy): add interface for apy and incentive * feat(sdk): expose points and tokens incentives * feat(sdk): add apys * remove unused map * tweaks following review * chore(enriched-Tokens): refactor to make code cleaner * bump to v3.3.0
1 parent 93b045b commit effb335

File tree

6 files changed

+257
-11
lines changed

6 files changed

+257
-11
lines changed

sdk/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gobob/bob-sdk",
3-
"version": "3.2.1",
3+
"version": "3.3.0",
44
"main": "dist/index.js",
55
"types": "dist/index.d.ts",
66
"scripts": {

sdk/src/gateway/client.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -803,16 +803,20 @@ export class GatewayApiClient {
803803
async getEnrichedTokens(includeStrategies: boolean = true): Promise<EnrichedToken[]> {
804804
const [tokens, prices] = await Promise.all([this.getTokenAddresses(includeStrategies), this.getPrices()]);
805805

806+
const tokensIncentives = await this.strategy.getTokensIncentives(tokens);
807+
806808
return Promise.all(
807-
tokens.map(async (address) => {
809+
tokens.map(async (address, i) => {
808810
const token = ADDRESS_LOOKUP[this.chainId][address];
811+
const tokenIncentives = tokensIncentives[i];
809812

810813
const { address: underlyingAddress, totalUnderlying } =
811814
await this.strategy.getStrategyAssetState(token);
812815

813816
if (underlyingAddress === 'usd') {
814817
return {
815818
...token,
819+
...tokenIncentives,
816820
tvl: Number(totalUnderlying),
817821
};
818822
}
@@ -822,12 +826,14 @@ export class GatewayApiClient {
822826
if (!underlyingToken) {
823827
return {
824828
...token,
829+
...tokenIncentives,
825830
tvl: 0,
826831
};
827832
}
828833

829834
return {
830835
...token,
836+
...tokenIncentives,
831837
tvl:
832838
bigIntToFloatingNumber(totalUnderlying, underlyingToken.decimals) *
833839
(prices.get(underlyingAddress.toLowerCase()) ?? 0),

sdk/src/gateway/strategy.ts

Lines changed: 196 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,165 @@
11
import { createPublicClient, http, type PublicClient, type Chain, erc20Abi, Address } from 'viem';
2-
import { tokenToStrategyTypeMap } from './tokens';
3-
import type { StrategyAssetState, Token } from './types';
2+
import { ADDRESS_LOOKUP, tokenToStrategyTypeMap } from './tokens';
3+
import {
4+
ChainId,
5+
type DefiLlamaPool,
6+
type EnrichedToken,
7+
type PointsIncentive,
8+
type StrategyAssetState,
9+
type Token,
10+
} from './types';
411
import { aaveV2AtokenAbi, compoundV2CTokenAbi } from './abi';
512

13+
const projectPointsIncentives: Map<string, PointsIncentive[]> = new Map([
14+
[
15+
'veda',
16+
[
17+
{
18+
id: 'veda',
19+
name: 'Veda Points',
20+
},
21+
],
22+
],
23+
[
24+
'bob',
25+
[
26+
{
27+
id: 'bob',
28+
name: 'Bob Spice',
29+
},
30+
],
31+
],
32+
[
33+
'segment',
34+
[
35+
{
36+
id: 'bob',
37+
name: 'Bob Spice',
38+
},
39+
{
40+
id: 'segment',
41+
name: 'Segment Finance Points',
42+
},
43+
],
44+
],
45+
[
46+
'ionic',
47+
[
48+
{
49+
id: 'bob',
50+
name: 'Bob Spice',
51+
},
52+
],
53+
],
54+
[
55+
'avalon',
56+
[
57+
{
58+
id: 'bob',
59+
name: 'Bob Spice',
60+
},
61+
{
62+
id: 'avalon',
63+
name: 'Avalon Points',
64+
},
65+
],
66+
],
67+
]);
68+
69+
const berockPoints = {
70+
id: 'bedrock',
71+
name: 'Bedrock Diamonds',
72+
};
73+
const babylonPoints = {
74+
id: 'babylon',
75+
name: 'Babylon Points',
76+
};
77+
const solvPoints = {
78+
id: 'solv',
79+
name: 'Solv XP',
80+
};
81+
const opRewards = {
82+
id: 'op',
83+
name: 'OP',
84+
};
85+
86+
const tokensPointsIncentives: Map<string, PointsIncentive[]> = new Map([
87+
[
88+
// uniBTC
89+
'0x236f8c0a61da474db21b693fb2ea7aab0c803894',
90+
[berockPoints, babylonPoints],
91+
],
92+
[
93+
// Solv (xSolvBTC)
94+
'0x0bef2a8b771e37763c1ce02a88f404c6b2573843',
95+
[solvPoints, babylonPoints],
96+
],
97+
[
98+
// Segment xSolvBTC
99+
'0x5ef2b8fbcc8aea2a9dbe2729f0acf33e073fa43e',
100+
[solvPoints, babylonPoints],
101+
],
102+
[
103+
// Segment uniBTC
104+
'0x7848f0775eebabbf55cb74490ce6d3673e68773a',
105+
[berockPoints, babylonPoints],
106+
],
107+
[
108+
// Avalon xSolvBTC
109+
'0x2e6500a7add9a788753a897e4e3477f651c612eb',
110+
[solvPoints, babylonPoints],
111+
],
112+
[
113+
// hybridBTC
114+
'0x9998e05030aee3af9ad3df35a34f5c51e1628779',
115+
[opRewards],
116+
],
117+
]);
118+
119+
const tokenToDefiLlamaPoolIdMap = new Map<string, string>([
120+
[
121+
// hybridBTC
122+
'0x9998e05030aee3af9ad3df35a34f5c51e1628779',
123+
'e8bfea35-ff6d-48db-aa08-51599b363219',
124+
],
125+
[
126+
// Segment xSolvBTC
127+
'0x5ef2b8fbcc8aea2a9dbe2729f0acf33e073fa43e',
128+
'a430e355-9a6d-4435-90e5-7e74c10f523c',
129+
],
130+
[
131+
// Segment wBTC
132+
'0x6265c05158f672016b771d6fb7422823ed2cbcdd',
133+
'56eed6bb-80ac-42e3-a7fb-f93c0438c72b',
134+
],
135+
[
136+
// Segment uniBTC
137+
'0x7848f0775eebabbf55cb74490ce6d3673e68773a',
138+
'a944439e-73ca-4de7-a48d-fe1e75e0b1f2',
139+
],
140+
[
141+
// Segment tBTC
142+
'0xd30288ea9873f376016a0250433b7ea375676077',
143+
'ecf4821d-b550-4d5f-b92f-b12d9c279271',
144+
],
145+
[
146+
// Avalon xSolvBTC
147+
'0x2e6500a7add9a788753a897e4e3477f651c612eb',
148+
'19c9b477-6ce9-4e59-897e-1b3ef76afa3a',
149+
],
150+
[
151+
// Avalon WBTC
152+
'0xd6890176e8d912142ac489e8b5d8d93f8de74d60',
153+
'8eb8b8bc-718c-41cf-9cd5-1a1c3c6045c1',
154+
],
155+
// Not enough liquidity to be listest on DefiLlama
156+
// [
157+
// // Avalon tBTC
158+
// '0x5e007ed35c7d89f5889eb6fd0cdcaa38059560ef',
159+
// ''
160+
// ],
161+
]);
162+
6163
export default class StrategyClient {
7164
private viemClient: PublicClient;
8165

@@ -21,11 +178,46 @@ export default class StrategyClient {
21178
}) as PublicClient;
22179
}
23180

181+
async getTokensIncentives(
182+
tokens: string[]
183+
): Promise<Pick<EnrichedToken, 'apyBase' | 'apyReward' | 'rewardTokens' | 'points'>[]> {
184+
const res = await fetch('https://yields.llama.fi/pools');
185+
186+
const defillamaPools: DefiLlamaPool[] = res.ok ? (await res.json()).data : [];
187+
const defillamaPoolMap = new Map<string, DefiLlamaPool>(
188+
defillamaPools.filter((pool) => pool.chain === 'Bob').map((pool) => [pool.pool, pool])
189+
);
190+
191+
return tokens.map((token) => {
192+
const tokenAddress = token.toLowerCase();
193+
194+
const strategyType = tokenToStrategyTypeMap.get(tokenAddress) ?? 'bob';
195+
const defillamaPoolId = tokenToDefiLlamaPoolIdMap.get(tokenAddress);
196+
const defillamaPool = defillamaPoolMap.get(defillamaPoolId);
197+
198+
return {
199+
apyBase: defillamaPool?.apyBase ?? 0,
200+
apyReward: defillamaPool?.apyReward ?? 0,
201+
rewardTokens: (defillamaPool?.rewardTokens ?? [])
202+
.map(
203+
(addr) =>
204+
ADDRESS_LOOKUP[ChainId.BOB][addr.toLowerCase()] ||
205+
ADDRESS_LOOKUP[ChainId.OPTIMISM][addr.toLowerCase()]
206+
)
207+
.filter(Boolean),
208+
points: [
209+
...(projectPointsIncentives.get(strategyType) ?? []),
210+
...(tokensPointsIncentives.get(tokenAddress) ?? []),
211+
],
212+
};
213+
});
214+
}
215+
24216
async getStrategyAssetState(token: Token): Promise<StrategyAssetState> {
25-
const strategyType = tokenToStrategyTypeMap.get(token.address.toLowerCase()) ?? 'none';
217+
const strategyType = tokenToStrategyTypeMap.get(token.address.toLowerCase()) ?? 'bob';
26218

27219
switch (strategyType) {
28-
case 'none': {
220+
case 'bob': {
29221
return this.getTokenAssetState(token);
30222
}
31223

sdk/src/gateway/tokens.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ const bobSepoliaTokens = [
107107
},
108108
];
109109

110+
const optimismTokens = [
111+
{
112+
name: 'Optimism',
113+
symbol: 'OP',
114+
decimals: 18,
115+
tokens: {
116+
optimism: {
117+
address: '0x4200000000000000000000000000000000000042',
118+
},
119+
},
120+
logoURI: 'https://optimistic.etherscan.io/token/images/optimism_32.png',
121+
},
122+
];
123+
110124
const shoebillTokens = [
111125
{
112126
name: 'sb tBTC v2',
@@ -279,11 +293,15 @@ const TOKENS: Array<{
279293
'bob-sepolia'?: {
280294
address: string;
281295
};
296+
optimism?: {
297+
address: string;
298+
};
282299
};
283300
logoURI: string;
284301
}> = [
285302
...bobTokens,
286303
...bobSepoliaTokens,
304+
...optimismTokens,
287305
...shoebillTokens,
288306
...segmentTokens,
289307
...avalonTokens,
@@ -297,14 +315,16 @@ export const ADDRESS_LOOKUP: { [key in number]: { [key in string]: Token } } = {
297315

298316
SYMBOL_LOOKUP[ChainId.BOB] = {};
299317
SYMBOL_LOOKUP[ChainId.BOB_SEPOLIA] = {};
318+
SYMBOL_LOOKUP[ChainId.OPTIMISM] = {};
300319

301320
ADDRESS_LOOKUP[ChainId.BOB] = {};
302321
ADDRESS_LOOKUP[ChainId.BOB_SEPOLIA] = {};
322+
ADDRESS_LOOKUP[ChainId.OPTIMISM] = {};
303323

304324
function addToken(address: string, token: (typeof TOKENS)[number], chainId: ChainId) {
305325
const lowerAddress = address.toLowerCase();
306326
const lowerToken: Token = {
307-
chainId: ChainId.BOB,
327+
chainId,
308328
address: lowerAddress,
309329
name: token.name,
310330
symbol: token.symbol,
@@ -324,11 +344,15 @@ for (const token of TOKENS) {
324344
if (token.tokens['bob-sepolia']) {
325345
addToken(token.tokens['bob-sepolia'].address, token, ChainId.BOB_SEPOLIA);
326346
}
347+
348+
if (token.tokens.optimism) {
349+
addToken(token.tokens.optimism.address, token, ChainId.OPTIMISM);
350+
}
327351
}
328352

329353
export const tokenToStrategyTypeMap = new Map([
330-
...bobTokens.map((token) => [token.tokens['bob'].address.toLowerCase(), 'none'] as const),
331-
...bobSepoliaTokens.map((token) => [token.tokens['bob-sepolia'].address.toLowerCase(), 'none'] as const),
354+
...bobTokens.map((token) => [token.tokens['bob'].address.toLowerCase(), 'bob'] as const),
355+
...bobSepoliaTokens.map((token) => [token.tokens['bob-sepolia'].address.toLowerCase(), 'bob'] as const),
332356
...segmentTokens.map((token) => [token.tokens['bob'].address.toLowerCase(), 'segment'] as const),
333357
...ionicTokens.map((token) => [token.tokens['bob'].address.toLowerCase(), 'ionic'] as const),
334358
...vedaTokens.map((token) => [token.tokens['bob'].address.toLowerCase(), 'veda'] as const),

sdk/src/gateway/types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export enum Chain {
1717
export enum ChainId {
1818
BOB = 60808,
1919
BOB_SEPOLIA = 808813,
20+
OPTIMISM = 10,
2021
}
2122

2223
/**
@@ -70,8 +71,17 @@ export interface Token {
7071
logoURI: string;
7172
}
7273

74+
export interface PointsIncentive {
75+
id: string;
76+
name: string;
77+
}
78+
7379
export interface EnrichedToken extends Token {
7480
tvl: number;
81+
apyBase: number;
82+
apyReward: number;
83+
rewardTokens: Token[];
84+
points: PointsIncentive[];
7585
}
7686

7787
/**
@@ -490,3 +500,17 @@ export interface StrategyAssetState {
490500
address: Address | 'usd';
491501
totalUnderlying: bigint;
492502
}
503+
504+
/** @dev Internal */
505+
506+
export interface DefiLlamaPool {
507+
pool: string;
508+
chain: string;
509+
project: string;
510+
tvlUsd: number;
511+
apy: number | null;
512+
apyBase: number | null;
513+
apyReward: number | null;
514+
underlyingTokens: null | string[];
515+
rewardTokens: null | string[];
516+
}

0 commit comments

Comments
 (0)