diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 139583bb0..24c150bc8 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -476,6 +476,7 @@ import * as hatsProtocolHatId from './hats-protocol-hat-id'; import * as hatsProtocolHatIds from './hats-protocol-hat-ids'; import * as bubblegumKids from './bubblegum-kids'; import * as clipperStakedSail from './clipper-staked-sail'; +import * as plearn from './plearn'; import * as snote from './snote'; const strategies = { @@ -962,6 +963,7 @@ const strategies = { 'hats-protocol-hat-ids': hatsProtocolHatIds, 'bubblegum-kids': bubblegumKids, 'clipper-staked-sail': clipperStakedSail, + plearn, snote }; diff --git a/src/strategies/plearn/README.md b/src/strategies/plearn/README.md new file mode 100644 index 000000000..45f64f8e2 --- /dev/null +++ b/src/strategies/plearn/README.md @@ -0,0 +1,47 @@ +# Plearn + +This is the most common strategy, it returns the balances of the voters for a balances PLN token +in Plearn project(pools, token). + +Here is an example of parameters: + +```json +[ + { + "name": "Example query", + "strategy": { + "name": "plearn", + "params": { + "lockedPoolAddresses": [ + { + "address": "0xc38d542326545470a12B06Bf8e315DE55B0B6B46" + }, + { + "address": "0x9b45a8eeD3eF6DA3bE222147533Da542aa384006" + } + ], + "foundingInvestorPoolAddresses": [], + "pendingWithdrawalAddresses": [ + { + "address": "0x7E4e06C81B41284198C0693cd98eb357257Fc3d9" + }, + { + "address": "0xC26a3E07D8CCF34195e943C0bb705f206Dd57030" + } + ], + "symbol": "PLN", + "address": "0xBe0D3526fc797583Dada3F30BC390013062A048B", + "decimals": 18 + } + }, + "network": "56", + "addresses": [ + "0xE0d54117600e592E7a78C985996d11b8Fb1B69C3", + "0x3C97c372B45cC96Fe73814721ebbE6db02C9D88E", + "0xB6605F98A5562b1AC821Bc5f2B75934239e8c6D6", + "0x8900cCBdC60fD97E3B7c8529A9987F8c0f8A1125" + ], + "snapshot": 33739024 + } +] +``` diff --git a/src/strategies/plearn/examples.json b/src/strategies/plearn/examples.json new file mode 100644 index 000000000..e64080805 --- /dev/null +++ b/src/strategies/plearn/examples.json @@ -0,0 +1,38 @@ +[ + { + "name": "Example query", + "strategy": { + "name": "plearn", + "params": { + "lockedPoolAddresses": [ + { + "address": "0xc38d542326545470a12B06Bf8e315DE55B0B6B46" + }, + { + "address": "0x9b45a8eeD3eF6DA3bE222147533Da542aa384006" + } + ], + "foundingInvestorPoolAddresses": [], + "pendingWithdrawalAddresses": [ + { + "address": "0x7E4e06C81B41284198C0693cd98eb357257Fc3d9" + }, + { + "address": "0xC26a3E07D8CCF34195e943C0bb705f206Dd57030" + } + ], + "symbol": "PLN", + "address": "0xBe0D3526fc797583Dada3F30BC390013062A048B", + "decimals": 18 + } + }, + "network": "56", + "addresses": [ + "0xE0d54117600e592E7a78C985996d11b8Fb1B69C3", + "0x3C97c372B45cC96Fe73814721ebbE6db02C9D88E", + "0xB6605F98A5562b1AC821Bc5f2B75934239e8c6D6", + "0x8900cCBdC60fD97E3B7c8529A9987F8c0f8A1125" + ], + "snapshot": 33739024 + } +] diff --git a/src/strategies/plearn/index.ts b/src/strategies/plearn/index.ts new file mode 100644 index 000000000..be80146f2 --- /dev/null +++ b/src/strategies/plearn/index.ts @@ -0,0 +1,144 @@ +import { Provider } from '@ethersproject/providers'; +import { formatUnits } from '@ethersproject/units'; +import { BigNumber } from '@ethersproject/bignumber'; +import { multicall } from '../../utils'; +import { strategy as erc20BalanceOfStrategy } from '../erc20-balance-of'; + +export const author = 'plearnclub'; +export const version = '0.0.1'; + +const lockedPoolabi = [ + 'function userInfo(address) view returns (uint256 amount)' +]; + +const foundingInvestorPoolabi = [ + 'function userInfo(address) view returns (uint256 initialAmount, uint256 amount)' +]; + +const pendingWithdrawalabi = [ + 'function lockedBalances(address user) view returns (uint256 total, uint256 unlockable, uint256 locked, tuple(uint256 amount, uint256 unlockTime)[] lockData)' +]; + +function transformResults( + res: any[], + addresses: string[], + balanceTransformer: (result: any) => number +): { [address: string]: number } { + return res.reduce((acc: { [address: string]: number }, result, index) => { + const address = addresses[index % addresses.length]; + if (!acc[address]) { + acc[address] = 0; + } + + const amount = balanceTransformer(result); + acc[address] += amount; + return acc; + }, {}); +} + +export async function strategy( + space: string, + network: string, + provider: Provider, + addresses: string[], + options: { + lockedPoolAddresses: { address: string }[]; + foundingInvestorPoolAddresses: { address: string }[]; + pendingWithdrawalAddresses: { address: string }[]; + symbol: string; + address: string; + decimals: number; + }, + snapshot: number | string +): Promise<{ [address: string]: number }> { + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + const score = await erc20BalanceOfStrategy( + space, + network, + provider, + addresses, + options, + snapshot + ); + + const lockedPoolCalls = options.lockedPoolAddresses.flatMap((item) => + addresses.map((address) => [ + item.address, + 'userInfo', + [address], + { blockTag } + ]) + ); + + const foundingInvestorPoolCalls = + options.foundingInvestorPoolAddresses.flatMap((item) => + addresses.map((address) => [ + item.address, + 'userInfo', + [address], + { blockTag } + ]) + ); + + const pendingWithdrawalCalls = options.pendingWithdrawalAddresses.flatMap( + (item) => + addresses.map((address) => [ + item.address, + 'lockedBalances', + [address], + { blockTag } + ]) + ); + + const [ + lockedPoolBalancesRes, + foundingInvestorPoolBalancesRes, + pendingWithdrawalBalancesRes + ] = await Promise.all([ + multicall(network, provider, lockedPoolabi, lockedPoolCalls, { blockTag }), + multicall( + network, + provider, + foundingInvestorPoolabi, + foundingInvestorPoolCalls, + { blockTag } + ), + multicall(network, provider, pendingWithdrawalabi, pendingWithdrawalCalls, { + blockTag + }) + ]); + + const pf = (amount: BigNumber) => + parseFloat(formatUnits(amount, options.decimals)); + + const lockedPoolScore = transformResults( + lockedPoolBalancesRes, + addresses, + (r) => pf(r.amount) + ); + const foundingInvestorPoolScore = transformResults( + foundingInvestorPoolBalancesRes, + addresses, + (r) => pf(r.amount) + ); + const pendingWithdrawalScore = transformResults( + pendingWithdrawalBalancesRes, + addresses, + (r) => pf(r.total) + ); + + const finalScore = Object.keys(score).reduce( + (acc: { [address: string]: number }, address) => { + acc[address] = Math.trunc( + score[address] + + (lockedPoolScore[address] || 0) + + (foundingInvestorPoolScore[address] || 0) + + (pendingWithdrawalScore[address] || 0) + ); + return acc; + }, + {} + ); + + return finalScore; +}