@snapshot-labs/snapshot.js 0.9.0 → 0.9.2
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.
- package/dist/snapshot.cjs.js +59 -3
- package/dist/snapshot.esm.js +59 -3
- package/dist/snapshot.min.js +3 -3
- package/package.json +1 -1
- package/src/constants.json +1 -1
- package/src/utils.spec.js +475 -0
- package/src/utils.ts +81 -0
package/package.json
CHANGED
package/src/constants.json
CHANGED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { describe, test, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import * as crossFetch from 'cross-fetch';
|
|
3
|
+
import { validate, getScores, getVp } from './utils';
|
|
4
|
+
|
|
5
|
+
vi.mock('cross-fetch', async () => {
|
|
6
|
+
const actual = await vi.importActual('cross-fetch');
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
default: vi.fn()
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
const fetch = vi.mocked(crossFetch.default);
|
|
14
|
+
|
|
15
|
+
describe('utils', () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.resetAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('validate', () => {
|
|
21
|
+
const payload = {
|
|
22
|
+
validation: 'basic',
|
|
23
|
+
author: '0xeF8305E140ac520225DAf050e2f71d5fBcC543e7',
|
|
24
|
+
space: 'fabien.eth',
|
|
25
|
+
network: '1',
|
|
26
|
+
snapshot: 7929876,
|
|
27
|
+
params: {
|
|
28
|
+
minScore: 0.9,
|
|
29
|
+
strategies: [
|
|
30
|
+
{
|
|
31
|
+
name: 'eth-balance',
|
|
32
|
+
params: {}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function _validate({
|
|
39
|
+
validation,
|
|
40
|
+
author,
|
|
41
|
+
space,
|
|
42
|
+
network,
|
|
43
|
+
snapshot,
|
|
44
|
+
params,
|
|
45
|
+
options
|
|
46
|
+
}) {
|
|
47
|
+
return validate(
|
|
48
|
+
validation ?? payload.validation,
|
|
49
|
+
author ?? payload.author,
|
|
50
|
+
space ?? payload.space,
|
|
51
|
+
network ?? payload.network,
|
|
52
|
+
snapshot ?? payload.snapshot,
|
|
53
|
+
params ?? payload.params,
|
|
54
|
+
options ?? {}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe('when passing invalid args', () => {
|
|
59
|
+
const cases = [
|
|
60
|
+
[
|
|
61
|
+
'author is an invalid address',
|
|
62
|
+
{ author: 'test-address' },
|
|
63
|
+
/invalid author/i
|
|
64
|
+
],
|
|
65
|
+
['network is not valid', { network: 'mainnet' }, /invalid network/i],
|
|
66
|
+
['network is empty', { network: '' }, /invalid network/i],
|
|
67
|
+
[
|
|
68
|
+
'snapshot is smaller than start block',
|
|
69
|
+
{ snapshot: 1234 },
|
|
70
|
+
/snapshot \([0-9]+\) must be 'latest' or greater than network start block/i
|
|
71
|
+
]
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
test.each(cases)('throw an error when %s', async (title, args, err) => {
|
|
75
|
+
await expect(_validate(args)).rejects.toMatch(err);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('when passing valid args', () => {
|
|
80
|
+
test('send a JSON-RPC payload to score-api', async () => {
|
|
81
|
+
fetch.mockReturnValue({
|
|
82
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(_validate({})).resolves;
|
|
86
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
87
|
+
'https://score.snapshot.org',
|
|
88
|
+
expect.objectContaining({
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
jsonrpc: '2.0',
|
|
91
|
+
method: 'validate',
|
|
92
|
+
params: payload
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('send a POST request with JSON content-type', async () => {
|
|
99
|
+
fetch.mockReturnValue({
|
|
100
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(_validate({})).resolves;
|
|
104
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
105
|
+
'https://score.snapshot.org',
|
|
106
|
+
expect.objectContaining({
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
Accept: 'application/json',
|
|
110
|
+
'Content-Type': 'application/json'
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('can customize the score-api url', () => {
|
|
117
|
+
fetch.mockReturnValue({
|
|
118
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(
|
|
122
|
+
_validate({ options: { url: 'https://snapshot.org/?apiKey=xxx' } })
|
|
123
|
+
).resolves;
|
|
124
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
125
|
+
'https://snapshot.org/?apiKey=xxx',
|
|
126
|
+
expect.anything()
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('returns the JSON-RPC result property', () => {
|
|
131
|
+
const result = { result: 'OK' };
|
|
132
|
+
fetch.mockReturnValue({
|
|
133
|
+
json: () => new Promise((resolve) => resolve(result))
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(_validate({})).resolves.toEqual('OK');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('when score-api is sending a JSON-RPC error', () => {
|
|
141
|
+
test('rejects with the JSON-RPC error object', () => {
|
|
142
|
+
const result = { error: { message: 'Oh no' } };
|
|
143
|
+
fetch.mockReturnValue({
|
|
144
|
+
json: () => new Promise((resolve) => resolve(result))
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(_validate({})).rejects.toEqual(result.error);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('when the fetch request is failing with not network error', () => {
|
|
152
|
+
test('rejects with the error', () => {
|
|
153
|
+
const result = new Error('Oh no');
|
|
154
|
+
fetch.mockReturnValue({
|
|
155
|
+
json: () => {
|
|
156
|
+
throw result;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(_validate({})).rejects.toEqual(result);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('getScores', () => {
|
|
165
|
+
const payload = {
|
|
166
|
+
space: 'test.eth',
|
|
167
|
+
network: '1',
|
|
168
|
+
snapshot: 7929876,
|
|
169
|
+
strategies: [
|
|
170
|
+
{
|
|
171
|
+
name: 'erc20-balance-of',
|
|
172
|
+
params: {
|
|
173
|
+
symbol: 'TEST',
|
|
174
|
+
address: '0xc23F41519D7DFaDf9eed53c00f08C06CD5cDde54',
|
|
175
|
+
network: '1',
|
|
176
|
+
decimals: 18
|
|
177
|
+
},
|
|
178
|
+
network: '1'
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
addresses: ['0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11']
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
function _getScores({
|
|
185
|
+
space,
|
|
186
|
+
strategies,
|
|
187
|
+
network,
|
|
188
|
+
addresses,
|
|
189
|
+
snapshot,
|
|
190
|
+
scoreApiUrl,
|
|
191
|
+
options
|
|
192
|
+
}) {
|
|
193
|
+
return getScores(
|
|
194
|
+
space ?? payload.space,
|
|
195
|
+
strategies ?? payload.strategies,
|
|
196
|
+
network ?? payload.network,
|
|
197
|
+
addresses ?? payload.addresses,
|
|
198
|
+
snapshot ?? payload.snapshot,
|
|
199
|
+
scoreApiUrl ?? 'https://score.snapshot.org',
|
|
200
|
+
options ?? {}
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
describe('when passing invalid args', () => {
|
|
205
|
+
const cases = [
|
|
206
|
+
[
|
|
207
|
+
'addresses contains invalid address',
|
|
208
|
+
{ addresses: ['test-address'] },
|
|
209
|
+
/invalid address/i
|
|
210
|
+
],
|
|
211
|
+
[
|
|
212
|
+
'addresses is not an array',
|
|
213
|
+
{ addresses: 'test-address' },
|
|
214
|
+
/addresses should be an array/i
|
|
215
|
+
],
|
|
216
|
+
[
|
|
217
|
+
'addresses is an empty array',
|
|
218
|
+
{ addresses: [] },
|
|
219
|
+
/addresses can not be empty/i
|
|
220
|
+
],
|
|
221
|
+
['network is not valid', { network: 'mainnet' }, /invalid network/i],
|
|
222
|
+
['network is empty', { network: '' }, /invalid network/i],
|
|
223
|
+
[
|
|
224
|
+
'snapshot is smaller than start block',
|
|
225
|
+
{ snapshot: 1234 },
|
|
226
|
+
/snapshot \([0-9]+\) must be 'latest' or greater than network start block/i
|
|
227
|
+
],
|
|
228
|
+
[
|
|
229
|
+
'strategy contains invalid network',
|
|
230
|
+
{ strategies: [{ name: '', network: 'test' }] },
|
|
231
|
+
/invalid network \(.*\) in strategy/i
|
|
232
|
+
]
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
test.each(cases)('throw an error when %s', async (title, args, err) => {
|
|
236
|
+
await expect(_getScores(args)).rejects.toMatch(err);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('when passing valid args', () => {
|
|
241
|
+
test('send a JSON-RPC payload to score-api', async () => {
|
|
242
|
+
fetch.mockReturnValue({
|
|
243
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(_getScores({})).resolves;
|
|
247
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
248
|
+
'https://score.snapshot.org/api/scores',
|
|
249
|
+
expect.objectContaining({
|
|
250
|
+
body: JSON.stringify({ params: payload })
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('send a POST request with JSON content-type', async () => {
|
|
256
|
+
fetch.mockReturnValue({
|
|
257
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(_getScores({})).resolves;
|
|
261
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
262
|
+
'https://score.snapshot.org/api/scores',
|
|
263
|
+
expect.objectContaining({
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: {
|
|
266
|
+
Accept: 'application/json',
|
|
267
|
+
'Content-Type': 'application/json'
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('can customize the score-api url', () => {
|
|
274
|
+
fetch.mockReturnValue({
|
|
275
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(_getScores({ scoreApiUrl: 'https://snapshot.org/?apiKey=xxx' }))
|
|
279
|
+
.resolves;
|
|
280
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
281
|
+
'https://snapshot.org/api/scores?apiKey=xxx',
|
|
282
|
+
expect.anything()
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('returns the JSON-RPC result scores property', () => {
|
|
287
|
+
const result = { scores: 'SCORES', other: 'Other' };
|
|
288
|
+
fetch.mockReturnValue({
|
|
289
|
+
json: () => new Promise((resolve) => resolve({ result }))
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(_getScores({})).resolves.toEqual('SCORES');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('returns the JSON-RPC all properties', () => {
|
|
296
|
+
const result = { scores: 'SCORES', other: 'Other' };
|
|
297
|
+
fetch.mockReturnValue({
|
|
298
|
+
json: () => new Promise((resolve) => resolve({ result }))
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(
|
|
302
|
+
_getScores({ options: { returnValue: 'all' } })
|
|
303
|
+
).resolves.toEqual(result);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('when score-api is sending a JSON-RPC error', () => {
|
|
308
|
+
test('rejects with the JSON-RPC error object', () => {
|
|
309
|
+
const result = { error: { message: 'Oh no' } };
|
|
310
|
+
fetch.mockReturnValue({
|
|
311
|
+
json: () => new Promise((resolve) => resolve(result))
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(_getScores({})).rejects.toEqual(result.error);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('when the fetch request is failing with not network error', () => {
|
|
319
|
+
test('rejects with the error', () => {
|
|
320
|
+
const result = new Error('Oh no');
|
|
321
|
+
fetch.mockReturnValue({
|
|
322
|
+
json: () => {
|
|
323
|
+
throw result;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
expect(_getScores({})).rejects.toEqual(result);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
describe('getVp', () => {
|
|
332
|
+
const payload = {
|
|
333
|
+
address: '0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11',
|
|
334
|
+
network: '1',
|
|
335
|
+
strategies: [
|
|
336
|
+
{
|
|
337
|
+
name: 'erc20-balance-of',
|
|
338
|
+
params: {
|
|
339
|
+
symbol: 'TEST',
|
|
340
|
+
address: '0xc23F41519D7DFaDf9eed53c00f08C06CD5cDde54',
|
|
341
|
+
network: '1',
|
|
342
|
+
decimals: 18
|
|
343
|
+
},
|
|
344
|
+
network: '1'
|
|
345
|
+
}
|
|
346
|
+
],
|
|
347
|
+
snapshot: 7929876,
|
|
348
|
+
space: 'test.eth',
|
|
349
|
+
delegation: false,
|
|
350
|
+
options: undefined
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
function _getVp({ voter, network, strategies, snapshot, options }) {
|
|
354
|
+
return getVp(
|
|
355
|
+
voter ?? payload.address,
|
|
356
|
+
network ?? payload.network,
|
|
357
|
+
strategies ?? payload.strategies,
|
|
358
|
+
snapshot ?? payload.snapshot,
|
|
359
|
+
'test.eth' ?? payload.space,
|
|
360
|
+
false ?? payload.delegation,
|
|
361
|
+
options ?? payload.options
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
describe('when passing invalid args', () => {
|
|
366
|
+
const cases = [
|
|
367
|
+
[
|
|
368
|
+
'voter is not a valid address',
|
|
369
|
+
{ voter: 'test-address' },
|
|
370
|
+
/invalid voter address/i
|
|
371
|
+
],
|
|
372
|
+
['voter is empty', { voter: '' }, /invalid voter address/i],
|
|
373
|
+
['network is not valid', { network: 'mainnet' }, /invalid network/i],
|
|
374
|
+
['network is empty', { network: '' }, /invalid network/i],
|
|
375
|
+
[
|
|
376
|
+
'snapshot is smaller than start block',
|
|
377
|
+
{ snapshot: 1234 },
|
|
378
|
+
/snapshot \([0-9]+\) must be 'latest' or greater than network start block/i
|
|
379
|
+
],
|
|
380
|
+
[
|
|
381
|
+
'strategy contains invalid network',
|
|
382
|
+
{ strategies: [{ name: '', network: 'test' }] },
|
|
383
|
+
/invalid network \(.*\) in strategy/i
|
|
384
|
+
]
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
test.each(cases)('throw an error when %s', async (title, args, err) => {
|
|
388
|
+
await expect(_getVp(args)).rejects.toMatch(err);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('when passing valid args', () => {
|
|
393
|
+
test('send a JSON-RPC payload to score-api', async () => {
|
|
394
|
+
fetch.mockReturnValue({
|
|
395
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(_getVp({})).resolves;
|
|
399
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
400
|
+
'https://score.snapshot.org',
|
|
401
|
+
expect.objectContaining({
|
|
402
|
+
body: JSON.stringify({
|
|
403
|
+
jsonrpc: '2.0',
|
|
404
|
+
method: 'get_vp',
|
|
405
|
+
params: payload
|
|
406
|
+
})
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('send a POST request with JSON content-type', async () => {
|
|
412
|
+
fetch.mockReturnValue({
|
|
413
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(_getVp({})).resolves;
|
|
417
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
418
|
+
'https://score.snapshot.org',
|
|
419
|
+
expect.objectContaining({
|
|
420
|
+
method: 'POST',
|
|
421
|
+
headers: {
|
|
422
|
+
Accept: 'application/json',
|
|
423
|
+
'Content-Type': 'application/json'
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('can customize the score-api url', () => {
|
|
430
|
+
fetch.mockReturnValue({
|
|
431
|
+
json: () => new Promise((resolve) => resolve({ result: 'OK' }))
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
expect(_getVp({ options: { url: 'https://snapshot.org' } })).resolves;
|
|
435
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
436
|
+
'https://snapshot.org',
|
|
437
|
+
expect.anything()
|
|
438
|
+
);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('returns the JSON-RPC result property', () => {
|
|
442
|
+
const result = { data: 'OK' };
|
|
443
|
+
fetch.mockReturnValue({
|
|
444
|
+
json: () => new Promise((resolve) => resolve({ result }))
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
expect(_getVp({})).resolves.toEqual(result);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe('when score-api is sending a JSON-RPC error', () => {
|
|
452
|
+
test('rejects with the JSON-RPC error object', () => {
|
|
453
|
+
const result = { error: { message: 'Oh no' } };
|
|
454
|
+
fetch.mockReturnValue({
|
|
455
|
+
json: () => new Promise((resolve) => resolve(result))
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
expect(_getVp({})).rejects.toEqual(result.error);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('when the fetch request is failing with not network error', () => {
|
|
463
|
+
test('rejects with the error', () => {
|
|
464
|
+
const result = new Error('Oh no');
|
|
465
|
+
fetch.mockReturnValue({
|
|
466
|
+
json: () => {
|
|
467
|
+
throw result;
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(_getVp({})).rejects.toEqual(result);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
});
|
package/src/utils.ts
CHANGED
|
@@ -31,6 +31,7 @@ export const SNAPSHOT_SUBGRAPH_URL = delegationSubgraphs;
|
|
|
31
31
|
const ENS_RESOLVER_ABI = [
|
|
32
32
|
'function text(bytes32 node, string calldata key) external view returns (string memory)'
|
|
33
33
|
];
|
|
34
|
+
const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
34
35
|
|
|
35
36
|
const scoreApiHeaders = {
|
|
36
37
|
Accept: 'application/json',
|
|
@@ -249,6 +250,33 @@ export async function getScores(
|
|
|
249
250
|
scoreApiUrl = 'https://score.snapshot.org',
|
|
250
251
|
options: any = {}
|
|
251
252
|
) {
|
|
253
|
+
if (!Array.isArray(addresses)) {
|
|
254
|
+
return inputError('addresses should be an array of addresses');
|
|
255
|
+
}
|
|
256
|
+
if (addresses.length === 0) {
|
|
257
|
+
return inputError('addresses can not be empty');
|
|
258
|
+
}
|
|
259
|
+
const invalidAddress = addresses.find((address) => !isValidAddress(address));
|
|
260
|
+
if (invalidAddress) {
|
|
261
|
+
return inputError(`Invalid address: ${invalidAddress}`);
|
|
262
|
+
}
|
|
263
|
+
if (!isValidNetwork(network)) {
|
|
264
|
+
return inputError(`Invalid network: ${network}`);
|
|
265
|
+
}
|
|
266
|
+
const invalidStrategy = strategies.find(
|
|
267
|
+
(strategy) => strategy.network && !isValidNetwork(strategy.network)
|
|
268
|
+
);
|
|
269
|
+
if (invalidStrategy) {
|
|
270
|
+
return inputError(
|
|
271
|
+
`Invalid network (${invalidStrategy.network}) in strategy ${invalidStrategy.name}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
if (!isValidSnapshot(snapshot, network)) {
|
|
275
|
+
return inputError(
|
|
276
|
+
`Snapshot (${snapshot}) must be 'latest' or greater than network start block (${networks[network].start})`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
252
280
|
const url = new URL(scoreApiUrl);
|
|
253
281
|
url.pathname = '/api/scores';
|
|
254
282
|
scoreApiUrl = url.toString();
|
|
@@ -294,6 +322,27 @@ export async function getVp(
|
|
|
294
322
|
) {
|
|
295
323
|
if (!options) options = {};
|
|
296
324
|
if (!options.url) options.url = 'https://score.snapshot.org';
|
|
325
|
+
if (!isValidAddress(address)) {
|
|
326
|
+
return inputError(`Invalid voter address: ${address}`);
|
|
327
|
+
}
|
|
328
|
+
if (!isValidNetwork(network)) {
|
|
329
|
+
return inputError(`Invalid network: ${network}`);
|
|
330
|
+
}
|
|
331
|
+
const invalidStrategy = strategies.find(
|
|
332
|
+
(strategy) => strategy.network && !isValidNetwork(strategy.network)
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (invalidStrategy) {
|
|
336
|
+
return inputError(
|
|
337
|
+
`Invalid network (${invalidStrategy.network}) in strategy ${invalidStrategy.name}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (!isValidSnapshot(snapshot, network)) {
|
|
341
|
+
return inputError(
|
|
342
|
+
`Snapshot (${snapshot}) must be 'latest' or greater than network start block (${networks[network].start})`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
297
346
|
const init = {
|
|
298
347
|
method: 'POST',
|
|
299
348
|
headers: scoreApiHeaders,
|
|
@@ -333,6 +382,19 @@ export async function validate(
|
|
|
333
382
|
params: any,
|
|
334
383
|
options: any
|
|
335
384
|
) {
|
|
385
|
+
if (!isValidAddress(author)) {
|
|
386
|
+
return inputError(`Invalid author: ${author}`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!isValidNetwork(network)) {
|
|
390
|
+
return inputError(`Invalid network: ${network}`);
|
|
391
|
+
}
|
|
392
|
+
if (!isValidSnapshot(snapshot, network)) {
|
|
393
|
+
return inputError(
|
|
394
|
+
`Snapshot (${snapshot}) must be 'latest' or greater than network start block (${networks[network].start})`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
336
398
|
if (!options) options = {};
|
|
337
399
|
if (!options.url) options.url = 'https://score.snapshot.org';
|
|
338
400
|
const init = {
|
|
@@ -530,6 +592,25 @@ export function getNumberWithOrdinal(n) {
|
|
|
530
592
|
return n + (s[(v - 20) % 10] || s[v] || s[0]);
|
|
531
593
|
}
|
|
532
594
|
|
|
595
|
+
function isValidNetwork(network: string) {
|
|
596
|
+
return !!networks[network];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function isValidAddress(address: string) {
|
|
600
|
+
return isAddress(address) && address !== EMPTY_ADDRESS;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function isValidSnapshot(snapshot: number | string, network: string) {
|
|
604
|
+
return (
|
|
605
|
+
snapshot === 'latest' ||
|
|
606
|
+
(typeof snapshot === 'number' && snapshot >= networks[network].start)
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function inputError(message: string) {
|
|
611
|
+
return Promise.reject(new Error(message));
|
|
612
|
+
}
|
|
613
|
+
|
|
533
614
|
export default {
|
|
534
615
|
call,
|
|
535
616
|
multicall,
|