Skip to content

Commit

Permalink
feat(plutus): Add new locks, split plsJONES and plsDPX groups, add cl…
Browse files Browse the repository at this point in the history
…aimables (Zapper-fi#939)
  • Loading branch information
immasandwich committed Jul 24, 2022
1 parent 615691e commit 7c137e2
Show file tree
Hide file tree
Showing 16 changed files with 1,385 additions and 93 deletions.
4 changes: 3 additions & 1 deletion src/apps/plutus/arbitrum/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ export const VAULTS = {
DPX_VAULT: '0x20df4953ba19c74b2a46b6873803f28bf640c1b5',
JONES_VAULT: '0x23b87748b615096d1a0f48870daee203a720723d',
PLS_VAULT: '0x5593473e318f0314eb2518239c474e183c4cbed5',
PLS_LOCK: '0xbeb981021ed9c85aa51d96c0c2eda10ee4404a2e',
PLS_LOCK_1_MONTH: '0x27aaa9d562237bf8e024f9b21de177e20ae50c05',
PLS_LOCK_3_MONTH: '0xe59dadf5f7a9decb8337402ccdf06abe5c0b2b3e',
PLS_LOCK_6_MONTH: '0xbeb981021ed9c85aa51d96c0c2eda10ee4404a2e',
};

export const ADDRESSES = {
Expand Down
97 changes: 85 additions & 12 deletions src/apps/plutus/arbitrum/plutus.balance-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Inject } from '@nestjs/common';
import { BigNumber } from 'bignumber.js';
import { range } from 'lodash';

import { drillBalance } from '~app-toolkit';
import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { ZERO_ADDRESS } from '~app-toolkit/constants/address';
import { Register } from '~app-toolkit/decorators';
import { presentBalanceFetcherResponse } from '~app-toolkit/helpers/presentation/balance-fetcher-response.present';
import { BalanceFetcher } from '~balance/balance-fetcher.interface';
Expand All @@ -27,12 +30,21 @@ export class ArbitrumPlutusBalanceFetcher implements BalanceFetcher {
@Inject(PlutusContractFactory) private readonly contractFactory: PlutusContractFactory,
) {}

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

async getPlsJonesTokenAddresses(address: string) {
return this.appToolkit.helpers.tokenBalanceHelper.getTokenBalances({
address,
network,
appId,
groupId: PLUTUS_DEFINITION.groups.plsJones.id,
});
}

Expand All @@ -48,7 +60,62 @@ export class ArbitrumPlutusBalanceFetcher implements BalanceFetcher {
.wrap(contract)
.stakedDetails(address)
.then(details => details.amount),
resolveRewardTokenBalances: () => [], // TODO: implement
resolveRewardTokenBalances: async ({ contract, address, contractPosition, multicall, network }) => {
const plsDpx = contractPosition.tokens.find(v => v.symbol === 'plsDPX');
const plsJones = contractPosition.tokens.find(v => v.symbol === 'plsJONES');
if (!plsDpx || !plsJones) return [];

const rewardsAddress = await multicall.wrap(contract).stakingRewards();
if (rewardsAddress === ZERO_ADDRESS) return [];

const rewardsContract = this.contractFactory.plutusEpochStakingRewardsRolling({
address: rewardsAddress,
network,
});

const currentEpoch = await multicall.wrap(contract).currentEpoch();
const epochsToClaim = range(0, Number(currentEpoch) - 1); // 1 based
const claimAmounts = await Promise.all(
epochsToClaim.map(async epoch => {
const EPOCH_DURATION = 2_628_000; // seconds

const rewardsForEpoch = await multicall.wrap(rewardsContract).epochRewards(epoch);
const claimDetails = await multicall.wrap(rewardsContract).claimDetails(address, epoch);
const userPlsDpxShare = await multicall
.wrap(rewardsContract)
.calculateShare(address, epoch, rewardsForEpoch.plsDpx);
const userPlsJonesShare = await multicall
.wrap(rewardsContract)
.calculateShare(address, epoch, rewardsForEpoch.plsJones);
if (Number(userPlsDpxShare) === 0 && Number(userPlsJonesShare) === 0) return [];

const now = Date.now() / 1000;
const vestedDuration =
claimDetails.lastClaimedTimestamp > rewardsForEpoch.addedAtTimestamp
? now - claimDetails.lastClaimedTimestamp
: now - rewardsForEpoch.addedAtTimestamp;

const claimablePlsDpx = BigNumber.min(
new BigNumber(userPlsDpxShare.toString()).times(vestedDuration).div(EPOCH_DURATION),
new BigNumber(userPlsDpxShare.toString()).minus(claimDetails.plsDpxClaimedAmt.toString()),
);

const claimablePlsJones = BigNumber.min(
new BigNumber(userPlsJonesShare.toString()).times(vestedDuration).div(EPOCH_DURATION),
new BigNumber(userPlsJonesShare.toString()).minus(claimDetails.plsJonesClaimedAmt.toString()),
);

return [claimablePlsDpx, claimablePlsJones];
}),
);

const amounts = claimAmounts.reduce(
(acc, amounts) => [acc[0].plus(amounts[0]), acc[1].plus(amounts[1])],
[new BigNumber(0), new BigNumber(0)],
);

return [amounts[0].toFixed(0), amounts[1].toFixed(0)];
},
});
}

Expand Down Expand Up @@ -136,18 +203,24 @@ export class ArbitrumPlutusBalanceFetcher implements BalanceFetcher {
}

async getBalances(address: string) {
const [tokenBalances, lockedBalances, dpxBalances, jonesBalances, plsBalances] = await Promise.all([
this.getTokenBalances(address),
this.getLockedBalances(address),
this.getStakedDPXBalances(address),
this.getStakedJonesBalances(address),
this.getStakedPlsBalances(address),
]);
const [plsDpxTokenBalances, plsJonesTokenBalances, lockedBalances, dpxBalances, jonesBalances, plsBalances] =
await Promise.all([
this.getPlsDpxTokenBalances(address),
this.getPlsJonesTokenAddresses(address),
this.getLockedBalances(address),
this.getStakedDPXBalances(address),
this.getStakedJonesBalances(address),
this.getStakedPlsBalances(address),
]);

return presentBalanceFetcherResponse([
{
label: 'Tokens',
assets: tokenBalances,
label: 'plsDPX',
assets: plsDpxTokenBalances,
},
{
label: 'plsJONES',
assets: plsJonesTokenBalances,
},
{
label: 'Locked',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,13 @@ export class ArbitrumPlutusDpxContractPositionFetcher implements PositionFetcher
appId,
groupId,
network,
dependencies: [{ appId, groupIds: [PLUTUS_DEFINITION.groups.ve.id], network }],
dependencies: [
{
appId: PLUTUS_DEFINITION.id,
groupIds: [PLUTUS_DEFINITION.groups.plsDpx.id, PLUTUS_DEFINITION.groups.plsJones.id],
network,
},
],
resolveContract: opts => this.contractFactory.plsDpxPlutusChef(opts),
resolvePoolLength: async () => 1,
resolveDepositTokenAddress: async ({ multicall, contract }) => multicall.wrap(contract).plsDpx(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export class ArbitrumPlutusJonesContractPositionFetcher implements PositionFetch
appId,
groupId,
network,
dependencies: [{ appId, groupIds: [PLUTUS_DEFINITION.groups.ve.id], network }],
dependencies: [
{ appId, groupIds: [PLUTUS_DEFINITION.groups.plsDpx.id, PLUTUS_DEFINITION.groups.plsJones.id], network },
],
resolveContract: opts => this.contractFactory.plsJonesPlutusChef(opts),
resolvePoolLength: async () => 1,
resolveDepositTokenAddress: async ({ multicall, contract }) => multicall.wrap(contract).plsJones(),
Expand Down
33 changes: 31 additions & 2 deletions src/apps/plutus/arbitrum/plutus.lock.contract-position-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Inject } from '@nestjs/common';

import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { ZERO_ADDRESS } from '~app-toolkit/constants/address';
import { Register } from '~app-toolkit/decorators';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
Expand All @@ -15,6 +16,12 @@ const appId = PLUTUS_DEFINITION.id;
const groupId = PLUTUS_DEFINITION.groups.lock.id;
const network = Network.ARBITRUM_MAINNET;

const labels = {
[VAULTS.PLS_LOCK_1_MONTH]: 'PLS 1 Month Lock',
[VAULTS.PLS_LOCK_3_MONTH]: 'PLS 3 Month Lock',
[VAULTS.PLS_LOCK_6_MONTH]: 'PLS 6 Month Lock',
};

@Register.ContractPositionFetcher({ appId, groupId, network })
export class ArbitrumPlutusLockContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
Expand All @@ -28,13 +35,35 @@ export class ArbitrumPlutusLockContractPositionFetcher implements PositionFetche
network,
appId,
groupId,
resolveFarmAddresses: () => [VAULTS.PLS_LOCK],
dependencies: [
{
appId: PLUTUS_DEFINITION.id,
groupIds: [PLUTUS_DEFINITION.groups.plsDpx.id, PLUTUS_DEFINITION.groups.plsJones.id],
network,
},
],
resolveLabel: (address: string) => labels[address],
resolveFarmAddresses: () => [VAULTS.PLS_LOCK_1_MONTH, VAULTS.PLS_LOCK_3_MONTH, VAULTS.PLS_LOCK_6_MONTH],
resolveFarmContract: ({ address, network }) =>
this.plutusContractFactory.plutusEpochStaking({ address, network }),
resolveStakedTokenAddress: ({ contract, multicall }) => multicall.wrap(contract).pls(),
resolveRewardTokenAddresses: ({ contract, multicall }) => multicall.wrap(contract).stakingRewards(),
resolveRewardTokenAddresses: async ({ contract, multicall }) => {
const stakingRewardsAddress = await multicall.wrap(contract).stakingRewards();
if (stakingRewardsAddress === ZERO_ADDRESS) return [];

const stakingRewardsContract = this.plutusContractFactory.plutusEpochStakingRewardsRolling({
address: stakingRewardsAddress,
network,
});

return Promise.all([
multicall.wrap(stakingRewardsContract).plsDPX(),
multicall.wrap(stakingRewardsContract).plsJONES(),
]);
},
resolveRois: async () => ({ dailyROI: 0, weeklyROI: 0, yearlyROI: 0 }),
});

return positions;
}
}
73 changes: 73 additions & 0 deletions src/apps/plutus/arbitrum/plutus.pls-dpx.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present';
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 { PlutusContractFactory } from '../contracts';
import PLUTUS_DEFINITION from '../plutus.definition';

const appId = PLUTUS_DEFINITION.id;
const groupId = PLUTUS_DEFINITION.groups.plsDpx.id;
const network = Network.ARBITRUM_MAINNET;

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

async getPositions() {
const multicall = this.appToolkit.getMulticall(network);
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const dpxToken = baseTokens.find(v => v.address === '0x6c2c06790b3e3e3c38e12ee22f8183b37a13ee55');
if (!dpxToken) return [];

const plsDpxAddress = '0xf236ea74b515ef96a9898f5a4ed4aa591f253ce1';
const contract = this.plutusContractFactory.erc20({ address: plsDpxAddress, network });
const [symbol, decimals, supplyRaw] = await Promise.all([
multicall.wrap(contract).symbol(),
multicall.wrap(contract).decimals(),
multicall.wrap(contract).totalSupply(),
]);

const supply = Number(supplyRaw) / 10 ** decimals;
const price = dpxToken.price; // @TODO Use pool price when peg pools are live
const pricePerShare = 1;
const liquidity = price * supply;
const tokens = [dpxToken];

const token: AppTokenPosition = {
type: ContractType.APP_TOKEN,
address: plsDpxAddress,
network,
appId,
groupId,
symbol,
decimals,
supply,
price,
pricePerShare,
tokens,
dataProps: {},
displayProps: {
label: symbol,
images: getImagesFromToken(dpxToken),
statsItems: [
{
label: 'Liquidity',
value: buildDollarDisplayItem(liquidity),
},
],
},
};

return [token];
}
}
73 changes: 73 additions & 0 deletions src/apps/plutus/arbitrum/plutus.pls-jones.token-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present';
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 { PlutusContractFactory } from '../contracts';
import PLUTUS_DEFINITION from '../plutus.definition';

const appId = PLUTUS_DEFINITION.id;
const groupId = PLUTUS_DEFINITION.groups.plsJones.id;
const network = Network.ARBITRUM_MAINNET;

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

async getPositions() {
const multicall = this.appToolkit.getMulticall(network);
const appTokens = await this.appToolkit.getAppTokenPositions({ appId: 'sushiswap', groupIds: ['pool'], network });
const jonesEthToken = appTokens.find(v => v.address === '0xe8ee01ae5959d3231506fcdef2d5f3e85987a39c');
if (!jonesEthToken) return [];

const plsJonesAddress = '0xe7f6c3c1f0018e4c08acc52965e5cbff99e34a44';
const contract = this.plutusContractFactory.erc20({ address: plsJonesAddress, network });
const [symbol, decimals, supplyRaw] = await Promise.all([
multicall.wrap(contract).symbol(),
multicall.wrap(contract).decimals(),
multicall.wrap(contract).totalSupply(),
]);

const supply = Number(supplyRaw) / 10 ** decimals;
const price = jonesEthToken.price; // @TODO Use pool price when peg pools are live
const pricePerShare = 1;
const liquidity = price * supply;
const tokens = [jonesEthToken];

const token: AppTokenPosition = {
type: ContractType.APP_TOKEN,
address: plsJonesAddress,
network,
appId,
groupId,
symbol,
decimals,
supply,
price,
pricePerShare,
tokens,
dataProps: {},
displayProps: {
label: symbol,
images: getImagesFromToken(jonesEthToken),
statsItems: [
{
label: 'Liquidity',
value: buildDollarDisplayItem(liquidity),
},
],
},
};

return [token];
}
}

0 comments on commit 7c137e2

Please sign in to comment.