Skip to content
Closed
Show file tree
Hide file tree
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 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
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
5 changes: 5 additions & 0 deletions src/strategies/strategies/nato-uniswap-v3/README.md
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).
16 changes: 16 additions & 0 deletions src/strategies/strategies/nato-uniswap-v3/examples.json
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"
}
Comment on lines +10 to +18
Copy link
Member

Choose a reason for hiding this comment

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

network should be passed at this param https://github.com/snapshot-labs/score-api/blob/master/src/strategies/strategies/erc20-balance-of/examples.json#L12
check other strategies for examples

src/strategies/strategies/nato-uniswap-v3/index.ts

]
189 changes: 189 additions & 0 deletions src/strategies/strategies/nato-uniswap-v3/index.ts
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;
};
34 changes: 34 additions & 0 deletions src/strategies/strategies/nato-uniswap-v3/schema.json
Copy link
Member

Choose a reason for hiding this comment

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

Schema doesn't look correct

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"
]
}
Loading