@snapshot-labs/snapshot.js 0.12.1 → 0.12.3
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 +32 -11
- package/dist/snapshot.esm.js +32 -11
- package/dist/snapshot.min.js +15 -15
- package/package.json +2 -1
- package/src/utils.spec.js +17 -1
- package/src/utils.ts +17 -2
- package/src/verify/starknet.spec.ts +57 -23
- package/src/verify/starknet.ts +26 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snapshot-labs/snapshot.js",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"repository": "snapshot-labs/snapshot.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/snapshot.cjs.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"@ensdomains/eth-ens-namehash": "^2.0.15",
|
|
12
12
|
"@ethersproject/abi": "^5.6.4",
|
|
13
13
|
"@ethersproject/address": "^5.6.1",
|
|
14
|
+
"@ethersproject/bignumber": "^5.7.0",
|
|
14
15
|
"@ethersproject/bytes": "^5.6.1",
|
|
15
16
|
"@ethersproject/contracts": "^5.6.2",
|
|
16
17
|
"@ethersproject/hash": "^5.7.0",
|
package/src/utils.spec.js
CHANGED
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
validateSchema,
|
|
6
6
|
getScores,
|
|
7
7
|
getVp,
|
|
8
|
-
getFormattedAddress
|
|
8
|
+
getFormattedAddress,
|
|
9
|
+
getEnsOwner,
|
|
10
|
+
getEnsTextRecord
|
|
9
11
|
} from './utils';
|
|
10
12
|
|
|
11
13
|
vi.mock('cross-fetch', async () => {
|
|
@@ -611,4 +613,18 @@ describe('utils', () => {
|
|
|
611
613
|
expect(result).not.toBe(true);
|
|
612
614
|
});
|
|
613
615
|
});
|
|
616
|
+
|
|
617
|
+
describe('getEnsOwner', () => {
|
|
618
|
+
test('should return null when the ENS is not valid', () => {
|
|
619
|
+
// special hidden characters after the k
|
|
620
|
+
expect(getEnsOwner('elonmusk.eth')).resolves.toBe(null);
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
describe('getEnsTextRecord', () => {
|
|
625
|
+
test('should return null when the ENS is not valid', () => {
|
|
626
|
+
// special hidden characters after the k
|
|
627
|
+
expect(getEnsTextRecord('elonmusk.eth')).resolves.toBe(null);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
614
630
|
});
|
package/src/utils.ts
CHANGED
|
@@ -544,7 +544,14 @@ export async function getEnsTextRecord(
|
|
|
544
544
|
...multicallOptions
|
|
545
545
|
} = options;
|
|
546
546
|
|
|
547
|
-
|
|
547
|
+
let ensHash: string;
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
ensHash = namehash(ensNormalize(ens));
|
|
551
|
+
} catch (e: any) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
|
|
548
555
|
const provider = getProvider(network, { broviderUrl });
|
|
549
556
|
|
|
550
557
|
const calls = [
|
|
@@ -592,9 +599,17 @@ export async function getEnsOwner(
|
|
|
592
599
|
['function owner(bytes32) view returns (address)'],
|
|
593
600
|
provider
|
|
594
601
|
);
|
|
602
|
+
|
|
603
|
+
let ensHash: string;
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
ensHash = namehash(ensNormalize(ens));
|
|
607
|
+
} catch (e: any) {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
|
|
595
611
|
const ensNameWrapper =
|
|
596
612
|
options.ensNameWrapper || networks[network].ensNameWrapper;
|
|
597
|
-
const ensHash = namehash(ensNormalize(ens));
|
|
598
613
|
let owner = await ensRegistry.owner(ensHash);
|
|
599
614
|
// If owner is the ENSNameWrapper contract, resolve the owner of the name
|
|
600
615
|
if (owner === ensNameWrapper) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { test, expect, describe } from 'vitest';
|
|
2
2
|
import starknetMessage from '../../test/fixtures/starknet/message-alias.json';
|
|
3
|
+
import starknetMessageRsv from '../../test/fixtures/starknet/message-alias-rsv.json';
|
|
3
4
|
import verify, { getHash } from './starknet';
|
|
4
5
|
import { validateAndParseAddress } from 'starknet';
|
|
6
|
+
import { clone } from '../utils';
|
|
5
7
|
|
|
6
8
|
describe('verify/starknet', () => {
|
|
7
9
|
describe('getHash()', () => {
|
|
@@ -21,47 +23,79 @@ describe('verify/starknet', () => {
|
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
describe('verify()', () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'
|
|
31
|
-
)
|
|
32
|
-
)
|
|
26
|
+
describe.each([
|
|
27
|
+
['2', starknetMessage],
|
|
28
|
+
['3', starknetMessageRsv]
|
|
29
|
+
])('with a %s items signature', (title, message) => {
|
|
30
|
+
test('should return true if the signature is valid', () => {
|
|
31
|
+
expect(
|
|
32
|
+
verify(message.address, message.sig, message.data, 'SN_MAIN')
|
|
33
|
+
).resolves.toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('should return true if the signature is valid with a padded address', () => {
|
|
37
|
+
expect(
|
|
38
|
+
verify(
|
|
39
|
+
validateAndParseAddress(message.address),
|
|
40
|
+
message.sig,
|
|
41
|
+
message.data,
|
|
42
|
+
'SN_MAIN'
|
|
43
|
+
)
|
|
44
|
+
).resolves.toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should return true when verifying on a different network', () => {
|
|
48
|
+
expect(
|
|
49
|
+
verify(message.address, message.sig, message.data, 'SN_SEPOLIA')
|
|
50
|
+
).resolves.toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should throw an error if the signature is invalid', () => {
|
|
54
|
+
expect(
|
|
55
|
+
verify(
|
|
56
|
+
'0x7667469b8e93faa642573078b6bf8c790d3a6184b2a1bb39c5c923a732862e1',
|
|
57
|
+
message.sig,
|
|
58
|
+
message.data
|
|
59
|
+
)
|
|
60
|
+
).rejects.toThrow();
|
|
61
|
+
});
|
|
33
62
|
});
|
|
34
63
|
|
|
35
|
-
test('should
|
|
64
|
+
test('should throw an error when the contract is not deployed', () => {
|
|
36
65
|
expect(
|
|
37
66
|
verify(
|
|
38
|
-
|
|
67
|
+
'0x07f71118e351c02f6EC7099C8CDf93AED66CEd8406E94631cC91637f7D7F203A',
|
|
39
68
|
starknetMessage.sig,
|
|
40
69
|
starknetMessage.data,
|
|
41
|
-
'
|
|
70
|
+
'SN_MAIN'
|
|
42
71
|
)
|
|
43
|
-
).
|
|
72
|
+
).rejects.toThrowError('Contract not deployed');
|
|
44
73
|
});
|
|
45
74
|
|
|
46
|
-
test('should
|
|
75
|
+
test('should return false when the signature is not valid', () => {
|
|
47
76
|
expect(
|
|
48
77
|
verify(
|
|
49
78
|
starknetMessage.address,
|
|
50
|
-
|
|
79
|
+
['1', '2'],
|
|
51
80
|
starknetMessage.data,
|
|
52
81
|
'SN_MAIN'
|
|
53
82
|
)
|
|
54
|
-
).
|
|
83
|
+
).resolves.toBe(false);
|
|
55
84
|
});
|
|
56
85
|
|
|
57
|
-
test('should
|
|
86
|
+
test('should return false when the signature is not valid', () => {
|
|
87
|
+
const data = clone(starknetMessage.data);
|
|
88
|
+
data.message.timestamp = 1234;
|
|
89
|
+
|
|
58
90
|
expect(
|
|
59
|
-
verify(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
91
|
+
verify(starknetMessage.address, starknetMessage.sig, data, 'SN_MAIN')
|
|
92
|
+
).resolves.toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should throw an error on wrong signature length', () => {
|
|
96
|
+
expect(
|
|
97
|
+
verify(starknetMessage.address, ['1'], starknetMessage.data, 'SN_MAIN')
|
|
98
|
+
).rejects.toThrowError('Invalid signature format');
|
|
65
99
|
});
|
|
66
100
|
});
|
|
67
101
|
});
|
package/src/verify/starknet.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Contract, RpcProvider, typedData } from 'starknet';
|
|
2
|
+
import { BigNumber } from '@ethersproject/bignumber';
|
|
2
3
|
import type { SignaturePayload } from '.';
|
|
3
4
|
import type { ProviderOptions } from '../utils/provider';
|
|
4
5
|
|
|
@@ -11,11 +12,11 @@ const RPC_URLS: Record<NetworkType, string> = {
|
|
|
11
12
|
|
|
12
13
|
const ABI = [
|
|
13
14
|
{
|
|
14
|
-
name: 'argent::account::
|
|
15
|
+
name: 'argent::common::account::IAccount',
|
|
15
16
|
type: 'interface',
|
|
16
17
|
items: [
|
|
17
18
|
{
|
|
18
|
-
name: '
|
|
19
|
+
name: 'is_valid_signature',
|
|
19
20
|
type: 'function',
|
|
20
21
|
inputs: [
|
|
21
22
|
{
|
|
@@ -23,7 +24,7 @@ const ABI = [
|
|
|
23
24
|
type: 'core::felt252'
|
|
24
25
|
},
|
|
25
26
|
{
|
|
26
|
-
name: '
|
|
27
|
+
name: 'signature',
|
|
27
28
|
type: 'core::array::Array::<core::felt252>'
|
|
28
29
|
}
|
|
29
30
|
],
|
|
@@ -67,16 +68,28 @@ export default async function verify(
|
|
|
67
68
|
network: NetworkType = 'SN_MAIN',
|
|
68
69
|
options: ProviderOptions = {}
|
|
69
70
|
): Promise<boolean> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
try {
|
|
72
|
+
const contractAccount = new Contract(
|
|
73
|
+
ABI,
|
|
74
|
+
address,
|
|
75
|
+
getProvider(network, options)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (sig.length < 2) {
|
|
79
|
+
throw new Error('Invalid signature format');
|
|
80
|
+
}
|
|
75
81
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
const result = await contractAccount.is_valid_signature(
|
|
83
|
+
getHash(data, address),
|
|
84
|
+
sig.slice(-2)
|
|
85
|
+
);
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
return BigNumber.from(result).eq(BigNumber.from('370462705988'));
|
|
88
|
+
} catch (e: any) {
|
|
89
|
+
if (e.message.includes('Contract not found')) {
|
|
90
|
+
throw new Error('Contract not deployed');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
82
95
|
}
|