Skip to content

Commit 1aa4d3c

Browse files
authored
Create num.ts
1 parent b8d5cf4 commit 1aa4d3c

File tree

1 file changed

+395
-0
lines changed

1 file changed

+395
-0
lines changed

src/utils/num.ts

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
import { hexToBytes as hexToBytesNoble } from '@noble/curves/abstract/utils';
2+
import { sha256 } from '@noble/hashes/sha256';
3+
4+
import { MASK_31 } from '../global/constants';
5+
import { BigNumberish } from '../types';
6+
import assert from './assert';
7+
import { addHexPrefix, buf2hex, removeHexPrefix } from './encode';
8+
import { isBigInt, isNumber, isString } from './typed';
9+
10+
/**
11+
* Test if string is hex-string
12+
*
13+
* @param hex hex-string
14+
* @returns {boolean} true if the input string is a hexadecimal string, false otherwise
15+
* @example
16+
* ```typescript
17+
* const hexString1 = "0x2fd23d9182193775423497fc0c472e156c57c69e4089a1967fb288a2d84e914";
18+
* const result1 = isHex(hexString1);
19+
* // result1 = true
20+
*
21+
* const hexString2 = "2fd23d9182193775423497fc0c472e156c57c69e4089a1967fb288a2d84e914";
22+
* const result2 = isHex(hexString2);
23+
* // result2 = false
24+
* ```
25+
*/
26+
export function isHex(hex: string): boolean {
27+
return /^0x[0-9a-f]*$/i.test(hex);
28+
}
29+
30+
/**
31+
* Convert BigNumberish to bigint
32+
*
33+
* @param {BigNumberish} value value to convert
34+
* @returns {BigInt} converted value
35+
* @example
36+
* ```typescript
37+
* const str = '123';
38+
* const result = toBigInt(str);
39+
* // result = 123n
40+
* ```
41+
*/
42+
export function toBigInt(value: BigNumberish): bigint {
43+
return BigInt(value);
44+
}
45+
46+
/**
47+
* try to convert BigNumberish to bigint
48+
* in case of undefined return undefined
49+
*/
50+
export function tryToBigInt(value: BigNumberish | undefined) {
51+
return value ? BigInt(value) : undefined;
52+
}
53+
54+
/**
55+
* Convert BigNumberish to hex-string
56+
*
57+
* @param {BigNumberish} value value to convert
58+
* @returns {string} converted number in hex-string format
59+
* @example
60+
* ```typescript
61+
* toHex(100); // '0x64'
62+
* toHex('200'); // '0xc8'
63+
* ```
64+
*/
65+
export function toHex(value: BigNumberish): string {
66+
return addHexPrefix(toBigInt(value).toString(16));
67+
}
68+
69+
/**
70+
* Alias of ToHex
71+
*/
72+
export const toHexString = toHex;
73+
74+
/**
75+
* Convert BigNumberish to storage-key-string
76+
*
77+
* Same as toHex but conforming to the STORAGE_KEY pattern `^0x0[0-7]{1}[a-fA-F0-9]{0,62}$`.
78+
*
79+
* A storage key is represented as up to 62 hex digits, 3 bits, and 5 leading zeroes:
80+
* `0x0 + [0-7] + 62 hex = 0x + 64 hex`
81+
* @returns format: storage-key-string
82+
* @example
83+
* ```typescript
84+
* toStorageKey(0x123); // '0x0000000000000000000000000000000000000000000000000000000000000123'
85+
* toStorageKey(123); // '0x000000000000000000000000000000000000000000000000000000000000007b'
86+
* toStorageKey('test'); // 'Error'
87+
* ```
88+
*/
89+
export function toStorageKey(number: BigNumberish): string {
90+
// TODO: This is not completely correct as it will not enforce first 0 and second [0-7], 0x82bda... will pass as valid and should be false
91+
return addHexPrefix(toBigInt(number).toString(16).padStart(64, '0'));
92+
}
93+
94+
/**
95+
* Convert BigNumberish to hex format 0x + 64 hex chars
96+
*
97+
* Similar as toStorageKey but conforming to exactly 0x(64 hex chars).
98+
*
99+
* @returns format: hex-0x(64)-string
100+
* @example
101+
* ```typescript
102+
* toHex64(123); // '0x000000000000000000000000000000000000000000000000000000000000007b'
103+
* toHex64(123n); // '0x000000000000000000000000000000000000000000000000000000000000007b'
104+
* toHex64('test'); // 'Error'
105+
* ```
106+
*/
107+
export function toHex64(number: BigNumberish): string {
108+
const res = addHexPrefix(toBigInt(number).toString(16).padStart(64, '0'));
109+
if (res.length !== 66) throw TypeError('number is too big for hex 0x(64) representation');
110+
return res;
111+
}
112+
113+
/**
114+
* Convert hexadecimal string to decimal string
115+
*
116+
* @param {string} hex hex-string to convert
117+
* @returns {string} converted number in decimal string format
118+
* @example
119+
* ```typescript
120+
* hexToDecimalString('64'); // '100'
121+
* hexToDecimalString('c8'); // '200'
122+
* ```
123+
*/
124+
export function hexToDecimalString(hex: string): string {
125+
return BigInt(addHexPrefix(hex)).toString(10);
126+
}
127+
128+
/**
129+
* Remove hex-string leading zeroes and lowercase it
130+
*
131+
* @param {string} hex hex-string
132+
* @returns {string} updated string in hex-string format
133+
* @example
134+
* ```typescript
135+
* cleanHex('0x00023AB'); // '0x23ab'
136+
* ```
137+
*/
138+
export function cleanHex(hex: string): string {
139+
return hex.toLowerCase().replace(/^(0x)0+/, '$1');
140+
}
141+
142+
/**
143+
* Asserts input is equal to or greater then lowerBound and lower then upperBound.
144+
*
145+
* The `inputName` parameter is used in the assertion message.
146+
* @param input Value to check
147+
* @param lowerBound Lower bound value
148+
* @param upperBound Upper bound value
149+
* @param inputName Name of the input for error message
150+
* @throws Error if input is out of range
151+
* @example
152+
* ```typescript
153+
* const input1:BigNumberish = 10;
154+
* assertInRange(input1, 5, 20, 'value')
155+
*
156+
* const input2: BigNumberish = 25;
157+
* assertInRange(input2, 5, 20, 'value');
158+
* // throws Error: Message not signable, invalid value length.
159+
* ```
160+
*/
161+
export function assertInRange(
162+
input: BigNumberish,
163+
lowerBound: BigNumberish,
164+
upperBound: BigNumberish,
165+
inputName = ''
166+
) {
167+
const messageSuffix = inputName === '' ? 'invalid length' : `invalid ${inputName} length`;
168+
const inputBigInt = BigInt(input);
169+
const lowerBoundBigInt = BigInt(lowerBound);
170+
const upperBoundBigInt = BigInt(upperBound);
171+
172+
assert(
173+
inputBigInt >= lowerBoundBigInt && inputBigInt <= upperBoundBigInt,
174+
`Message not signable, ${messageSuffix}.`
175+
);
176+
}
177+
178+
/**
179+
* Convert BigNumberish array to decimal string array
180+
*
181+
* @param {BigNumberish[]} data array of big-numberish elements
182+
* @returns {string[]} array of decimal strings
183+
* @example
184+
* ```typescript
185+
* const data = [100, 200n];
186+
* const result = bigNumberishArrayToDecimalStringArray(data);
187+
* // result = ['100', '200']
188+
* ```
189+
*/
190+
export function bigNumberishArrayToDecimalStringArray(data: BigNumberish[]): string[] {
191+
return data.map((x) => toBigInt(x).toString(10));
192+
}
193+
194+
/**
195+
* Convert BigNumberish array to hexadecimal string array
196+
*
197+
* @param {BigNumberish[]} data array of big-numberish elements
198+
* @returns array of hex-strings
199+
* @example
200+
* ```typescript
201+
* const data = [100, 200n];
202+
* const result = bigNumberishArrayToHexadecimalStringArray(data);
203+
* // result = ['0x64', '0xc8']
204+
* ```
205+
*/
206+
export function bigNumberishArrayToHexadecimalStringArray(data: BigNumberish[]): string[] {
207+
return data.map((x) => toHex(x));
208+
}
209+
210+
/**
211+
* Test if string is a whole number (0, 1, 2, 3...)
212+
*
213+
* @param {string} str string to test
214+
* @returns {boolean}: true if string is a whole number, false otherwise
215+
* @example
216+
* ```typescript
217+
* isStringWholeNumber('100'); // true
218+
* isStringWholeNumber('10.0'); // false
219+
* isStringWholeNumber('test'); // false
220+
* ```
221+
*/
222+
export function isStringWholeNumber(str: string): boolean {
223+
return /^\d+$/.test(str);
224+
}
225+
226+
/**
227+
* Convert string to decimal string
228+
*
229+
* @param {string} str string to convert
230+
* @returns converted string in decimal format
231+
* @throws str needs to be a number string in hex or whole number format
232+
* @example
233+
* ```typescript
234+
* const result = getDecimalString("0x1a");
235+
* // result = "26"
236+
*
237+
* const result2 = getDecimalString("Hello");
238+
* // throws Error: "Hello needs to be a hex-string or whole-number-string"
239+
* ```
240+
*/
241+
export function getDecimalString(str: string) {
242+
if (isHex(str)) {
243+
return hexToDecimalString(str);
244+
}
245+
if (isStringWholeNumber(str)) {
246+
return str;
247+
}
248+
throw new Error(`${str} needs to be a hex-string or whole-number-string`);
249+
}
250+
251+
/**
252+
* Convert string to hexadecimal string
253+
*
254+
* @param {string} str string to convert
255+
* @returns converted hex-string
256+
* @throws str needs to be a number string in hex or whole number format
257+
* @example
258+
* ```typescript
259+
* const result = getHexString("123");
260+
* // result = "0x7b"
261+
*
262+
* const result2 = getHexString("Hello");
263+
* // throws Error: Hello needs to be a hex-string or whole-number-string
264+
* ```
265+
*/
266+
export function getHexString(str: string) {
267+
if (isHex(str)) {
268+
return str;
269+
}
270+
if (isStringWholeNumber(str)) {
271+
return toHexString(str);
272+
}
273+
throw new Error(`${str} needs to be a hex-string or whole-number-string`);
274+
}
275+
276+
/**
277+
* Convert string array to hex-string array
278+
*
279+
* @param {Array<string>} array array of string elements
280+
* @returns array of converted elements in hex-string format
281+
* @example
282+
* ```typescript
283+
* const data = ['100', '200', '0xaa'];
284+
* const result = getHexStringArray(data);
285+
* // result = ['0x64', '0xc8', '0xaa']
286+
* ```
287+
*/
288+
export function getHexStringArray(array: Array<string>) {
289+
return array.map(getHexString);
290+
}
291+
292+
/**
293+
* Convert boolean to "0" or "1"
294+
*
295+
* @param value The boolean value to be converted.
296+
* @returns {boolean} Returns true if the value is a number, otherwise returns false.
297+
* @example
298+
* ```typescript
299+
* const result = toCairoBool(true);
300+
* // result ="1"
301+
*
302+
* const result2 = toCairoBool(false);
303+
* // result2 = "0"
304+
* ```
305+
*/
306+
export function toCairoBool(value: boolean): string {
307+
return (+value).toString();
308+
}
309+
310+
/**
311+
* Convert hex-string to an array of Bytes (Uint8Array)
312+
*
313+
* @param {string} str hex-string
314+
* @returns {Uint8Array} array containing the converted elements
315+
* @throws str must be a hex-string
316+
* @example
317+
* ```typescript
318+
* let result;
319+
*
320+
* result = hexToBytes('0x64');
321+
* // result = [100]
322+
*
323+
* result = hexToBytes('test');
324+
* // throws Error: test needs to be a hex-string
325+
* ```
326+
*/
327+
export function hexToBytes(str: string): Uint8Array {
328+
if (!isHex(str)) throw new Error(`${str} needs to be a hex-string`);
329+
330+
let adaptedValue: string = removeHexPrefix(str);
331+
if (adaptedValue.length % 2 !== 0) {
332+
adaptedValue = `0${adaptedValue}`;
333+
}
334+
return hexToBytesNoble(adaptedValue);
335+
}
336+
337+
/**
338+
* Adds a percentage amount to the value
339+
*
340+
* @param number value to be modified
341+
* @param percent integer as percent ex. 50 for 50%
342+
* @returns {bigint} modified value
343+
* @example
344+
* ```typescript
345+
* addPercent(100, 50); // 150n
346+
* addPercent(100, 100); // 200n
347+
* addPercent(200, 50); // 300n
348+
* addPercent(200, -50); // 100n
349+
* addPercent(200, -100); // 0n
350+
* addPercent(200, -150); // -100n
351+
* ```
352+
*/
353+
export function addPercent(number: BigNumberish, percent: number): bigint {
354+
const bigIntNum = BigInt(number);
355+
return bigIntNum + (bigIntNum * BigInt(percent)) / 100n;
356+
}
357+
358+
/**
359+
* Calculate the sha256 hash of an utf8 string, then encode the
360+
* result in an uint8Array of 4 elements.
361+
* Useful in wallet path calculation.
362+
* @param {string} str utf8 string (hex string not handled).
363+
* @returns a uint8Array of 4 bytes.
364+
* @example
365+
* ```typescript
366+
* const ledgerPathApplicationName = 'LedgerW';
367+
* const path2Buffer = num.stringToSha256ToArrayBuff4(ledgerPathApplicationName);
368+
* // path2Buffer = Uint8Array(4) [43, 206, 231, 219]
369+
* ```
370+
*/
371+
export function stringToSha256ToArrayBuff4(str: string): Uint8Array {
372+
// eslint-disable-next-line no-bitwise
373+
const int31 = (n: bigint) => Number(n & MASK_31);
374+
const result: number = int31(BigInt(addHexPrefix(buf2hex(sha256(str)))));
375+
return hexToBytes(toHex(result));
376+
}
377+
378+
/**
379+
* Checks if a given value is of BigNumberish type.
380+
* 234, 234n, "234", "0xea" are valid
381+
* @param {unknown} input a value
382+
* @returns {boolean} true if type of input is `BigNumberish`
383+
* @example
384+
* ```typescript
385+
* const res = num.isBigNumberish("ZERO");
386+
* // res = false
387+
* ```
388+
*/
389+
export function isBigNumberish(input: unknown): input is BigNumberish {
390+
return (
391+
isNumber(input) ||
392+
isBigInt(input) ||
393+
(isString(input) && (isHex(input) || isStringWholeNumber(input)))
394+
);
395+
}

0 commit comments

Comments
 (0)