Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

feat(tenderize): ✨ add farm support #2219

Merged
merged 3 commits into from
Jan 31, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { PositionTemplate } from '~app-toolkit/decorators/position-template.decorator';

import { FarmContractPositionFetcher } from '../common/tenderize.farm.contract-position-fetcher';

@PositionTemplate()
export class ArbitrumTenderizeFarmTokenFetcher extends FarmContractPositionFetcher {
groupLabel = 'Farm';
}
2 changes: 2 additions & 0 deletions src/apps/tenderize/common/tenderize-token-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export type TenderizeTokenDefinition = {
address: string;
steak: string;
lpToken: string;
tenderToken: string;
tenderSwap: string;
tenderFarm: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { GetDataPropsParams, GetTokenBalancesParams } from '~position/template/contract-position.template.types';
import {
SingleStakingFarmDataProps,
SingleStakingFarmTemplateContractPositionFetcher,
} from '~position/template/single-staking.template.contract-position-fetcher';

import { TenderizeContractFactory, TenderFarm } from '../contracts';

import { TenderizeTokenDefinitionsResolver } from './tenderize.token-definition-resolver';

export abstract class FarmContractPositionFetcher extends SingleStakingFarmTemplateContractPositionFetcher<
TenderFarm,
SingleStakingFarmDataProps
> {
constructor(
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
@Inject(TenderizeTokenDefinitionsResolver)
private readonly tokenDefinitionsResolver: TenderizeTokenDefinitionsResolver,
@Inject(TenderizeContractFactory) protected readonly contractFactory: TenderizeContractFactory,
) {
super(appToolkit);
}

getContract(address: string): TenderFarm {
return this.contractFactory.tenderFarm({ network: this.network, address });
}

async getFarmDefinitions() {
const definitions = await this.tokenDefinitionsResolver.getTokenDefinitions(this.network);
return definitions.map(v => ({
address: v.tenderFarm,
stakedTokenAddress: v.lpToken,
rewardTokenAddresses: [v.tenderToken],
}));
}

async getStakedTokenBalance({ address, contract }: GetTokenBalancesParams<TenderFarm, SingleStakingFarmDataProps>) {
return contract.stakeOf(address);
}

async getRewardTokenBalances({ address, contract }: GetTokenBalancesParams<TenderFarm, SingleStakingFarmDataProps>) {
return contract.availableRewards(address);
}

async getRewardRates({ address }: GetDataPropsParams<TenderFarm, SingleStakingFarmDataProps>) {
return [await this.tokenDefinitionsResolver.getRewardRate(this.network, address)];
}
}
8 changes: 4 additions & 4 deletions src/apps/tenderize/common/tenderize.swap.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { DefaultDataProps } from '~position/display.interface';
import { AppTokenTemplatePositionFetcher } from '~position/template/app-token.template.position-fetcher';
import {
GetUnderlyingTokensParams,
Expand Down Expand Up @@ -35,7 +34,8 @@ export abstract class SwapTokenFetcher extends AppTokenTemplatePositionFetcher<
}

async getDefinitions(): Promise<TenderizeTokenDefinition[]> {
return this.tokenDefinitionsResolver.getTokenDefinitions(this.network);
const definitions = await this.tokenDefinitionsResolver.getTokenDefinitions(this.network);
return definitions.map(v => ({ ...v, address: v.lpToken }));
}

async getAddresses({ definitions }: GetAddressesParams<TenderizeTokenDefinition>): Promise<string[]> {
Expand All @@ -47,7 +47,7 @@ export abstract class SwapTokenFetcher extends AppTokenTemplatePositionFetcher<
}: GetUnderlyingTokensParams<TenderToken, TenderizeTokenDefinition>) {
return [
{ address: definition.steak, network: this.network },
{ address: definition.address, network: this.network },
{ address: definition.tenderToken, network: this.network },
];
}

Expand All @@ -57,7 +57,7 @@ export abstract class SwapTokenFetcher extends AppTokenTemplatePositionFetcher<

async getPrice({
appToken,
}: GetPriceParams<TenderToken, DefaultDataProps, TenderizeTokenDefinition>): Promise<number> {
}: GetPriceParams<TenderToken, DefaultAppTokenDataProps, TenderizeTokenDefinition>): Promise<number> {
return appToken.tokens[0].price;
}

Expand Down
5 changes: 3 additions & 2 deletions src/apps/tenderize/common/tenderize.tender.token-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ export abstract class TenderTokenFetcher extends AppTokenTemplatePositionFetcher
}

async getDefinitions(): Promise<TenderizeTokenDefinition[]> {
return this.tokenDefinitionsResolver.getTokenDefinitions(this.network);
const definitions = await this.tokenDefinitionsResolver.getTokenDefinitions(this.network);
return definitions.map(v => ({ ...v, address: v.tenderToken }));
}

async getAddresses({ definitions }: GetAddressesParams<TenderizeTokenDefinition>): Promise<string[]> {
return definitions.map(v => v.address);
return definitions.map(v => v.tenderToken);
}

async getUnderlyingTokenDefinitions({
Expand Down
39 changes: 38 additions & 1 deletion src/apps/tenderize/common/tenderize.token-definition-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export type TenderTokenFetcherResponse = {
symbol: string;
steak: string;
tenderSwap: string;
tenderFarm: string;
}[];
};

export type RewardRateFetcherResponse = {
rewardsAddedEvents: {
amount: string;
timestamp: string;
}[];
};

Expand All @@ -35,6 +43,7 @@ export const TOKEN_QUERY = gql`
symbol
steak
tenderSwap
tenderFarm
}
}
`;
Expand Down Expand Up @@ -62,10 +71,11 @@ export class TenderizeTokenDefinitionsResolver {
const tokenDefinitions = definitionsData.configs.map(token => {
return {
id: token.id.toLowerCase(),
address: token.tenderToken.toLowerCase(),
steak: token.steak.toLowerCase(),
lpToken: token.lpToken.toLowerCase(),
tenderToken: token.tenderToken.toLowerCase(),
tenderSwap: token.tenderSwap.toLowerCase(),
tenderFarm: token.tenderFarm.toLowerCase(),
};
});
return tokenDefinitions;
Expand All @@ -88,4 +98,31 @@ export class TenderizeTokenDefinitionsResolver {

return Number(apyRaw);
}

@Cache({
key: (network, tenderFarm) => `studio:tenderize:${network}:${tenderFarm}-reward-data`,
ttl: 5 * 60, // 5 minutes
})
private async getRewardRateData(network: Network, tenderFarm: string) {
return gqlFetch<RewardRateFetcherResponse>({
endpoint: `https://api.thegraph.com/subgraphs/name/tenderize/tenderize-${network}`,
variables: { tenderFarm },
query: gql`
query ($tenderFarm: String!) {
rewardsAddedEvents(where: { tenderFarm: $tenderFarm }, first: 2, orderBy: timestamp, orderDirection: desc) {
amount
timestamp
}
}
`,
});
}

async getRewardRate(network: Network, tenderFarm: string) {
const { rewardsAddedEvents } = await this.getRewardRateData(network, tenderFarm.toLowerCase());
if (rewardsAddedEvents.length != 2) return 0;

const [{ amount, timestamp }, { timestamp: prevTimestamp }] = rewardsAddedEvents;
return BigInt(amount) / (BigInt(timestamp) - BigInt(prevTimestamp));
}
}
192 changes: 192 additions & 0 deletions src/apps/tenderize/contracts/abis/tender-farm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
[
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "account", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "Farm",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "account", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "Harvest",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }],
"name": "RewardsAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "account", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "Unfarm",
"type": "event"
},
{
"inputs": [],
"name": "CRF",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }],
"name": "addRewards",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "_for", "type": "address" }],
"name": "availableRewards",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }],
"name": "farm",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_for", "type": "address" },
{ "internalType": "uint256", "name": "_amount", "type": "uint256" }
],
"name": "farmFor",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
{ "internalType": "uint256", "name": "_deadline", "type": "uint256" },
{ "internalType": "uint8", "name": "_v", "type": "uint8" },
{ "internalType": "bytes32", "name": "_r", "type": "bytes32" },
{ "internalType": "bytes32", "name": "_s", "type": "bytes32" }
],
"name": "farmWithPermit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ "inputs": [], "name": "harvest", "outputs": [], "stateMutability": "nonpayable", "type": "function" },
{
"inputs": [
{ "internalType": "contract IERC20", "name": "_stakeToken", "type": "address" },
{ "internalType": "contract ITenderToken", "name": "_rewardToken", "type": "address" },
{ "internalType": "contract ITenderizer", "name": "_tenderizer", "type": "address" }
],
"name": "initialize",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "nextTotalStake",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardToken",
"outputs": [{ "internalType": "contract ITenderToken", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_token", "type": "address" },
{ "internalType": "uint256", "name": "_value", "type": "uint256" },
{ "internalType": "uint256", "name": "_deadline", "type": "uint256" },
{ "internalType": "uint8", "name": "_v", "type": "uint8" },
{ "internalType": "bytes32", "name": "_r", "type": "bytes32" },
{ "internalType": "bytes32", "name": "_s", "type": "bytes32" }
],
"name": "selfPermit",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_token", "type": "address" },
{ "internalType": "uint256", "name": "_value", "type": "uint256" },
{ "internalType": "uint256", "name": "_deadline", "type": "uint256" },
{ "internalType": "uint8", "name": "_v", "type": "uint8" },
{ "internalType": "bytes32", "name": "_r", "type": "bytes32" },
{ "internalType": "bytes32", "name": "_s", "type": "bytes32" }
],
"name": "selfPermitIfNecessary",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [{ "internalType": "contract ITenderizer", "name": "_tenderizer", "type": "address" }],
"name": "setTenderizer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "_of", "type": "address" }],
"name": "stakeOf",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "stakes",
"outputs": [
{ "internalType": "uint256", "name": "stake", "type": "uint256" },
{ "internalType": "uint256", "name": "lastCRF", "type": "uint256" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tenderizer",
"outputs": [{ "internalType": "contract ITenderizer", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token",
"outputs": [{ "internalType": "contract IERC20", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalStake",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }],
"name": "unfarm",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
Loading