Skip to content

Commit

Permalink
[moxie] Moxie DAO voting strategy (#1618)
Browse files Browse the repository at this point in the history
* hardcoding the subgraph ids and refactoring

* updated strategy to use moxie endpoints

* Updated to post request to handle upto 500 addresses

* Changing timeout from 20 to 60 sec

---------

Co-authored-by: Hrishikesh-Thakkar <[email protected]>
  • Loading branch information
0xsarvesh and Hrishikesh-Thakkar authored Oct 27, 2024
1 parent bcaa0bd commit c414a07
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 228 deletions.
7 changes: 6 additions & 1 deletion src/strategies/moxie/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
"name": "moxie"
},
"network": "8453",
"addresses": ["0xb59aa5bb9270d44be3fa9b6d67520a2d28cf80ab"],
"addresses": [
"0x764e427020ad72624075c61260192c6e486d15a5",
"0xb59aa5bb9270d44be3fa9b6d67520a2d28cf80ab",
"0xcf03287A85298166522002c97aE4B1556fF026B3",
"0xcBFBcbFcA74955B8AB75Dec41F7b9eF36F329879"
],
"snapshot": 20631331
}
]
249 changes: 22 additions & 227 deletions src/strategies/moxie/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import { BigNumberish } from '@ethersproject/bignumber';
import { formatUnits } from '@ethersproject/units';
import { Multicaller, subgraphRequest } from '../../utils';
import { getAddress } from '@ethersproject/address';
import { customFetch } from '../../utils';

export const author = 'Hrishikesh-Thakkar';
export const version = '0.0.1';
export const author = '0xsarvesh';
export const version = '0.0.2';

const abi = [
'function balanceOf(address account) external view returns (uint256)'
];

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;
const PORTFOLIO_SUBGRAPH_PAGE_LIMIT = 2;
const LIQUIDITY_POOL_SUBGRAPH_PAGE_LIMIT = 1;
const MOXIE_ENDPOINT = 'https://api.moxie.xyz/protocol/address-votes';
const MOXIE_API_KEY = process.env.MOXIE_API_KEY || '';

//Strategy to Compute Voting Power for MoxieDAO
export async function strategy(
Expand All @@ -28,216 +15,24 @@ 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 = '7zS29h4BDSujQq8R3TFF37JfpjtPQsRUpoC9p4vo4scx';
const MOXIE_VESTING_ID = 'BuR6zAj2GSVZz6smGbJZkgQx8S6GUS881R493ZYZKSk3';
const MOXIE_LIQUIDITY_ID = '2rv5XN3LDQiuc9BXFzUri7ZLnS6K1ZqNJzp8Zj8TqMhy';

//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_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_PROTOCOL_ID}`;
MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL = `https://gateway.thegraph.com/api/${MOXIE_API_KEY}/subgraphs/id/${MOXIE_LIQUIDITY_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';
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
const response = await customFetch(
MOXIE_ENDPOINT,
{
method: 'POST',
body: JSON.stringify({
block: snapshot,
addresses
}),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'x-airstack-protocol': MOXIE_API_KEY
}
}
};

//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,
pageLimit
) => {
let next_page = 0;
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]];

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<string, BigNumberish> = await multi.execute();
Object.entries(result).forEach(([address, balance]) => {
const 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',
PORTFOLIO_SUBGRAPH_PAGE_LIMIT
),
fetchSubgraphData(
MOXIE_LIQUIDITY_POOL_SUBGRAPH_URL,
liquidityPoolSubgraphQuery,
processLiquidityPoolData,
'userPools',
LIQUIDITY_POOL_SUBGRAPH_PAGE_LIMIT
),
fetchBalances()
]);
},
60000
);

// 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];
}
});
const votes = await response.json();

return addressesMap;
return votes.scores;
}

0 comments on commit c414a07

Please sign in to comment.