Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[erc721-with-landtype-multiplier] Add strategy erc721-with-landtype-multiplier #1529

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
23 changes: 23 additions & 0 deletions src/strategies/erc721-with-landtype-multiplier/README.md
Original file line number Diff line number Diff line change
@@ -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"
}
```
19 changes: 19 additions & 0 deletions src/strategies/erc721-with-landtype-multiplier/examples.json
Original file line number Diff line number Diff line change
@@ -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
}
]
96 changes: 96 additions & 0 deletions src/strategies/erc721-with-landtype-multiplier/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 2 additions & 0 deletions src/strategies/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand Down
Loading