@snapshot-labs/snapshot.js 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,52 +1,99 @@
1
- import {
2
- JsonRpcBatchProvider,
3
- StaticJsonRpcProvider
4
- } from '@ethersproject/providers';
1
+ import { StaticJsonRpcProvider } from '@ethersproject/providers';
2
+ import { RpcProvider } from 'starknet';
3
+ import networks from '../networks.json';
5
4
 
6
- const providers = {};
7
- const batchedProviders = {};
5
+ export interface ProviderOptions {
6
+ readonly broviderUrl?: string;
7
+ readonly timeout?: number;
8
+ }
9
+
10
+ type ProviderInstance = StaticJsonRpcProvider | RpcProvider;
11
+
12
+ type ProviderType = 'evm' | 'starknet';
13
+
14
+ const DEFAULT_BROVIDER_URL = 'https://rpc.snapshot.org' as const;
15
+ const DEFAULT_TIMEOUT = 25000 as const;
16
+ const STARKNET_BROVIDER_KEYS: string[] = ['sn', 'sn-sep'] as const;
17
+
18
+ const providerMemo = new Map<string, ProviderInstance>();
8
19
 
9
- export type ProviderOptions = {
10
- broviderUrl?: string;
11
- timeout?: number;
20
+ const providerFnMap: Record<
21
+ ProviderType,
22
+ (networkId: string, options: Required<ProviderOptions>) => ProviderInstance
23
+ > = {
24
+ evm: getEvmProvider,
25
+ starknet: getStarknetProvider
12
26
  };
13
27
 
14
- const DEFAULT_BROVIDER_URL = 'https://rpc.snapshot.org';
15
- const DEFAULT_TIMEOUT = 25000;
28
+ function normalizeOptions(
29
+ options: ProviderOptions = {}
30
+ ): Required<ProviderOptions> {
31
+ return {
32
+ broviderUrl: options.broviderUrl || DEFAULT_BROVIDER_URL,
33
+ timeout: options.timeout ?? DEFAULT_TIMEOUT
34
+ };
35
+ }
36
+
37
+ function getBroviderNetworkId(network: string | number): string {
38
+ const config = networks[network];
39
+ if (!config) {
40
+ throw new Error(`Network '${network}' is not supported`);
41
+ }
42
+ return String(config.key);
43
+ }
44
+
45
+ function getProviderType(networkId: string): ProviderType {
46
+ const isStarknet = STARKNET_BROVIDER_KEYS.includes(networkId);
47
+ return isStarknet ? 'starknet' : 'evm';
48
+ }
16
49
 
50
+ function createMemoKey(
51
+ networkId: string,
52
+ options: Required<ProviderOptions>
53
+ ): string {
54
+ return `${networkId}:${options.broviderUrl}:${options.timeout}`;
55
+ }
56
+
57
+ // return loose `any` type to avoid typecheck issues on package consumers
17
58
  export default function getProvider(
18
- network,
19
- {
20
- broviderUrl = DEFAULT_BROVIDER_URL,
21
- timeout = DEFAULT_TIMEOUT
22
- }: ProviderOptions = {}
23
- ) {
24
- const url = `${broviderUrl}/${network}`;
25
- if (!providers[network])
26
- providers[network] = new StaticJsonRpcProvider(
27
- {
28
- url,
29
- timeout,
30
- allowGzip: true
31
- },
32
- Number(network)
33
- );
34
- return providers[network];
35
- }
36
-
37
- export function getBatchedProvider(
38
- network,
39
- {
40
- broviderUrl = DEFAULT_BROVIDER_URL,
41
- timeout = DEFAULT_TIMEOUT
42
- }: ProviderOptions = {}
43
- ) {
44
- const url = `${broviderUrl}/${network}`;
45
- if (!batchedProviders[network])
46
- batchedProviders[network] = new JsonRpcBatchProvider({
47
- url,
48
- timeout,
59
+ network: string | number,
60
+ options: ProviderOptions = {}
61
+ ): any {
62
+ const networkId = getBroviderNetworkId(network);
63
+ const normalizedOptions = normalizeOptions(options);
64
+ const memoKey = createMemoKey(networkId, normalizedOptions);
65
+
66
+ const memoized = providerMemo.get(memoKey);
67
+ if (memoized) {
68
+ return memoized;
69
+ }
70
+
71
+ const providerType = getProviderType(networkId);
72
+ const provider = providerFnMap[providerType](networkId, normalizedOptions);
73
+
74
+ providerMemo.set(memoKey, provider);
75
+ return provider;
76
+ }
77
+
78
+ function getEvmProvider(
79
+ networkId: string,
80
+ options: Required<ProviderOptions>
81
+ ): StaticJsonRpcProvider {
82
+ return new StaticJsonRpcProvider(
83
+ {
84
+ url: `${options.broviderUrl}/${networkId}`,
85
+ timeout: options.timeout,
49
86
  allowGzip: true
50
- });
51
- return batchedProviders[network];
87
+ },
88
+ Number(networkId)
89
+ );
90
+ }
91
+
92
+ function getStarknetProvider(
93
+ networkKey: string,
94
+ options: Required<ProviderOptions>
95
+ ): RpcProvider {
96
+ return new RpcProvider({
97
+ nodeUrl: `${options.broviderUrl}/${networkKey}`
98
+ });
52
99
  }
package/src/utils.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import fetch from 'cross-fetch';
2
- import { Interface } from '@ethersproject/abi';
3
2
  import { Contract } from '@ethersproject/contracts';
4
3
  import { getAddress, isAddress } from '@ethersproject/address';
5
4
  import { parseUnits } from '@ethersproject/units';
@@ -8,7 +7,6 @@ import { jsonToGraphQLQuery } from 'json-to-graphql-query';
8
7
  import Ajv from 'ajv';
9
8
  import addFormats from 'ajv-formats';
10
9
  import addErrors from 'ajv-errors';
11
- import Multicaller from './utils/multicaller';
12
10
  import { getSnapshots } from './utils/blockfinder';
13
11
  import getProvider from './utils/provider';
14
12
  import { signMessage, getBlockNumber } from './utils/web3';
@@ -18,6 +16,7 @@ import networks from './networks.json';
18
16
  import voting from './voting';
19
17
  import getDelegatesBySpace, { SNAPSHOT_SUBGRAPH_URL } from './utils/delegation';
20
18
  import { validateAndParseAddress } from 'starknet';
19
+ import { multicall, Multicaller } from './multicall';
21
20
 
22
21
  interface Options {
23
22
  url?: string;
@@ -45,16 +44,6 @@ const ENS_ABI = [
45
44
  'function resolver(bytes32 node) view returns (address)' // ENS registry ABI
46
45
  ];
47
46
  const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000';
48
- const STARKNET_NETWORKS = {
49
- '0x534e5f4d41494e': {
50
- name: 'Starknet',
51
- testnet: false
52
- },
53
- '0x534e5f5345504f4c4941': {
54
- name: 'Starknet Sepolia',
55
- testnet: true
56
- }
57
- };
58
47
 
59
48
  const scoreApiHeaders = {
60
49
  Accept: 'application/json',
@@ -189,24 +178,6 @@ ajv.addKeyword({
189
178
  }
190
179
  });
191
180
 
192
- ajv.addKeyword({
193
- keyword: 'starknetNetwork',
194
- validate: function (schema, data) {
195
- // @ts-ignore
196
- const snapshotEnv = this.snapshotEnv || 'default';
197
- if (snapshotEnv === 'mainnet') {
198
- return Object.keys(STARKNET_NETWORKS)
199
- .filter((id) => !STARKNET_NETWORKS[id].testnet)
200
- .includes(data);
201
- }
202
-
203
- return Object.keys(STARKNET_NETWORKS).includes(data);
204
- },
205
- error: {
206
- message: 'network not allowed'
207
- }
208
- });
209
-
210
181
  // Custom URL format to allow empty string values
211
182
  // https://github.com/snapshot-labs/snapshot.js/pull/541/files
212
183
  ajv.addFormat('customUrl', {
@@ -280,48 +251,6 @@ export async function call(provider, abi: any[], call: any[], options?) {
280
251
  return Promise.reject(e);
281
252
  }
282
253
  }
283
-
284
- export async function multicall(
285
- network: string,
286
- provider,
287
- abi: any[],
288
- calls: any[],
289
- options?
290
- ) {
291
- const multicallAbi = [
292
- 'function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)'
293
- ];
294
- const multicallAddress =
295
- options?.multicallAddress || networks[network].multicall;
296
- const multi = new Contract(multicallAddress, multicallAbi, provider);
297
- const itf = new Interface(abi);
298
- try {
299
- const max = options?.limit || 500;
300
- if (options?.limit) delete options.limit;
301
- const pages = Math.ceil(calls.length / max);
302
- const promises: any = [];
303
- Array.from(Array(pages)).forEach((x, i) => {
304
- const callsInPage = calls.slice(max * i, max * (i + 1));
305
- promises.push(
306
- multi.aggregate(
307
- callsInPage.map((call) => [
308
- call[0].toLowerCase(),
309
- itf.encodeFunctionData(call[1], call[2])
310
- ]),
311
- options || {}
312
- )
313
- );
314
- });
315
- let results: any = await Promise.all(promises);
316
- results = results.reduce((prev: any, [, res]: any) => prev.concat(res), []);
317
- return results.map((call, i) =>
318
- itf.decodeFunctionResult(calls[i][1], call)
319
- );
320
- } catch (e: any) {
321
- return Promise.reject(e);
322
- }
323
- }
324
-
325
254
  export async function subgraphRequest(url: string, query, options: any = {}) {
326
255
  const body: Record<string, any> = { query: jsonToGraphQLQuery({ query }) };
327
256
  if (options.variables) body.variables = options.variables;
@@ -634,13 +563,13 @@ export async function getEnsTextRecord(
634
563
  ]) // Query for text record from each resolver
635
564
  ];
636
565
 
637
- const [[resolverAddress], ...textRecords]: string[][] = await multicall(
566
+ const [[resolverAddress], ...textRecords] = (await multicall(
638
567
  network,
639
568
  provider,
640
569
  ENS_ABI,
641
570
  calls,
642
571
  multicallOptions
643
- );
572
+ )) as string[][];
644
573
 
645
574
  const resolverIndex = ensResolvers.indexOf(resolverAddress);
646
575
  return resolverIndex !== -1 ? textRecords[resolverIndex]?.[0] : null;
@@ -1,12 +0,0 @@
1
- import { StaticJsonRpcProvider } from '@ethersproject/providers';
2
- export default class Multicaller {
3
- network: string;
4
- provider: StaticJsonRpcProvider;
5
- abi: any[];
6
- options: any;
7
- calls: any[];
8
- paths: any[];
9
- constructor(network: string, provider: StaticJsonRpcProvider, abi: any[], options?: any);
10
- call(path: any, address: any, fn: any, params?: any): Multicaller;
11
- execute(from?: any): Promise<any>;
12
- }