diff --git a/src/strategies/erc721-with-landtype-multiplier/README.md b/src/strategies/erc721-with-landtype-multiplier/README.md new file mode 100644 index 000000000..eeed7bb57 --- /dev/null +++ b/src/strategies/erc721-with-landtype-multiplier/README.md @@ -0,0 +1,23 @@ +# ERC721 with Multiplier Landtype Strategy + +This strategy returns the balances of the voters for a specific ERC721 NFT with an arbitrary multiplier based on the type of land they own. +Types Of Land : +Mega contributes 25000 VP +Large contributes 10000 VP +Medium contributes 4000 VP +Unit contributes 2000 VP + +## Parameters + +- **address**: The address of the ERC721 contract. +- **multiplier**: The multiplier to be applied to the balance. +- **symbol**: The symbol of the ERC721 token. + +Here is an example of parameters: + +```json +{ + "address": "0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB", + "symbol": "LAND" +} +``` diff --git a/src/strategies/erc721-with-landtype-multiplier/examples.json b/src/strategies/erc721-with-landtype-multiplier/examples.json new file mode 100644 index 000000000..4bfc3c619 --- /dev/null +++ b/src/strategies/erc721-with-landtype-multiplier/examples.json @@ -0,0 +1,19 @@ +[ + { + "name": "Example query", + "strategy": { + "name": "erc721-with-landtype-multiplier", + "params": { + "address": "0xdBd34637BC7793DDC2A02A89b3E6592249a45a12", + "symbol": "LAND" + } + }, + "network": "137", + "addresses": [ + "0xf3597bc963b657203177e59184d5a3b93d465c94", + "0xa2fe5ff21c1e634723f5847cc61033a929e1dcfc", + "0x9069fdde8df22aab332b326d34c7c376c62d0076" + ], + "snapshot": 12453212 + } + ] \ No newline at end of file diff --git a/src/strategies/erc721-with-landtype-multiplier/index.ts b/src/strategies/erc721-with-landtype-multiplier/index.ts new file mode 100644 index 000000000..9e550caea --- /dev/null +++ b/src/strategies/erc721-with-landtype-multiplier/index.ts @@ -0,0 +1,96 @@ +import { multicall } from '../../utils'; +import { BigNumber } from '@ethersproject/bignumber'; + +export const author = 'monish-nagre'; +export const version = '0.1.0'; + +const abi = [ + 'function balanceOf(address owner) public view returns (uint256)', + 'function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256)', + 'function landData(uint256 tokenId) public view returns (uint256 landId, string landType, string x, string y, string z)' +]; + +// Voting power based on land type +const landTypeVotingPower: { [key: string]: number } = { + 'Mega': 25000, + 'Large': 10000, + 'Medium': 4000, + 'Unit': 2000 +}; + +export async function strategy( + space: string, + network: string, + provider: any, + addresses: string[], + options: any, + snapshot: number | string +): Promise<{ [address: string]: number }> { + const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; + + const MAX_SUPPLY_THRESHOLD = 5000; + + + // Get the balance of each address + const balanceCalls = addresses.map((address: string) => [options.address, 'balanceOf', [address]]); + const balanceResponse = await multicall(network, provider, abi, balanceCalls, { blockTag }); + + // Check if balanceResponse is an array and has valid data + if (!Array.isArray(balanceResponse) || balanceResponse.length !== addresses.length) { + throw new Error('Balance response is not valid'); + } + + // Parse balance response + const balances = balanceResponse.map((response: any) => BigNumber.from(response[0]).toNumber()); + + // Get all token IDs for each address + const tokenCalls: [string, string, [string, number]][] = []; + addresses.forEach((address: string, i: number) => { + const balance = balances[i]; + for (let j = 0; j < balance; j++) { + tokenCalls.push([options.address, 'tokenOfOwnerByIndex', [address, j]]); + } + }); + + if (tokenCalls.length === 0) { + return {}; + } + + // Check if the number of calls exceeds the maximum threshold + if (tokenCalls.length > MAX_SUPPLY_THRESHOLD) { + throw new Error(`Number of token calls (${tokenCalls.length}) exceeds the maximum threshold (${MAX_SUPPLY_THRESHOLD})`); + } + + const tokenResponse = await multicall(network, provider, abi, tokenCalls, { blockTag }); + + // Check if tokenResponse is an array and has valid data + if (!Array.isArray(tokenResponse)) { + throw new Error('Token response is not an array'); + } + + // Parse token response + const tokenIds = tokenResponse.map((response: any) => BigNumber.from(response[0]).toString()); + + // Get land type for each token ID + const landDataCalls: [string, string, [BigNumber]][] = tokenIds.map((tokenId: string) => [options.address, 'landData', [BigNumber.from(tokenId)]]); + const landDataResponse = await multicall(network, provider, abi, landDataCalls, { blockTag }); + + // Check if landDataResponse is an array and has valid data + if (!Array.isArray(landDataResponse) || landDataResponse.length !== tokenIds.length) { + throw new Error('Land data response is not valid'); + } + + // Calculate voting power based on land type + const votingPower: { [address: string]: number } = {}; + let tokenIndex = 0; + addresses.forEach((address: string, i: number) => { + votingPower[address] = 0; + const balance = balances[i]; + for (let j = 0; j < balance; j++) { + const landType = landDataResponse[tokenIndex].landType; + votingPower[address] += landTypeVotingPower[landType] || 0; + tokenIndex++; + } + }); + return votingPower; +} diff --git a/src/strategies/index.ts b/src/strategies/index.ts index e849a3eb2..10950c0f1 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'fs'; import path from 'path'; +import * as erc721WithLandtypeMultiplier from './erc721-with-landtype-multiplier'; import * as urbitGalaxies from './urbit-galaxies/index'; import * as ecoVotingPower from './eco-voting-power'; import * as dpsNFTStrategy from './dps-nft-strategy'; @@ -440,6 +441,7 @@ import * as csv from './csv'; import * as swarmStaking from './swarm-staking'; const strategies = { + 'erc721-with-landtype-multiplier': erc721WithLandtypeMultiplier, 'giveth-balances-supply-weighted': givethBalancesSupplyWeighted, 'giveth-gnosis-balance-supply-weighted-v3': givethGnosisBalanceSupplyWeightedV3,