From 3990451977f815d07de2695a0baee7bfa4d51755 Mon Sep 17 00:00:00 2001 From: QYuQianchen <10935300+QYuQianchen@users.noreply.github.com> Date: Thu, 23 Sep 2021 16:32:18 +0200 Subject: [PATCH] [hopr-staking] Hopr staking (#76) * Add strategies for staking program and wx/x/-/hopr tokens. Work despite network (1 or 100) * Keep only staking strategy --- src/strategies/hopr-staking/examples.json | 60 +++++++++++++ src/strategies/hopr-staking/index.ts | 104 ++++++++++++++++++++++ src/strategies/index.ts | 2 + 3 files changed, 166 insertions(+) create mode 100644 src/strategies/hopr-staking/examples.json create mode 100644 src/strategies/hopr-staking/index.ts diff --git a/src/strategies/hopr-staking/examples.json b/src/strategies/hopr-staking/examples.json new file mode 100644 index 000000000..c3c98a4b8 --- /dev/null +++ b/src/strategies/hopr-staking/examples.json @@ -0,0 +1,60 @@ +[ + { + "name": "Stakes and unclaimed rewards from HOPR Stake program", + "strategy": { + "name": "hopr-staking", + "params": { + "tokenAddress": "0xf5581dfefd8fb0e4aec526be659cfab1f8c781da" + } + }, + "network": "1", + "addresses": [ + "0x04BBB7eA18EA570aE47d4489991645E4E49bBf37", + "0x2aF80738aC01e7883d11c912dFe8322C129ae5C5", + "0x0bb43EFc1a613658177D8f67CcF9CFFD8B25b906", + "0x53e85186ebF5A7d4BD06324F7b9D8B3623EF0307", + "0x2DCDB99930E279f1e9Ad11F491163051432542A0", + "0x4326990033eCd87A5444383Cf8c715E696301910", + "0xEd6a59A7C1D5a88b7cb5eb877A7A6078A7e801C7", + "0xeFC05B0D0C8bE8D4Cb3a220ef582E9f7E6FBCd00", + "0xC7B169b108c5e75991C520AEA97140534291C81D", + "0x04Be52434EB64aDdF373137310551ac42013677c", + "0xBE8C93a8C18AF63aAB449994AFAc13E71240ccC4", + "0xf813773eBDD4759c1B780d745081f046A5B776fB", + "0x7F26C34Ed10bF66602009231bBFF24f2f84e9270", + "0x4abd7276C53279b3aBFFF2B5D8A47c0AFc84833B", + "0x3e1A12a6019ee26418F22B656926fE38F5e58C5f", + "0x7A27A4D91231aCB3282b410Cc784517B417FA0DA" + ], + "snapshot": 13269966 + }, + { + "name": "Stakes and unclaimed rewards from HOPR Stake program", + "strategy": { + "name": "hopr-staking", + "params": { + "tokenAddress": "0xf5581dfefd8fb0e4aec526be659cfab1f8c781da" + } + }, + "network": "100", + "addresses": [ + "0x04BBB7eA18EA570aE47d4489991645E4E49bBf37", + "0x2aF80738aC01e7883d11c912dFe8322C129ae5C5", + "0x0bb43EFc1a613658177D8f67CcF9CFFD8B25b906", + "0x53e85186ebF5A7d4BD06324F7b9D8B3623EF0307", + "0x2DCDB99930E279f1e9Ad11F491163051432542A0", + "0x4326990033eCd87A5444383Cf8c715E696301910", + "0xEd6a59A7C1D5a88b7cb5eb877A7A6078A7e801C7", + "0xeFC05B0D0C8bE8D4Cb3a220ef582E9f7E6FBCd00", + "0xC7B169b108c5e75991C520AEA97140534291C81D", + "0x04Be52434EB64aDdF373137310551ac42013677c", + "0xBE8C93a8C18AF63aAB449994AFAc13E71240ccC4", + "0xf813773eBDD4759c1B780d745081f046A5B776fB", + "0x7F26C34Ed10bF66602009231bBFF24f2f84e9270", + "0x4abd7276C53279b3aBFFF2B5D8A47c0AFc84833B", + "0x3e1A12a6019ee26418F22B656926fE38F5e58C5f", + "0x7A27A4D91231aCB3282b410Cc784517B417FA0DA" + ], + "snapshot": 18200908 + } +] diff --git a/src/strategies/hopr-staking/index.ts b/src/strategies/hopr-staking/index.ts new file mode 100644 index 000000000..33f630559 --- /dev/null +++ b/src/strategies/hopr-staking/index.ts @@ -0,0 +1,104 @@ +import { formatUnits } from '@ethersproject/units'; +import { BigNumber } from '@ethersproject/bignumber'; +import { subgraphRequest } from '../../utils'; + +export const author = 'QYuQianchen'; +export const version = '0.1.0'; + +const XDAI_BLOCK_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/1hive/xdai-blocks'; +const HOPR_STAKING_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/hoprnet/hopr-staking-program'; +const LIMIT = 1000; // 1000 addresses per query in Subgraph + +async function getXdaiBlockNumber(timestamp: number): Promise { + const query = { + blocks: { + __args: { + first: 1, + orderBy: 'number', + orderDirection: 'desc', + where: { + timestamp_lte: timestamp + } + }, + number: true, + timestamp: true + } + }; + const data = await subgraphRequest(XDAI_BLOCK_SUBGRAPH_URL, query); + return Number(data.blocks[0].number); +} + +async function stakingSubgraphQuery( + addresses: string[], + blockNumber: number +): Promise<{ [propName: string]: BigNumber }> { + const query = { + accounts: { + __args: { + first: LIMIT, + block: { + number: blockNumber + }, + where: { + id_in: addresses.map((adr) => adr.toLowerCase()) + } + }, + id: true, + actualStake: true, + virtualStake: true, + unclaimedRewards: true + } + }; + const data = await subgraphRequest(HOPR_STAKING_SUBGRAPH_URL, query); + // map result (data.accounts) to addresses + const entries = data.accounts.map((d) => [ + d.id, + BigNumber.from(d.actualStake).add( + BigNumber.from(d.virtualStake).add(BigNumber.from(d.unclaimedRewards)) + ) + ]); + return Object.fromEntries(entries); +} + +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +) { + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + const isXdai = network === '100'; // either xDAI or ETH mainnet + const [block] = await Promise.all([provider.getBlock(blockTag)]); + + // get the block number for subgraph query + const subgraphBlock = isXdai + ? block.number + : await getXdaiBlockNumber(block.timestamp); + + // trim addresses to sub of "LIMIT" addresses. + const addressSubsets = Array.apply( + null, + Array(Math.ceil(addresses.length / LIMIT)) + ).map((_e, i) => addresses.slice(i * LIMIT, (i + 1) * LIMIT)); + + const returnedFromSubgraph = await Promise.all( + addressSubsets.map((subset) => stakingSubgraphQuery(subset, subgraphBlock)) + ); + + // get and parse balance from subgraph + const subgraphBalance = Object.assign({}, ...returnedFromSubgraph); + const subgraphScore = addresses.map( + (address) => subgraphBalance[address.toLowerCase()] ?? 0 + ); + + return Object.fromEntries( + addresses.map((adr, i) => [ + adr, + parseFloat(formatUnits(subgraphScore[i], 18)) // subgraph balance + ]) + ); +} diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 011b0535a..90c7d4aaf 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -151,6 +151,7 @@ import * as sunriseGamingUniv2Lp from './sunrisegaming-univ2-lp'; import * as sunriseGamingStaking from './sunrisegaming-staking'; import * as singleStakingPoolsBalanceOf from './single-staking-pools-balanceof'; import * as occStakeOf from './occ-stake-of'; +import * as hoprStaking from './hopr-staking'; import * as hoprBridgedBalance from './hopr-bridged-balance'; const strategies = { @@ -304,6 +305,7 @@ const strategies = { 'sunrisegaming-univ2-lp': sunriseGamingUniv2Lp, 'sunrisegaming-staking': sunriseGamingStaking, 'single-staking-pools-balanceof': singleStakingPoolsBalanceOf, + 'hopr-staking': hoprStaking, 'hopr-bridged-balance': hoprBridgedBalance, 'occ-stake-of': occStakeOf };