Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mabeets apr from hh #1468

Draft
wants to merge 1 commit into
base: v3-canary
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chilled-years-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'backend': patch
---

add HH mabeets apr
3 changes: 2 additions & 1 deletion modules/network/sonic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import config from '../../config';
import { GithubContentService } from '../content/github-content.service';
import { ReliquaryFarmAprService } from '../pool/lib/apr-data-sources/fantom/reliquary-farm-apr.service';
import { UserSyncReliquaryFarmBalanceService } from '../user/lib/user-sync-reliquary-farm-balance.service';
import { MaBeetsVotingAprService } from '../pool/lib/apr-data-sources/mabeets-voting-apr.service';

const sonicNetworkData: NetworkData = config.SONIC;

Expand All @@ -25,7 +26,7 @@ export const sonicNetworkConfig: NetworkConfig = {
new SwapFeeAprService(),
new GaugeAprService(),
new ReliquaryFarmAprService(sonicNetworkData.beets!.address),
// new BeetswarsGaugeVotingAprService(),
new MaBeetsVotingAprService(),
],
userStakedBalanceServices: [
new UserSyncGaugeBalanceService(),
Expand Down
142 changes: 142 additions & 0 deletions modules/pool/lib/apr-data-sources/mabeets-voting-apr.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { PoolAprService } from '../../pool-types';
import { PrismaPoolWithTokens } from '../../../../prisma/prisma-types';
import { Chain } from '@prisma/client';
import { prisma } from '../../../../prisma/prisma-client';

const HIDDEN_HAND_API_URL = 'https://api.hiddenhand.finance/proposal/beets';
const FRESH_BEETS = '0x10ac2f9dae6539e77e372adb14b1bf8fbd16b3e8';

const freshBeetsPool = '0x10ac2f9dae6539e77e372adb14b1bf8fbd16b3e8000200000000000000000005';
const id = `${freshBeetsPool}-voting-apr`;
const chain = 'SONIC';

type HiddenHandResponse = {
error: boolean;
data: {
poolId: string;
proposal: string;
proposalHash: string;
title: string;
proposalDeadline: number;
totalValue: number;
maxTotalValue: number;
voteCount: number;
valuePerVote: number;
maxValuePerVote: number;
bribes: {
token: string;
symbol: string;
decimals: number;
value: number;
maxValue: number;
amount: number;
maxTokensPerVote: number;
briber: string;
periodIndex: number;
chainId: number;
}[];
}[];
};

const fetchHiddenHandRound = async (timestamp?: number) => {
const response = await fetch(`${HIDDEN_HAND_API_URL}/${timestamp || ''}`);
const data = (await response.json()) as HiddenHandResponse;
if (data.error) {
throw new Error('Failed to fetch voting APR');
}

// Get sum of all incentivized votes and total value
const total = data.data.reduce((acc, proposal) => acc + proposal.totalValue, 0);
const votes = data.data
.filter((proposal) => proposal.totalValue > 0)
.reduce((acc, proposal) => acc + proposal.voteCount, 0);

return { total, votes, timestamp: data.data[0].proposalDeadline };
};

export const getHiddenHandAPR = async (timestamp: number) => {
const round = await fetchHiddenHandRound(timestamp);

// Debugging purposes
console.log('Hiddenhand round', timestamp, round.timestamp, round.total, round.votes);

timestamp = round.timestamp;

const avgValuePerVote = round.total / round.votes;

let freshBeetsPrice;
// When the timestamp is older than 24 hours, we can fetch the historical price
if (timestamp < Math.ceil(+Date.now() / 1000) - 86400) {
freshBeetsPrice = await prisma.prismaTokenPrice.findFirst({
where: {
tokenAddress: FRESH_BEETS,
chain: Chain.SONIC,
timestamp,
},
});
}
// Otherwise we fetch the current price
else {
freshBeetsPrice = await prisma.prismaTokenCurrentPrice.findFirst({
where: {
tokenAddress: FRESH_BEETS,
chain: Chain.SONIC,
},
});
}

if (!freshBeetsPrice) {
throw new Error('Failed to fetch fresh beets price');
}

const apr = (avgValuePerVote * 52) / freshBeetsPrice.price;

return apr;
};

export class MaBeetsVotingAprService implements PoolAprService {
constructor() {}

public getAprServiceName(): string {
return 'MaBeetsVotingAprService';
}

async getApr(): Promise<number> {
// Get APRs for last 6 weeks, if available
const timestamp = (await fetchHiddenHandRound()).timestamp;

const aprs = await Promise.allSettled([
getHiddenHandAPR(timestamp - 1 * 604800 * 2),
getHiddenHandAPR(timestamp - 2 * 604800 * 2),
getHiddenHandAPR(timestamp - 3 * 604800 * 2),
]);

// Average successfully fetched APRs
const avg = aprs
.filter((apr): apr is PromiseFulfilledResult<number> => apr.status === 'fulfilled')
.map((apr) => apr.value);

if (avg.length === 0) {
throw new Error('Failed to fetch APRs');
}

return avg.reduce((acc, val) => acc + val, 0) / avg.length;
}

async updateAprForPools(pools: PrismaPoolWithTokens[]): Promise<void> {
const apr = await this.getApr();

await prisma.prismaPoolAprItem.upsert({
where: { id_chain: { id, chain } },
create: {
id,
chain,
poolId: freshBeetsPool,
apr,
title: 'Voting APR',
type: 'VOTING',
},
update: { apr },
});
}
}
Loading