@talismn/util 0.2.0 → 0.3.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.
@@ -0,0 +1 @@
1
+ export declare const addTrailingSlash: (url: string) => string;
@@ -0,0 +1,7 @@
1
+ /**
2
+ *
3
+ * @param address substrate SS58 address
4
+ * @param prefix prefix used to format the address
5
+ * @returns address encoded with supplied prefix
6
+ */
7
+ export declare const convertAddress: (address: string, prefix: number | null) => string;
@@ -0,0 +1 @@
1
+ export declare const decodeSs58Format: (address?: string) => number | undefined;
@@ -0,0 +1,8 @@
1
+ import { OperatorFunction } from "rxjs";
2
+ /**
3
+ * An rxjs operator which:
4
+ *
5
+ * 1. Emits the first value it receives from the source observable, then:
6
+ * 2. Debounces any future values by `timeout` ms.
7
+ */
8
+ export declare const firstThenDebounce: <T>(timeout: number) => OperatorFunction<T, T>;
@@ -0,0 +1 @@
1
+ export declare const formatPrice: (price: number, currency: string, compact: boolean) => string;
@@ -1,16 +1,28 @@
1
+ export * from "./addTrailingSlash";
1
2
  export * from "./BigMath";
2
- export * from "./FunctionPropertyNames";
3
3
  export * from "./blake2Concat";
4
4
  export * from "./classNames";
5
+ export * from "./convertAddress";
5
6
  export * from "./decodeAnyAddress";
7
+ export * from "./decodeSs58Format";
6
8
  export * from "./deferred";
7
9
  export * from "./encodeAnyAddress";
10
+ export * from "./firstThenDebounce";
8
11
  export * from "./formatDecimals";
12
+ export * from "./formatPrice";
13
+ export * from "./FunctionPropertyNames";
9
14
  export * from "./hasOwnProperty";
10
15
  export * from "./isArrayOf";
16
+ export * from "./isBigInt";
17
+ export * from "./isBooleanTrue";
11
18
  export * from "./isEthereumAddress";
19
+ export * from "./isValidSubstrateAddress";
12
20
  export * from "./planckToTokens";
13
21
  export * from "./sleep";
14
22
  export * from "./throwAfter";
15
23
  export * from "./tokensToPlanck";
16
24
  export * from "./twox64Concat";
25
+ export * from "./validateHexString";
26
+ export * from "./normalizeAddress";
27
+ export * from "./isAddressEqual";
28
+ export * from "./isAscii";
@@ -0,0 +1 @@
1
+ export declare const isAddressEqual: (address1: string, address2: string) => boolean;
@@ -0,0 +1 @@
1
+ export declare const isAscii: (str: string) => boolean;
@@ -0,0 +1 @@
1
+ export declare const isBigInt: (value: unknown) => value is bigint;
@@ -0,0 +1 @@
1
+ export declare const isBooleanTrue: <T>(x: T | null | undefined) => x is T;
@@ -1 +1 @@
1
- export declare const isEthereumAddress: (address: string) => boolean;
1
+ export declare const isEthereumAddress: (address: string | undefined | null) => address is `0x${string}`;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Similar to isValidAddress but will not call isEthereumAddress under the hood
3
+ * @param address
4
+ * @param prefix if supplied, the method will also check the prefix
5
+ * @returns true if valid substrate (SS58) address, false otherwise
6
+ */
7
+ export declare const isValidSubstrateAddress: (address: string, prefix?: number | null) => boolean;
@@ -0,0 +1 @@
1
+ export declare const normalizeAddress: (address: string) => string;
@@ -1 +1 @@
1
- export declare const throwAfter: (ms: number, reason: string) => Promise<void>;
1
+ export declare const throwAfter: (ms: number, reason: string) => Promise<never>;
@@ -1,2 +1 @@
1
- /// <reference types="node" />
2
1
  export declare function twox64Concat(input: string | Buffer | Uint8Array): `0x${string}`;
@@ -0,0 +1,12 @@
1
+ import { HexString } from "@polkadot/util/types";
2
+ /**
3
+ * @name validateHexString
4
+ * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
5
+ * @param {string} str - string to check
6
+ * @returns {HexString} - boolean
7
+ * @example
8
+ * validateHexString("0x1234") // "0x1234"
9
+ * validateHexString("1234") // Error: Expected a hex string
10
+ * validateHexString(1234) // Error: Expected a string
11
+ **/
12
+ export declare const validateHexString: (str: string) => HexString;
@@ -1,17 +1,23 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var util = require('@polkadot/util');
6
4
  var utilCrypto = require('@polkadot/util-crypto');
7
5
  var tailwindMerge = require('tailwind-merge');
8
6
  var keyring = require('@polkadot/keyring');
7
+ var rxjs = require('rxjs');
9
8
  var BigNumber = require('bignumber.js');
10
9
 
11
- function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
11
 
13
12
  var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
14
13
 
14
+ const addTrailingSlash = url => {
15
+ if (url.endsWith("/")) {
16
+ return url;
17
+ }
18
+ return `${url}/`;
19
+ };
20
+
15
21
  /**
16
22
  * Javascript's `Math` library for `BigInt`.
17
23
  * Taken from https://stackoverflow.com/questions/51867270/is-there-a-library-similar-to-math-that-supports-javascript-bigint/64953280#64953280
@@ -46,6 +52,26 @@ function blake2Concat(input) {
46
52
 
47
53
  const classNames = tailwindMerge.twMerge;
48
54
 
55
+ function encodeAnyAddress(key, ss58Format) {
56
+ try {
57
+ return keyring.encodeAddress(key, ss58Format);
58
+ } catch (error) {
59
+ if (typeof key !== "string") throw error;
60
+ if (!utilCrypto.isEthereumAddress(key)) throw error;
61
+ return utilCrypto.ethereumEncode(key);
62
+ }
63
+ }
64
+
65
+ /**
66
+ *
67
+ * @param address substrate SS58 address
68
+ * @param prefix prefix used to format the address
69
+ * @returns address encoded with supplied prefix
70
+ */
71
+ const convertAddress = (address, prefix) => {
72
+ return encodeAnyAddress(address, prefix ?? undefined);
73
+ };
74
+
49
75
  function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
50
76
  try {
51
77
  return keyring.decodeAddress(encoded, ignoreChecksum, ss58Format);
@@ -56,6 +82,18 @@ function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
56
82
  }
57
83
  }
58
84
 
85
+ const decodeSs58Format = address => {
86
+ if (!address) return;
87
+ try {
88
+ utilCrypto.decodeAddress(address);
89
+ const decoded = utilCrypto.base58Decode(address);
90
+ const [,,, ss58Format] = utilCrypto.checkAddressChecksum(decoded);
91
+ return ss58Format;
92
+ } catch {
93
+ return; // invalid address
94
+ }
95
+ };
96
+
59
97
  /**
60
98
  * In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
61
99
  * resolved or rejected at a later point in time, typically by code outside of the current function scope.
@@ -91,15 +129,13 @@ function Deferred() {
91
129
  };
92
130
  }
93
131
 
94
- function encodeAnyAddress(key, ss58Format) {
95
- try {
96
- return keyring.encodeAddress(key, ss58Format);
97
- } catch (error) {
98
- if (typeof key !== "string") throw error;
99
- if (!utilCrypto.isEthereumAddress(key)) throw error;
100
- return utilCrypto.ethereumEncode(key);
101
- }
102
- }
132
+ /**
133
+ * An rxjs operator which:
134
+ *
135
+ * 1. Emits the first value it receives from the source observable, then:
136
+ * 2. Debounces any future values by `timeout` ms.
137
+ */
138
+ const firstThenDebounce = timeout => source => rxjs.concat(source.pipe(rxjs.take(1)), source.pipe(rxjs.skip(1)).pipe(rxjs.debounceTime(timeout)));
103
139
 
104
140
  const MIN_DIGITS = 4; // less truncates more than what compact formating is
105
141
  const MAX_DECIMALS_FORMAT = 12;
@@ -116,7 +152,7 @@ const MAX_DECIMALS_FORMAT = 12;
116
152
  const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US") => {
117
153
  if (num === null || num === undefined) return "";
118
154
  if (digits < MIN_DIGITS) digits = MIN_DIGITS;
119
- const value = new BigNumber__default["default"](num);
155
+ const value = new BigNumber__default.default(num);
120
156
  // very small numbers should display "< 0.0001"
121
157
  const minDisplayVal = 1 / Math.pow(10, digits);
122
158
  if (value.gt(0) && value.lt(minDisplayVal)) return `< ${formatDecimals(minDisplayVal)}`;
@@ -129,12 +165,12 @@ const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US"
129
165
  // to prevent JS default rounding, we will remove/truncate insignificant digits ourselves before formatting
130
166
  let truncatedValue = value;
131
167
  //remove insignificant fraction digits
132
- const excessFractionDigitsPow10 = new BigNumber__default["default"](10).pow(digits > intDigits ? digits - intDigits : 0);
168
+ const excessFractionDigitsPow10 = new BigNumber__default.default(10).pow(digits > intDigits ? digits - intDigits : 0);
133
169
  truncatedValue = truncatedValue.multipliedBy(excessFractionDigitsPow10).integerValue().dividedBy(excessFractionDigitsPow10);
134
170
 
135
171
  //remove insignificant integer digits
136
- const excessIntegerDigits = new BigNumber__default["default"](intDigits > digits ? intDigits - digits : 0);
137
- const excessIntegerDigitsPow10 = new BigNumber__default["default"](10).pow(excessIntegerDigits);
172
+ const excessIntegerDigits = new BigNumber__default.default(intDigits > digits ? intDigits - digits : 0);
173
+ const excessIntegerDigitsPow10 = new BigNumber__default.default(10).pow(excessIntegerDigits);
138
174
  if (excessIntegerDigits.gt(0)) truncatedValue = truncatedValue.dividedBy(excessIntegerDigitsPow10).integerValue().multipliedBy(excessIntegerDigitsPow10);
139
175
 
140
176
  // format
@@ -147,6 +183,17 @@ const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US"
147
183
  }).format(truncatedValue.toNumber());
148
184
  };
149
185
 
186
+ const formatPrice = (price, currency, compact) => {
187
+ return Intl.NumberFormat(undefined, {
188
+ style: "currency",
189
+ currency,
190
+ currencyDisplay: currency === "usd" ? "narrowSymbol" : "symbol",
191
+ maximumSignificantDigits: compact ? price < 1 ? 3 : 4 : undefined,
192
+ roundingPriority: compact ? "auto" : "morePrecision",
193
+ notation: compact && price >= 10_000 ? "compact" : "standard"
194
+ }).format(price);
195
+ };
196
+
150
197
  function hasOwnProperty(obj, prop) {
151
198
  if (typeof obj !== "object") return false;
152
199
  if (obj === null) return false;
@@ -158,46 +205,129 @@ function isArrayOf(array, func) {
158
205
  return false;
159
206
  }
160
207
 
161
- const isEthereumAddress = address => address.startsWith("0x") && address.length === 42;
208
+ const isBigInt = value => typeof value === "bigint";
209
+
210
+ const isBooleanTrue = x => !!x;
211
+
212
+ const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
213
+
214
+ /**
215
+ * Similar to isValidAddress but will not call isEthereumAddress under the hood
216
+ * @param address
217
+ * @param prefix if supplied, the method will also check the prefix
218
+ * @returns true if valid substrate (SS58) address, false otherwise
219
+ */
220
+ const isValidSubstrateAddress = (address, prefix) => {
221
+ try {
222
+ // attempt to encode, it will throw an error if address is invalid
223
+ const encoded = keyring.encodeAddress(address, prefix ?? undefined);
224
+
225
+ //if a prefix is supplied, check that reencoding using this prefix matches the input address
226
+ if (prefix !== undefined) return address === encoded;
227
+
228
+ //if no prefix supplied, the fact that decoding + encoding succeded indicates that the address is valid
229
+ return true;
230
+ } catch (error) {
231
+ // input is not a substrate (SS58) address
232
+ return false;
233
+ }
234
+ };
162
235
 
163
236
  function planckToTokens(planck, tokenDecimals) {
164
237
  if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
165
238
  const base = 10;
166
239
  const exponent = -1 * tokenDecimals;
167
240
  const multiplier = base ** exponent;
168
- return new BigNumber__default["default"](planck).multipliedBy(multiplier).toString(10);
241
+ return new BigNumber__default.default(planck).multipliedBy(multiplier).toString(10);
169
242
  }
170
243
 
171
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
244
+ const sleep = ms => new Promise(resolve => {
245
+ if (process.env.NODE_ENV === "test") resolve();else setTimeout(resolve, ms);
246
+ });
172
247
 
173
- const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(reason), ms));
248
+ const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
174
249
 
175
250
  function tokensToPlanck(tokens, tokenDecimals) {
176
251
  if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
177
252
  const base = 10;
178
253
  const exponent = tokenDecimals;
179
254
  const multiplier = base ** exponent;
180
- return new BigNumber__default["default"](tokens).multipliedBy(multiplier).toString(10);
255
+ return new BigNumber__default.default(tokens).multipliedBy(multiplier).toString(10);
181
256
  }
182
257
 
183
258
  const bitLength = 64;
184
259
  function twox64Concat(input) {
185
- return util.u8aToHex(util.u8aConcat(utilCrypto.xxhashAsU8a(input, bitLength), util.u8aToU8a(input)));
260
+ const inputAsU8a = typeof input === "string" ? input : new Uint8Array(input);
261
+ return util.u8aToHex(util.u8aConcat(utilCrypto.xxhashAsU8a(inputAsU8a, bitLength), util.u8aToU8a(inputAsU8a)));
186
262
  }
187
263
 
264
+ /**
265
+ * @name validateHexString
266
+ * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
267
+ * @param {string} str - string to check
268
+ * @returns {HexString} - boolean
269
+ * @example
270
+ * validateHexString("0x1234") // "0x1234"
271
+ * validateHexString("1234") // Error: Expected a hex string
272
+ * validateHexString(1234) // Error: Expected a string
273
+ **/
274
+ const validateHexString = str => {
275
+ if (typeof str !== "string") {
276
+ throw new Error("Expected a string");
277
+ }
278
+ if (str.startsWith("0x")) {
279
+ return str;
280
+ }
281
+ throw new Error("Expected a hex string");
282
+ };
283
+
284
+ const CACHE = new Map();
285
+
286
+ // Normalize an address in a way that it can be compared to other addresses that have also been normalized
287
+ const normalizeAddress = address => {
288
+ try {
289
+ if (!CACHE.has(address)) CACHE.set(address, encodeAnyAddress(address));
290
+ return CACHE.get(address);
291
+ } catch (cause) {
292
+ throw new Error(`Unable to normalize address: ${address}`, {
293
+ cause
294
+ });
295
+ }
296
+ };
297
+
298
+ const isAddressEqual = (address1, address2) => {
299
+ return normalizeAddress(address1) === normalizeAddress(address2);
300
+ };
301
+
302
+ const isAscii = str => {
303
+ return [...str].every(char => char.charCodeAt(0) <= 127);
304
+ };
305
+
188
306
  exports.BigMath = BigMath;
189
307
  exports.Deferred = Deferred;
190
308
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
309
+ exports.addTrailingSlash = addTrailingSlash;
191
310
  exports.blake2Concat = blake2Concat;
192
311
  exports.classNames = classNames;
312
+ exports.convertAddress = convertAddress;
193
313
  exports.decodeAnyAddress = decodeAnyAddress;
314
+ exports.decodeSs58Format = decodeSs58Format;
194
315
  exports.encodeAnyAddress = encodeAnyAddress;
316
+ exports.firstThenDebounce = firstThenDebounce;
195
317
  exports.formatDecimals = formatDecimals;
318
+ exports.formatPrice = formatPrice;
196
319
  exports.hasOwnProperty = hasOwnProperty;
320
+ exports.isAddressEqual = isAddressEqual;
197
321
  exports.isArrayOf = isArrayOf;
322
+ exports.isAscii = isAscii;
323
+ exports.isBigInt = isBigInt;
324
+ exports.isBooleanTrue = isBooleanTrue;
198
325
  exports.isEthereumAddress = isEthereumAddress;
326
+ exports.isValidSubstrateAddress = isValidSubstrateAddress;
327
+ exports.normalizeAddress = normalizeAddress;
199
328
  exports.planckToTokens = planckToTokens;
200
329
  exports.sleep = sleep;
201
330
  exports.throwAfter = throwAfter;
202
331
  exports.tokensToPlanck = tokensToPlanck;
203
332
  exports.twox64Concat = twox64Concat;
333
+ exports.validateHexString = validateHexString;
@@ -1,17 +1,23 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var util = require('@polkadot/util');
6
4
  var utilCrypto = require('@polkadot/util-crypto');
7
5
  var tailwindMerge = require('tailwind-merge');
8
6
  var keyring = require('@polkadot/keyring');
7
+ var rxjs = require('rxjs');
9
8
  var BigNumber = require('bignumber.js');
10
9
 
11
- function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
11
 
13
12
  var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
14
13
 
14
+ const addTrailingSlash = url => {
15
+ if (url.endsWith("/")) {
16
+ return url;
17
+ }
18
+ return `${url}/`;
19
+ };
20
+
15
21
  /**
16
22
  * Javascript's `Math` library for `BigInt`.
17
23
  * Taken from https://stackoverflow.com/questions/51867270/is-there-a-library-similar-to-math-that-supports-javascript-bigint/64953280#64953280
@@ -46,6 +52,26 @@ function blake2Concat(input) {
46
52
 
47
53
  const classNames = tailwindMerge.twMerge;
48
54
 
55
+ function encodeAnyAddress(key, ss58Format) {
56
+ try {
57
+ return keyring.encodeAddress(key, ss58Format);
58
+ } catch (error) {
59
+ if (typeof key !== "string") throw error;
60
+ if (!utilCrypto.isEthereumAddress(key)) throw error;
61
+ return utilCrypto.ethereumEncode(key);
62
+ }
63
+ }
64
+
65
+ /**
66
+ *
67
+ * @param address substrate SS58 address
68
+ * @param prefix prefix used to format the address
69
+ * @returns address encoded with supplied prefix
70
+ */
71
+ const convertAddress = (address, prefix) => {
72
+ return encodeAnyAddress(address, prefix ?? undefined);
73
+ };
74
+
49
75
  function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
50
76
  try {
51
77
  return keyring.decodeAddress(encoded, ignoreChecksum, ss58Format);
@@ -56,6 +82,18 @@ function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
56
82
  }
57
83
  }
58
84
 
85
+ const decodeSs58Format = address => {
86
+ if (!address) return;
87
+ try {
88
+ utilCrypto.decodeAddress(address);
89
+ const decoded = utilCrypto.base58Decode(address);
90
+ const [,,, ss58Format] = utilCrypto.checkAddressChecksum(decoded);
91
+ return ss58Format;
92
+ } catch {
93
+ return; // invalid address
94
+ }
95
+ };
96
+
59
97
  /**
60
98
  * In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
61
99
  * resolved or rejected at a later point in time, typically by code outside of the current function scope.
@@ -91,15 +129,13 @@ function Deferred() {
91
129
  };
92
130
  }
93
131
 
94
- function encodeAnyAddress(key, ss58Format) {
95
- try {
96
- return keyring.encodeAddress(key, ss58Format);
97
- } catch (error) {
98
- if (typeof key !== "string") throw error;
99
- if (!utilCrypto.isEthereumAddress(key)) throw error;
100
- return utilCrypto.ethereumEncode(key);
101
- }
102
- }
132
+ /**
133
+ * An rxjs operator which:
134
+ *
135
+ * 1. Emits the first value it receives from the source observable, then:
136
+ * 2. Debounces any future values by `timeout` ms.
137
+ */
138
+ const firstThenDebounce = timeout => source => rxjs.concat(source.pipe(rxjs.take(1)), source.pipe(rxjs.skip(1)).pipe(rxjs.debounceTime(timeout)));
103
139
 
104
140
  const MIN_DIGITS = 4; // less truncates more than what compact formating is
105
141
  const MAX_DECIMALS_FORMAT = 12;
@@ -116,7 +152,7 @@ const MAX_DECIMALS_FORMAT = 12;
116
152
  const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US") => {
117
153
  if (num === null || num === undefined) return "";
118
154
  if (digits < MIN_DIGITS) digits = MIN_DIGITS;
119
- const value = new BigNumber__default["default"](num);
155
+ const value = new BigNumber__default.default(num);
120
156
  // very small numbers should display "< 0.0001"
121
157
  const minDisplayVal = 1 / Math.pow(10, digits);
122
158
  if (value.gt(0) && value.lt(minDisplayVal)) return `< ${formatDecimals(minDisplayVal)}`;
@@ -129,12 +165,12 @@ const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US"
129
165
  // to prevent JS default rounding, we will remove/truncate insignificant digits ourselves before formatting
130
166
  let truncatedValue = value;
131
167
  //remove insignificant fraction digits
132
- const excessFractionDigitsPow10 = new BigNumber__default["default"](10).pow(digits > intDigits ? digits - intDigits : 0);
168
+ const excessFractionDigitsPow10 = new BigNumber__default.default(10).pow(digits > intDigits ? digits - intDigits : 0);
133
169
  truncatedValue = truncatedValue.multipliedBy(excessFractionDigitsPow10).integerValue().dividedBy(excessFractionDigitsPow10);
134
170
 
135
171
  //remove insignificant integer digits
136
- const excessIntegerDigits = new BigNumber__default["default"](intDigits > digits ? intDigits - digits : 0);
137
- const excessIntegerDigitsPow10 = new BigNumber__default["default"](10).pow(excessIntegerDigits);
172
+ const excessIntegerDigits = new BigNumber__default.default(intDigits > digits ? intDigits - digits : 0);
173
+ const excessIntegerDigitsPow10 = new BigNumber__default.default(10).pow(excessIntegerDigits);
138
174
  if (excessIntegerDigits.gt(0)) truncatedValue = truncatedValue.dividedBy(excessIntegerDigitsPow10).integerValue().multipliedBy(excessIntegerDigitsPow10);
139
175
 
140
176
  // format
@@ -147,6 +183,17 @@ const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US"
147
183
  }).format(truncatedValue.toNumber());
148
184
  };
149
185
 
186
+ const formatPrice = (price, currency, compact) => {
187
+ return Intl.NumberFormat(undefined, {
188
+ style: "currency",
189
+ currency,
190
+ currencyDisplay: currency === "usd" ? "narrowSymbol" : "symbol",
191
+ maximumSignificantDigits: compact ? price < 1 ? 3 : 4 : undefined,
192
+ roundingPriority: compact ? "auto" : "morePrecision",
193
+ notation: compact && price >= 10_000 ? "compact" : "standard"
194
+ }).format(price);
195
+ };
196
+
150
197
  function hasOwnProperty(obj, prop) {
151
198
  if (typeof obj !== "object") return false;
152
199
  if (obj === null) return false;
@@ -158,46 +205,129 @@ function isArrayOf(array, func) {
158
205
  return false;
159
206
  }
160
207
 
161
- const isEthereumAddress = address => address.startsWith("0x") && address.length === 42;
208
+ const isBigInt = value => typeof value === "bigint";
209
+
210
+ const isBooleanTrue = x => !!x;
211
+
212
+ const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
213
+
214
+ /**
215
+ * Similar to isValidAddress but will not call isEthereumAddress under the hood
216
+ * @param address
217
+ * @param prefix if supplied, the method will also check the prefix
218
+ * @returns true if valid substrate (SS58) address, false otherwise
219
+ */
220
+ const isValidSubstrateAddress = (address, prefix) => {
221
+ try {
222
+ // attempt to encode, it will throw an error if address is invalid
223
+ const encoded = keyring.encodeAddress(address, prefix ?? undefined);
224
+
225
+ //if a prefix is supplied, check that reencoding using this prefix matches the input address
226
+ if (prefix !== undefined) return address === encoded;
227
+
228
+ //if no prefix supplied, the fact that decoding + encoding succeded indicates that the address is valid
229
+ return true;
230
+ } catch (error) {
231
+ // input is not a substrate (SS58) address
232
+ return false;
233
+ }
234
+ };
162
235
 
163
236
  function planckToTokens(planck, tokenDecimals) {
164
237
  if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
165
238
  const base = 10;
166
239
  const exponent = -1 * tokenDecimals;
167
240
  const multiplier = base ** exponent;
168
- return new BigNumber__default["default"](planck).multipliedBy(multiplier).toString(10);
241
+ return new BigNumber__default.default(planck).multipliedBy(multiplier).toString(10);
169
242
  }
170
243
 
171
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
244
+ const sleep = ms => new Promise(resolve => {
245
+ setTimeout(resolve, ms);
246
+ });
172
247
 
173
- const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(reason), ms));
248
+ const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
174
249
 
175
250
  function tokensToPlanck(tokens, tokenDecimals) {
176
251
  if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
177
252
  const base = 10;
178
253
  const exponent = tokenDecimals;
179
254
  const multiplier = base ** exponent;
180
- return new BigNumber__default["default"](tokens).multipliedBy(multiplier).toString(10);
255
+ return new BigNumber__default.default(tokens).multipliedBy(multiplier).toString(10);
181
256
  }
182
257
 
183
258
  const bitLength = 64;
184
259
  function twox64Concat(input) {
185
- return util.u8aToHex(util.u8aConcat(utilCrypto.xxhashAsU8a(input, bitLength), util.u8aToU8a(input)));
260
+ const inputAsU8a = typeof input === "string" ? input : new Uint8Array(input);
261
+ return util.u8aToHex(util.u8aConcat(utilCrypto.xxhashAsU8a(inputAsU8a, bitLength), util.u8aToU8a(inputAsU8a)));
186
262
  }
187
263
 
264
+ /**
265
+ * @name validateHexString
266
+ * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
267
+ * @param {string} str - string to check
268
+ * @returns {HexString} - boolean
269
+ * @example
270
+ * validateHexString("0x1234") // "0x1234"
271
+ * validateHexString("1234") // Error: Expected a hex string
272
+ * validateHexString(1234) // Error: Expected a string
273
+ **/
274
+ const validateHexString = str => {
275
+ if (typeof str !== "string") {
276
+ throw new Error("Expected a string");
277
+ }
278
+ if (str.startsWith("0x")) {
279
+ return str;
280
+ }
281
+ throw new Error("Expected a hex string");
282
+ };
283
+
284
+ const CACHE = new Map();
285
+
286
+ // Normalize an address in a way that it can be compared to other addresses that have also been normalized
287
+ const normalizeAddress = address => {
288
+ try {
289
+ if (!CACHE.has(address)) CACHE.set(address, encodeAnyAddress(address));
290
+ return CACHE.get(address);
291
+ } catch (cause) {
292
+ throw new Error(`Unable to normalize address: ${address}`, {
293
+ cause
294
+ });
295
+ }
296
+ };
297
+
298
+ const isAddressEqual = (address1, address2) => {
299
+ return normalizeAddress(address1) === normalizeAddress(address2);
300
+ };
301
+
302
+ const isAscii = str => {
303
+ return [...str].every(char => char.charCodeAt(0) <= 127);
304
+ };
305
+
188
306
  exports.BigMath = BigMath;
189
307
  exports.Deferred = Deferred;
190
308
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
309
+ exports.addTrailingSlash = addTrailingSlash;
191
310
  exports.blake2Concat = blake2Concat;
192
311
  exports.classNames = classNames;
312
+ exports.convertAddress = convertAddress;
193
313
  exports.decodeAnyAddress = decodeAnyAddress;
314
+ exports.decodeSs58Format = decodeSs58Format;
194
315
  exports.encodeAnyAddress = encodeAnyAddress;
316
+ exports.firstThenDebounce = firstThenDebounce;
195
317
  exports.formatDecimals = formatDecimals;
318
+ exports.formatPrice = formatPrice;
196
319
  exports.hasOwnProperty = hasOwnProperty;
320
+ exports.isAddressEqual = isAddressEqual;
197
321
  exports.isArrayOf = isArrayOf;
322
+ exports.isAscii = isAscii;
323
+ exports.isBigInt = isBigInt;
324
+ exports.isBooleanTrue = isBooleanTrue;
198
325
  exports.isEthereumAddress = isEthereumAddress;
326
+ exports.isValidSubstrateAddress = isValidSubstrateAddress;
327
+ exports.normalizeAddress = normalizeAddress;
199
328
  exports.planckToTokens = planckToTokens;
200
329
  exports.sleep = sleep;
201
330
  exports.throwAfter = throwAfter;
202
331
  exports.tokensToPlanck = tokensToPlanck;
203
332
  exports.twox64Concat = twox64Concat;
333
+ exports.validateHexString = validateHexString;
@@ -1,9 +1,17 @@
1
1
  import { u8aToHex, u8aConcat, u8aToU8a, hexToU8a } from '@polkadot/util';
2
- import { blake2AsU8a, isEthereumAddress as isEthereumAddress$1, ethereumEncode, xxhashAsU8a } from '@polkadot/util-crypto';
2
+ import { blake2AsU8a, isEthereumAddress as isEthereumAddress$1, ethereumEncode, decodeAddress as decodeAddress$1, base58Decode, checkAddressChecksum, xxhashAsU8a } from '@polkadot/util-crypto';
3
3
  import { twMerge } from 'tailwind-merge';
4
- import { decodeAddress, encodeAddress } from '@polkadot/keyring';
4
+ import { encodeAddress, decodeAddress } from '@polkadot/keyring';
5
+ import { concat, take, skip, debounceTime } from 'rxjs';
5
6
  import BigNumber from 'bignumber.js';
6
7
 
8
+ const addTrailingSlash = url => {
9
+ if (url.endsWith("/")) {
10
+ return url;
11
+ }
12
+ return `${url}/`;
13
+ };
14
+
7
15
  /**
8
16
  * Javascript's `Math` library for `BigInt`.
9
17
  * Taken from https://stackoverflow.com/questions/51867270/is-there-a-library-similar-to-math-that-supports-javascript-bigint/64953280#64953280
@@ -38,6 +46,26 @@ function blake2Concat(input) {
38
46
 
39
47
  const classNames = twMerge;
40
48
 
49
+ function encodeAnyAddress(key, ss58Format) {
50
+ try {
51
+ return encodeAddress(key, ss58Format);
52
+ } catch (error) {
53
+ if (typeof key !== "string") throw error;
54
+ if (!isEthereumAddress$1(key)) throw error;
55
+ return ethereumEncode(key);
56
+ }
57
+ }
58
+
59
+ /**
60
+ *
61
+ * @param address substrate SS58 address
62
+ * @param prefix prefix used to format the address
63
+ * @returns address encoded with supplied prefix
64
+ */
65
+ const convertAddress = (address, prefix) => {
66
+ return encodeAnyAddress(address, prefix ?? undefined);
67
+ };
68
+
41
69
  function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
42
70
  try {
43
71
  return decodeAddress(encoded, ignoreChecksum, ss58Format);
@@ -48,6 +76,18 @@ function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
48
76
  }
49
77
  }
50
78
 
79
+ const decodeSs58Format = address => {
80
+ if (!address) return;
81
+ try {
82
+ decodeAddress$1(address);
83
+ const decoded = base58Decode(address);
84
+ const [,,, ss58Format] = checkAddressChecksum(decoded);
85
+ return ss58Format;
86
+ } catch {
87
+ return; // invalid address
88
+ }
89
+ };
90
+
51
91
  /**
52
92
  * In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
53
93
  * resolved or rejected at a later point in time, typically by code outside of the current function scope.
@@ -83,15 +123,13 @@ function Deferred() {
83
123
  };
84
124
  }
85
125
 
86
- function encodeAnyAddress(key, ss58Format) {
87
- try {
88
- return encodeAddress(key, ss58Format);
89
- } catch (error) {
90
- if (typeof key !== "string") throw error;
91
- if (!isEthereumAddress$1(key)) throw error;
92
- return ethereumEncode(key);
93
- }
94
- }
126
+ /**
127
+ * An rxjs operator which:
128
+ *
129
+ * 1. Emits the first value it receives from the source observable, then:
130
+ * 2. Debounces any future values by `timeout` ms.
131
+ */
132
+ const firstThenDebounce = timeout => source => concat(source.pipe(take(1)), source.pipe(skip(1)).pipe(debounceTime(timeout)));
95
133
 
96
134
  const MIN_DIGITS = 4; // less truncates more than what compact formating is
97
135
  const MAX_DECIMALS_FORMAT = 12;
@@ -139,6 +177,17 @@ const formatDecimals = (num, digits = MIN_DIGITS, options = {}, locale = "en-US"
139
177
  }).format(truncatedValue.toNumber());
140
178
  };
141
179
 
180
+ const formatPrice = (price, currency, compact) => {
181
+ return Intl.NumberFormat(undefined, {
182
+ style: "currency",
183
+ currency,
184
+ currencyDisplay: currency === "usd" ? "narrowSymbol" : "symbol",
185
+ maximumSignificantDigits: compact ? price < 1 ? 3 : 4 : undefined,
186
+ roundingPriority: compact ? "auto" : "morePrecision",
187
+ notation: compact && price >= 10_000 ? "compact" : "standard"
188
+ }).format(price);
189
+ };
190
+
142
191
  function hasOwnProperty(obj, prop) {
143
192
  if (typeof obj !== "object") return false;
144
193
  if (obj === null) return false;
@@ -150,7 +199,33 @@ function isArrayOf(array, func) {
150
199
  return false;
151
200
  }
152
201
 
153
- const isEthereumAddress = address => address.startsWith("0x") && address.length === 42;
202
+ const isBigInt = value => typeof value === "bigint";
203
+
204
+ const isBooleanTrue = x => !!x;
205
+
206
+ const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
207
+
208
+ /**
209
+ * Similar to isValidAddress but will not call isEthereumAddress under the hood
210
+ * @param address
211
+ * @param prefix if supplied, the method will also check the prefix
212
+ * @returns true if valid substrate (SS58) address, false otherwise
213
+ */
214
+ const isValidSubstrateAddress = (address, prefix) => {
215
+ try {
216
+ // attempt to encode, it will throw an error if address is invalid
217
+ const encoded = encodeAddress(address, prefix ?? undefined);
218
+
219
+ //if a prefix is supplied, check that reencoding using this prefix matches the input address
220
+ if (prefix !== undefined) return address === encoded;
221
+
222
+ //if no prefix supplied, the fact that decoding + encoding succeded indicates that the address is valid
223
+ return true;
224
+ } catch (error) {
225
+ // input is not a substrate (SS58) address
226
+ return false;
227
+ }
228
+ };
154
229
 
155
230
  function planckToTokens(planck, tokenDecimals) {
156
231
  if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
@@ -160,9 +235,11 @@ function planckToTokens(planck, tokenDecimals) {
160
235
  return new BigNumber(planck).multipliedBy(multiplier).toString(10);
161
236
  }
162
237
 
163
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
238
+ const sleep = ms => new Promise(resolve => {
239
+ if (process.env.NODE_ENV === "test") resolve();else setTimeout(resolve, ms);
240
+ });
164
241
 
165
- const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(reason), ms));
242
+ const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
166
243
 
167
244
  function tokensToPlanck(tokens, tokenDecimals) {
168
245
  if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
@@ -174,7 +251,50 @@ function tokensToPlanck(tokens, tokenDecimals) {
174
251
 
175
252
  const bitLength = 64;
176
253
  function twox64Concat(input) {
177
- return u8aToHex(u8aConcat(xxhashAsU8a(input, bitLength), u8aToU8a(input)));
254
+ const inputAsU8a = typeof input === "string" ? input : new Uint8Array(input);
255
+ return u8aToHex(u8aConcat(xxhashAsU8a(inputAsU8a, bitLength), u8aToU8a(inputAsU8a)));
178
256
  }
179
257
 
180
- export { BigMath, Deferred, MAX_DECIMALS_FORMAT, blake2Concat, classNames, decodeAnyAddress, encodeAnyAddress, formatDecimals, hasOwnProperty, isArrayOf, isEthereumAddress, planckToTokens, sleep, throwAfter, tokensToPlanck, twox64Concat };
258
+ /**
259
+ * @name validateHexString
260
+ * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
261
+ * @param {string} str - string to check
262
+ * @returns {HexString} - boolean
263
+ * @example
264
+ * validateHexString("0x1234") // "0x1234"
265
+ * validateHexString("1234") // Error: Expected a hex string
266
+ * validateHexString(1234) // Error: Expected a string
267
+ **/
268
+ const validateHexString = str => {
269
+ if (typeof str !== "string") {
270
+ throw new Error("Expected a string");
271
+ }
272
+ if (str.startsWith("0x")) {
273
+ return str;
274
+ }
275
+ throw new Error("Expected a hex string");
276
+ };
277
+
278
+ const CACHE = new Map();
279
+
280
+ // Normalize an address in a way that it can be compared to other addresses that have also been normalized
281
+ const normalizeAddress = address => {
282
+ try {
283
+ if (!CACHE.has(address)) CACHE.set(address, encodeAnyAddress(address));
284
+ return CACHE.get(address);
285
+ } catch (cause) {
286
+ throw new Error(`Unable to normalize address: ${address}`, {
287
+ cause
288
+ });
289
+ }
290
+ };
291
+
292
+ const isAddressEqual = (address1, address2) => {
293
+ return normalizeAddress(address1) === normalizeAddress(address2);
294
+ };
295
+
296
+ const isAscii = str => {
297
+ return [...str].every(char => char.charCodeAt(0) <= 127);
298
+ };
299
+
300
+ export { BigMath, Deferred, MAX_DECIMALS_FORMAT, addTrailingSlash, blake2Concat, classNames, convertAddress, decodeAnyAddress, decodeSs58Format, encodeAnyAddress, firstThenDebounce, formatDecimals, formatPrice, hasOwnProperty, isAddressEqual, isArrayOf, isAscii, isBigInt, isBooleanTrue, isEthereumAddress, isValidSubstrateAddress, normalizeAddress, planckToTokens, sleep, throwAfter, tokensToPlanck, twox64Concat, validateHexString };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/util",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -20,36 +20,38 @@
20
20
  "engines": {
21
21
  "node": ">=18"
22
22
  },
23
- "scripts": {
24
- "test": "jest",
25
- "lint": "eslint src --max-warnings 0",
26
- "clean": "rm -rf dist && rm -rf .turbo rm -rf node_modules"
27
- },
28
23
  "dependencies": {
29
- "bignumber.js": "^9.1.1",
30
- "tailwind-merge": "^1.13.2"
24
+ "bignumber.js": "^9.1.2",
25
+ "rxjs": "^7.8.1",
26
+ "tailwind-merge": "^2.5.4"
31
27
  },
32
28
  "devDependencies": {
33
- "@polkadot/keyring": "^12.2.1",
34
- "@polkadot/util": "^12.2.1",
35
- "@polkadot/util-crypto": "^12.2.1",
36
- "@talismn/eslint-config": "0.0.2",
37
- "@talismn/tsconfig": "0.0.2",
38
- "@types/jest": "^27.5.1",
39
- "eslint": "^8.4.0",
40
- "jest": "^28.1.0",
41
- "ts-jest": "^28.0.2",
42
- "typescript": "^4.6.4"
29
+ "@polkadot/keyring": "13.4.3",
30
+ "@polkadot/util": "13.4.3",
31
+ "@polkadot/util-crypto": "13.4.3",
32
+ "@types/jest": "^29.5.14",
33
+ "eslint": "^8.57.1",
34
+ "jest": "^29.7.0",
35
+ "ts-jest": "^29.2.5",
36
+ "typescript": "^5.6.3",
37
+ "@talismn/eslint-config": "0.0.3",
38
+ "@talismn/tsconfig": "0.0.2"
43
39
  },
44
40
  "peerDependencies": {
45
- "@polkadot/keyring": "12.x",
46
- "@polkadot/util": "12.x",
47
- "@polkadot/util-crypto": "12.x"
41
+ "@polkadot/keyring": "*",
42
+ "@polkadot/util": "*",
43
+ "@polkadot/util-crypto": "*",
44
+ "rxjs": ">= 7.8.1"
48
45
  },
49
46
  "eslintConfig": {
50
47
  "root": true,
51
48
  "extends": [
52
49
  "@talismn/eslint-config/base"
53
50
  ]
51
+ },
52
+ "scripts": {
53
+ "test": "jest",
54
+ "lint": "eslint src --max-warnings 0",
55
+ "clean": "rm -rf dist .turbo node_modules"
54
56
  }
55
57
  }
package/CHANGELOG.md DELETED
@@ -1,76 +0,0 @@
1
- # @talismn/util
2
-
3
- ## 0.2.0
4
-
5
- ### Minor Changes
6
-
7
- - b920ab98: Added GPL licence
8
- - 7573864f: chore: remove dead code
9
-
10
- ### Patch Changes
11
-
12
- - 2d0ae30b: fix: non-conflict tw class merging for classNames via twMerge
13
-
14
- ## 0.1.9
15
-
16
- ### Patch Changes
17
-
18
- - f7aca48b: eslint rules
19
- - 01bf239b: feat: crowdloan and nom pool balances
20
- - 48f0222e: fix: removed some explicit `any`s
21
- - 01bf239b: fix: packages publishing with incorrect interdependency versions
22
-
23
- ## 0.1.8
24
-
25
- ### Patch Changes
26
-
27
- - 3068bd60: feat: stale balances and exponential rpc backoff
28
-
29
- ## 0.1.7
30
-
31
- ### Patch Changes
32
-
33
- - c651551c: build: move `@polkadot` dependencies to `peerDependencies`
34
-
35
- ## 0.1.6
36
-
37
- ### Patch Changes
38
-
39
- - 8adc7f06: feat: switched build tool to preconstruct
40
-
41
- ## 0.1.5
42
-
43
- ### Patch Changes
44
-
45
- - 4aa691d: feat: new balance modules
46
- - a63dbb3: feat: formatDecimal allow for BigNumber amount
47
-
48
- ## 0.1.4
49
-
50
- ### Patch Changes
51
-
52
- - fix: a variety of improvements from the wallet integration
53
-
54
- ## 0.1.3
55
-
56
- ### Patch Changes
57
-
58
- - 5af305c: switched build output from esm to commonjs for ecosystem compatibility
59
-
60
- ## 0.1.2
61
-
62
- ### Patch Changes
63
-
64
- - 2ccd51b: feat: implemented @talismn/balances-substrate-native
65
-
66
- ## 0.1.1
67
-
68
- ### Patch Changes
69
-
70
- - Fixed publish config
71
-
72
- ## 0.1.0
73
-
74
- ### Minor Changes
75
-
76
- - 43c1a3a: Initial release