1 | /*
|
2 | # Why starts with uppercase?
|
3 | - emphasis there are kind of type
|
4 | - less names conflict
|
5 |
|
6 | # Why use lambda?
|
7 | - should be pure functions
|
8 | */
|
9 |
|
10 | const lodash = require('lodash');
|
11 | const BigNumber = require('bignumber.js');
|
12 |
|
13 | BigNumber.config({
|
14 | EXPONENTIAL_AT: 1e9,
|
15 | ROUNDING_MODE: BigNumber.ROUND_HALF_UP,
|
16 | });
|
17 |
|
18 | // ----------------------------------- Hex ------------------------------------
|
19 | /**
|
20 | * Hex formatter, trans value to hex string
|
21 | *
|
22 | * @memberOf type
|
23 | * @param value {string|number|Buffer|Date|BigNumber|null} - The value to gen hex string.
|
24 | * @return {string} Hex string.
|
25 | *
|
26 | * @example
|
27 | * > Hex(null)
|
28 | "0x"
|
29 | * > Hex(1) // also BigNumber
|
30 | "0x01"
|
31 | * > Hex('10') // from naked hex string
|
32 | "0x10"
|
33 | * > Hex('0x1') // pad prefix 0 auto
|
34 | "0x01"
|
35 | * > Hex(Buffer.from([1, 2]))
|
36 | "0x0102"
|
37 | */
|
38 | const Hex = value => {
|
39 | if (value === null) {
|
40 | return '0x';
|
41 | }
|
42 |
|
43 | if (lodash.isNumber(value) || BigNumber.isBigNumber(value)) {
|
44 | return Hex(value.toString(16));
|
45 | }
|
46 |
|
47 | if (lodash.isString(value)) {
|
48 | if (Hex.isHex(value)) { // In order not to copy hex string in most case.
|
49 | return value;
|
50 | }
|
51 |
|
52 | let string = value.toLowerCase();
|
53 | string = string.startsWith('0x') ? string : `0x${string}`;
|
54 | string = string.length % 2 ? `0x0${string.substring(2)}` : string;
|
55 | if (!Hex.isHex(string)) {
|
56 | throw new Error(`"${value}" do not match hex string`);
|
57 | }
|
58 | return string;
|
59 | }
|
60 |
|
61 | if (Buffer.isBuffer(value)) {
|
62 | return `0x${value.toString('hex')}`;
|
63 | }
|
64 |
|
65 | if (lodash.isDate(value)) {
|
66 | return Hex(value.valueOf());
|
67 | }
|
68 |
|
69 | return Hex(`${value}`); // for magic method `toString`
|
70 | };
|
71 |
|
72 | /**
|
73 | * Check if is hex string.
|
74 | *
|
75 | * > Hex: /^0x([0-9a-f][0-9a-f])*$/
|
76 | *
|
77 | * @param hex {string} - Value to be check.
|
78 | * @return {boolean}
|
79 | *
|
80 | * @example
|
81 | * > Hex.isHex('0x')
|
82 | true
|
83 | * > Hex.isHex('0x01')
|
84 | true
|
85 | * > Hex.isHex('0x1')
|
86 | false
|
87 | * > Hex.isHex('01')
|
88 | false
|
89 | */
|
90 | Hex.isHex = hex => {
|
91 | return /^0x([0-9a-f][0-9a-f])*$/.test(hex);
|
92 | };
|
93 |
|
94 | /**
|
95 | * Get hex string from number.
|
96 | *
|
97 | * @param value {number|BigNumber|string}
|
98 | * @return {string}
|
99 | *
|
100 | * @example
|
101 | * > Hex.fromNumber('10')
|
102 | "0x0a"
|
103 | * > Hex('10')
|
104 | "0x10"
|
105 | */
|
106 | Hex.fromNumber = value => {
|
107 | return Hex(lodash.isNumber(value) ? value : BigNumber(value));
|
108 | };
|
109 |
|
110 | /**
|
111 | * Get `Buffer` by `Hex` string.
|
112 | *
|
113 | * > NOTE: It's importance to only support `Hex` string, cause `Transaction.encode` will not check hex again.
|
114 | *
|
115 | * @param hex {string} - The hex string.
|
116 | * @return {Buffer}
|
117 | *
|
118 | * @example
|
119 | * > Hex.toBuffer('0x0102')
|
120 | <Buffer 01 02>
|
121 | */
|
122 | Hex.toBuffer = hex => {
|
123 | if (!Hex.isHex(hex)) {
|
124 | throw new Error(`"${hex}" do not match hex string`);
|
125 | }
|
126 |
|
127 | return Buffer.from(hex.substring(2), 'hex');
|
128 | };
|
129 |
|
130 | /**
|
131 | * Concat `Hex` string by order.
|
132 | *
|
133 | * @param values {array} - Array of hex string
|
134 | * @return {string}
|
135 | *
|
136 | * @example
|
137 | * > Hex.concat('0x01', '0x02', '0x0304')
|
138 | "0x01020304"
|
139 | * > Hex.concat()
|
140 | "0x"
|
141 | */
|
142 | Hex.concat = (...values) => {
|
143 | values.forEach((value, index) => {
|
144 | if (!Hex.isHex(value)) {
|
145 | throw new Error(`values[${index}] do not match hex string, got "${value}"`);
|
146 | }
|
147 | });
|
148 |
|
149 | return `0x${values.map(v => Hex(v).substring(2)).join('')}`;
|
150 | };
|
151 |
|
152 | // ----------------------------------- Address --------------------------------
|
153 | /**
|
154 | * Get and validate `Address` from value
|
155 | *
|
156 | * @memberOf type
|
157 | * @param value {string|number|Buffer|BigNumber}
|
158 | * @return {string}
|
159 | *
|
160 | * @example
|
161 | * > Address('0123456789012345678901234567890123456789')
|
162 | "0x0123456789012345678901234567890123456789"
|
163 | */
|
164 | const Address = value => {
|
165 | const hex = Hex(value);
|
166 | if (hex.length !== 2 + 2 * 20) {
|
167 | throw new Error(`${value} do not match Address`);
|
168 | }
|
169 | return hex;
|
170 | };
|
171 |
|
172 | // ------------------------------------ Hash ----------------------------------
|
173 | /**
|
174 | *
|
175 | * @memberOf type
|
176 | * @param value {string|number|Buffer|BigNumber}
|
177 | * @return {string}
|
178 | *
|
179 | * @example
|
180 | * > Hex32('0123456789012345678901234567890123456789012345678901234567890123')
|
181 | "0x0123456789012345678901234567890123456789012345678901234567890123"
|
182 | */
|
183 | const Hex32 = value => {
|
184 | const hex = Hex(value);
|
185 | if (hex.length !== 2 + 2 * 32) {
|
186 | throw new Error(`${value} do not match Hex32`);
|
187 | }
|
188 | return hex;
|
189 | };
|
190 |
|
191 | // ---------------------------------- PrivateKey ------------------------------
|
192 | /**
|
193 | * Get and validate `PrivateKey` from value.
|
194 | *
|
195 | * > same as `Hex32` in coincidence
|
196 | *
|
197 | * @memberOf type
|
198 | * @param value {string|number|Buffer|BigNumber}
|
199 | * @return {string}
|
200 | */
|
201 | const PrivateKey = value => {
|
202 | try {
|
203 | return Hex32(value);
|
204 | } catch (e) {
|
205 | throw new Error(`${value} do not match PrivateKey`);
|
206 | }
|
207 | };
|
208 |
|
209 | // ----------------------------------- BlockHash ------------------------------
|
210 | /**
|
211 | * Get and validate `BlockHash` from value
|
212 | *
|
213 | * > same as `Hex32` in coincidence
|
214 | *
|
215 | * @memberOf type
|
216 | * @param value {string|number|Buffer|BigNumber}
|
217 | * @return {string}
|
218 | */
|
219 | const BlockHash = value => {
|
220 | try {
|
221 | return Hex32(value);
|
222 | } catch (e) {
|
223 | throw new Error(`${value} do not match BlockHash`);
|
224 | }
|
225 | };
|
226 |
|
227 | // ----------------------------------- TxHash ---------------------------------
|
228 | /**
|
229 | * Get and validate `TxHash` from value
|
230 | *
|
231 | * > same as `Hex32` in coincidence
|
232 | *
|
233 | * @memberOf type
|
234 | * @param value {string|number|Buffer|BigNumber}
|
235 | * @return {string}
|
236 | *
|
237 | * @example
|
238 | * > TxHash('0123456789012345678901234567890123456789012345678901234567890123')
|
239 | "0x0123456789012345678901234567890123456789012345678901234567890123"
|
240 | */
|
241 | const TxHash = value => {
|
242 | try {
|
243 | return Hex32(value);
|
244 | } catch (e) {
|
245 | throw new Error(`${value} do not match TxHash`);
|
246 | }
|
247 | };
|
248 |
|
249 | // ---------------------------------- Drip ------------------------------------
|
250 | /**
|
251 | * @memberOf type
|
252 | * @param value {string|number|Buffer|BigNumber}
|
253 | * @return {string}
|
254 | *
|
255 | * @example
|
256 | * > Drip(1)
|
257 | "0x01"
|
258 |
|
259 | * @example
|
260 | * > Drip.toGDrip(Drip.fromCFX(1));
|
261 | "1000000000"
|
262 | */
|
263 | const Drip = value => {
|
264 | return Hex.fromNumber(value);
|
265 | };
|
266 |
|
267 | /**
|
268 | * Get Drip hex string by GDrip value.
|
269 | *
|
270 | * > NOTE: Rounds towards nearest neighbour. If equidistant, rounds towards zero.
|
271 | *
|
272 | * @param value {string|number|BigNumber} - Value in GDrip.
|
273 | * @return {string} Hex string in drip.
|
274 | *
|
275 | * @example
|
276 | * > Drip.fromGDrip(1)
|
277 | "0x3b9aca00"
|
278 | * > Drip.fromGDrip(0.1)
|
279 | "0x05f5e100"
|
280 | */
|
281 | Drip.fromGDrip = value => {
|
282 | const number = BigNumber(value).times(1e9);
|
283 | return Drip(number.integerValue());
|
284 | };
|
285 |
|
286 | /**
|
287 | * Get Drip hex string by CFX value.
|
288 | *
|
289 | * > NOTE: Rounds towards nearest neighbour. If equidistant, rounds towards zero.
|
290 | *
|
291 | * @param value {string|number|BigNumber} - Value in CFX.
|
292 | * @return {string} Hex string in drip.
|
293 | *
|
294 | * @example
|
295 | * > Drip.fromCFX(1)
|
296 | "0x0de0b6b3a7640000"
|
297 | * > Drip.fromCFX(0.1)
|
298 | "0x016345785d8a0000"
|
299 | */
|
300 | Drip.fromCFX = value => {
|
301 | const number = BigNumber(value).times(1e9).times(1e9); // XXX: 1e18 > Number.MAX_SAFE_INTEGER > 1e9
|
302 | return Drip(number.integerValue());
|
303 | };
|
304 |
|
305 | /**
|
306 | * Get `GDrip` from Drip.
|
307 | *
|
308 | * @param value {string|number|BigNumber}
|
309 | * @return {BigNumber}
|
310 | *
|
311 | * @example
|
312 | * > Drip.toGDrip(1e9)
|
313 | "1"
|
314 | * > Drip.toGDrip(Drip.fromCFX(1))
|
315 | "1000000000"
|
316 | */
|
317 | Drip.toGDrip = value => {
|
318 | return BigNumber(value).div(1e9);
|
319 | };
|
320 |
|
321 | /**
|
322 | * Get `CFX` from Drip.
|
323 | *
|
324 | * @param value {string|number|BigNumber}
|
325 | * @return {BigNumber}
|
326 | *
|
327 | * @example
|
328 | * > Drip.toCFX(1e18)
|
329 | "1"
|
330 | * > Drip.toCFX(Drip.fromGDrip(1e9))
|
331 | "1"
|
332 | */
|
333 | Drip.toCFX = value => {
|
334 | return BigNumber(value).div(1e9).div(1e9); // XXX: 1e18 > Number.MAX_SAFE_INTEGER > 1e9
|
335 | };
|
336 |
|
337 | // ------------------------------- EpochNumber --------------------------------
|
338 | /**
|
339 | * Get and validate `EpochNumber` from value
|
340 | *
|
341 | * @memberOf type
|
342 | * @param value {string|number|Buffer|BigNumber}
|
343 | * @return {string}
|
344 | *
|
345 | * @example
|
346 | * > EpochNumber(0)
|
347 | "0x00"
|
348 | * > EpochNumber('100')
|
349 | "0x64"
|
350 | * > EpochNumber('LATEST_STATE')
|
351 | "latest_state"
|
352 | */
|
353 | const EpochNumber = value => {
|
354 | if (lodash.isString(value)) {
|
355 | value = value.toLowerCase();
|
356 | }
|
357 |
|
358 | if ([EpochNumber.LATEST_STATE, EpochNumber.LATEST_MINED].includes(value)) {
|
359 | return value;
|
360 | }
|
361 |
|
362 | return Hex.fromNumber(value);
|
363 | };
|
364 |
|
365 | /**
|
366 | * The latest epochNumber where the latest block with an executed state in.
|
367 | *
|
368 | * @var {string}
|
369 | */
|
370 | EpochNumber.LATEST_STATE = 'latest_state';
|
371 |
|
372 | /**
|
373 | * The latest epochNumber where the latest mined block in.
|
374 | *
|
375 | * @var {string}
|
376 | */
|
377 | EpochNumber.LATEST_MINED = 'latest_mined';
|
378 |
|
379 | module.exports = {
|
380 | Hex,
|
381 | Address,
|
382 | Hex32,
|
383 | PrivateKey,
|
384 | BlockHash,
|
385 | TxHash,
|
386 | Drip,
|
387 | EpochNumber,
|
388 | };
|