Skip to content

Commit

Permalink
feat(steak-hut): Add SteakHut integration (Zapper-fi#890)
Browse files Browse the repository at this point in the history
  • Loading branch information
tonzgao authored and volt62 committed Aug 2, 2022
1 parent 6923a40 commit f51c02c
Show file tree
Hide file tree
Showing 21 changed files with 4,972 additions and 0 deletions.
Binary file added src/apps/steak-hut/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
91 changes: 91 additions & 0 deletions src/apps/steak-hut/avalanche/steak-hut.balance-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present';
import { BalanceFetcher } from '~balance/balance-fetcher.interface';
import { Network } from '~types/network.interface';

import { SteakHutContractFactory, SteakHutStaking, SteakHutPool } from '../contracts';
import { STEAK_HUT_DEFINITION } from '../steak-hut.definition';

const appId = STEAK_HUT_DEFINITION.id;
const network = Network.AVALANCHE_MAINNET;

@Register.BalanceFetcher(STEAK_HUT_DEFINITION.id, network)
export class AvalancheSteakHutBalanceFetcher implements BalanceFetcher {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(SteakHutContractFactory) private readonly contractFactory: SteakHutContractFactory,
) {}

private async getTokenBalances(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
appId,
groupId: STEAK_HUT_DEFINITION.groups.ve.id,
network,
address,
});
}

private async getStakedBalances(address: string) {
return this.appToolkit.helpers.singleStakingContractPositionBalanceHelper.getBalances<SteakHutStaking>({
appId,
network,
address,
groupId: STEAK_HUT_DEFINITION.groups.staking.id,
resolveContract: ({ address, network }) => this.contractFactory.steakHutStaking({ address, network }),
resolveStakedTokenBalance: ({ contract, address, multicall }) =>
multicall
.wrap(contract)
.userInfo(address)
.then(x => x.amount),
resolveRewardTokenBalances: ({ contract, address, multicall }) => multicall.wrap(contract).pendingTokens(address),
});
}

private async getPoolBalances(address: string) {
return this.appToolkit.helpers.masterChefContractPositionBalanceHelper.getBalances<SteakHutPool>({
address,
network,
appId,
groupId: STEAK_HUT_DEFINITION.groups.pool.id,
resolveChefContract: ({ contractAddress }) =>
this.contractFactory.steakHutPool({ network, address: contractAddress }),
resolveStakedTokenBalance: this.appToolkit.helpers.masterChefDefaultStakedBalanceStrategy.build({
resolveStakedBalance: ({ contract, multicall, contractPosition }) =>
multicall
.wrap(contract)
.userInfo(contractPosition.dataProps.poolIndex, address)
.then(v => v.amount),
}),
resolveClaimableTokenBalances: this.appToolkit.helpers.masterChefDefaultClaimableBalanceStrategy.build({
resolveClaimableBalance: ({ multicall, contract, contractPosition, address }) =>
multicall.wrap(contract).pendingJoe(contractPosition.dataProps.poolIndex, address),
}),
});
}

async getBalances(address: string) {
const [tokenBalances, stakedBalances, poolBalances] = await Promise.all([
this.getTokenBalances(address),
this.getStakedBalances(address),
this.getPoolBalances(address),
]);

return presentBalanceFetcherResponse([
{
label: 'Tokens',
assets: tokenBalances,
},
{
label: 'Staking',
assets: stakedBalances,
},
{
label: 'Pools',
assets: poolBalances,
},
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { TRADER_JOE_DEFINITION } from '~apps/trader-joe/trader-joe.definition';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { SteakHutContractFactory, SteakHutPool } from '../contracts';
import { STEAK_HUT_DEFINITION } from '../steak-hut.definition';

const appId = STEAK_HUT_DEFINITION.id;
const groupId = STEAK_HUT_DEFINITION.groups.pool.id;
const network = Network.AVALANCHE_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class AvalancheSteakHutPoolContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(SteakHutContractFactory) private readonly contractFactory: SteakHutContractFactory,
) {}

async getPositions() {
return this.appToolkit.helpers.masterChefContractPositionHelper.getContractPositions<SteakHutPool>({
address: '0xddBfBd5dc3BA0FeB96Cb513B689966b2176d4c09'.toLowerCase(),
appId,
groupId,
network,
dependencies: [{ appId: TRADER_JOE_DEFINITION.id, groupIds: [TRADER_JOE_DEFINITION.groups.pool.id], network }],
resolveContract: ({ address, network }) => this.contractFactory.steakHutPool({ address, network }),
resolvePoolLength: ({ multicall, contract }) => multicall.wrap(contract).poolLength(),
resolveDepositTokenAddress: ({ poolIndex, contract, multicall }) =>
multicall
.wrap(contract)
.poolInfo(poolIndex)
.then(v => v.lpToken),
resolveRewardTokenAddresses: ({ multicall, contract }) => multicall.wrap(contract).JOE(),
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Inject } from '@nestjs/common';
import { flatten } from 'lodash';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { TRADER_JOE_DEFINITION } from '~apps/trader-joe';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { SteakHutContractFactory, SteakHutStaking } from '../contracts';
import { STEAK_HUT_DEFINITION } from '../steak-hut.definition';

const appId = STEAK_HUT_DEFINITION.id;
const groupId = STEAK_HUT_DEFINITION.groups.staking.id;
const network = Network.AVALANCHE_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class AvalancheSteakHutStakingContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(SteakHutContractFactory) private readonly contractFactory: SteakHutContractFactory,
) {}

async getSingleStakingPosition(address: string) {
return this.appToolkit.helpers.singleStakingFarmContractPositionHelper.getContractPositions<SteakHutStaking>({
appId,
groupId,
network,
dependencies: [
{ appId, groupIds: [STEAK_HUT_DEFINITION.groups.ve.id], network },
{ appId: TRADER_JOE_DEFINITION.id, groupIds: [TRADER_JOE_DEFINITION.groups.xJoe.id], network },
],
resolveFarmAddresses: async () => [address],
resolveStakedTokenAddress: async ({ multicall, contract }) => multicall.wrap(contract).inputToken(),
resolveFarmContract: opts => this.contractFactory.steakHutStaking(opts),
resolveRewardTokenAddresses: async ({ multicall, contract }) => multicall.wrap(contract).rewardToken(),
resolveIsActive: async ({ multicall, contract }) => multicall.wrap(contract).isRewarderEnabled(),
resolveRois: () => ({ dailyROI: 0, weeklyROI: 0, yearlyROI: 0 }), // TODO: calculated based on tokenPerSec
});
}

async getHJoePositions() {
return this.getSingleStakingPosition('0x4E664284B7fbD10633768D59c17D959D9cB8deE2'.toLowerCase());
}

async getSteakPosition() {
return this.getSingleStakingPosition('0x1f6866E1A684247886545503F8E6e76e328aDE34'.toLowerCase());
}

async getPositions() {
const positions = await Promise.all([this.getHJoePositions(), this.getSteakPosition()]);
return flatten(positions);
}
}
68 changes: 68 additions & 0 deletions src/apps/steak-hut/avalanche/steak-hut.ve.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { ContractType } from '~position/contract.interface';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { AppTokenPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { SteakHutContractFactory } from '../contracts';
import { STEAK_HUT_DEFINITION } from '../steak-hut.definition';

const appId = STEAK_HUT_DEFINITION.id;
const groupId = STEAK_HUT_DEFINITION.groups.ve.id;
const network = Network.AVALANCHE_MAINNET;

const address = '0xe7250b05bd8DEE615ecC681eda1196aDD5156F2B'.toLowerCase();

// TODO: remove when this becomes a baseToken

@Register.TokenPositionFetcher({ appId, groupId, network })
export class AvalancheSteakHutVeTokenFetcher implements PositionFetcher<AppTokenPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(SteakHutContractFactory) private readonly contractFactory: SteakHutContractFactory,
) {}

async getPositions() {
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const multicall = this.appToolkit.getMulticall(network);
const contract = this.contractFactory.steakHutHjoe({ network, address });

const [symbol, decimals, supplyRaw, underlyingTokenAddressRaw] = await Promise.all([
multicall.wrap(contract).symbol(),
multicall.wrap(contract).decimals(),
multicall.wrap(contract).totalSupply(),
multicall.wrap(contract).JOE(),
]);

const supply = Number(supplyRaw) / 10 ** decimals;
const underlyingTokenAddress = underlyingTokenAddressRaw.toLowerCase();
const underlyingToken = baseTokens.find(v => v.address === underlyingTokenAddress)!;

const images = getImagesFromToken(underlyingToken);

const token: AppTokenPosition = {
type: ContractType.APP_TOKEN,
address,
network,
appId,
groupId,
symbol,
decimals,
supply,
price: underlyingToken.price,
pricePerShare: 1,
tokens: [underlyingToken],
dataProps: {},
displayProps: {
label: symbol,
images,
},
};

return [token];
}
}
Loading

0 comments on commit f51c02c

Please sign in to comment.