From 77ab37af1422c50ec690c4bb232631fbd073f57a Mon Sep 17 00:00:00 2001 From: Javier Ortiz Date: Tue, 11 Feb 2025 09:12:45 +0100 Subject: [PATCH] feat(xgt3-getvotingunits) added strategy --- src/strategies/index.ts | 4 +- .../xgt3-getvotingunits/examples.json | 39 +++++ src/strategies/xgt3-getvotingunits/index.ts | 144 ++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/strategies/xgt3-getvotingunits/examples.json create mode 100644 src/strategies/xgt3-getvotingunits/index.ts diff --git a/src/strategies/index.ts b/src/strategies/index.ts index f5df229f1..8ae3182c5 100644 --- a/src/strategies/index.ts +++ b/src/strategies/index.ts @@ -475,6 +475,7 @@ import * as superfluidVesting from './superfluid-vesting'; import * as spookyLpSonic from './spooky-lp-sonic'; import * as synapse from './synapse'; import * as dappcomposerGetVotingUnits from './dappcomposer-getvotingunits'; +import * as xgt3GetVotingUnits from './xgt3-getvotingunits'; const strategies = { 'spooky-lp-sonic': spookyLpSonic, @@ -960,7 +961,8 @@ const strategies = { realt, 'superfluid-vesting': superfluidVesting, synapse, - 'dappcomposer-getvotingunits': dappcomposerGetVotingUnits + 'dappcomposer-getvotingunits': dappcomposerGetVotingUnits, + 'xgt3-getvotingunits': xgt3GetVotingUnits, }; Object.keys(strategies).forEach(function (strategyName) { diff --git a/src/strategies/xgt3-getvotingunits/examples.json b/src/strategies/xgt3-getvotingunits/examples.json new file mode 100644 index 000000000..c6d9f1edd --- /dev/null +++ b/src/strategies/xgt3-getvotingunits/examples.json @@ -0,0 +1,39 @@ +[ + { + "name": "xGT3 query", + "strategy": { + "name": "xgt3-getvotingunits", + "params": { + "subgraphURI": "https://subgraph.satsuma-prod.com/15c928d3b406/tutellus/gt3-polygon/api", + "address": "0x79bE3dd832b5a61C9c7f1d0Ea6425e515b88f5bb", + "decimals": 18 + } + }, + "network": "137", + "addresses": [ + "0x943B71Dd451dAA8097bC2aD6d4afb7517cB4Cf3f", + "0x7d867806BB8388794Bc02BcE322ae199fb390FD9", + "0xA1447C1531F1774D2c6Ca9E9cC04577A55B8e524", + "0x8e5812ae342AE21360b577318a0D9BD0364BE9BE" + ], + "snapshot": 65600114 + }, { + "name": "xGT3 query", + "strategy": { + "name": "xgt3-getvotingunits", + "params": { + "subgraphURI": "https://subgraph.satsuma-prod.com/15c928d3b406/tutellus/gt3-polygon/api", + "address": "0x79bE3dd832b5a61C9c7f1d0Ea6425e515b88f5bb", + "decimals": 18 + } + }, + "network": "137", + "addresses": [ + "0x943B71Dd451dAA8097bC2aD6d4afb7517cB4Cf3f", + "0x7d867806BB8388794Bc02BcE322ae199fb390FD9", + "0xA1447C1531F1774D2c6Ca9E9cC04577A55B8e524", + "0x8e5812ae342AE21360b577318a0D9BD0364BE9BE" + ], + "snapshot": 67776522 + } +] diff --git a/src/strategies/xgt3-getvotingunits/index.ts b/src/strategies/xgt3-getvotingunits/index.ts new file mode 100644 index 000000000..10ac645b0 --- /dev/null +++ b/src/strategies/xgt3-getvotingunits/index.ts @@ -0,0 +1,144 @@ +import { BigNumberish } from '@ethersproject/bignumber'; +import { Multicaller } from '../../utils'; +import { formatUnits } from '@ethersproject/units'; + +export const author = 'gt3'; +export const version = '0.1.0'; +export const name = 'xgt3-getvotingunits'; + + +const veTokenABI = [ + 'function getVotingUnitsNFT(uint256 tokenId_) view returns (uint256)' +]; + +const NFTS_QUERY = ` + query NFTS_QUERY( + $skip: Int, + $first: Int, + $orderBy: Nft_orderBy, + $orderDirection: OrderDirection, + $where: Nft_filter + $block: Block_height + ) { + nfts( + skip: $skip, + first: $first, + orderBy: $orderBy, + orderDirection: $orderDirection, + where: $where + block: $block + ) { + tokenId + owner + burned, + splitted, + } + } +` +type NFTSQueryVariable = { + skip?: number; + first?: number; + orderBy: string; + orderDirection: string; + where: { + owner_in: string[]; + }; + block?: { + number: number; + }; +}; + +type GetNftsOptions = { + subgraphURI: string; + addresses: string[]; + blockTag?: number; + all?: any[]; + skip?: number; + first?: number; +}; + +// Consultamos a la subgraph para obtener los NFTs de cada address +const getNfts = async ({ subgraphURI, addresses = [], blockTag, all = [], skip = 0, first = 1000 } : GetNftsOptions) => { + + const variables = { + skip, + first, + orderBy: 'tokenId', + orderDirection: 'asc', + where: { + burned: false, + splitted: false, + owner_in: addresses + } + } as NFTSQueryVariable; + + if (blockTag) { + variables.block = { number: blockTag }; + } + + const query = await fetch(subgraphURI, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: NFTS_QUERY, variables }) + }); + const result = await query.json(); + + const { nfts = [] } = result.data; + all.push(...nfts); + + if (nfts.length === first) { + return getNfts({ subgraphURI, addresses, blockTag, all, skip: skip + first, first }); + } + return all; +} + +export async function strategy( + space, + network, + provider, + addresses, + options, + snapshot +) { + const blockTag = typeof snapshot === 'number' ? snapshot : undefined; + + const nfts = await getNfts({ subgraphURI: options.subgraphURI, addresses, blockTag }); + + const nftsByAddress = nfts.reduce((acc, nft) => { + acc[nft.owner] = acc[nft.owner] || []; + acc[nft.owner].push(nft.tokenId); + return acc; + }, {}); + + // Initialize Multicaller + const multi = new Multicaller(network, provider, veTokenABI, { blockTag }); + + // Third batch: Get voting power for each token + addresses.forEach((address) => { + const tokensIds = nftsByAddress[address.toLowerCase()] || []; + tokensIds.forEach((tokenId) => { + multi.call( + `${tokenId}`, + options.address, + 'getVotingUnitsNFT', + [tokenId] + ); + }); + }); + + const voteByTokenIds: Record = await multi.execute(); + + // Calculate final scores + const scores = {}; + addresses.forEach((address) => { + const tokensIds = nftsByAddress[address.toLowerCase()] || []; + let totalVotingPower = 0; + tokensIds.forEach((tokenId) => { + const power = voteByTokenIds[tokenId]; + totalVotingPower += Number(formatUnits(power, options.decimals || 18)); + }); + scores[address] = totalVotingPower; + }); + + return scores; +}