@snapshot-labs/snapshot.js 0.12.0-beta.0 → 0.12.0-beta.1

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.
Files changed (56) hide show
  1. package/README.md +2 -2
  2. package/dist/index.d.ts +20 -2
  3. package/dist/schemas/index.d.ts +19 -1
  4. package/dist/sign/types.d.ts +2 -0
  5. package/dist/snapshot.cjs.js +1851 -1929
  6. package/dist/snapshot.esm.js +1852 -1930
  7. package/dist/snapshot.min.js +16 -5
  8. package/dist/src/index.d.ts +725 -0
  9. package/dist/src/schemas/index.d.ts +671 -0
  10. package/dist/src/sign/index.d.ts +29 -0
  11. package/dist/src/sign/types.d.ts +227 -0
  12. package/dist/src/utils/blockfinder.d.ts +1 -0
  13. package/dist/src/utils/delegation.d.ts +18 -0
  14. package/dist/src/utils/multicaller.d.ts +12 -0
  15. package/dist/src/utils/provider.d.ts +5 -0
  16. package/dist/src/utils/web3.d.ts +2 -0
  17. package/dist/src/utils.d.ts +91 -0
  18. package/dist/src/verify/evm.d.ts +4 -0
  19. package/dist/src/verify/evm.spec.d.ts +1 -0
  20. package/dist/src/verify/index.d.ts +11 -0
  21. package/dist/src/verify/index.spec.d.ts +1 -0
  22. package/dist/src/verify/starknet.d.ts +6 -0
  23. package/dist/src/verify/starknet.spec.d.ts +1 -0
  24. package/dist/src/voting/approval.d.ts +22 -0
  25. package/dist/src/voting/index.d.ts +14 -0
  26. package/dist/src/voting/quadratic.d.ts +20 -0
  27. package/dist/src/voting/rankedChoice.d.ts +18 -0
  28. package/dist/src/voting/singleChoice.d.ts +18 -0
  29. package/dist/src/voting/types.d.ts +35 -0
  30. package/dist/src/voting/weighted.d.ts +26 -0
  31. package/dist/utils/delegation.d.ts +1 -1
  32. package/dist/utils.d.ts +3 -2
  33. package/package.json +6 -3
  34. package/src/delegationSubgraphs.json +8 -8
  35. package/src/gateways.json +1 -2
  36. package/src/networks.json +222 -60
  37. package/src/schemas/profile.json +30 -0
  38. package/src/schemas/proposal.json +1 -1
  39. package/src/schemas/space.json +8 -7
  40. package/src/schemas/statement.json +17 -2
  41. package/src/schemas/vote.json +1 -1
  42. package/src/sign/hashedTypes.json +8 -1
  43. package/src/sign/index.ts +8 -6
  44. package/src/sign/types.ts +11 -1
  45. package/src/utils/provider.ts +8 -2
  46. package/src/utils/web3.ts +1 -1
  47. package/src/utils.spec.js +86 -31
  48. package/src/utils.ts +111 -38
  49. package/src/verify/evm.spec.ts +32 -0
  50. package/src/verify/evm.ts +82 -0
  51. package/src/verify/index.spec.ts +84 -0
  52. package/src/verify/index.ts +41 -0
  53. package/src/verify/starknet.spec.ts +55 -0
  54. package/src/verify/starknet.ts +82 -0
  55. package/src/sign/eip1271.ts +0 -55
  56. package/src/sign/utils.ts +0 -27
package/src/utils.spec.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { describe, test, expect, vi, afterEach } from 'vitest';
2
2
  import * as crossFetch from 'cross-fetch';
3
- import { validate, getScores, getVp } from './utils';
3
+ import { validate, getScores, getVp, getFormattedAddress } from './utils';
4
4
 
5
5
  vi.mock('cross-fetch', async () => {
6
6
  const actual = await vi.importActual('cross-fetch');
@@ -78,13 +78,14 @@ describe('utils', () => {
78
78
 
79
79
  describe('when passing valid args', () => {
80
80
  test('send a JSON-RPC payload to score-api', async () => {
81
+ const result = { result: 'OK' };
81
82
  fetch.mockReturnValue({
82
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
83
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
83
84
  });
84
85
 
85
86
  expect(_validate({})).resolves;
86
87
  expect(fetch).toHaveBeenCalledWith(
87
- 'https://score.snapshot.org',
88
+ 'https://score.snapshot.org/',
88
89
  expect.objectContaining({
89
90
  body: JSON.stringify({
90
91
  jsonrpc: '2.0',
@@ -96,13 +97,14 @@ describe('utils', () => {
96
97
  });
97
98
 
98
99
  test('send a POST request with JSON content-type', async () => {
100
+ const result = { result: 'OK' };
99
101
  fetch.mockReturnValue({
100
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
102
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
101
103
  });
102
104
 
103
105
  expect(_validate({})).resolves;
104
106
  expect(fetch).toHaveBeenCalledWith(
105
- 'https://score.snapshot.org',
107
+ 'https://score.snapshot.org/',
106
108
  expect.objectContaining({
107
109
  method: 'POST',
108
110
  headers: {
@@ -114,23 +116,28 @@ describe('utils', () => {
114
116
  });
115
117
 
116
118
  test('can customize the score-api url', () => {
119
+ const result = { result: 'OK' };
117
120
  fetch.mockReturnValue({
118
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
121
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
119
122
  });
120
123
 
121
124
  expect(
122
125
  _validate({ options: { url: 'https://snapshot.org/?apiKey=xxx' } })
123
126
  ).resolves;
124
127
  expect(fetch).toHaveBeenCalledWith(
125
- 'https://snapshot.org/?apiKey=xxx',
126
- expect.anything()
128
+ 'https://snapshot.org/',
129
+ expect.objectContaining({
130
+ headers: expect.objectContaining({
131
+ 'X-API-KEY': 'xxx'
132
+ })
133
+ })
127
134
  );
128
135
  });
129
136
 
130
137
  test('returns the JSON-RPC result property', () => {
131
138
  const result = { result: 'OK' };
132
139
  fetch.mockReturnValue({
133
- json: () => new Promise((resolve) => resolve(result))
140
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
134
141
  });
135
142
 
136
143
  expect(_validate({})).resolves.toEqual('OK');
@@ -141,7 +148,7 @@ describe('utils', () => {
141
148
  test('rejects with the JSON-RPC error object', () => {
142
149
  const result = { error: { message: 'Oh no' } };
143
150
  fetch.mockReturnValue({
144
- json: () => new Promise((resolve) => resolve(result))
151
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
145
152
  });
146
153
 
147
154
  expect(_validate({})).rejects.toEqual(result.error);
@@ -152,7 +159,7 @@ describe('utils', () => {
152
159
  test('rejects with the error', () => {
153
160
  const result = new Error('Oh no');
154
161
  fetch.mockReturnValue({
155
- json: () => {
162
+ text: () => {
156
163
  throw result;
157
164
  }
158
165
  });
@@ -196,7 +203,7 @@ describe('utils', () => {
196
203
  network ?? payload.network,
197
204
  addresses ?? payload.addresses,
198
205
  snapshot ?? payload.snapshot,
199
- scoreApiUrl ?? 'https://score.snapshot.org',
206
+ scoreApiUrl ?? 'https://score.snapshot.org/',
200
207
  options ?? {}
201
208
  );
202
209
  }
@@ -239,8 +246,9 @@ describe('utils', () => {
239
246
 
240
247
  describe('when passing valid args', () => {
241
248
  test('send a JSON-RPC payload to score-api', async () => {
249
+ const result = { result: 'OK' };
242
250
  fetch.mockReturnValue({
243
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
251
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
244
252
  });
245
253
 
246
254
  expect(_getScores({})).resolves;
@@ -253,8 +261,9 @@ describe('utils', () => {
253
261
  });
254
262
 
255
263
  test('send a POST request with JSON content-type', async () => {
264
+ const result = { result: 'OK' };
256
265
  fetch.mockReturnValue({
257
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
266
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
258
267
  });
259
268
 
260
269
  expect(_getScores({})).resolves;
@@ -270,23 +279,29 @@ describe('utils', () => {
270
279
  );
271
280
  });
272
281
 
273
- test('can customize the score-api url', () => {
282
+ test('can customize the score-api url and if apiKey should be passed in headers', () => {
283
+ const result = { result: 'OK' };
274
284
  fetch.mockReturnValue({
275
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
285
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
276
286
  });
277
287
 
278
288
  expect(_getScores({ scoreApiUrl: 'https://snapshot.org/?apiKey=xxx' }))
279
289
  .resolves;
280
290
  expect(fetch).toHaveBeenCalledWith(
281
- 'https://snapshot.org/api/scores?apiKey=xxx',
282
- expect.anything()
291
+ 'https://snapshot.org/api/scores',
292
+ expect.objectContaining({
293
+ headers: expect.objectContaining({
294
+ 'X-API-KEY': 'xxx'
295
+ })
296
+ })
283
297
  );
284
298
  });
285
299
 
286
300
  test('returns the JSON-RPC result scores property', () => {
287
301
  const result = { scores: 'SCORES', other: 'Other' };
288
302
  fetch.mockReturnValue({
289
- json: () => new Promise((resolve) => resolve({ result }))
303
+ text: () =>
304
+ new Promise((resolve) => resolve(JSON.stringify({ result })))
290
305
  });
291
306
 
292
307
  expect(_getScores({})).resolves.toEqual('SCORES');
@@ -295,7 +310,8 @@ describe('utils', () => {
295
310
  test('returns the JSON-RPC all properties', () => {
296
311
  const result = { scores: 'SCORES', other: 'Other' };
297
312
  fetch.mockReturnValue({
298
- json: () => new Promise((resolve) => resolve({ result }))
313
+ text: () =>
314
+ new Promise((resolve) => resolve(JSON.stringify({ result })))
299
315
  });
300
316
 
301
317
  expect(
@@ -308,7 +324,7 @@ describe('utils', () => {
308
324
  test('rejects with the JSON-RPC error object', () => {
309
325
  const result = { error: { message: 'Oh no' } };
310
326
  fetch.mockReturnValue({
311
- json: () => new Promise((resolve) => resolve(result))
327
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
312
328
  });
313
329
 
314
330
  expect(_getScores({})).rejects.toEqual(result.error);
@@ -319,7 +335,7 @@ describe('utils', () => {
319
335
  test('rejects with the error', () => {
320
336
  const result = new Error('Oh no');
321
337
  fetch.mockReturnValue({
322
- json: () => {
338
+ text: () => {
323
339
  throw result;
324
340
  }
325
341
  });
@@ -391,13 +407,14 @@ describe('utils', () => {
391
407
 
392
408
  describe('when passing valid args', () => {
393
409
  test('send a JSON-RPC payload to score-api', async () => {
410
+ const result = { result: 'OK' };
394
411
  fetch.mockReturnValue({
395
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
412
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
396
413
  });
397
414
 
398
415
  expect(_getVp({})).resolves;
399
416
  expect(fetch).toHaveBeenCalledWith(
400
- 'https://score.snapshot.org',
417
+ 'https://score.snapshot.org/',
401
418
  expect.objectContaining({
402
419
  body: JSON.stringify({
403
420
  jsonrpc: '2.0',
@@ -409,13 +426,14 @@ describe('utils', () => {
409
426
  });
410
427
 
411
428
  test('send a POST request with JSON content-type', async () => {
429
+ const result = { result: 'OK' };
412
430
  fetch.mockReturnValue({
413
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
431
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
414
432
  });
415
433
 
416
434
  expect(_getVp({})).resolves;
417
435
  expect(fetch).toHaveBeenCalledWith(
418
- 'https://score.snapshot.org',
436
+ 'https://score.snapshot.org/',
419
437
  expect.objectContaining({
420
438
  method: 'POST',
421
439
  headers: {
@@ -427,13 +445,14 @@ describe('utils', () => {
427
445
  });
428
446
 
429
447
  test('can customize the score-api url', () => {
448
+ const result = { result: 'OK' };
430
449
  fetch.mockReturnValue({
431
- json: () => new Promise((resolve) => resolve({ result: 'OK' }))
450
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
432
451
  });
433
452
 
434
453
  expect(_getVp({ options: { url: 'https://snapshot.org' } })).resolves;
435
454
  expect(fetch).toHaveBeenCalledWith(
436
- 'https://snapshot.org',
455
+ 'https://snapshot.org/',
437
456
  expect.anything()
438
457
  );
439
458
  });
@@ -441,7 +460,8 @@ describe('utils', () => {
441
460
  test('returns the JSON-RPC result property', () => {
442
461
  const result = { data: 'OK' };
443
462
  fetch.mockReturnValue({
444
- json: () => new Promise((resolve) => resolve({ result }))
463
+ text: () =>
464
+ new Promise((resolve) => resolve(JSON.stringify({ result })))
445
465
  });
446
466
 
447
467
  expect(_getVp({})).resolves.toEqual(result);
@@ -452,7 +472,7 @@ describe('utils', () => {
452
472
  test('rejects with the JSON-RPC error object', () => {
453
473
  const result = { error: { message: 'Oh no' } };
454
474
  fetch.mockReturnValue({
455
- json: () => new Promise((resolve) => resolve(result))
475
+ text: () => new Promise((resolve) => resolve(JSON.stringify(result)))
456
476
  });
457
477
 
458
478
  expect(_getVp({})).rejects.toEqual(result.error);
@@ -463,7 +483,7 @@ describe('utils', () => {
463
483
  test('rejects with the error', () => {
464
484
  const result = new Error('Oh no');
465
485
  fetch.mockReturnValue({
466
- json: () => {
486
+ text: () => {
467
487
  throw result;
468
488
  }
469
489
  });
@@ -472,4 +492,39 @@ describe('utils', () => {
472
492
  });
473
493
  });
474
494
  });
495
+
496
+ describe('getFormattedAddress', () => {
497
+ test('returns a checksummed EVM address', () => {
498
+ const address = '0x91fd2c8d24767db4ece7069aa27832ffaf8590f3';
499
+ expect(getFormattedAddress(address, ['evm'])).toEqual(
500
+ '0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
501
+ );
502
+ });
503
+
504
+ test('returns a padded and lowercased starknet address', () => {
505
+ const address =
506
+ '0x2a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
507
+ expect(getFormattedAddress(address, ['starknet'])).toEqual(
508
+ '0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
509
+ );
510
+ });
511
+
512
+ test('returns an EVM address as starknet address', () => {
513
+ const address = '0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3';
514
+ expect(getFormattedAddress(address, ['starknet'])).toEqual(
515
+ '0x00000000000000000000000091fd2c8d24767db4ece7069aa27832ffaf8590f3'
516
+ );
517
+ });
518
+
519
+ test('throws an error when the address is not a starknet address', () => {
520
+ const address = 'hello';
521
+ expect(() => getFormattedAddress(address, ['starknet'])).toThrow();
522
+ });
523
+
524
+ test('throws an error when the address is not an EVM address', () => {
525
+ const address =
526
+ '0x2a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
527
+ expect(() => getFormattedAddress(address, ['evm'])).toThrow();
528
+ });
529
+ });
475
530
  });
package/src/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import fetch from 'cross-fetch';
2
2
  import { Interface } from '@ethersproject/abi';
3
3
  import { Contract } from '@ethersproject/contracts';
4
- import { isAddress } from '@ethersproject/address';
4
+ import { getAddress, isAddress } from '@ethersproject/address';
5
5
  import { parseUnits } from '@ethersproject/units';
6
6
  import { namehash, ensNormalize } from '@ethersproject/hash';
7
7
  import { jsonToGraphQLQuery } from 'json-to-graphql-query';
@@ -12,14 +12,16 @@ import Multicaller from './utils/multicaller';
12
12
  import { getSnapshots } from './utils/blockfinder';
13
13
  import getProvider from './utils/provider';
14
14
  import { signMessage, getBlockNumber } from './utils/web3';
15
- import { getHash, verify } from './sign/utils';
15
+ import { getHash, verify } from './verify';
16
16
  import gateways from './gateways.json';
17
17
  import networks from './networks.json';
18
18
  import voting from './voting';
19
19
  import getDelegatesBySpace, { SNAPSHOT_SUBGRAPH_URL } from './utils/delegation';
20
+ import { validateAndParseAddress } from 'starknet';
20
21
 
21
22
  interface Options {
22
23
  url?: string;
24
+ headers?: any;
23
25
  }
24
26
 
25
27
  interface Strategy {
@@ -38,6 +40,43 @@ const scoreApiHeaders = {
38
40
  'Content-Type': 'application/json'
39
41
  };
40
42
 
43
+ const DEFAULT_SCORE_API_URL = 'https://score.snapshot.org';
44
+
45
+ function formatScoreAPIUrl(
46
+ url = DEFAULT_SCORE_API_URL,
47
+ options = {
48
+ path: ''
49
+ }
50
+ ) {
51
+ const scoreURL = new URL(url);
52
+ if (options.path) scoreURL.pathname = options.path;
53
+ const apiKey = scoreURL.searchParams.get('apiKey');
54
+ let headers: any = { ...scoreApiHeaders };
55
+ if (apiKey) {
56
+ scoreURL.searchParams.delete('apiKey');
57
+ headers = { ...scoreApiHeaders, 'X-API-KEY': apiKey };
58
+ }
59
+ return {
60
+ url: scoreURL.toString(),
61
+ headers
62
+ };
63
+ }
64
+
65
+ async function parseScoreAPIResponse(res: any) {
66
+ let data: any = await res.text();
67
+ try {
68
+ data = JSON.parse(data);
69
+ } catch (e: any) {
70
+ return Promise.reject({
71
+ code: res.status || 500,
72
+ message: 'Failed to parse response from score API',
73
+ data
74
+ });
75
+ }
76
+ if (data.error) return Promise.reject(data.error);
77
+ return data;
78
+ }
79
+
41
80
  const ajv = new Ajv({
42
81
  allErrors: true,
43
82
  allowUnionTypes: true,
@@ -52,7 +91,17 @@ ajv.addFormat('address', {
52
91
  validate: (value: string) => {
53
92
  try {
54
93
  return isAddress(value);
55
- } catch (err) {
94
+ } catch (e: any) {
95
+ return false;
96
+ }
97
+ }
98
+ });
99
+
100
+ ajv.addFormat('starknetAddress', {
101
+ validate: (value: string) => {
102
+ try {
103
+ return isStarknetAddress(value);
104
+ } catch (e: any) {
56
105
  return false;
57
106
  }
58
107
  }
@@ -135,7 +184,6 @@ ajv.addKeyword({
135
184
  errors: true
136
185
  });
137
186
 
138
-
139
187
  // Custom URL format to allow empty string values
140
188
  // https://github.com/snapshot-labs/snapshot.js/pull/541/files
141
189
  ajv.addFormat('customUrl', {
@@ -157,7 +205,7 @@ export async function call(provider, abi: any[], call: any[], options?) {
157
205
  try {
158
206
  const params = call[2] || [];
159
207
  return await contract[call[1]](...params, options || {});
160
- } catch (e) {
208
+ } catch (e: any) {
161
209
  return Promise.reject(e);
162
210
  }
163
211
  }
@@ -198,7 +246,7 @@ export async function multicall(
198
246
  return results.map((call, i) =>
199
247
  itf.decodeFunctionResult(calls[i][1], call)
200
248
  );
201
- } catch (e) {
249
+ } catch (e: any) {
202
250
  return Promise.reject(e);
203
251
  }
204
252
  }
@@ -216,7 +264,7 @@ export async function subgraphRequest(url: string, query, options: any = {}) {
216
264
  let responseData: any = await res.text();
217
265
  try {
218
266
  responseData = JSON.parse(responseData);
219
- } catch (e) {
267
+ } catch (e: any) {
220
268
  throw new Error(
221
269
  `Errors found in subgraphRequest: URL: ${url}, Status: ${
222
270
  res.status
@@ -287,7 +335,7 @@ export async function getScores(
287
335
  network: string,
288
336
  addresses: string[],
289
337
  snapshot: number | string = 'latest',
290
- scoreApiUrl = 'https://score.snapshot.org',
338
+ scoreApiUrl = DEFAULT_SCORE_API_URL,
291
339
  options: any = {}
292
340
  ) {
293
341
  if (!Array.isArray(addresses)) {
@@ -317,9 +365,11 @@ export async function getScores(
317
365
  );
318
366
  }
319
367
 
320
- const url = new URL(scoreApiUrl);
321
- url.pathname = '/api/scores';
322
- scoreApiUrl = url.toString();
368
+ const urlObject = new URL(scoreApiUrl);
369
+ urlObject.pathname = '/api/scores';
370
+ const { url, headers } = formatScoreAPIUrl(scoreApiUrl, {
371
+ path: '/api/scores'
372
+ });
323
373
 
324
374
  try {
325
375
  const params = {
@@ -329,21 +379,17 @@ export async function getScores(
329
379
  strategies,
330
380
  addresses
331
381
  };
332
- const res = await fetch(scoreApiUrl, {
382
+ const res = await fetch(url, {
333
383
  method: 'POST',
334
- headers: scoreApiHeaders,
384
+ headers,
335
385
  body: JSON.stringify({ params })
336
386
  });
337
- const obj = await res.json();
338
-
339
- if (obj.error) {
340
- return Promise.reject(obj.error);
341
- }
387
+ const response = await parseScoreAPIResponse(res);
342
388
 
343
389
  return options.returnValue === 'all'
344
- ? obj.result
345
- : obj.result[options.returnValue || 'scores'];
346
- } catch (e) {
390
+ ? response.result
391
+ : response.result[options.returnValue || 'scores'];
392
+ } catch (e: any) {
347
393
  if (e.errno) {
348
394
  return Promise.reject({ code: e.errno, message: e.toString(), data: '' });
349
395
  }
@@ -360,8 +406,7 @@ export async function getVp(
360
406
  delegation: boolean,
361
407
  options?: Options
362
408
  ) {
363
- if (!options) options = {};
364
- if (!options.url) options.url = 'https://score.snapshot.org';
409
+ const { url, headers } = formatScoreAPIUrl(options?.url);
365
410
  if (!isValidAddress(address)) {
366
411
  return inputError(`Invalid voter address: ${address}`);
367
412
  }
@@ -385,7 +430,7 @@ export async function getVp(
385
430
 
386
431
  const init = {
387
432
  method: 'POST',
388
- headers: scoreApiHeaders,
433
+ headers,
389
434
  body: JSON.stringify({
390
435
  jsonrpc: '2.0',
391
436
  method: 'get_vp',
@@ -401,11 +446,10 @@ export async function getVp(
401
446
  };
402
447
 
403
448
  try {
404
- const res = await fetch(options.url, init);
405
- const json = await res.json();
406
- if (json.error) return Promise.reject(json.error);
407
- if (json.result) return json.result;
408
- } catch (e) {
449
+ const res = await fetch(url, init);
450
+ const response = await parseScoreAPIResponse(res);
451
+ return response.result;
452
+ } catch (e: any) {
409
453
  if (e.errno) {
410
454
  return Promise.reject({ code: e.errno, message: e.toString(), data: '' });
411
455
  }
@@ -420,7 +464,7 @@ export async function validate(
420
464
  network: string,
421
465
  snapshot: number | 'latest',
422
466
  params: any,
423
- options: any
467
+ options?: Options
424
468
  ) {
425
469
  if (!isValidAddress(author)) {
426
470
  return inputError(`Invalid author: ${author}`);
@@ -436,10 +480,11 @@ export async function validate(
436
480
  }
437
481
 
438
482
  if (!options) options = {};
439
- if (!options.url) options.url = 'https://score.snapshot.org';
483
+ const { url, headers } = formatScoreAPIUrl(options.url);
484
+
440
485
  const init = {
441
486
  method: 'POST',
442
- headers: scoreApiHeaders,
487
+ headers,
443
488
  body: JSON.stringify({
444
489
  jsonrpc: '2.0',
445
490
  method: 'validate',
@@ -455,11 +500,10 @@ export async function validate(
455
500
  };
456
501
 
457
502
  try {
458
- const res = await fetch(options.url, init);
459
- const json = await res.json();
460
- if (json.error) return Promise.reject(json.error);
461
- return json.result;
462
- } catch (e) {
503
+ const res = await fetch(url, init);
504
+ const response = await parseScoreAPIResponse(res);
505
+ return response.result;
506
+ } catch (e: any) {
463
507
  if (e.errno) {
464
508
  return Promise.reject({ code: e.errno, message: e.toString(), data: '' });
465
509
  }
@@ -520,7 +564,7 @@ export async function getSpaceUri(
520
564
  ): Promise<string | null> {
521
565
  try {
522
566
  return await getEnsTextRecord(id, 'snapshot', network, options);
523
- } catch (e) {
567
+ } catch (e: any) {
524
568
  console.log(e);
525
569
  return null;
526
570
  }
@@ -604,6 +648,32 @@ function isValidSnapshot(snapshot: number | string, network: string) {
604
648
  );
605
649
  }
606
650
 
651
+ export function isStarknetAddress(address: string): boolean {
652
+ if (!address) return false;
653
+
654
+ try {
655
+ validateAndParseAddress(address);
656
+ return true;
657
+ } catch (e: any) {
658
+ return false;
659
+ }
660
+ }
661
+
662
+ export function isEvmAddress(address: string): boolean {
663
+ return isAddress(address);
664
+ }
665
+
666
+ export function getFormattedAddress(
667
+ address: string,
668
+ format: string[] = ['evm', 'starknet']
669
+ ): string {
670
+ if (format.includes('evm') && isAddress(address)) return getAddress(address);
671
+ if (format.includes('starknet') && isStarknetAddress(address))
672
+ return validateAndParseAddress(address);
673
+
674
+ throw new Error(`Invalid address: ${address}`);
675
+ }
676
+
607
677
  function inputError(message: string) {
608
678
  return Promise.reject(new Error(message));
609
679
  }
@@ -638,5 +708,8 @@ export default {
638
708
  getHash,
639
709
  verify,
640
710
  validate,
711
+ isStarknetAddress,
712
+ isEvmAddress,
713
+ getFormattedAddress,
641
714
  SNAPSHOT_SUBGRAPH_URL
642
715
  };
@@ -0,0 +1,32 @@
1
+ import { test, expect, describe } from 'vitest';
2
+ import evmMessage from '../../test/fixtures/evm/message-alias.json';
3
+ import verify, { getHash } from './evm';
4
+
5
+ describe('verify/evm', () => {
6
+ describe('getHash()', () => {
7
+ test('should return a hash string', () => {
8
+ const hash = getHash(evmMessage.data);
9
+ expect(hash).toBe(
10
+ '0x82ed8be33f43c86f9b83d14736e5762c89108fbc9b8b54f6e993818fc8a53525'
11
+ );
12
+ });
13
+ });
14
+
15
+ describe('verify()', () => {
16
+ test('should return true if the signature is valid', () => {
17
+ expect(
18
+ verify(evmMessage.address, evmMessage.sig, evmMessage.data)
19
+ ).resolves.toBe(true);
20
+ });
21
+
22
+ test('should throw an error if the signature is invalid', () => {
23
+ expect(
24
+ verify(
25
+ '0xDD983E11Cf84746f3b7589ee1Dc2081c08c40Cb3',
26
+ evmMessage.sig,
27
+ evmMessage.data
28
+ )
29
+ ).rejects.toThrowError(/isValidSignature/);
30
+ });
31
+ });
32
+ });