@avalabs/evm-module 0.0.15 → 0.0.17
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +35 -15
- package/CHANGELOG.md +17 -0
- package/dist/index.cjs +31 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +28 -10
- package/dist/index.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +6 -5
- package/src/constants.ts +1 -0
- package/src/handlers/eth-send-transaction/eth-send-transaction.test.ts +233 -2
- package/src/handlers/eth-send-transaction/eth-send-transaction.ts +27 -8
- package/src/handlers/eth-sign/eth-sign.test.ts +320 -0
- package/src/handlers/eth-sign/eth-sign.ts +158 -0
- package/src/handlers/eth-sign/schemas/eth-sign-typed-data.ts +65 -0
- package/src/handlers/eth-sign/schemas/eth-sign.ts +9 -0
- package/src/handlers/eth-sign/schemas/parse-request-params/fixture.ts +47 -0
- package/src/handlers/eth-sign/schemas/parse-request-params/parse-request-params.test.ts +284 -0
- package/src/handlers/eth-sign/schemas/parse-request-params/parse-request-params.ts +94 -0
- package/src/handlers/eth-sign/schemas/personal-sign.ts +12 -0
- package/src/handlers/eth-sign/schemas/shared.ts +5 -0
- package/src/handlers/eth-sign/utils/beautify-message/beautify-message.test.ts +29 -0
- package/src/handlers/eth-sign/utils/beautify-message/beautify-message.ts +134 -0
- package/src/handlers/eth-sign/utils/is-typed-data-valid.ts +26 -0
- package/src/handlers/eth-sign/utils/typeguards.ts +10 -0
- package/src/handlers/get-balances/evm-balance-service/get-erc20-balances.test.ts +2 -2
- package/src/handlers/get-balances/evm-balance-service/get-erc20-balances.ts +4 -6
- package/src/handlers/get-balances/get-balances.test.ts +0 -5
- package/src/handlers/get-balances/get-balances.ts +14 -3
- package/src/handlers/get-balances/glacier-balance-service/get-erc20-balances.test.ts +0 -1
- package/src/handlers/get-balances/glacier-balance-service/get-erc20-balances.ts +10 -7
- package/src/handlers/get-tokens/get-tokens.test.ts +6 -6
- package/src/index.ts +1 -0
- package/src/module.ts +14 -0
- package/src/types.ts +9 -0
- package/src/utils/parse-erc20-transaction-type.ts +35 -0
- package/src/utils/process-transaction-simulation.test.ts +105 -0
- package/src/utils/process-transaction-simulation.ts +294 -0
- package/src/utils/scan-transaction.ts +63 -0
- package/tsconfig.json +6 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { parseRequestParams } from './parse-request-params';
|
|
2
|
+
import { RpcMethod } from '@avalabs/vm-module-types';
|
|
3
|
+
import { typedData as testTypedData } from './fixture';
|
|
4
|
+
|
|
5
|
+
const testAddress = '0x4e3F23eA4E5D483B5aD5dF0A6233dEEA093EFFD2';
|
|
6
|
+
const testData = '0x123';
|
|
7
|
+
|
|
8
|
+
describe('parseRequestParams', () => {
|
|
9
|
+
describe('PERSONAL_SIGN', () => {
|
|
10
|
+
it('parses with password correctly', () => {
|
|
11
|
+
const params = {
|
|
12
|
+
method: RpcMethod.PERSONAL_SIGN,
|
|
13
|
+
params: [testData, testAddress, 'password'],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const result = parseRequestParams(params);
|
|
17
|
+
expect(result.success).toBeTruthy();
|
|
18
|
+
expect(result.data).toEqual({
|
|
19
|
+
data: testData,
|
|
20
|
+
address: testAddress,
|
|
21
|
+
method: RpcMethod.PERSONAL_SIGN,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('parses without password correctly', () => {
|
|
26
|
+
const params = {
|
|
27
|
+
method: RpcMethod.PERSONAL_SIGN,
|
|
28
|
+
params: [testData, testAddress],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const result = parseRequestParams(params);
|
|
32
|
+
expect(result.success).toBeTruthy();
|
|
33
|
+
expect(result.data).toEqual({
|
|
34
|
+
data: testData,
|
|
35
|
+
address: testAddress,
|
|
36
|
+
method: RpcMethod.PERSONAL_SIGN,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('fails for invalid params', () => {
|
|
41
|
+
const params = {
|
|
42
|
+
method: RpcMethod.PERSONAL_SIGN,
|
|
43
|
+
params: [],
|
|
44
|
+
};
|
|
45
|
+
const result = parseRequestParams(params);
|
|
46
|
+
expect(result.success).toBeFalsy();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('ETH_SIGN', () => {
|
|
51
|
+
it('parses correctly', () => {
|
|
52
|
+
const params = {
|
|
53
|
+
method: RpcMethod.ETH_SIGN,
|
|
54
|
+
params: [testAddress, testData],
|
|
55
|
+
};
|
|
56
|
+
const result = parseRequestParams(params);
|
|
57
|
+
expect(result.success).toBeTruthy();
|
|
58
|
+
expect(result.data).toEqual({
|
|
59
|
+
data: testData,
|
|
60
|
+
address: testAddress,
|
|
61
|
+
method: RpcMethod.ETH_SIGN,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('fails for invalid params', () => {
|
|
66
|
+
const params = {
|
|
67
|
+
method: RpcMethod.ETH_SIGN,
|
|
68
|
+
params: [],
|
|
69
|
+
};
|
|
70
|
+
const result = parseRequestParams(params);
|
|
71
|
+
expect(result.success).toBeFalsy();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('SIGN_TYPED_DATA', () => {
|
|
76
|
+
it('parses with valid JSON string correctly', () => {
|
|
77
|
+
const testObject = [{ type: 'string', name: 'message', value: 'Hello' }];
|
|
78
|
+
const validJson = JSON.stringify(testObject);
|
|
79
|
+
const params = {
|
|
80
|
+
method: RpcMethod.SIGN_TYPED_DATA,
|
|
81
|
+
params: [testAddress, validJson],
|
|
82
|
+
};
|
|
83
|
+
const result = parseRequestParams(params);
|
|
84
|
+
expect(result.success).toBeTruthy();
|
|
85
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
86
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA);
|
|
87
|
+
expect(result.data?.data).toEqual(testObject);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('parses with valid object correctly', () => {
|
|
91
|
+
const validObject = [{ type: 'string', name: 'message', value: 'Hello' }];
|
|
92
|
+
|
|
93
|
+
const params = {
|
|
94
|
+
method: RpcMethod.SIGN_TYPED_DATA,
|
|
95
|
+
params: [testAddress, validObject],
|
|
96
|
+
};
|
|
97
|
+
const result = parseRequestParams(params);
|
|
98
|
+
expect(result.success).toBeTruthy();
|
|
99
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
100
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA);
|
|
101
|
+
expect(result.data?.data).toEqual(validObject);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('fails with invalid JSON string', () => {
|
|
105
|
+
const invalidJson = "{ 'message': 'Hello' }";
|
|
106
|
+
const params = {
|
|
107
|
+
method: RpcMethod.SIGN_TYPED_DATA,
|
|
108
|
+
params: [testAddress, invalidJson],
|
|
109
|
+
};
|
|
110
|
+
const result = parseRequestParams(params);
|
|
111
|
+
expect(result.success).toBeFalsy();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('fails with invalid object', () => {
|
|
115
|
+
const invalidObject = [{ type: 'string', name: 'message' }]; // missing value
|
|
116
|
+
const params = {
|
|
117
|
+
method: RpcMethod.SIGN_TYPED_DATA,
|
|
118
|
+
params: [testAddress, invalidObject],
|
|
119
|
+
};
|
|
120
|
+
const result = parseRequestParams(params);
|
|
121
|
+
expect(result.success).toBeFalsy();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('SIGN_TYPED_DATA_V1', () => {
|
|
126
|
+
it('parses with valid JSON string correctly', () => {
|
|
127
|
+
const testObject = [{ type: 'string', name: 'message', value: 'Hello' }];
|
|
128
|
+
const validJson = JSON.stringify(testObject);
|
|
129
|
+
const params = {
|
|
130
|
+
method: RpcMethod.SIGN_TYPED_DATA_V1,
|
|
131
|
+
params: [testAddress, validJson],
|
|
132
|
+
};
|
|
133
|
+
const result = parseRequestParams(params);
|
|
134
|
+
expect(result.success).toBeTruthy();
|
|
135
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
136
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA_V1);
|
|
137
|
+
expect(result.data?.data).toEqual(testObject);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('parses with valid object correctly', () => {
|
|
141
|
+
const validObject = [{ type: 'string', name: 'message', value: 'Hello' }];
|
|
142
|
+
|
|
143
|
+
const params = {
|
|
144
|
+
method: RpcMethod.SIGN_TYPED_DATA_V1,
|
|
145
|
+
params: [testAddress, validObject],
|
|
146
|
+
};
|
|
147
|
+
const result = parseRequestParams(params);
|
|
148
|
+
expect(result.success).toBeTruthy();
|
|
149
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
150
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA_V1);
|
|
151
|
+
expect(result.data?.data).toEqual(validObject);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('fails with invalid JSON string', () => {
|
|
155
|
+
const invalidJson = "{ 'message': 'Hello' }";
|
|
156
|
+
const params = {
|
|
157
|
+
method: RpcMethod.SIGN_TYPED_DATA_V1,
|
|
158
|
+
params: [testAddress, invalidJson],
|
|
159
|
+
};
|
|
160
|
+
const result = parseRequestParams(params);
|
|
161
|
+
expect(result.success).toBeFalsy();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('fails with invalid object', () => {
|
|
165
|
+
const invalidObject = [{ type: 'string', name: 'message' }]; // missing value
|
|
166
|
+
const params = {
|
|
167
|
+
method: RpcMethod.SIGN_TYPED_DATA_V1,
|
|
168
|
+
params: [testAddress, invalidObject],
|
|
169
|
+
};
|
|
170
|
+
const result = parseRequestParams(params);
|
|
171
|
+
expect(result.success).toBeFalsy();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('SIGN_TYPED_DATA_V3', () => {
|
|
176
|
+
it('parses with valid JSON string correctly', () => {
|
|
177
|
+
const testObject = testTypedData;
|
|
178
|
+
const validJson = JSON.stringify(testObject);
|
|
179
|
+
const params = {
|
|
180
|
+
method: RpcMethod.SIGN_TYPED_DATA_V3,
|
|
181
|
+
params: [testAddress, validJson],
|
|
182
|
+
};
|
|
183
|
+
const result = parseRequestParams(params);
|
|
184
|
+
expect(result.success).toBeTruthy();
|
|
185
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
186
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA_V3);
|
|
187
|
+
expect(result.data?.data).toEqual(testObject);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('parses with valid object correctly', () => {
|
|
191
|
+
const validObject = testTypedData;
|
|
192
|
+
|
|
193
|
+
const params = {
|
|
194
|
+
method: RpcMethod.SIGN_TYPED_DATA_V3,
|
|
195
|
+
params: [testAddress, validObject],
|
|
196
|
+
};
|
|
197
|
+
const result = parseRequestParams(params);
|
|
198
|
+
expect(result.success).toBeTruthy();
|
|
199
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
200
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA_V3);
|
|
201
|
+
expect(result.data?.data).toEqual(validObject);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('fails with invalid JSON string', () => {
|
|
205
|
+
const invalidJson = "{ 'message': 'Hello' }";
|
|
206
|
+
const params = {
|
|
207
|
+
method: RpcMethod.SIGN_TYPED_DATA_V3,
|
|
208
|
+
params: [testAddress, invalidJson],
|
|
209
|
+
};
|
|
210
|
+
const result = parseRequestParams(params);
|
|
211
|
+
expect(result.success).toBeFalsy();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('fails with invalid object', () => {
|
|
215
|
+
const invalidObject = [{ type: 'string', name: 'message' }];
|
|
216
|
+
const params = {
|
|
217
|
+
method: RpcMethod.SIGN_TYPED_DATA_V3,
|
|
218
|
+
params: [testAddress, invalidObject],
|
|
219
|
+
};
|
|
220
|
+
const result = parseRequestParams(params);
|
|
221
|
+
expect(result.success).toBeFalsy();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('SIGN_TYPED_DATA_V4', () => {
|
|
226
|
+
it('parses with valid JSON string correctly', () => {
|
|
227
|
+
const testObject = testTypedData;
|
|
228
|
+
const validJson = JSON.stringify(testObject);
|
|
229
|
+
const params = {
|
|
230
|
+
method: RpcMethod.SIGN_TYPED_DATA_V4,
|
|
231
|
+
params: [testAddress, validJson],
|
|
232
|
+
};
|
|
233
|
+
const result = parseRequestParams(params);
|
|
234
|
+
expect(result.success).toBeTruthy();
|
|
235
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
236
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA_V4);
|
|
237
|
+
expect(result.data?.data).toEqual(testObject);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('parses with valid object correctly', () => {
|
|
241
|
+
const validObject = testTypedData;
|
|
242
|
+
|
|
243
|
+
const params = {
|
|
244
|
+
method: RpcMethod.SIGN_TYPED_DATA_V4,
|
|
245
|
+
params: [testAddress, validObject],
|
|
246
|
+
};
|
|
247
|
+
const result = parseRequestParams(params);
|
|
248
|
+
expect(result.success).toBeTruthy();
|
|
249
|
+
expect(result.data?.address).toEqual(testAddress);
|
|
250
|
+
expect(result.data?.method).toEqual(RpcMethod.SIGN_TYPED_DATA_V4);
|
|
251
|
+
expect(result.data?.data).toEqual(validObject);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('fails with invalid JSON string', () => {
|
|
255
|
+
const invalidJson = "{ 'message': 'Hello' }";
|
|
256
|
+
const params = {
|
|
257
|
+
method: RpcMethod.SIGN_TYPED_DATA_V4,
|
|
258
|
+
params: [testAddress, invalidJson],
|
|
259
|
+
};
|
|
260
|
+
const result = parseRequestParams(params);
|
|
261
|
+
expect(result.success).toBeFalsy();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('fails with invalid object', () => {
|
|
265
|
+
const invalidObject = [{ type: 'string', name: 'message' }];
|
|
266
|
+
const params = {
|
|
267
|
+
method: RpcMethod.SIGN_TYPED_DATA_V4,
|
|
268
|
+
params: [testAddress, invalidObject],
|
|
269
|
+
};
|
|
270
|
+
const result = parseRequestParams(params);
|
|
271
|
+
expect(result.success).toBeFalsy();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('fails for unsupported method', () => {
|
|
276
|
+
const params = {
|
|
277
|
+
method: 'UNSUPPORTED_METHOD',
|
|
278
|
+
params: [testAddress, testData],
|
|
279
|
+
};
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
281
|
+
const result = parseRequestParams(params as any);
|
|
282
|
+
expect(result.success).toBeFalsy();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ethSignSchema } from '../eth-sign';
|
|
3
|
+
import {
|
|
4
|
+
combinedTypedDataSchema,
|
|
5
|
+
ethSignTypedDataSchema,
|
|
6
|
+
ethSignTypedDataV1Schema,
|
|
7
|
+
ethSignTypedDataV3Schema,
|
|
8
|
+
ethSignTypedDataV4Schema,
|
|
9
|
+
typedDataSchema,
|
|
10
|
+
} from '../eth-sign-typed-data';
|
|
11
|
+
import { personalSignSchema } from '../personal-sign';
|
|
12
|
+
import { RpcMethod } from '@avalabs/vm-module-types';
|
|
13
|
+
|
|
14
|
+
const paramsSchema = z
|
|
15
|
+
.discriminatedUnion('method', [
|
|
16
|
+
personalSignSchema,
|
|
17
|
+
ethSignSchema,
|
|
18
|
+
ethSignTypedDataSchema,
|
|
19
|
+
ethSignTypedDataV1Schema,
|
|
20
|
+
ethSignTypedDataV3Schema,
|
|
21
|
+
ethSignTypedDataV4Schema,
|
|
22
|
+
])
|
|
23
|
+
.transform((value, ctx) => {
|
|
24
|
+
const { method, params } = value;
|
|
25
|
+
|
|
26
|
+
switch (method) {
|
|
27
|
+
case RpcMethod.PERSONAL_SIGN:
|
|
28
|
+
return {
|
|
29
|
+
data: params[0],
|
|
30
|
+
address: params[1],
|
|
31
|
+
method,
|
|
32
|
+
};
|
|
33
|
+
case RpcMethod.ETH_SIGN:
|
|
34
|
+
return {
|
|
35
|
+
data: params[1],
|
|
36
|
+
address: params[0],
|
|
37
|
+
method,
|
|
38
|
+
};
|
|
39
|
+
case RpcMethod.SIGN_TYPED_DATA:
|
|
40
|
+
case RpcMethod.SIGN_TYPED_DATA_V1: {
|
|
41
|
+
const address = params[0];
|
|
42
|
+
const data = params[1];
|
|
43
|
+
|
|
44
|
+
if (typeof data !== 'string') return { data, address, method };
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(data);
|
|
48
|
+
const result = combinedTypedDataSchema.parse(parsed);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
data: result,
|
|
52
|
+
address,
|
|
53
|
+
method,
|
|
54
|
+
};
|
|
55
|
+
} catch (e) {
|
|
56
|
+
ctx.addIssue({
|
|
57
|
+
code: z.ZodIssueCode.custom,
|
|
58
|
+
message: 'param is not a valid json',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return z.NEVER;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
case RpcMethod.SIGN_TYPED_DATA_V3:
|
|
65
|
+
case RpcMethod.SIGN_TYPED_DATA_V4: {
|
|
66
|
+
const address = params[0];
|
|
67
|
+
const data = params[1];
|
|
68
|
+
|
|
69
|
+
if (typeof data !== 'string') return { data, address, method };
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(data);
|
|
73
|
+
const result = typedDataSchema.parse(parsed);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
data: result,
|
|
77
|
+
address,
|
|
78
|
+
method,
|
|
79
|
+
};
|
|
80
|
+
} catch (e) {
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: z.ZodIssueCode.custom,
|
|
83
|
+
message: 'param is not a valid json',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return z.NEVER;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export function parseRequestParams(params: { method: RpcMethod; params: unknown }) {
|
|
93
|
+
return paramsSchema.safeParse(params);
|
|
94
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { addressSchema, messageSchema } from './shared';
|
|
3
|
+
import { RpcMethod } from '@avalabs/vm-module-types';
|
|
4
|
+
|
|
5
|
+
// https://github.com/ethereum/go-ethereum/pull/2940
|
|
6
|
+
export const personalSignSchema = z.object({
|
|
7
|
+
method: z.literal(RpcMethod.PERSONAL_SIGN),
|
|
8
|
+
params: z.union([
|
|
9
|
+
z.tuple([messageSchema, addressSchema]),
|
|
10
|
+
z.tuple([messageSchema, addressSchema, z.string().optional().describe('password')]),
|
|
11
|
+
]),
|
|
12
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { beautifyComplexMessage, beautifySimpleMessage } from './beautify-message';
|
|
2
|
+
|
|
3
|
+
describe('beautifySimpleMessage', () => {
|
|
4
|
+
it('should correctly format a simple message (typed data v1)', () => {
|
|
5
|
+
const input = [
|
|
6
|
+
{ name: 'Name', type: 'string', value: 'John Doe' },
|
|
7
|
+
{ name: 'Age', type: 'number', value: 30 },
|
|
8
|
+
];
|
|
9
|
+
const expectedOutput = `Name:\n𝗝𝗼𝗵𝗻 𝗗𝗼𝗲\n\nAge:\n𝟯𝟬\n\n`;
|
|
10
|
+
expect(beautifySimpleMessage(input)).toEqual(expectedOutput);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('beautifyComplexMessage', () => {
|
|
15
|
+
it('should correctly format a complex message', () => {
|
|
16
|
+
const input = {
|
|
17
|
+
domain: { name: 'TestDomain', id: '123' },
|
|
18
|
+
message: {
|
|
19
|
+
title: 'TestTitle',
|
|
20
|
+
details: {
|
|
21
|
+
date: '2023-04-01',
|
|
22
|
+
items: ['item1', 'item2'],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const expectedOutput = `Domain\n name: 𝗧𝗲𝘀𝘁𝗗𝗼𝗺𝗮𝗶𝗻\n id: 𝟭𝟮𝟯\n\nMessage\n title: 𝗧𝗲𝘀𝘁𝗧𝗶𝘁𝗹𝗲\n details: \n date: 𝟮𝟬𝟮𝟯-𝟬𝟰-𝟬𝟭\n items: \n 0: 𝗶𝘁𝗲𝗺𝟭\n 1: 𝗶𝘁𝗲𝗺𝟮\n`;
|
|
27
|
+
expect(beautifyComplexMessage(input)).toEqual(expectedOutput);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export const beautifyComplexMessage = (data: { domain: Record<string, any>; message: Record<string, any> }) => {
|
|
3
|
+
let result = '';
|
|
4
|
+
|
|
5
|
+
result += 'Domain\n';
|
|
6
|
+
for (const key in data.domain) {
|
|
7
|
+
result += ` ${key}: ${toUnicodeBold(String(data.domain[key]))}\n`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
result += '\nMessage\n';
|
|
11
|
+
for (const key in data.message) {
|
|
12
|
+
if (typeof data.message[key] === 'object' && !Array.isArray(data.message[key])) {
|
|
13
|
+
result += ` ${key}: \n`;
|
|
14
|
+
for (const subKey in data.message[key]) {
|
|
15
|
+
if (Array.isArray(data.message[key][subKey])) {
|
|
16
|
+
result += ` ${subKey}: \n`;
|
|
17
|
+
data.message[key][subKey].forEach((item: any, index: number) => {
|
|
18
|
+
result += ` ${index}: ${toUnicodeBold(item)}\n`;
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
result += ` ${subKey}: ${toUnicodeBold(String(data.message[key][subKey]))}\n`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} else if (Array.isArray(data.message[key])) {
|
|
25
|
+
result += ` ${key}: \n`;
|
|
26
|
+
data.message[key].forEach((item: any, index: number) => {
|
|
27
|
+
result += ` ${index}: \n`;
|
|
28
|
+
for (const subKey in item) {
|
|
29
|
+
if (Array.isArray(item[subKey])) {
|
|
30
|
+
result += ` ${subKey}: \n`;
|
|
31
|
+
item[subKey].forEach((item: any, index: number) => {
|
|
32
|
+
result += ` ${index}: ${toUnicodeBold(item)}\n`;
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
result += ` ${subKey}: ${toUnicodeBold(String(item[subKey]))}\n`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
result += ` ${key}: ${toUnicodeBold(String(data.message[key]))}\n`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const beautifySimpleMessage = (
|
|
48
|
+
data: {
|
|
49
|
+
name: string;
|
|
50
|
+
type: string;
|
|
51
|
+
value: any;
|
|
52
|
+
}[],
|
|
53
|
+
) => {
|
|
54
|
+
let result = '';
|
|
55
|
+
|
|
56
|
+
data.forEach((item) => {
|
|
57
|
+
result += `${item.name}:\n`;
|
|
58
|
+
result += `${toUnicodeBold(String(item.value))}\n\n`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const toUnicodeBold = (str: string) => {
|
|
65
|
+
const boldMap: Record<string, string> = {
|
|
66
|
+
'0': '𝟬',
|
|
67
|
+
'1': '𝟭',
|
|
68
|
+
'2': '𝟮',
|
|
69
|
+
'3': '𝟯',
|
|
70
|
+
'4': '𝟰',
|
|
71
|
+
'5': '𝟱',
|
|
72
|
+
'6': '𝟲',
|
|
73
|
+
'7': '𝟳',
|
|
74
|
+
'8': '𝟴',
|
|
75
|
+
'9': '𝟵',
|
|
76
|
+
a: '𝗮',
|
|
77
|
+
b: '𝗯',
|
|
78
|
+
c: '𝗰',
|
|
79
|
+
d: '𝗱',
|
|
80
|
+
e: '𝗲',
|
|
81
|
+
f: '𝗳',
|
|
82
|
+
g: '𝗴',
|
|
83
|
+
h: '𝗵',
|
|
84
|
+
i: '𝗶',
|
|
85
|
+
j: '𝗷',
|
|
86
|
+
k: '𝗸',
|
|
87
|
+
l: '𝗹',
|
|
88
|
+
m: '𝗺',
|
|
89
|
+
n: '𝗻',
|
|
90
|
+
o: '𝗼',
|
|
91
|
+
p: '𝗽',
|
|
92
|
+
q: '𝗾',
|
|
93
|
+
r: '𝗿',
|
|
94
|
+
s: '𝘀',
|
|
95
|
+
t: '𝘁',
|
|
96
|
+
u: '𝘂',
|
|
97
|
+
v: '𝘃',
|
|
98
|
+
w: '𝘄',
|
|
99
|
+
x: '𝘅',
|
|
100
|
+
y: '𝘆',
|
|
101
|
+
z: '𝘇',
|
|
102
|
+
A: '𝗔',
|
|
103
|
+
B: '𝗕',
|
|
104
|
+
C: '𝗖',
|
|
105
|
+
D: '𝗗',
|
|
106
|
+
E: '𝗘',
|
|
107
|
+
F: '𝗙',
|
|
108
|
+
G: '𝗚',
|
|
109
|
+
H: '𝗛',
|
|
110
|
+
I: '𝗜',
|
|
111
|
+
J: '𝗝',
|
|
112
|
+
K: '𝗞',
|
|
113
|
+
L: '𝗟',
|
|
114
|
+
M: '𝗠',
|
|
115
|
+
N: '𝗡',
|
|
116
|
+
O: '𝗢',
|
|
117
|
+
P: '𝗣',
|
|
118
|
+
Q: '𝗤',
|
|
119
|
+
R: '𝗥',
|
|
120
|
+
S: '𝗦',
|
|
121
|
+
T: '𝗧',
|
|
122
|
+
U: '𝗨',
|
|
123
|
+
V: '𝗩',
|
|
124
|
+
W: '𝗪',
|
|
125
|
+
X: '𝗫',
|
|
126
|
+
Y: '𝗬',
|
|
127
|
+
Z: '𝗭',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return str
|
|
131
|
+
.split('')
|
|
132
|
+
.map((char) => boldMap[char] || char)
|
|
133
|
+
.join('');
|
|
134
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type TypedData, type MessageTypes } from '@avalabs/vm-module-types';
|
|
2
|
+
import { TypedDataEncoder } from 'ethers';
|
|
3
|
+
|
|
4
|
+
type Result = { isValid: true } | { isValid: false; error: unknown };
|
|
5
|
+
|
|
6
|
+
export const isTypedDataValid = (data: TypedData<MessageTypes>): Result => {
|
|
7
|
+
try {
|
|
8
|
+
// getPayload verifies the types and the content of the message throwing an error if the data is not valid.
|
|
9
|
+
// We don't want to immediately reject the request even if there are errors for compatiblity reasons.
|
|
10
|
+
// dApps tend to make small mistakes in the message format like leaving the verifyingContract emptry,
|
|
11
|
+
// in which cases we should be able to continue just like other wallets do (even if it's technically incorrect).
|
|
12
|
+
|
|
13
|
+
// remove EIP712Domain from types since ethers.js handles it separately
|
|
14
|
+
const { EIP712Domain, ...types } = data.types;
|
|
15
|
+
TypedDataEncoder.getPayload(data.domain, types, data.message);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
isValid: true,
|
|
19
|
+
};
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return {
|
|
22
|
+
isValid: false,
|
|
23
|
+
error: e,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TypedDataV1, TypedData, MessageTypes } from '@avalabs/vm-module-types';
|
|
2
|
+
import { typedDataSchema, typedDataV1Schema } from '../schemas/eth-sign-typed-data';
|
|
3
|
+
|
|
4
|
+
export const isTypedDataV1 = (data: unknown): data is TypedDataV1 => {
|
|
5
|
+
return typedDataV1Schema.safeParse(data).success;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const isTypedData = (data: unknown): data is TypedData<MessageTypes> => {
|
|
9
|
+
return typedDataSchema.safeParse(data).success;
|
|
10
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BN } from 'bn.js';
|
|
2
2
|
import { getErc20Balances } from './get-erc20-balances';
|
|
3
3
|
import { ethers } from 'ethers';
|
|
4
|
+
import { TokenType } from '@avalabs/vm-module-types';
|
|
4
5
|
|
|
5
6
|
describe('get-erc20-balances', () => {
|
|
6
7
|
it('should return erc20 token balances', async () => {
|
|
@@ -18,7 +19,7 @@ describe('get-erc20-balances', () => {
|
|
|
18
19
|
symbol: 'ETH',
|
|
19
20
|
decimals: 18,
|
|
20
21
|
logoUri: 'https://example.com/logo.png',
|
|
21
|
-
|
|
22
|
+
type: TokenType.ERC20,
|
|
22
23
|
},
|
|
23
24
|
],
|
|
24
25
|
provider: {
|
|
@@ -67,7 +68,6 @@ describe('get-erc20-balances', () => {
|
|
|
67
68
|
balanceDisplayValue: '1',
|
|
68
69
|
balanceInCurrency: 1000,
|
|
69
70
|
priceInCurrency: 1000,
|
|
70
|
-
contractType: 'ERC-20',
|
|
71
71
|
type: 'ERC20',
|
|
72
72
|
change24: 0,
|
|
73
73
|
marketCap: 0,
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { numberToBN, bnToBig, balanceToDisplayValue } from '@avalabs/utils-sdk';
|
|
2
|
-
import { TokenType, type
|
|
2
|
+
import { TokenType, type ERC20Token, type Network, type TokenWithBalance } from '@avalabs/vm-module-types';
|
|
3
3
|
import { ethers, type Provider } from 'ethers';
|
|
4
4
|
import ERC20 from '@openzeppelin/contracts/build/contracts/ERC20.json';
|
|
5
5
|
import type { TokenService } from '@internal/utils';
|
|
6
6
|
import { VsCurrencyType } from '@avalabs/coingecko-sdk';
|
|
7
7
|
import BN from 'bn.js';
|
|
8
8
|
|
|
9
|
-
const DEFAULT_DECIMALS = 18;
|
|
10
|
-
|
|
11
9
|
export const getErc20Balances = async ({
|
|
12
10
|
provider,
|
|
13
11
|
tokenService,
|
|
@@ -20,7 +18,7 @@ export const getErc20Balances = async ({
|
|
|
20
18
|
tokenService: TokenService;
|
|
21
19
|
address: string;
|
|
22
20
|
currency: string;
|
|
23
|
-
tokens:
|
|
21
|
+
tokens: ERC20Token[];
|
|
24
22
|
network: Network;
|
|
25
23
|
}): Promise<Record<string, TokenWithBalance>> => {
|
|
26
24
|
const coingeckoPlatformId = network.pricingProviders?.coingecko.assetPlatformId;
|
|
@@ -31,7 +29,7 @@ export const getErc20Balances = async ({
|
|
|
31
29
|
tokens.map(async (token) => {
|
|
32
30
|
const contract = new ethers.Contract(token.address, ERC20.abi, provider);
|
|
33
31
|
const balanceBig = await contract.balanceOf?.(userAddress);
|
|
34
|
-
const balance = new BN(balanceBig) || numberToBN(0, token.decimals
|
|
32
|
+
const balance = new BN(balanceBig) || numberToBN(0, token.decimals);
|
|
35
33
|
|
|
36
34
|
const tokenWithBalance = {
|
|
37
35
|
...token,
|
|
@@ -41,7 +39,7 @@ export const getErc20Balances = async ({
|
|
|
41
39
|
return tokenWithBalance;
|
|
42
40
|
}),
|
|
43
41
|
).then((res) => {
|
|
44
|
-
return res.reduce<(
|
|
42
|
+
return res.reduce<(ERC20Token & { balance: BN })[]>((acc, result) => {
|
|
45
43
|
return result.status === 'fulfilled' && !result.value.balance.isZero() ? [...acc, result.value] : acc;
|
|
46
44
|
}, []);
|
|
47
45
|
});
|