Skip to content

Commit

Permalink
Merge pull request #6 from Gearbox-protocol/points
Browse files Browse the repository at this point in the history
feat: points
  • Loading branch information
essserrr authored Jan 9, 2025
2 parents 606c37e + 813eac3 commit 31d1bf5
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 57 deletions.
88 changes: 58 additions & 30 deletions src/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { isAddress } from "viem";

import type { GearAPY } from "./apy";
import type { ApyDetails, Fetcher } from "./fetcher";
import { isSupportedNetwork } from "./utils";
import type { PointsInfo } from "./points/constants";
import { isSupportedNetwork, toJSONWithBigint } from "./utils";

interface Response {
status: string;
Expand All @@ -17,6 +18,7 @@ interface OutputDetails {
symbol: string;
rewards: {
apy: Array<ApyDetails>;
points: Array<PointsInfo>;
};
}

Expand All @@ -31,23 +33,22 @@ export async function getByChainAndToken(req: any, res: any, fetcher: Fetcher) {
if (!checkResp(isTokenValid, res)) {
return;
}

const a = fetcher.cache[chainId]?.apyList?.[tokenAddress as Address];
const p = fetcher.cache[chainId]?.pointsList?.[tokenAddress as Address];

const data: OutputDetails = {
chainId: chainId,
address: tokenAddress.toLowerCase(),
symbol: "",
symbol: a?.symbol || p?.symbol || "",
rewards: {
apy: [],
apy: a.apys || [],
points: p ? [p] : [],
},
};

const d = fetcher.cache[chainId]?.tokens?.[tokenAddress as Address];
if (d) {
data.rewards.apy = d.apys;
data.symbol = d.symbol;
}

res.set({ "Content-Type": "application/json" });
res.send(JSON.stringify({ data: data, status: "ok" } as Response));
res.send(toJSONWithBigint({ data: data, status: "ok" } as Response));
//
}

Expand All @@ -56,21 +57,45 @@ export async function getAll(req: any, res: any, fetcher: Fetcher) {
if (!checkResp(isChainIdValid, res)) {
return;
}
const data: Array<OutputDetails> = [];

Object.entries(fetcher.cache[chainId]?.tokens).forEach(([token, apy]) => {
data.push({
const data = Object.entries(fetcher.cache[chainId]?.apyList || {}).reduce<
Record<Address, OutputDetails>
>((acc, [t, a]) => {
acc[t as Address] = {
chainId: chainId,
address: token,
symbol: apy.symbol,
address: t,
symbol: a.symbol,
rewards: {
apy: apy.apys,
apy: a.apys,
points: [],
},
});
};

return acc;
}, {});

Object.entries(fetcher.cache[chainId]?.pointsList || {}).forEach(([t, p]) => {
const token = t as Address;

if (data[token]) {
data[token].rewards.points.push(p);
} else {
data[token] = {
chainId: chainId,
address: t,
symbol: p.symbol,
rewards: {
apy: [],
points: [p],
},
};
}
});

res.set({ "Content-Type": "application/json" });
res.send(JSON.stringify({ data: data, status: "ok" } as Response));
res.send(
toJSONWithBigint({ data: Object.values(data), status: "ok" } as Response),
);
}

export async function getRewardList(req: any, res: any, fetcher: Fetcher) {
Expand All @@ -83,27 +108,30 @@ export async function getRewardList(req: any, res: any, fetcher: Fetcher) {
res,
);
}
const [isTokenList, tokenList] = checkTokenList(JSON.stringify(req.body));
const [isTokenList, tokenList] = checkTokenList(toJSONWithBigint(req.body));
if (!checkResp(isTokenList, res)) {
return;
}

const data: Array<OutputDetails> = [];
for (const entry of tokenList) {
const apys =
fetcher.cache[entry.chain_id]?.tokens?.[entry.token_address as Address];
for (const t of tokenList) {
const a = fetcher.cache[t.chain_id]?.apyList?.[t.token_address as Address];
const p =
fetcher.cache[t.chain_id]?.pointsList?.[t.token_address as Address];

data.push({
chainId: entry.chain_id,
address: entry.token_address.toLowerCase(),
symbol: apys.symbol,
chainId: t.chain_id,
address: t.token_address.toLowerCase(),
symbol: a.symbol,
rewards: {
apy: apys.apys,
apy: a.apys || [],
points: p ? [p] : [],
},
});
}

res.set({ "Content-Type": "application/json" });
res.send(JSON.stringify({ data: data, status: "ok" } as Response));
res.send(toJSONWithBigint({ data: data, status: "ok" } as Response));
}

export async function getGearAPY(req: any, res: any, fetcher: Fetcher) {
Expand All @@ -114,7 +142,7 @@ export async function getGearAPY(req: any, res: any, fetcher: Fetcher) {

res.set({ "Content-Type": "application/json" });
res.send(
JSON.stringify({
toJSONWithBigint({
data: fetcher.cache[chainId]?.gear,
status: "ok",
} as Response),
Expand Down Expand Up @@ -157,13 +185,13 @@ function checkTokenAddress(data: any): [Response, string] {
"",
];
}
return [{ status: "ok" }, (notUndefined as Address).toString()];
return [{ status: "ok" }, notUndefined.toString()];
}

export function checkResp(res: Response, out: any): boolean {
if (res.status === "error") {
out.set({ "Content-Type": "application/json" });
out.send(JSON.stringify(res));
out.send(toJSONWithBigint(res));
return false;
}
return true;
Expand Down
76 changes: 50 additions & 26 deletions src/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,24 @@ import {
getAPYYearn,
getGearAPY,
} from "./apy";
import { getPoints } from "./points";
import type { PointsInfo } from "./points/constants";
import type { Apy, APYResult, NetworkType, TokenAPY } from "./utils";
import { getChainId, supportedChains } from "./utils";

export type ApyDetails = Apy & { lastUpdated: string };
type TokenDetails = TokenAPY<ApyDetails>;

interface NetworkState {
tokens: Record<Address, TokenDetails>;
apyList: Record<Address, TokenDetails>;
pointsList: Record<Address, PointsInfo>;
gear: GearAPY;
}

function log(
network: NetworkType,
allProtocolAPYs: Array<PromiseSettledResult<APYResult>>,
pointsList: PromiseSettledResult<Record<Address, PointsInfo>>,
gearAPY: PromiseSettledResult<GearAPY>,
) {
const list = allProtocolAPYs.map(apyRes => {
Expand All @@ -52,6 +56,16 @@ function log(
} else {
console.log(`Gear error: ${gearAPY.reason}`);
}

if (pointsList.status === "fulfilled") {
console.log(
`Fetched points for ${Object.values(pointsList.value)
.map(p => p.symbol)
.join(", ")} for ${network}`,
);
} else {
console.log(`Points error: ${pointsList.reason}`);
}
}

export class Fetcher {
Expand All @@ -62,8 +76,11 @@ export class Fetcher {
}

private async getNetworkState(network: NetworkType): Promise<NetworkState> {
const [gearAPY, ...allProtocolAPYs] = await Promise.allSettled([
const [gearAPY, points, ...allProtocolAPYs] = await Promise.allSettled([
getGearAPY(network),

getPoints(network),

getAPYCurve(network),
getAPYEthena(network),
getAPYLama(network),
Expand All @@ -72,39 +89,46 @@ export class Fetcher {
getAPYYearn(network),
getAPYConstant(network),
]);
log(network, allProtocolAPYs, gearAPY);
log(network, allProtocolAPYs, points, gearAPY);

const result: Record<Address, TokenDetails> = {};
const time = moment().utc().format();

allProtocolAPYs.forEach(apyRes => {
if (apyRes.status === "fulfilled") {
Object.entries(apyRes.value).forEach(([addr, tokenAPY]) => {
const address = addr.toLowerCase() as Address;

const apyList = tokenAPY?.apys.map(
({ reward, ...rest }): ApyDetails => ({
...rest,
lastUpdated: time,
reward: reward.toLowerCase() as Address,
}),
);

result[address] = {
...tokenAPY,
address,
apys: [...(result[address]?.apys || []), ...apyList],
};
});
}
});
const apyList = allProtocolAPYs.reduce<Record<Address, TokenDetails>>(
(acc, apyRes) => {
if (apyRes.status === "fulfilled") {
Object.entries(apyRes.value).forEach(([addr, tokenAPY]) => {
const address = addr.toLowerCase() as Address;

const apyList = tokenAPY?.apys.map(
({ reward, ...rest }): ApyDetails => ({
...rest,
lastUpdated: time,
reward: reward.toLowerCase() as Address,
}),
);

acc[address] = {
...tokenAPY,
address,
apys: [...(acc[address]?.apys || []), ...apyList],
};
});
}

return acc;
},
{},
);

const pointsList = points.status === "fulfilled" ? points.value : {};

return {
gear:
gearAPY.status === "fulfilled"
? gearAPY.value
: { base: 0, crv: 0, gear: 0 },
tokens: result,
apyList,
pointsList,
};
}

Expand Down
Loading

0 comments on commit 31d1bf5

Please sign in to comment.