11import { formatBytes32String } from '@ethersproject/strings' ;
2- import { getAddress } from '@ethersproject/address' ;
2+ import { getAddress , isAddress } from '@ethersproject/address' ;
33import subgraphs from '@snapshot-labs/snapshot.js/src/delegationSubgraphs.json' ;
4+ import snapshot from '@snapshot-labs/snapshot.js' ;
45import {
5- getFormattedAddressesByProtocol ,
66 getProvider ,
77 getSnapshots ,
88 Multicaller ,
99 subgraphRequest
1010} from '../utils' ;
1111import _strategies from '../strategies' ;
12- import { Score , Snapshot , VotingPower } from '../types' ;
13- import { DEFAULT_SUPPORTED_PROTOCOLS } from '../constants' ;
12+ import { Score , Snapshot , VotingPower , Protocol } from '../types' ;
13+ import { DEFAULT_SUPPORTED_PROTOCOLS , VALID_PROTOCOLS } from '../constants' ;
1414
1515const DELEGATION_CONTRACT = '0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446' ;
1616const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000' ;
1717const EMPTY_SPACE = formatBytes32String ( '' ) ;
1818const abi = [ 'function delegation(address, bytes32) view returns (address)' ] ;
1919
20+ // Address format constants
21+ const EVM_ADDRESS_REGEX = / ^ 0 x [ a - f A - F 0 - 9 ] { 40 } $ / ;
22+ const STARKNET_ADDRESS_REGEX = / ^ 0 x [ a - f A - F 0 - 9 ] { 64 } $ / ;
23+
2024interface Delegation {
2125 in : string [ ] ;
2226 out : string | null ;
@@ -30,6 +34,9 @@ export async function getVp(
3034 space : string ,
3135 delegation ?: boolean
3236) : Promise < VotingPower > {
37+ validateStrategies ( strategies ) ;
38+ validateVoterAddress ( address ) ;
39+
3340 const networks = [ ...new Set ( strategies . map ( ( s ) => s . network || network ) ) ] ;
3441 const snapshots = await getSnapshots (
3542 network ,
@@ -38,8 +45,10 @@ export async function getVp(
3845 networks
3946 ) ;
4047
48+ // TODO: Delegation support for Starknet
49+ const delegationSupported = delegation && isAddress ( address ) ;
4150 const delegations = { } ;
42- if ( delegation ) {
51+ if ( delegationSupported ) {
4352 const ds = await Promise . all (
4453 networks . map ( ( n ) => getDelegations ( address , n , snapshots [ n ] , space ) )
4554 ) ;
@@ -49,19 +58,19 @@ export async function getVp(
4958 const p : Score [ ] = strategies . map ( ( strategy ) => {
5059 const n = strategy . network || network ;
5160 let addresses = [ address ] ;
61+ const supportedProtocols = _strategies [ strategy . name ] . supportedProtocols ;
5262
53- if ( delegation ) {
63+ if ( delegationSupported && supportedProtocols . includes ( 'evm' ) ) {
5464 addresses = delegations [ n ] . in ;
5565 if ( ! delegations [ n ] . out ) addresses . push ( address ) ;
5666 addresses = [ ...new Set ( addresses ) ] ;
5767 if ( addresses . length === 0 ) return { } ;
5868 }
5969
60- addresses = getFormattedAddressesByProtocol (
61- addresses ,
62- _strategies [ strategy . name ] . supportedProtocols ??
63- DEFAULT_SUPPORTED_PROTOCOLS
64- ) ;
70+ addresses = formatSupportedAddresses ( addresses , supportedProtocols ) ;
71+
72+ if ( addresses . length === 0 ) return { } ;
73+
6574 return _strategies [ strategy . name ] . strategy (
6675 space ,
6776 n ,
@@ -76,18 +85,16 @@ export async function getVp(
7685 const vpByStrategy = scores . map ( ( score , i ) => {
7786 const n = strategies [ i ] . network || network ;
7887 let addresses = [ address ] ;
88+ const supportedProtocols =
89+ _strategies [ strategies [ i ] . name ] . supportedProtocols ;
7990
80- if ( delegation ) {
91+ if ( delegationSupported && supportedProtocols . includes ( 'evm' ) ) {
8192 addresses = delegations [ n ] . in ;
8293 if ( ! delegations [ n ] . out ) addresses . push ( address ) ;
8394 addresses = [ ...new Set ( addresses ) ] ;
8495 }
8596
86- addresses = getFormattedAddressesByProtocol (
87- addresses ,
88- _strategies [ strategies [ i ] . name ] . supportedProtocols ??
89- DEFAULT_SUPPORTED_PROTOCOLS
90- ) ;
97+ addresses = formatSupportedAddresses ( addresses , supportedProtocols ) ;
9198 return addresses . reduce ( ( a , b ) => a + ( score [ b ] || 0 ) , 0 ) ;
9299 } ) ;
93100 const vp = vpByStrategy . reduce ( ( a , b ) => a + b , 0 ) ;
@@ -228,3 +235,91 @@ export async function getDelegations(
228235 out : delegationOut
229236 } ;
230237}
238+
239+ // Ensure that the address is either a valid EVM address or Starknet address
240+ function validateVoterAddress ( address : string ) : void {
241+ try {
242+ const result = formatSupportedAddresses ( [ address ] , VALID_PROTOCOLS ) ;
243+ if ( ! result . length ) {
244+ throw new Error ( 'invalid address' ) ;
245+ }
246+ } catch {
247+ throw new Error ( 'invalid address' ) ;
248+ }
249+ }
250+
251+ function validateStrategies ( strategies : any [ ] ) : void {
252+ const invalidStrategies = strategies
253+ . filter ( ( s ) => ! _strategies [ s . name ] )
254+ . map ( ( s ) => s . name ) ;
255+
256+ if ( invalidStrategies . length > 0 ) {
257+ throw new Error ( `invalid strategies: ${ invalidStrategies . join ( ', ' ) } ` ) ;
258+ }
259+ }
260+
261+ /**
262+ * Validates that protocols are non-empty and contain only valid protocol names.
263+ *
264+ * @param protocols - Array of protocol names to validate
265+ */
266+ function validateProtocols ( protocols : Protocol [ ] ) : void {
267+ if ( ! protocols . length ) {
268+ throw new Error ( 'At least one protocol must be specified' ) ;
269+ }
270+
271+ const invalidProtocols = protocols . filter (
272+ ( p ) => ! VALID_PROTOCOLS . includes ( p )
273+ ) ;
274+ if ( invalidProtocols . length > 0 ) {
275+ throw new Error ( `Invalid protocol(s): ${ invalidProtocols . join ( ', ' ) } ` ) ;
276+ }
277+ }
278+
279+ /**
280+ * Formats addresses relevant to the given protocols according to the specified blockchain protocols.
281+ *
282+ * Will ignore addresses that are not evm or starknet like addresses
283+ *
284+ * @param addresses - Array of blockchain addresses to format
285+ * @param protocols - Array of protocol names to validate against. Defaults to ['evm'].
286+ * Valid protocols are 'evm' and 'starknet'.
287+ *
288+ * @returns Array of formatted addresses in the same order as input
289+ */
290+ export function formatSupportedAddresses (
291+ addresses : string [ ] ,
292+ protocols : Protocol [ ] = DEFAULT_SUPPORTED_PROTOCOLS
293+ ) : string [ ] {
294+ validateProtocols ( protocols ) ;
295+
296+ const supportsEvm = protocols . includes ( 'evm' ) ;
297+ const supportsStarknet = protocols . includes ( 'starknet' ) ;
298+
299+ const getAddressType = ( address : string ) : 'evm' | 'starknet' | null => {
300+ if ( supportsEvm && EVM_ADDRESS_REGEX . test ( address ) ) return 'evm' ;
301+ if ( supportsStarknet && STARKNET_ADDRESS_REGEX . test ( address ) )
302+ return 'starknet' ;
303+ return null ;
304+ } ;
305+
306+ const result : string [ ] = [ ] ;
307+ for ( const address of addresses ) {
308+ const addressType = getAddressType ( address ) ;
309+ if ( ! addressType ) continue ; // Skip unsupported addresses
310+
311+ try {
312+ const formattedAddress = snapshot . utils . getFormattedAddress (
313+ address ,
314+ addressType
315+ ) ;
316+ result . push ( formattedAddress ) ;
317+ } catch ( e ) {
318+ throw new Error (
319+ `Address "${ address } " is not a valid ${ protocols . join ( ' or ' ) } address`
320+ ) ;
321+ }
322+ }
323+
324+ return result ;
325+ }
0 commit comments