From 02a36f34b0725467446a002e543778704a1c174c Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Sat, 5 Oct 2024 16:16:42 +0530 Subject: [PATCH 1/9] Adding Snapshot Strategy for Moxie --- src/strategies/index.ts | 4 +- src/strategies/moxie/README.md | 13 ++ src/strategies/moxie/examples.json | 15 +++ src/strategies/moxie/index.ts | 190 +++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/strategies/moxie/README.md create mode 100644 src/strategies/moxie/examples.json create mode 100644 src/strategies/moxie/index.ts diff --git a/src/strategies/index.ts b/src/strategies/index.ts index 50dc61925..21d2459ea 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -456,6 +456,7 @@ import * as pom from './pom'; import * as superboring from './superboring'; import * as erableGovernanceV1 from './erable-governance-v1'; import * as snxMultichain from './snx-multichain'; +import * as moxie from './moxie' const strategies = { 'delegatexyz-erc721-balance-of': delegatexyzErc721BalanceOf, @@ -922,7 +923,8 @@ const strategies = { pom, superboring, 'erable-governance-v1': erableGovernanceV1, - 'snx-multichain': snxMultichain + 'snx-multichain': snxMultichain, + 'moxie': moxie }; Object.keys(strategies).forEach(function (strategyName) { diff --git a/src/strategies/moxie/README.md b/src/strategies/moxie/README.md new file mode 100644 index 000000000..47ca4394d --- /dev/null +++ b/src/strategies/moxie/README.md @@ -0,0 +1,13 @@ +# moxie + +This strategy returns the voting power of users. This takes into consideration +1. Vesting Contract Moxie Balance +2. Wallet Moxie Balance +3. Vesting Contract Fan Token Balance +4. Wallet Fan Token Balance +5. Liquidity Pool Contributions +6. Staked fan tokens for Wallet & Vesting Contracts + +There are special multipliers provided for Fan Token Balances and Liquidity Pool Contributions + + diff --git a/src/strategies/moxie/examples.json b/src/strategies/moxie/examples.json new file mode 100644 index 000000000..5a4495d57 --- /dev/null +++ b/src/strategies/moxie/examples.json @@ -0,0 +1,15 @@ +[ + { + "name": "Example query", + "strategy": { + "name": "moxie" + }, + "network": "8453", + "addresses": [ + "0x05ad66873e4bc86aadffac20bf807197485d154d", + "0xcf03287A85298166522002c97aE4B1556fF026B3" + ], + "snapshot": 20631331 + } + ] + \ No newline at end of file diff --git a/src/strategies/moxie/index.ts b/src/strategies/moxie/index.ts new file mode 100644 index 000000000..e0f245d39 --- /dev/null +++ b/src/strategies/moxie/index.ts @@ -0,0 +1,190 @@ +import { BigNumberish } from '@ethersproject/bignumber'; +import { formatUnits } from '@ethersproject/units'; +import { Multicaller, subgraphRequest } from '../../utils'; +import { getAddress } from '@ethersproject/address'; + +export const author = 'Hrishikesh-Thakkar'; +export const version = '0.0.1'; + +const abi = [ + 'function balanceOf(address account) external view returns (uint256)' +]; + +const MOXIE_VESTING_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-vesting/version/latest"; +const MOXIE_PROTOCOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-protocol/version/latest"; +const MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-liquidity/version/latest"; +const QUERY_LIMIT = 1000; +const UNSTAKED_FAN_TOKEN_MULTIPLIER = 2; +const STAKED_FAN_TOKEN_MULTIPLIER = 3; +const MOXIE_LIQUIDITY_MULTIPLIER = 2; +const MOXIE_CONTRACT_ADDRESS = "0x8C9037D1Ef5c6D1f6816278C7AAF5491d24CD527"; +const MOXIE_DECIMALS = 18; + +//Strategy to Compute Voting Power for MoxieDAO +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +) { + //Check if the snapshot is for a specific block number or it's latest + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + const addressesMap = addresses.reduce((map, address) => { + map[getAddress(address)] = 0; + return map; + }, {}); + + const lowercaseAddresses = Object.keys(addressesMap).map((address) => + address.toLowerCase() + ); + + // Once we have the addresses we need to query the subgraphs to get the vesting contract addresses + const vestingSubgraphQuery = { + tokenLockWallets: { + __args: { + where: { + beneficiary_in: lowercaseAddresses + } + }, + id: true, + beneficiary: true, + } + }; + //Adding block to the query if the snapshot is not latest + if (snapshot !== 'latest') { + vestingSubgraphQuery.tokenLockWallets.__args['block'] = { number: snapshot }; + } + + //Query the vesting subgraph to get the vesting contract addresses + const vestingContractResponse = await subgraphRequest(MOXIE_VESTING_SUBGRAPH_URL, vestingSubgraphQuery); + + // Generate a map of vesting contract addresses to beneficiaries + const addressToBeneficiaryMap = vestingContractResponse.tokenLockWallets.reduce((map, wallet) => { + map[wallet.id.toLowerCase()] = wallet.beneficiary.toLowerCase(); + return map; + }, {}); + + // Add vesting contract addresses to the list of addresses to query + const allAddresses = [ + ...Object.keys(addressToBeneficiaryMap), + ...lowercaseAddresses + ]; + + // Initialise all the addresses with a score of 0 + const allAddressesScoreMap = allAddresses.reduce((map, address) => { + map[getAddress(address)] = 0; + return map; + }, {}); + + const protocolSubgraphQuery = { + portfolios: { + __args: { + where: { + user_in: allAddresses, + balance_gt: 0 + }, + first: QUERY_LIMIT, + skip: 0 + }, + unstakedBalance: true, + stakedBalance: true, + subjectToken: { + currentPriceInMoxie: true + }, + user: { + id: true + } + } + }; + + const liquidityPoolSubgraphQuery = { + userPools: { + __args: { + where: { + user_in: allAddresses, + }, + first: QUERY_LIMIT, + skip: 0 + }, + totalLPAmount: true, + pool: { + totalSupply: true, + moxieReserve: true, + }, + user: { + id: true + } + } + }; + + //Adding block to the queries if the snapshot is not latest + if (snapshot !== 'latest') { + protocolSubgraphQuery.portfolios.__args['block'] = { number: snapshot }; + liquidityPoolSubgraphQuery.userPools.__args['block'] = { number: snapshot }; + } + + const fetchSubgraphData = async (subgraphUrl, query, processFunction, key) => { + let next_page = 0; + while (true) { + query[key].__args.skip = next_page * QUERY_LIMIT; + const response = await subgraphRequest(subgraphUrl, query); + const data = response[Object.keys(response)[0]]; + + processFunction(data); + + if (data.length < QUERY_LIMIT) break; + next_page++; + } + }; + + const processProtocolData = (portfolios) => { + portfolios.forEach((portfolio) => { + const userAddress = getAddress(portfolio.user.id); + allAddressesScoreMap[userAddress] += parseFloat(formatUnits(portfolio.unstakedBalance, MOXIE_DECIMALS)) * UNSTAKED_FAN_TOKEN_MULTIPLIER * portfolio.subjectToken.currentPriceInMoxie + + parseFloat(formatUnits(portfolio.stakedBalance, MOXIE_DECIMALS)) * STAKED_FAN_TOKEN_MULTIPLIER * portfolio.subjectToken.currentPriceInMoxie; + }); + }; + + const processLiquidityPoolData = (userPools) => { + userPools.forEach((userPool) => { + const userAddress = getAddress(userPool.user.id); + allAddressesScoreMap[userAddress] += MOXIE_LIQUIDITY_MULTIPLIER * parseFloat(formatUnits(userPool.totalLPAmount, MOXIE_DECIMALS)) * + parseFloat(formatUnits(userPool.pool.moxieReserve, MOXIE_DECIMALS)) / + parseFloat(formatUnits(userPool.pool.totalSupply, MOXIE_DECIMALS)); + }); + }; + + // RPC Call to get balance of Moxie at a block for users + const fetchBalances = async () => { + const multi = new Multicaller(network, provider, abi, { blockTag }); + allAddresses.forEach((address) => + multi.call(address, MOXIE_CONTRACT_ADDRESS, 'balanceOf', [address]) + ); + const result: Record = await multi.execute(); + Object.entries(result).forEach(([address, balance]) => { + let formattedBalance = parseFloat(formatUnits(balance, MOXIE_DECIMALS)); + allAddressesScoreMap[getAddress(address)] += formattedBalance; + }); + }; + + // Fetch data from both subgraphs in parallel + await Promise.all([ + fetchSubgraphData(MOXIE_PROTOCOL_SUBGRAPH_URL, protocolSubgraphQuery, processProtocolData, "portfolios"), + fetchSubgraphData(MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL, liquidityPoolSubgraphQuery, processLiquidityPoolData, "userPools"), + fetchBalances(), + ]); + + // Now we have the score for each address we need to ensure it is added to the beneficiary address if it exists + Object.keys(allAddressesScoreMap).forEach((address) => { + const beneficiary = addressToBeneficiaryMap[address.toLowerCase()]; + if (beneficiary) { + addressesMap[getAddress(beneficiary)] += allAddressesScoreMap[address]; + } else { + addressesMap[address] += allAddressesScoreMap[address]; + } + }); + + return addressesMap; +} \ No newline at end of file From cc9ed517ca8b1e0340a2ec9c26c48c51e647eee3 Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Fri, 11 Oct 2024 19:14:20 +0530 Subject: [PATCH 2/9] adding page limit in subgraph calls --- src/strategies/moxie/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/strategies/moxie/index.ts b/src/strategies/moxie/index.ts index e0f245d39..00ed94c78 100644 --- a/src/strategies/moxie/index.ts +++ b/src/strategies/moxie/index.ts @@ -19,6 +19,8 @@ const STAKED_FAN_TOKEN_MULTIPLIER = 3; const MOXIE_LIQUIDITY_MULTIPLIER = 2; const MOXIE_CONTRACT_ADDRESS = "0x8C9037D1Ef5c6D1f6816278C7AAF5491d24CD527"; const MOXIE_DECIMALS = 18; +const PORTFOLIO_SUBGRAPH_PAGE_LIMIT = 2; +const LIQUIDITY_POOL_SUBGRAPH_PAGE_LIMIT = 1; //Strategy to Compute Voting Power for MoxieDAO export async function strategy( @@ -125,9 +127,9 @@ export async function strategy( liquidityPoolSubgraphQuery.userPools.__args['block'] = { number: snapshot }; } - const fetchSubgraphData = async (subgraphUrl, query, processFunction, key) => { + const fetchSubgraphData = async (subgraphUrl, query, processFunction, key, pageLimit) => { let next_page = 0; - while (true) { + while (next_page < pageLimit) { query[key].__args.skip = next_page * QUERY_LIMIT; const response = await subgraphRequest(subgraphUrl, query); const data = response[Object.keys(response)[0]]; @@ -171,8 +173,8 @@ export async function strategy( // Fetch data from both subgraphs in parallel await Promise.all([ - fetchSubgraphData(MOXIE_PROTOCOL_SUBGRAPH_URL, protocolSubgraphQuery, processProtocolData, "portfolios"), - fetchSubgraphData(MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL, liquidityPoolSubgraphQuery, processLiquidityPoolData, "userPools"), + fetchSubgraphData(MOXIE_PROTOCOL_SUBGRAPH_URL, protocolSubgraphQuery, processProtocolData, "portfolios", PORTFOLIO_SUBGRAPH_PAGE_LIMIT), + fetchSubgraphData(MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL, liquidityPoolSubgraphQuery, processLiquidityPoolData, "userPools", LIQUIDITY_POOL_SUBGRAPH_PAGE_LIMIT), fetchBalances(), ]); From 5429642590f0fdd7a9823d72f8fb7162abc024d4 Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Sun, 13 Oct 2024 21:19:51 +0530 Subject: [PATCH 3/9] Adding URLs through options --- src/strategies/moxie/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/strategies/moxie/index.ts b/src/strategies/moxie/index.ts index 00ed94c78..5ad2dce12 100644 --- a/src/strategies/moxie/index.ts +++ b/src/strategies/moxie/index.ts @@ -10,9 +10,6 @@ const abi = [ 'function balanceOf(address account) external view returns (uint256)' ]; -const MOXIE_VESTING_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-vesting/version/latest"; -const MOXIE_PROTOCOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-protocol/version/latest"; -const MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-liquidity/version/latest"; const QUERY_LIMIT = 1000; const UNSTAKED_FAN_TOKEN_MULTIPLIER = 2; const STAKED_FAN_TOKEN_MULTIPLIER = 3; @@ -31,6 +28,10 @@ export async function strategy( options, snapshot ) { + const MOXIE_PROTOCOL_SUBGRAPH_URL = options?.protocolSubgraphUrl || "https://api.studio.thegraph.com/query/88457/moxie-protocol/version/latest"; + const MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = options?.liquidityPoolSubgraphUrl || "https://api.studio.thegraph.com/query/88457/moxie-liquidity/version/latest"; + const MOXIE_VESTING_SUBGRAPH_URL = options?.vestingSubgraphUrl || "https://api.studio.thegraph.com/query/88457/moxie-vesting/version/latest"; + //Check if the snapshot is for a specific block number or it's latest const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; const addressesMap = addresses.reduce((map, address) => { From b7db99965f82add95b77cd9b0cea550bb8e45327 Mon Sep 17 00:00:00 2001 From: Hrishikesh K Thakkar Date: Mon, 14 Oct 2024 15:45:31 +0530 Subject: [PATCH 4/9] Update src/strategies/index.ts Co-authored-by: Chaitanya --- src/strategies/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/strategies/index.ts b/src/strategies/index.ts index fe1d93bf5..b09307e9b 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -924,7 +924,6 @@ const strategies = { pom, superboring, 'erable-governance-v1': erableGovernanceV1, - 'snx-multichain': snxMultichain, 'world-liberty-financial-erc20-balance-of-votes': worldLibertyFinancial, 'snx-multichain': snxMultichain, 'moxie': moxie From 68f8e801ed69d7009a0375134644c98becf65bd4 Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Mon, 14 Oct 2024 15:46:17 +0530 Subject: [PATCH 5/9] fixing indentation --- src/strategies/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategies/index.ts b/src/strategies/index.ts index b09307e9b..753760ccb 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -926,7 +926,7 @@ const strategies = { 'erable-governance-v1': erableGovernanceV1, 'world-liberty-financial-erc20-balance-of-votes': worldLibertyFinancial, 'snx-multichain': snxMultichain, - 'moxie': moxie + 'moxie': moxie }; Object.keys(strategies).forEach(function (strategyName) { From beff45dd0762932edc42f85a2bf5ba2192face84 Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Mon, 14 Oct 2024 15:58:48 +0530 Subject: [PATCH 6/9] updating example --- src/strategies/moxie/examples.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strategies/moxie/examples.json b/src/strategies/moxie/examples.json index 5a4495d57..69534cf1f 100644 --- a/src/strategies/moxie/examples.json +++ b/src/strategies/moxie/examples.json @@ -7,7 +7,8 @@ "network": "8453", "addresses": [ "0x05ad66873e4bc86aadffac20bf807197485d154d", - "0xcf03287A85298166522002c97aE4B1556fF026B3" + "0xcf03287A85298166522002c97aE4B1556fF026B3", + "0xb59aa5bb9270d44be3fa9b6d67520a2d28cf80ab" ], "snapshot": 20631331 } From 90d4f5dea942fcccaedc4e818f55be37074baa79 Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Mon, 14 Oct 2024 19:15:08 +0530 Subject: [PATCH 7/9] Updating PR Feedback to add env --- .env.example | 4 ++++ src/strategies/moxie/index.ts | 22 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index e8f1655fe..8af16a922 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ SNAPSHOT_API_STRATEGY_SALT=12345 # Salt for the snapshot API strategy to send a key in header (optional) PASSPORT_API_KEY= # API key for the passport API (optional for other strategies) PASSPORT_SCORER_ID= # Scorer ID for the passport API (optional for other strategies) +MOXIE_API_KEY= # API key for the moxie APIs (optional for other strategies) +MOXIE_PROTOCOL_ID= #Protocol ID for the moxie API (optional for other strategies) +MOXIE_VESTING_ID= # Vesting ID for the moxie API (optional for other strategies) +MOXIE_LIQUDITY_ID= # Liquidity ID for the moxie API (optional for other strategies) \ No newline at end of file diff --git a/src/strategies/moxie/index.ts b/src/strategies/moxie/index.ts index 5ad2dce12..5ae4cf0c0 100644 --- a/src/strategies/moxie/index.ts +++ b/src/strategies/moxie/index.ts @@ -28,9 +28,25 @@ export async function strategy( options, snapshot ) { - const MOXIE_PROTOCOL_SUBGRAPH_URL = options?.protocolSubgraphUrl || "https://api.studio.thegraph.com/query/88457/moxie-protocol/version/latest"; - const MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = options?.liquidityPoolSubgraphUrl || "https://api.studio.thegraph.com/query/88457/moxie-liquidity/version/latest"; - const MOXIE_VESTING_SUBGRAPH_URL = options?.vestingSubgraphUrl || "https://api.studio.thegraph.com/query/88457/moxie-vesting/version/latest"; + const MOXIE_API_KEY = process.env.MOXIE_API_KEY || ""; + const MOXIE_PROTOCOL_ID = process.env.MOXIE_PROTOCOL_ID || ""; + const MOXIE_VESTING_ID = process.env.MOXIE_VESTING_ID || ""; + const MOXIE_LIQUIDITY_ID = process.env.MOXIE_LIQUIDITY_ID || ""; + + //SETTING DEFAULT SUBGRAPH URLS + let MOXIE_PROTOCOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-protocol/version/latest"; + let MOXIE_VESTING_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-vesting/version/latest"; + let MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = "https://api.studio.thegraph.com/query/88457/moxie-liquidity/version/latest"; + + if (MOXIE_API_KEY !== "" && MOXIE_PROTOCOL_ID !== "") { + MOXIE_PROTOCOL_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_PROTOCOL_ID}`; + } + if (MOXIE_API_KEY !== "" && MOXIE_LIQUIDITY_ID !== "") { + MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_LIQUIDITY_ID}`; + } + if (MOXIE_API_KEY !== "" && MOXIE_VESTING_ID !== "") { + MOXIE_VESTING_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_VESTING_ID}`; + } //Check if the snapshot is for a specific block number or it's latest const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; From eb43e80a1220c3d9e4beb4488ebf7799a0d0253d Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Tue, 15 Oct 2024 11:42:02 +0530 Subject: [PATCH 8/9] Adding error check for addresses not equal to 1 --- src/strategies/moxie/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/strategies/moxie/index.ts b/src/strategies/moxie/index.ts index 5ae4cf0c0..32ee0e511 100644 --- a/src/strategies/moxie/index.ts +++ b/src/strategies/moxie/index.ts @@ -28,6 +28,10 @@ export async function strategy( options, snapshot ) { + //Check if the addresses array has length not equal to 1 + if(addresses.length != 1) { + throw new Error("This strategy expects a single address"); + }; const MOXIE_API_KEY = process.env.MOXIE_API_KEY || ""; const MOXIE_PROTOCOL_ID = process.env.MOXIE_PROTOCOL_ID || ""; const MOXIE_VESTING_ID = process.env.MOXIE_VESTING_ID || ""; From b7138f508ee9422137e040b2ff60d377e1574a6f Mon Sep 17 00:00:00 2001 From: Hrishikesh-Thakkar Date: Tue, 15 Oct 2024 11:47:52 +0530 Subject: [PATCH 9/9] updating the example --- src/strategies/index.ts | 2 +- src/strategies/moxie/examples.json | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/strategies/index.ts b/src/strategies/index.ts index b3043a6c9..4bb1ea820 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -928,7 +928,7 @@ const strategies = { 'erable-governance-v1': erableGovernanceV1, 'world-liberty-financial-erc20-balance-of-votes': worldLibertyFinancial, 'snx-multichain': snxMultichain, - 'moxie': moxie + 'moxie': moxie, 'staking-amount-duration-linear': stakingAmountDurationLinear, 'staking-amount-duration-exponential': stakingAmountDurationExponential }; diff --git a/src/strategies/moxie/examples.json b/src/strategies/moxie/examples.json index 69534cf1f..517ec6feb 100644 --- a/src/strategies/moxie/examples.json +++ b/src/strategies/moxie/examples.json @@ -6,8 +6,6 @@ }, "network": "8453", "addresses": [ - "0x05ad66873e4bc86aadffac20bf807197485d154d", - "0xcf03287A85298166522002c97aE4B1556fF026B3", "0xb59aa5bb9270d44be3fa9b6d67520a2d28cf80ab" ], "snapshot": 20631331