diff --git a/src/strategies/hopr-bridged-balance/examples.json b/src/strategies/hopr-bridged-balance/examples.json new file mode 100644 index 000000000..aa94514c7 --- /dev/null +++ b/src/strategies/hopr-bridged-balance/examples.json @@ -0,0 +1,64 @@ +[ + { + "name": "Get bridged HOPR token balance", + "strategy": { + "name": "hopr-bridged-balance", + "params": { + "xHopr": "0xd057604a14982fe8d88c5fc25aac3267ea142a08", + "wxHopr": "0xD4fdec44DB9D44B8f2b6d529620f9C0C7066A2c1", + "hopr": "0xf5581dfefd8fb0e4aec526be659cfab1f8c781da" + } + }, + "network": "100", + "addresses": [ + "0x04BBB7eA18EA570aE47d4489991645E4E49bBf37", + "0x2aF80738aC01e7883d11c912dFe8322C129ae5C5", + "0x0bb43EFc1a613658177D8f67CcF9CFFD8B25b906", + "0x53e85186ebF5A7d4BD06324F7b9D8B3623EF0307", + "0x2DCDB99930E279f1e9Ad11F491163051432542A0", + "0x4326990033eCd87A5444383Cf8c715E696301910", + "0xEd6a59A7C1D5a88b7cb5eb877A7A6078A7e801C7", + "0xeFC05B0D0C8bE8D4Cb3a220ef582E9f7E6FBCd00", + "0xC7B169b108c5e75991C520AEA97140534291C81D", + "0x04Be52434EB64aDdF373137310551ac42013677c", + "0xBE8C93a8C18AF63aAB449994AFAc13E71240ccC4", + "0xf813773eBDD4759c1B780d745081f046A5B776fB", + "0x7F26C34Ed10bF66602009231bBFF24f2f84e9270", + "0x4abd7276C53279b3aBFFF2B5D8A47c0AFc84833B", + "0x3e1A12a6019ee26418F22B656926fE38F5e58C5f", + "0x7A27A4D91231aCB3282b410Cc784517B417FA0DA" + ], + "snapshot": 18200908 + }, + { + "name": "Get bridged HOPR token balance", + "strategy": { + "name": "hopr-bridged-balance", + "params": { + "xHopr": "0xd057604a14982fe8d88c5fc25aac3267ea142a08", + "wxHopr": "0xD4fdec44DB9D44B8f2b6d529620f9C0C7066A2c1", + "hopr": "0xf5581dfefd8fb0e4aec526be659cfab1f8c781da" + } + }, + "network": "1", + "addresses": [ + "0x04BBB7eA18EA570aE47d4489991645E4E49bBf37", + "0x2aF80738aC01e7883d11c912dFe8322C129ae5C5", + "0x0bb43EFc1a613658177D8f67CcF9CFFD8B25b906", + "0x53e85186ebF5A7d4BD06324F7b9D8B3623EF0307", + "0x2DCDB99930E279f1e9Ad11F491163051432542A0", + "0x4326990033eCd87A5444383Cf8c715E696301910", + "0xEd6a59A7C1D5a88b7cb5eb877A7A6078A7e801C7", + "0xeFC05B0D0C8bE8D4Cb3a220ef582E9f7E6FBCd00", + "0xC7B169b108c5e75991C520AEA97140534291C81D", + "0x04Be52434EB64aDdF373137310551ac42013677c", + "0xBE8C93a8C18AF63aAB449994AFAc13E71240ccC4", + "0xf813773eBDD4759c1B780d745081f046A5B776fB", + "0x7F26C34Ed10bF66602009231bBFF24f2f84e9270", + "0x4abd7276C53279b3aBFFF2B5D8A47c0AFc84833B", + "0x3e1A12a6019ee26418F22B656926fE38F5e58C5f", + "0x7A27A4D91231aCB3282b410Cc784517B417FA0DA" + ], + "snapshot": 13269966 + } + ] diff --git a/src/strategies/hopr-bridged-balance/index.ts b/src/strategies/hopr-bridged-balance/index.ts new file mode 100644 index 000000000..d373fa1c5 --- /dev/null +++ b/src/strategies/hopr-bridged-balance/index.ts @@ -0,0 +1,180 @@ +import { formatUnits } from '@ethersproject/units'; +import { BigNumber } from '@ethersproject/bignumber'; +import { multicall, subgraphRequest } from '../../utils'; + +export const author = 'QYuQianchen'; +export const version = '0.1.0'; + +const tokenAbi = [ 'function balanceOf(address) view returns (uint256)' ] + +const XDAI_BLOCK_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/1hive/xdai-blocks'; +const MAINNET_BLOCK_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks'; +const HOPR_XDAI_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/hoprnet/hopr-on-xdai'; +const HOPR_MAINNET_SUBGRAPH_URL = + 'https://api.thegraph.com/subgraphs/name/hoprnet/hopr-on-mainnet'; +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 getMainnetBlockNumber(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(MAINNET_BLOCK_SUBGRAPH_URL, query); + return Number(data.blocks[0].number); +} + +async function xHoprSubgraphQuery( + addresses: string[], + blockNumber: number +): Promise<{ [propName: string]: number }> { + const query = { + accounts: { + __args: { + first: LIMIT, + block: { + number: blockNumber + }, + where: { + id_in: addresses.map((adr) => adr.toLowerCase()) + } + }, + id: true, + totalBalance: true + } + }; + const data = await subgraphRequest(HOPR_XDAI_SUBGRAPH_URL, query); + // map result (data.accounts) to addresses + const entries = data.accounts.map((d) => [d.id, Number(d.totalBalance)]); + return Object.fromEntries(entries); +} + +async function hoprSubgraphQuery( + addresses: string[], + blockNumber: number +): Promise<{ [propName: string]: number }> { + const query = { + accounts: { + __args: { + first: LIMIT, + block: { + number: blockNumber + }, + where: { + id_in: addresses.map((adr) => adr.toLowerCase()) + } + }, + id: true, + amount: true + } + }; + const data = await subgraphRequest(HOPR_MAINNET_SUBGRAPH_URL, query); + // map result (data.accounts) to addresses + const entries = data.accounts.map((d) => [d.id, Number(d.amount)]); + return Object.fromEntries(entries); +} + +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +) { + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + const isEth = network === '1'; // either ETH mainnet or xDAI + + const [res, block] = await Promise.all([ + multicall( + network, + provider, + tokenAbi, + addresses + .map((address: any) => [ + isEth ? options.hopr : options.xHopr, + 'balanceOf', + [address] + ]) + .concat( + isEth + ? [] + : addresses.map((address: any) => [ + options.wxHopr, + 'balanceOf', + [address] + ]) + ), + { blockTag } + ), + provider.getBlock(blockTag) + ]); + + const currentNetwork: BigNumber[] = isEth + ? res.map((r) => r[0] as BigNumber) + : addresses.map((r, i) => + (res[i][0] as BigNumber).add(res[i + addresses.length][0] as BigNumber) + ); + const subgraphBlock = isEth + ? await getXdaiBlockNumber(block.timestamp) + : await getMainnetBlockNumber(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 = isEth + ? await Promise.all( + addressSubsets.map((subset) => + xHoprSubgraphQuery(subset, subgraphBlock) + ) + ) + : await Promise.all( + addressSubsets.map((subset) => hoprSubgraphQuery(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( + currentNetwork.map((value, i) => [ + addresses[i], // current network balance + parseFloat(formatUnits(value, 18)) + subgraphScore[i] // subgraph balance + ]) + ); +} diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 51b1cdffc..011b0535a 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -149,8 +149,9 @@ import * as hasrock from './has-rock'; import * as flexaCapacityStaking from './flexa-capacity-staking'; 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 singleStakingPoolsBalanceOf from './single-staking-pools-balanceof'; +import * as occStakeOf from './occ-stake-of'; +import * as hoprBridgedBalance from './hopr-bridged-balance'; const strategies = { coordinape, @@ -303,6 +304,7 @@ const strategies = { 'sunrisegaming-univ2-lp': sunriseGamingUniv2Lp, 'sunrisegaming-staking': sunriseGamingStaking, 'single-staking-pools-balanceof': singleStakingPoolsBalanceOf, + 'hopr-bridged-balance': hoprBridgedBalance, 'occ-stake-of': occStakeOf };