Skip to content
This repository was archived by the owner on Aug 22, 2025. It is now read-only.

Commit 19afafd

Browse files
authored
[erc20-votes-undelegated-balance] new erc20-votes-undelegated-balance strategy (#1778)
1 parent a193aab commit 19afafd

5 files changed

Lines changed: 151 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# erc20-votes-undelegated-balance
2+
3+
This strategy gives voting power based on token balance, but only to users who haven't delegated their voting power at all (including self-delegation).
4+
5+
## How it works
6+
7+
1. **Checks delegation status**: Calls `delegates()` to see who the user has delegated their voting power to
8+
2. **Gets token balance**: Calls `balanceOf()` to get the token balance of each address
9+
3. **Calculates voting power**:
10+
- If user has delegated to anyone (including themselves): voting power = 0
11+
- If user hasn't delegated at all (delegates() returns zero address): voting power = token balance
12+
13+
This ensures only users who haven't participated in the delegation system at all get voting power from their token balance.
14+
15+
## Parameters
16+
17+
- `address` - (**Required**, `string`) The address of the ERC-20 Votes token contract
18+
- `decimals` - (*Optional*, `number`) The number of decimals for the token. Defaults to 18
19+
20+
## Examples
21+
22+
```json
23+
{
24+
"symbol": "VOTE",
25+
"address": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
26+
"decimals": 18
27+
}
28+
```
29+
30+
In this example:
31+
32+
- Alice has 100 tokens and hasn't delegated at all → gets 100 voting power
33+
- Bob has 50 tokens and delegated to Charlie → gets 0 voting power
34+
- Charlie has 75 tokens and self-delegated → gets 0 voting power (because he has delegated, even to himself)
35+
- Dave has 25 tokens and hasn't delegated at all → gets 25 voting power
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{
3+
"name": "Example query",
4+
"strategy": {
5+
"name": "erc20-votes-undelegated-balance",
6+
"params": {
7+
"address": "0xAcd2c239012D17BEB128B0944D49015104113650",
8+
"decimals": 18
9+
}
10+
},
11+
"network": "1",
12+
"addresses": [
13+
"0xAcd2c239012D17BEB128B0944D49015104113650",
14+
"0x30320770c27c747E6C9CD30F5046c5313b0a1858",
15+
"0xb656015c94a4516fae9fa744b78e050299d5201d",
16+
"0x6dE6AbF292224b9b293f304388735D08b4Db1398",
17+
"0xe068F7e056380e460DE88cd8290C5596F0803D53",
18+
"0x13883dc4326fe65a3ae0383d509166dfad620cc4"
19+
],
20+
"snapshot": 22926406
21+
}
22+
]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { formatUnits } from '@ethersproject/units';
2+
import { getAddress, isAddress } from '@ethersproject/address';
3+
import { multicall } from '../../utils';
4+
5+
export const author = 'snapshot-labs';
6+
export const version = '0.1.0';
7+
8+
const abi = [
9+
'function delegates(address account) view returns (address)',
10+
'function balanceOf(address account) view returns (uint256)'
11+
];
12+
13+
export async function strategy(
14+
space,
15+
network,
16+
provider,
17+
addresses,
18+
options,
19+
snapshot
20+
) {
21+
const blockTag = typeof snapshot === 'number' ? snapshot : 'latest';
22+
23+
// Combine both delegates and balanceOf calls in a single multicall
24+
const calls = addresses.flatMap((address: any) => [
25+
[options.address, 'delegates', [address.toLowerCase()]],
26+
[options.address, 'balanceOf', [address.toLowerCase()]]
27+
]);
28+
29+
const response = await multicall(network, provider, abi, calls, { blockTag });
30+
31+
return Object.fromEntries(
32+
addresses.map((address, i) => {
33+
const delegatesIndex = i * 2;
34+
const balanceOfIndex = i * 2 + 1;
35+
36+
const delegateTo = response[delegatesIndex]?.toString().toLowerCase();
37+
const balance = parseFloat(
38+
formatUnits(response[balanceOfIndex].toString(), options.decimals || 18)
39+
);
40+
41+
// If user has delegated their voting power (to anyone, including themselves), their voting power is 0
42+
// Otherwise, they get their balance as voting power
43+
const hasDelegated = hasDelegateAddress(delegateTo);
44+
const totalVotingPower = hasDelegated ? 0 : balance;
45+
46+
return [getAddress(address), totalVotingPower];
47+
})
48+
);
49+
}
50+
51+
function hasDelegateAddress(delegateAddress: string | undefined): boolean {
52+
return (
53+
!!delegateAddress &&
54+
isAddress(delegateAddress) &&
55+
delegateAddress !== '0x0000000000000000000000000000000000000000'
56+
);
57+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$ref": "#/definitions/Strategy",
4+
"definitions": {
5+
"Strategy": {
6+
"title": "Strategy",
7+
"type": "object",
8+
"properties": {
9+
"symbol": {
10+
"type": "string",
11+
"title": "Symbol",
12+
"examples": ["e.g. VOTE"],
13+
"maxLength": 16
14+
},
15+
"address": {
16+
"type": "string",
17+
"title": "Contract address",
18+
"examples": ["e.g. 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"],
19+
"pattern": "^0x[a-fA-F0-9]{40}$",
20+
"minLength": 42,
21+
"maxLength": 42
22+
},
23+
"decimals": {
24+
"type": "number",
25+
"title": "Decimals",
26+
"examples": ["e.g. 18"],
27+
"minimum": 0,
28+
"default": 18
29+
}
30+
},
31+
"required": ["address"],
32+
"additionalProperties": false
33+
}
34+
}
35+
}

src/strategies/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as dpsNFTStrategyNova from './dps-nft-strategy-nova';
1313
import * as nounsPower from './nouns-rfp-power';
1414
import * as erc20Votes from './erc20-votes';
1515
import * as erc20VotesWithOverride from './erc20-votes-with-override';
16+
import * as erc20VotesUndelegatedBalance from './erc20-votes-undelegated-balance';
1617
import * as antiWhale from './anti-whale';
1718
import * as balancer from './balancer';
1819
import * as balancerSmartPool from './balancer-smart-pool';
@@ -538,6 +539,7 @@ const strategies = {
538539
'erc20-balance-of-at': erc20BalanceOfAt,
539540
'erc20-votes': erc20Votes,
540541
'erc20-votes-with-override': erc20VotesWithOverride,
542+
'erc20-votes-undelegated-balance': erc20VotesUndelegatedBalance,
541543
'erc721-multi-registry-weighted': erc721MultiRegistryWeighted,
542544
'erc20-balance-of-fixed-total': erc20BalanceOfFixedTotal,
543545
'erc20-balance-of-cv': erc20BalanceOfCv,

0 commit comments

Comments
 (0)