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

[moxie] Moxie DAO voting strategy #1618

Merged
merged 5 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
]
245 changes: 19 additions & 226 deletions src/strategies/moxie/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
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 MOXIE_ENDPOINT = 'https://api.moxie.xyz/protocol/address-votes';
const MOXIE_API_KEY = process.env.MOXIE_API_KEY || '';

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 buildURL = (addresses, snapshot) => {
const addressesParam = addresses.join(',');
const snapshotParam = snapshot ? `&block=${snapshot}` : '&block=latest';
return `${MOXIE_ENDPOINT}?addresses=${addressesParam}${snapshotParam}`;
};

//Strategy to Compute Voting Power for MoxieDAO
export async function strategy(
Expand All @@ -28,216 +21,16 @@ 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
}
}
};

//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()
]);

// 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 response = await customFetch(buildURL(addresses, snapshot), {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'x-airstack-protocol': MOXIE_API_KEY
}
});

return addressesMap;
const votes = await response.json();

return votes.scores;
}
Loading