-
Notifications
You must be signed in to change notification settings - Fork 64
[nato-uniswap-v3] Add NATO Uniswap V3 strategy #1353
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
Closed
Closed
Changes from 13 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
aa07d2e
Add NATO Uniswap V3 strategy
TheNationToken 445ab34
Remove broken import for nation3VotesWithDelegations
TheNationToken 4aaea2c
Remove package-lock.json to comply with yarn-only policy
TheNationToken da5e6a4
fix: finalize NATO Uniswap V3 strategy with latest snapshot-compliant…
TheNationToken e611696
chore: revert package.json, yarn.lock, test file, and strategies inde…
TheNationToken efbfaff
fix: restore missing strategies and align Multicaller import in nato-…
TheNationToken 85caeb9
merge: resolve conflict by taking upstream version of spark-with-dele…
TheNationToken 072aa05
feat: add nato-uniswap-v3 strategy (final Snapshot-compliant, tested …
TheNationToken 47157ac
chore: register nato-uniswap-v3 strategy in strategies/index.ts
TheNationToken 44d1ba0
Merge branch 'master' into master
ChaituVR 017a4e8
fix: update schema.json and cleanup imports as per review
TheNationToken 05f53a3
Update index.ts
TheNationToken 92cba05
Merge branch 'master' into master
ChaituVR e79aa3d
Update examples.json
TheNationToken dfb0842
Update examples.json
TheNationToken b59088b
Update schema.json
TheNationToken 6ef6d26
Update README.md
TheNationToken 89df396
Update strategy.test.ts
TheNationToken e023ad6
Update strategy.test.ts
TheNationToken 171c32f
Update strategy.test.ts
TheNationToken 2404807
Update strategy.test.ts
TheNationToken 9d300ba
Update examples.json
TheNationToken File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # NATO Uniswap V3 Strategy | ||
|
|
||
| This strategy calculates voting power based on NATO token liquidity | ||
| in Uniswap V3 positions for a given fee tier. | ||
| It sums the amount of NATO (token1) and accrued fees (tokensOwed1). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| [ | ||
| { | ||
| "strategy": { | ||
| "name": "nato-uniswap-v3", | ||
| "params": { | ||
| "tokenAddress": "0xd968196fa6977c4e58f2af5ac01c655ea8332d22", | ||
| "poolAddress": "0x02623e0e65A1D8537f6235512839E2f7b76C7A12", | ||
| "feeTier": 10000, | ||
| "positionManager": "0x03a520b32c04bf3beef7beb72e919cf822ed34f1", | ||
| "network": "base" | ||
| } | ||
| }, | ||
| "addresses": ["0xdA1dbA3c1B1cdEAf1419a85bA5B454547bdA5662"], | ||
| "snapshot": "latest" | ||
| } | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| import { getAddress } from '@ethersproject/address'; | ||
| import { formatUnits } from '@ethersproject/units'; | ||
| import { Multicaller } from '../../utils'; | ||
|
|
||
| const POSITION_MANAGER_ABI = [ | ||
| 'function balanceOf(address owner) view returns (uint256)', | ||
| 'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)', | ||
| 'function positions(uint256 tokenId) view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)' | ||
| ]; | ||
|
|
||
| const POOL_ABI = [ | ||
| 'function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)' | ||
| ]; | ||
|
|
||
| function getAmount1ForLiquidity( | ||
| sqrtLower: number, | ||
| sqrtUpper: number, | ||
| sqrtPriceCurrent: bigint, | ||
| liquidity: bigint | ||
| ): bigint { | ||
| const lower = BigInt(sqrtLower); | ||
| const upper = BigInt(sqrtUpper); | ||
| const current = BigInt(sqrtPriceCurrent); | ||
| const L = BigInt(liquidity); | ||
|
|
||
| if (current <= lower) return 0n; | ||
| if (current < upper) { | ||
| return (L * (current - lower)) / 2n ** 96n; | ||
| } | ||
| return (L * (upper - lower)) / 2n ** 96n; | ||
| } | ||
|
|
||
| export const strategy = async ( | ||
| _space: string, | ||
| _network: string, | ||
| _provider: any, | ||
| addresses: string[], | ||
| options: any, | ||
| snapshot: number | 'latest' | ||
| ) => { | ||
| const results: Record<string, number> = {}; | ||
| const blockTag = typeof snapshot === 'number' ? snapshot : 'latest'; | ||
|
|
||
| // slot0 | ||
| const poolCaller = new Multicaller(_network, _provider, POOL_ABI, { | ||
| blockTag | ||
| }); | ||
| poolCaller.call('slot0', options.poolAddress, 'slot0', []); | ||
| const poolRes = await poolCaller.execute(); | ||
| const slot0Arr = poolRes.slot0 as any[]; | ||
| if (!slot0Arr || !slot0Arr[0]) | ||
| throw new Error('❌ Failed to fetch slot0() from pool'); | ||
| const sqrtPriceX96 = BigInt(slot0Arr[0]); | ||
|
|
||
| // balanceOf | ||
| const balancesCaller = new Multicaller( | ||
| _network, | ||
| _provider, | ||
| POSITION_MANAGER_ABI, | ||
| { blockTag } | ||
| ); | ||
| addresses.forEach(addr => { | ||
| const addrChecksum = getAddress(addr); | ||
| balancesCaller.call( | ||
| `${addrChecksum}.balance`, | ||
| options.positionManager, | ||
| 'balanceOf', | ||
| [addrChecksum] | ||
| ); | ||
| }); | ||
| const balancesRes = (await balancesCaller.execute()) as Record< | ||
| string, | ||
| { balance: any } | ||
| >; | ||
|
|
||
| // tokenOfOwnerByIndex | ||
| const tokensCaller = new Multicaller( | ||
| _network, | ||
| _provider, | ||
| POSITION_MANAGER_ABI, | ||
| { blockTag } | ||
| ); | ||
| Object.entries(balancesRes).forEach(([addrChecksum, { balance }]) => { | ||
| const count = balance ? parseInt(balance.toString()) : 0; | ||
| for (let i = 0; i < count; i++) { | ||
| tokensCaller.call( | ||
| `${addrChecksum}.tokenIds.${i}`, | ||
| options.positionManager, | ||
| 'tokenOfOwnerByIndex', | ||
| [addrChecksum, i] | ||
| ); | ||
| } | ||
| }); | ||
| const tokensRes = (await tokensCaller.execute()) as Record< | ||
| string, | ||
| { tokenIds: Record<string, any> } | ||
| >; | ||
|
|
||
| // positions | ||
| const positionsCaller = new Multicaller( | ||
| _network, | ||
| _provider, | ||
| POSITION_MANAGER_ABI, | ||
| { blockTag } | ||
| ); | ||
| Object.values(tokensRes).forEach((entry: any) => { | ||
| if (!entry?.tokenIds) return; | ||
| Object.values(entry.tokenIds).forEach((tokenId: any) => { | ||
| if (tokenId !== undefined) { | ||
| positionsCaller.call( | ||
| `${tokenId}`, | ||
| options.positionManager, | ||
| 'positions', | ||
| [tokenId.toString()] | ||
| ); | ||
| } | ||
| }); | ||
| }); | ||
| const positionsRes = (await positionsCaller.execute()) as Record< | ||
| string, | ||
| any[] | ||
| >; | ||
|
|
||
| // aggregate | ||
| for (const addrRaw of addresses) { | ||
| const addrChecksum = getAddress(addrRaw); | ||
| const balBN = balancesRes[addrChecksum]?.balance; | ||
| const bal = balBN ? parseInt(balBN.toString()) : 0; | ||
| if (!bal) { | ||
| results[addrChecksum] = 0; | ||
| continue; | ||
| } | ||
|
|
||
| const tokenIdsObj = (tokensRes[addrChecksum]?.tokenIds || {}) as Record< | ||
| string, | ||
| any | ||
| >; | ||
| const tokenIds = Object.values(tokenIdsObj) | ||
| .map((v: any) => v?.toString()) | ||
| .filter(Boolean); | ||
|
|
||
| let total = 0n; | ||
| for (const tokenId of tokenIds) { | ||
| const pos = positionsRes[tokenId]; | ||
| if (!pos) continue; | ||
|
|
||
| const [ | ||
| , | ||
| , | ||
| , | ||
| token1, | ||
| fee, | ||
| tickLower, | ||
| tickUpper, | ||
| liquidity, | ||
| , | ||
| , | ||
| , | ||
| tokensOwed1 | ||
| ] = pos; | ||
|
|
||
| if ( | ||
| String(token1).toLowerCase() !== | ||
| String(options.tokenAddress).toLowerCase() || | ||
| Number(fee) !== Number(options.feeTier) | ||
| ) | ||
| continue; | ||
|
|
||
| const sqrtLower = Math.floor( | ||
| Math.sqrt(Math.pow(1.0001, Number(tickLower))) * 2 ** 96 | ||
| ); | ||
| const sqrtUpper = Math.floor( | ||
| Math.sqrt(Math.pow(1.0001, Number(tickUpper))) * 2 ** 96 | ||
| ); | ||
|
|
||
| const amount1 = getAmount1ForLiquidity( | ||
| sqrtLower, | ||
| sqrtUpper, | ||
| sqrtPriceX96, | ||
| BigInt(liquidity) | ||
| ); | ||
| total += amount1 + BigInt(tokensOwed1); | ||
| } | ||
|
|
||
| results[addrChecksum] = parseFloat(formatUnits(total.toString(), 18)); | ||
| } | ||
|
|
||
| return results; | ||
| }; |
|
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. Schema doesn't look correct |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/draft-07/schema#", | ||
| "title": "NATO Uniswap V3 Strategy", | ||
| "type": "object", | ||
| "properties": { | ||
| "tokenAddress": { | ||
| "type": "string", | ||
| "title": "NATO Token Address" | ||
| }, | ||
| "poolAddress": { | ||
| "type": "string", | ||
| "title": "Uniswap V3 Pool Address" | ||
| }, | ||
| "feeTier": { | ||
| "type": "number", | ||
| "title": "Fee Tier (Uniswap V3)" | ||
| }, | ||
| "positionManager": { | ||
| "type": "string", | ||
| "title": "Uniswap V3 Position Manager Address" | ||
| }, | ||
| "network": { | ||
| "type": "string", | ||
| "title": "Network" | ||
| } | ||
| }, | ||
| "required": [ | ||
| "tokenAddress", | ||
| "poolAddress", | ||
| "feeTier", | ||
| "positionManager", | ||
| "network" | ||
| ] | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
networkshould be passed at this param https://github.com/snapshot-labs/score-api/blob/master/src/strategies/strategies/erc20-balance-of/examples.json#L12check other strategies for examples
src/strategies/strategies/nato-uniswap-v3/index.ts