-
Notifications
You must be signed in to change notification settings - Fork 54
[nato-uniswapv3-voting] TheNationToken PR #1391
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
base: master
Are you sure you want to change the base?
Changes from all commits
aa07d2e
445ab34
4aaea2c
da5e6a4
e611696
efbfaff
85caeb9
072aa05
47157ac
44d1ba0
017a4e8
05f53a3
92cba05
e79aa3d
dfb0842
b59088b
6ef6d26
89df396
e023ad6
171c32f
2404807
9d300ba
faa2df8
8572ffb
6a0a5e5
fe78623
b5a2923
3cb65f1
fdad3e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # nato-uniswapv3-voting | ||
|
|
||
| This strategy calculates voting power based on Uniswap V3 NFT positions in the 1% fee tier pool. It checks if a wallet holds Uniswap V3 NFTs and calculates the token holdings from 1% tier positions only. | ||
|
|
||
| The strategy: | ||
| 1. Checks if wallet holds Uniswap V3 NFTs | ||
| 2. Filters positions to only include 1% fee tier pools | ||
| 3. Calculates token holdings from those positions | ||
| 4. Returns the token amount as voting power (1 token = 1 vote) | ||
|
|
||
| Here is an example of parameters: | ||
|
|
||
| ```json | ||
| { | ||
| "poolAddress": "0x02623e0e65a1d8537f6235512839e2f7b76c7a12", | ||
| "tokenReserve": 0, | ||
| "feeTier": 10000 | ||
| } | ||
| ``` | ||
|
|
||
| Parameters: | ||
| - `poolAddress`: The Uniswap V3 pool address | ||
| - `tokenReserve`: Which token to count (0 for token0, 1 for token1) | ||
| - `feeTier`: The fee tier to filter for (10000 = 1%) | ||
| - `subgraph`: Optional custom subgraph URL |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| [ | ||
| { | ||
| "name": "NATO Uniswap V3 1% Tier Pool Example", | ||
| "strategy": { | ||
| "name": "nato-uniswapv3-voting", | ||
| "params": { | ||
| "poolAddress": "0x02623e0e65a1d8537f6235512839e2f7b76c7a12", | ||
| "tokenReserve": 0, | ||
| "feeTier": 10000 | ||
| } | ||
| }, | ||
| "network": "8453", | ||
| "addresses": [ | ||
| "0x4f0fd563be89ec8c3e7d595bf3639128c0a7c33a", | ||
| "0xda1dba3c1b1cdeaf1419a85ba5b454547bda5662", | ||
| "0x581ab14804196e9b20f02291cf863bc1b4745a87", | ||
| "0x5d62dd87f40090ba3d065b4c6bb63b8c8ff883b3", | ||
| "0x949dc9da1ce5bb121fbc1bea61b2a42dc8bedae6", | ||
| "0x7cee21fd74cff3b91c1c7251b7a4294349a0da79" | ||
| ], | ||
| "snapshot": 30937953 | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,179 @@ | ||||||||||||||||||||||||||||||
| import { subgraphRequest } from '../../utils'; | ||||||||||||||||||||||||||||||
| import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk'; | ||||||||||||||||||||||||||||||
| import { Token } from '@uniswap/sdk-core'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const UNISWAP_V3_SUBGRAPH_URL = { | ||||||||||||||||||||||||||||||
| '1': 'https://subgrapher.snapshot.org/subgraph/arbitrum/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV', | ||||||||||||||||||||||||||||||
| '8453': | ||||||||||||||||||||||||||||||
| 'https://subgrapher.snapshot.org/subgraph/arbitrum/43Hwfi3dJSoGpyas9VwNoDAv55yjgGrPpNSmbQZArzMG' | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export const author = 'vitalii'; | ||||||||||||||||||||||||||||||
| export const version = '0.1.0'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Custom function to calculate total token amounts from position | ||||||||||||||||||||||||||||||
| function calculateTotalTokenAmounts(position: any) { | ||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||
| tickLower, | ||||||||||||||||||||||||||||||
| tickUpper, | ||||||||||||||||||||||||||||||
| liquidity, | ||||||||||||||||||||||||||||||
| pool: { tick, sqrtPrice, feeTier }, | ||||||||||||||||||||||||||||||
| token0, | ||||||||||||||||||||||||||||||
| token1 | ||||||||||||||||||||||||||||||
| } = position; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const baseToken = new Token( | ||||||||||||||||||||||||||||||
| 1, | ||||||||||||||||||||||||||||||
| token0.id, | ||||||||||||||||||||||||||||||
| Number(token0.decimals), | ||||||||||||||||||||||||||||||
| token0.symbol | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| const quoteToken = new Token( | ||||||||||||||||||||||||||||||
| 1, | ||||||||||||||||||||||||||||||
| token1.id, | ||||||||||||||||||||||||||||||
| Number(token1.decimals), | ||||||||||||||||||||||||||||||
| token1.symbol | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const fee = Object.values(FeeAmount).includes(parseFloat(feeTier)) | ||||||||||||||||||||||||||||||
| ? parseFloat(feeTier) | ||||||||||||||||||||||||||||||
| : 0; | ||||||||||||||||||||||||||||||
| const pool = new Pool( | ||||||||||||||||||||||||||||||
| baseToken, | ||||||||||||||||||||||||||||||
| quoteToken, | ||||||||||||||||||||||||||||||
| fee, | ||||||||||||||||||||||||||||||
| sqrtPrice, | ||||||||||||||||||||||||||||||
| liquidity, | ||||||||||||||||||||||||||||||
| Number(tick) | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const position_obj = new Position({ | ||||||||||||||||||||||||||||||
| pool, | ||||||||||||||||||||||||||||||
| liquidity, | ||||||||||||||||||||||||||||||
| tickLower: Number(tickLower.tickIdx), | ||||||||||||||||||||||||||||||
| tickUpper: Number(tickUpper.tickIdx) | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Calculate the total amounts (this includes both in-range and out-of-range portions) | ||||||||||||||||||||||||||||||
| const amount0 = position_obj.amount0; | ||||||||||||||||||||||||||||||
| const amount1 = position_obj.amount1; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| token0Amount: parseFloat(amount0.toSignificant(18)), | ||||||||||||||||||||||||||||||
| token1Amount: parseFloat(amount1.toSignificant(18)), | ||||||||||||||||||||||||||||||
| inRange: | ||||||||||||||||||||||||||||||
| parseInt(tick) >= parseInt(tickLower.tickIdx) && | ||||||||||||||||||||||||||||||
| parseInt(tick) <= parseInt(tickUpper.tickIdx) | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export async function strategy( | ||||||||||||||||||||||||||||||
| _space, | ||||||||||||||||||||||||||||||
| network, | ||||||||||||||||||||||||||||||
| _provider, | ||||||||||||||||||||||||||||||
| addresses, | ||||||||||||||||||||||||||||||
| options, | ||||||||||||||||||||||||||||||
| snapshot | ||||||||||||||||||||||||||||||
| ): Promise<Record<string, number>> { | ||||||||||||||||||||||||||||||
| const tokenReserve = | ||||||||||||||||||||||||||||||
| options.tokenReserve === 0 ? 'token0Reserve' : 'token1Reserve'; | ||||||||||||||||||||||||||||||
| const requiredFeeTier = options.feeTier || 10000; // Default to 1% fee tier (10000 = 1%) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const _addresses = addresses.map(address => address.toLowerCase()); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const params = { | ||||||||||||||||||||||||||||||
| positions: { | ||||||||||||||||||||||||||||||
| __args: { | ||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||
| pool: options.poolAddress.toLowerCase(), | ||||||||||||||||||||||||||||||
| owner_in: _addresses | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| id: true, | ||||||||||||||||||||||||||||||
| owner: true, | ||||||||||||||||||||||||||||||
| liquidity: true, | ||||||||||||||||||||||||||||||
| tickLower: { | ||||||||||||||||||||||||||||||
| tickIdx: true | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| tickUpper: { | ||||||||||||||||||||||||||||||
| tickIdx: true | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| pool: { | ||||||||||||||||||||||||||||||
| tick: true, | ||||||||||||||||||||||||||||||
| sqrtPrice: true, | ||||||||||||||||||||||||||||||
| liquidity: true, | ||||||||||||||||||||||||||||||
| feeTier: true | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| token0: { | ||||||||||||||||||||||||||||||
| symbol: true, | ||||||||||||||||||||||||||||||
| decimals: true, | ||||||||||||||||||||||||||||||
| id: true | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| token1: { | ||||||||||||||||||||||||||||||
| symbol: true, | ||||||||||||||||||||||||||||||
| decimals: true, | ||||||||||||||||||||||||||||||
| id: true | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (snapshot !== 'latest') { | ||||||||||||||||||||||||||||||
| // @ts-ignore | ||||||||||||||||||||||||||||||
| params.positions.__args.block = { number: snapshot }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| let rawData; | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| rawData = await subgraphRequest( | ||||||||||||||||||||||||||||||
| options.subgraph || UNISWAP_V3_SUBGRAPH_URL[network], | ||||||||||||||||||||||||||||||
| params | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||
| console.error('Subgraph request failed:', error); | ||||||||||||||||||||||||||||||
| // Return zero scores for all addresses if subgraph fails | ||||||||||||||||||||||||||||||
| return Object.fromEntries(addresses.map(address => [address, 0])); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+135
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @Monarch717 adding a try catch can cause wrong voting power even if there is a network issue
Suggested change
The correct solution is to deploy a new subgraph on base network
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @ChaituVR , Do you want me to deploy our own subgraph?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think thats the best solution considering the issue with current subgraph |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!rawData || !rawData.positions) { | ||||||||||||||||||||||||||||||
| // Return zero scores if no data returned | ||||||||||||||||||||||||||||||
| return Object.fromEntries(addresses.map(address => [address, 0])); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const usersUniswap = addresses.map(() => ({ | ||||||||||||||||||||||||||||||
| positions: [] | ||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| rawData.positions.map(position => { | ||||||||||||||||||||||||||||||
| // Only include positions with the required fee tier (1% = 10000) | ||||||||||||||||||||||||||||||
| if (position?.pool?.feeTier === requiredFeeTier.toString()) { | ||||||||||||||||||||||||||||||
| const ownerIndex = _addresses.indexOf(position?.owner); | ||||||||||||||||||||||||||||||
| if (ownerIndex !== -1) { | ||||||||||||||||||||||||||||||
| usersUniswap[ownerIndex].positions.push(position); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const score = {}; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| usersUniswap?.forEach((user: any, idx) => { | ||||||||||||||||||||||||||||||
| let tokenReserveAdd = 0; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| user.positions.forEach((position: any) => { | ||||||||||||||||||||||||||||||
| // Calculate total token amounts using custom function | ||||||||||||||||||||||||||||||
| const tokenAmounts = calculateTotalTokenAmounts(position); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Add the token amount based on tokenReserve parameter | ||||||||||||||||||||||||||||||
| if (tokenReserve === 'token0Reserve') { | ||||||||||||||||||||||||||||||
| tokenReserveAdd += tokenAmounts.token0Amount; | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| tokenReserveAdd += tokenAmounts.token1Amount; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| score[addresses[idx]] = tokenReserveAdd; | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Return the actual scores from the subgraph | ||||||||||||||||||||||||||||||
| // If no positions found, return zero scores | ||||||||||||||||||||||||||||||
| return score || {}; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "name": "NATO Uniswap V3 1% Tier Voting", | ||
| "author": "TheNationToken", | ||
| "version": "0.1.0" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/draft-07/schema#", | ||
| "$ref": "#/definitions/Strategy", | ||
| "definitions": { | ||
| "Strategy": { | ||
| "title": "Strategy", | ||
| "type": "object", | ||
| "properties": { | ||
| "poolAddress": { | ||
| "type": "string", | ||
| "title": "Uniswap V3 Pool Address", | ||
| "examples": ["0x02623e0e65a1d8537f6235512839e2f7b76c7a12"], | ||
| "pattern": "^0x[a-fA-F0-9]{40}$", | ||
| "minLength": 42, | ||
| "maxLength": 42 | ||
| }, | ||
| "tokenReserve": { | ||
| "type": "number", | ||
| "title": "Token Reserve (0 for token0, 1 for token1)", | ||
| "examples": [0, 1], | ||
| "minimum": 0, | ||
| "maximum": 1 | ||
| }, | ||
| "feeTier": { | ||
| "type": "number", | ||
| "title": "Fee Tier (10000 = 1%)", | ||
| "examples": [10000], | ||
| "minimum": 100, | ||
| "maximum": 1000000 | ||
| }, | ||
| "subgraph": { | ||
| "type": "string", | ||
| "title": "Custom Subgraph URL (optional)", | ||
| "examples": ["https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"] | ||
| } | ||
| }, | ||
| "required": ["poolAddress", "tokenReserve"], | ||
| "additionalProperties": false | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"author": "TheNationToken", this is correct one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @ChaituVR, the last time, you want me to change it to "TheNationToken"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was on different file https://github.com/snapshot-labs/score-api/pull/1391/files#diff-4328882fcad6ddbe8bddf7c676eff45bafb96319570bdaa4d415c6686dd3d765R3
Can remove from here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChaituVR Is this not correct?