Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
aa07d2e
Add NATO Uniswap V3 strategy
TheNationToken Jul 30, 2025
445ab34
Remove broken import for nation3VotesWithDelegations
TheNationToken Jul 31, 2025
4aaea2c
Remove package-lock.json to comply with yarn-only policy
TheNationToken Jul 31, 2025
da5e6a4
fix: finalize NATO Uniswap V3 strategy with latest snapshot-compliant…
TheNationToken Jul 31, 2025
e611696
chore: revert package.json, yarn.lock, test file, and strategies inde…
TheNationToken Aug 11, 2025
efbfaff
fix: restore missing strategies and align Multicaller import in nato-…
TheNationToken Aug 11, 2025
85caeb9
merge: resolve conflict by taking upstream version of spark-with-dele…
TheNationToken Aug 18, 2025
072aa05
feat: add nato-uniswap-v3 strategy (final Snapshot-compliant, tested …
TheNationToken Aug 18, 2025
47157ac
chore: register nato-uniswap-v3 strategy in strategies/index.ts
TheNationToken Aug 19, 2025
44d1ba0
Merge branch 'master' into master
ChaituVR Aug 19, 2025
017a4e8
fix: update schema.json and cleanup imports as per review
TheNationToken Aug 21, 2025
05f53a3
Update index.ts
TheNationToken Aug 24, 2025
92cba05
Merge branch 'master' into master
ChaituVR Aug 25, 2025
e79aa3d
Update examples.json
TheNationToken Aug 25, 2025
dfb0842
Update examples.json
TheNationToken Aug 27, 2025
b59088b
Update schema.json
TheNationToken Aug 27, 2025
6ef6d26
Update README.md
TheNationToken Aug 27, 2025
89df396
Update strategy.test.ts
TheNationToken Aug 28, 2025
e023ad6
Update strategy.test.ts
TheNationToken Aug 28, 2025
171c32f
Update strategy.test.ts
TheNationToken Aug 28, 2025
2404807
Update strategy.test.ts
TheNationToken Aug 28, 2025
9d300ba
Update examples.json
TheNationToken Aug 28, 2025
faa2df8
Merge branch 'snapshot-labs:master' into master
TheNationToken Sep 18, 2025
8572ffb
Implemented nato-uniswapv3-voting strategy
Monarch717 Sep 18, 2025
6a0a5e5
remove nato-uniswap-v3
Monarch717 Sep 26, 2025
fe78623
restored test file and removed .zip
Monarch717 Sep 26, 2025
b5a2923
fixed feedbacks of 29th
Monarch717 Sep 29, 2025
3cb65f1
fixed yarn lint issue and testing issue
Monarch717 Oct 1, 2025
fdad3e4
Merge branch 'master' into master
ChaituVR Oct 6, 2025
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
25 changes: 25 additions & 0 deletions src/strategies/strategies/nato-uniswapv3-voting/README.md
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
23 changes: 23 additions & 0 deletions src/strategies/strategies/nato-uniswapv3-voting/examples.json
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
}
]
179 changes: 179 additions & 0 deletions src/strategies/strategies/nato-uniswapv3-voting/index.ts
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';
Comment on lines +11 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const author = 'vitalii';
export const version = '0.1.0';

Copy link
Author

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

Copy link
Author

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"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

@ChaituVR Is this not correct?


// 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
Copy link
Member

Choose a reason for hiding this comment

The 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
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]));
}
rawData = await subgraphRequest(
options.subgraph || UNISWAP_V3_SUBGRAPH_URL[network],
params
);

The correct solution is to deploy a new subgraph on base network

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ChaituVR , Do you want me to deploy our own subgraph?

Copy link
Member

Choose a reason for hiding this comment

The 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 || {};
}
5 changes: 5 additions & 0 deletions src/strategies/strategies/nato-uniswapv3-voting/manifest.json
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"
}
41 changes: 41 additions & 0 deletions src/strategies/strategies/nato-uniswapv3-voting/schema.json
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
}
}
}
Loading