@snapshot-labs/snapshot.js 0.13.0 → 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.
- package/dist/snapshot.cjs.js +505 -329
- package/dist/snapshot.esm.js +506 -330
- package/dist/snapshot.min.js +21 -21
- package/dist/src/index.d.ts +4 -20
- package/dist/src/multicall/evm.d.ts +4 -0
- package/dist/src/multicall/index.d.ts +3 -0
- package/dist/src/multicall/multicaller.d.ts +15 -0
- package/dist/src/multicall/starknet.d.ts +2 -0
- package/dist/src/schemas/index.d.ts +2 -18
- package/dist/src/utils/provider.d.ts +5 -6
- package/dist/src/utils.d.ts +1 -2
- package/package.json +1 -1
- package/src/multicall/evm.ts +48 -0
- package/src/multicall/index.ts +52 -0
- package/src/{utils → multicall}/multicaller.ts +13 -8
- package/src/multicall/starknet.ts +154 -0
- package/src/networks.json +30 -1
- package/src/schemas/space.json +3 -13
- package/src/utils/provider.ts +91 -44
- package/src/utils.spec.js +247 -46
- package/src/utils.ts +9 -78
- package/dist/src/utils/multicaller.d.ts +0 -12
package/src/utils/provider.ts
CHANGED
|
@@ -1,52 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from '@ethersproject/providers';
|
|
1
|
+
import { StaticJsonRpcProvider } from '@ethersproject/providers';
|
|
2
|
+
import { RpcProvider } from 'starknet';
|
|
3
|
+
import networks from '../networks.json';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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.spec.js
CHANGED
|
@@ -505,66 +505,267 @@ describe('utils', () => {
|
|
|
505
505
|
|
|
506
506
|
describe('getFormattedAddress', () => {
|
|
507
507
|
describe('when explicitly passing an address type', () => {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
'
|
|
512
|
-
|
|
513
|
-
|
|
508
|
+
describe('EVM type parsing', () => {
|
|
509
|
+
test('should return checksummed EVM address when given checksummed input', () => {
|
|
510
|
+
const address = '0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3';
|
|
511
|
+
expect(getFormattedAddress(address, 'evm')).toEqual(
|
|
512
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
513
|
+
);
|
|
514
|
+
});
|
|
514
515
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
'
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
);
|
|
521
|
-
});
|
|
516
|
+
test('should return checksummed EVM address when given lowercase input', () => {
|
|
517
|
+
const address = '0x91fd2c8d24767db4ece7069aa27832ffaf8590f3';
|
|
518
|
+
expect(getFormattedAddress(address, 'evm')).toEqual(
|
|
519
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
522
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
523
|
+
test('should return checksummed EVM address when given uppercase input', () => {
|
|
524
|
+
const uppercaseAddress = '0x91FD2C8D24767DB4ECE7069AA27832FFAF8590F3';
|
|
525
|
+
expect(getFormattedAddress(uppercaseAddress, 'evm')).toEqual(
|
|
526
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test('should throw error when forcing EVM parsing on address with uppercase 0X prefix', () => {
|
|
531
|
+
const uppercaseHexPrefix =
|
|
532
|
+
'0X91FD2C8D24767DB4ECE7069AA27832FFAF8590F3';
|
|
533
|
+
expect(() => getFormattedAddress(uppercaseHexPrefix, 'evm')).toThrow(
|
|
534
|
+
'Invalid evm address: 0X91FD2C8D24767DB4ECE7069AA27832FFAF8590F3'
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('should throw error when forcing EVM parsing on invalid mixed case address', () => {
|
|
539
|
+
const invalidMixedCaseAddress =
|
|
540
|
+
'0x91Fd2C8d24767Db4eCe7069aA27832FfaF8590F3';
|
|
541
|
+
expect(() =>
|
|
542
|
+
getFormattedAddress(invalidMixedCaseAddress, 'evm')
|
|
543
|
+
).toThrow(
|
|
544
|
+
'Invalid evm address: 0x91Fd2C8d24767Db4eCe7069aA27832FfaF8590F3'
|
|
545
|
+
);
|
|
546
|
+
});
|
|
529
547
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
548
|
+
test('should throw error when address is not an EVM address', () => {
|
|
549
|
+
const address =
|
|
550
|
+
'0x2a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
|
|
551
|
+
expect(() => getFormattedAddress(address, 'evm')).toThrow(
|
|
552
|
+
'Invalid evm address: 0x2a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
553
|
+
);
|
|
554
|
+
});
|
|
533
555
|
});
|
|
534
556
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
557
|
+
describe('Starknet type parsing', () => {
|
|
558
|
+
test('should return padded and lowercased starknet address when given unpadded input', () => {
|
|
559
|
+
const address =
|
|
560
|
+
'0x2a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
|
|
561
|
+
expect(getFormattedAddress(address, 'starknet')).toEqual(
|
|
562
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
563
|
+
);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test('should return padded and lowercased starknet address when given lowercase input', () => {
|
|
567
|
+
const address =
|
|
568
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
|
|
569
|
+
expect(getFormattedAddress(address, 'starknet')).toEqual(
|
|
570
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test('should return padded and lowercased starknet address when given uppercase Starknet input', () => {
|
|
575
|
+
const uppercaseAddress =
|
|
576
|
+
'0x02A0A8F3B6097E7A6BD7649DEB30715323072A159C0E6B71B689BD245C146CC0';
|
|
577
|
+
expect(getFormattedAddress(uppercaseAddress, 'starknet')).toEqual(
|
|
578
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('should return padded and lowercased starknet address when given checksum Starknet input', () => {
|
|
583
|
+
const checksumAddress =
|
|
584
|
+
'0x02a0a8F3B6097e7A6bd7649DEB30715323072A159c0E6B71B689Bd245c146cC0';
|
|
585
|
+
expect(getFormattedAddress(checksumAddress, 'starknet')).toEqual(
|
|
586
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
587
|
+
);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test('should return padded and lowercased starknet address when given mixed case Starknet input', () => {
|
|
591
|
+
const mixedCaseAddress =
|
|
592
|
+
'0x02A0a8F3B6097e7A6bD7649DEB30715323072a159C0e6b71B689BD245c146Cc0';
|
|
593
|
+
expect(getFormattedAddress(mixedCaseAddress, 'starknet')).toEqual(
|
|
594
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
595
|
+
);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test('should return EVM address as starknet address when explicitly formatted', () => {
|
|
599
|
+
const address = '0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3';
|
|
600
|
+
expect(getFormattedAddress(address, 'starknet')).toEqual(
|
|
601
|
+
'0x00000000000000000000000091fd2c8d24767db4ece7069aa27832ffaf8590f3'
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test('should throw error when given invalid Starknet address with explicit format', () => {
|
|
606
|
+
const invalidStarknetAddress = '0xinvalidstarknetaddresshere';
|
|
607
|
+
expect(() =>
|
|
608
|
+
getFormattedAddress(invalidStarknetAddress, 'starknet')
|
|
609
|
+
).toThrow('Invalid starknet address: 0xinvalidstarknetaddresshere');
|
|
610
|
+
});
|
|
539
611
|
});
|
|
540
612
|
});
|
|
541
613
|
|
|
542
614
|
describe('when not passing an address type', () => {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
615
|
+
describe('EVM address auto-detection', () => {
|
|
616
|
+
test('should auto-detect and format valid 42-char lowercase EVM address', () => {
|
|
617
|
+
const address = '0x91fd2c8d24767db4ece7069aa27832ffaf8590f3';
|
|
618
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
619
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
620
|
+
);
|
|
621
|
+
});
|
|
549
622
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
);
|
|
623
|
+
test('should auto-detect and format valid 42-char uppercase EVM address', () => {
|
|
624
|
+
const address = '0x91FD2C8D24767DB4ECE7069AA27832FFAF8590F3';
|
|
625
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
626
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
627
|
+
);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test('should auto-detect and format valid 42-char checksummed EVM address', () => {
|
|
631
|
+
const address = '0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3';
|
|
632
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
633
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test('should throw error when auto-detecting invalid mixed case EVM address', () => {
|
|
638
|
+
const invalidMixedCaseAddress =
|
|
639
|
+
'0x91Fd2C8d24767Db4eCe7069aA27832FfaF8590F3';
|
|
640
|
+
expect(() => getFormattedAddress(invalidMixedCaseAddress)).toThrow(
|
|
641
|
+
'Invalid evm address: 0x91Fd2C8d24767Db4eCe7069aA27832FfaF8590F3'
|
|
642
|
+
);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
test('should throw error when auto-detecting 42-char invalid hex address', () => {
|
|
646
|
+
const invalidHexAddress =
|
|
647
|
+
'0xgggggggggggggggggggggggggggggggggggggggg';
|
|
648
|
+
expect(() => getFormattedAddress(invalidHexAddress)).toThrow(
|
|
649
|
+
'Invalid evm address: 0xgggggggggggggggggggggggggggggggggggggggg'
|
|
650
|
+
);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test('should throw error when auto-detecting EVM address with uppercase 0X prefix', () => {
|
|
654
|
+
const uppercaseHexPrefix =
|
|
655
|
+
'0X91FD2C8D24767DB4ECE7069AA27832FFAF8590F3';
|
|
656
|
+
expect(() => getFormattedAddress(uppercaseHexPrefix)).toThrow(
|
|
657
|
+
'Invalid evm address: 0X91FD2C8D24767DB4ECE7069AA27832FFAF8590F3'
|
|
658
|
+
);
|
|
659
|
+
});
|
|
556
660
|
});
|
|
557
661
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
662
|
+
describe('Starknet address auto-detection', () => {
|
|
663
|
+
test('should auto-detect and format valid unpadded Starknet address', () => {
|
|
664
|
+
const address =
|
|
665
|
+
'0x2a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
|
|
666
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
667
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test('should auto-detect and format valid padded Starknet address', () => {
|
|
672
|
+
const address =
|
|
673
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0';
|
|
674
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
675
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test('should auto-detect and format uppercase Starknet address', () => {
|
|
680
|
+
const address =
|
|
681
|
+
'0x02A0A8F3B6097E7A6BD7649DEB30715323072A159C0E6B71B689BD245C146CC0';
|
|
682
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
683
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
684
|
+
);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test('should return padded and lowercased address when input has uppercase 0X prefix', () => {
|
|
688
|
+
const fullyUppercaseAddress =
|
|
689
|
+
'0X02A0A8F3B6097E7A6BD7649DEB30715323072A159C0E6B71B689BD245C146CC0';
|
|
690
|
+
expect(getFormattedAddress(fullyUppercaseAddress)).toEqual(
|
|
691
|
+
'0x02a0a8f3b6097e7a6bd7649deb30715323072a159c0e6b71b689bd245c146cc0'
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test('should return padded and lowercased address when given short input', () => {
|
|
696
|
+
const address = '0x1';
|
|
697
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
698
|
+
'0x0000000000000000000000000000000000000000000000000000000000000001'
|
|
699
|
+
);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test('should auto-detect actual 41-char address as Starknet', () => {
|
|
703
|
+
const address = '0x123456789012345678901234567890123456789';
|
|
704
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
705
|
+
'0x0000000000000000000000000123456789012345678901234567890123456789'
|
|
706
|
+
);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test('should auto-detect 43+ char address as Starknet', () => {
|
|
710
|
+
const address = '0x123456789012345678901234567890123456789012';
|
|
711
|
+
expect(getFormattedAddress(address)).toEqual(
|
|
712
|
+
'0x0000000000000000000000123456789012345678901234567890123456789012'
|
|
713
|
+
);
|
|
714
|
+
});
|
|
561
715
|
});
|
|
562
716
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
'
|
|
567
|
-
|
|
717
|
+
describe('Invalid address format', () => {
|
|
718
|
+
test('should throw error when passing invalid format argument', () => {
|
|
719
|
+
const validAddress = '0x91fd2c8d24767db4ece7069aa27832ffaf8590f3';
|
|
720
|
+
expect(() => getFormattedAddress(validAddress, 'invalid')).toThrow(
|
|
721
|
+
'Invalid invalid address: 0x91fd2c8d24767db4ece7069aa27832ffaf8590f3'
|
|
722
|
+
);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
test('should treat undefined format parameter as auto-detection', () => {
|
|
726
|
+
const evmAddress = '0x91fd2c8d24767db4ece7069aa27832ffaf8590f3';
|
|
727
|
+
expect(getFormattedAddress(evmAddress, undefined)).toEqual(
|
|
728
|
+
'0x91FD2c8d24767db4Ece7069AA27832ffaf8590f3'
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test('should throw error when parsing invalid string', () => {
|
|
733
|
+
const invalidString = 'hello';
|
|
734
|
+
expect(() => getFormattedAddress(invalidString)).toThrow(
|
|
735
|
+
'Invalid address: hello'
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test('should throw error when parsing empty string', () => {
|
|
740
|
+
const emptyString = '';
|
|
741
|
+
expect(() => getFormattedAddress(emptyString)).toThrow(
|
|
742
|
+
'Invalid address: '
|
|
743
|
+
);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test('should throw error when parsing null input', () => {
|
|
747
|
+
expect(() => getFormattedAddress(null)).toThrow(
|
|
748
|
+
'Invalid address: null'
|
|
749
|
+
);
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
test('should throw error when parsing undefined input', () => {
|
|
753
|
+
expect(() => getFormattedAddress(undefined)).toThrow(
|
|
754
|
+
'Invalid address: undefined'
|
|
755
|
+
);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test('should throw error when parsing number input', () => {
|
|
759
|
+
expect(() => getFormattedAddress(123)).toThrow(
|
|
760
|
+
'Invalid address: 123'
|
|
761
|
+
);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test('should throw error when parsing object input', () => {
|
|
765
|
+
expect(() => getFormattedAddress({})).toThrow(
|
|
766
|
+
'Invalid address: [object Object]'
|
|
767
|
+
);
|
|
768
|
+
});
|
|
568
769
|
});
|
|
569
770
|
});
|
|
570
771
|
});
|
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]
|
|
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;
|
|
@@ -830,16 +759,18 @@ export function getFormattedAddress(
|
|
|
830
759
|
address: string,
|
|
831
760
|
format?: 'evm' | 'starknet'
|
|
832
761
|
): string {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
762
|
+
if (typeof address !== 'string' || !/^0[xX]/.test(address)) {
|
|
763
|
+
throw new Error(`Invalid address: ${address}`);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const addressType = format ?? (address.length === 42 ? 'evm' : 'starknet');
|
|
836
767
|
|
|
837
768
|
if (addressType === 'evm' && isEvmAddress(address))
|
|
838
769
|
return getAddress(address);
|
|
839
770
|
if (addressType === 'starknet' && isStarknetAddress(address))
|
|
840
771
|
return validateAndParseAddress(address);
|
|
841
772
|
|
|
842
|
-
throw new Error(`Invalid address: ${address}`);
|
|
773
|
+
throw new Error(`Invalid ${addressType} address: ${address}`);
|
|
843
774
|
}
|
|
844
775
|
|
|
845
776
|
function inputError(message: string) {
|
|
@@ -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
|
-
}
|