@talismn/util 0.2.1 → 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,17 +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";
11
16
  export * from "./isBigInt";
17
+ export * from "./isBooleanTrue";
12
18
  export * from "./isEthereumAddress";
19
+ export * from "./isValidSubstrateAddress";
13
20
  export * from "./planckToTokens";
14
21
  export * from "./sleep";
15
22
  export * from "./throwAfter";
16
23
  export * from "./tokensToPlanck";
17
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 isBooleanTrue: <T>(x: T | null | undefined) => x is T;
@@ -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;
@@ -160,17 +207,43 @@ function isArrayOf(array, func) {
160
207
 
161
208
  const isBigInt = value => typeof value === "bigint";
162
209
 
210
+ const isBooleanTrue = x => !!x;
211
+
163
212
  const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
164
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
+ };
235
+
165
236
  function planckToTokens(planck, tokenDecimals) {
166
237
  if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
167
238
  const base = 10;
168
239
  const exponent = -1 * tokenDecimals;
169
240
  const multiplier = base ** exponent;
170
- return new BigNumber__default["default"](planck).multipliedBy(multiplier).toString(10);
241
+ return new BigNumber__default.default(planck).multipliedBy(multiplier).toString(10);
171
242
  }
172
243
 
173
- 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
+ });
174
247
 
175
248
  const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
176
249
 
@@ -179,28 +252,82 @@ function tokensToPlanck(tokens, tokenDecimals) {
179
252
  const base = 10;
180
253
  const exponent = tokenDecimals;
181
254
  const multiplier = base ** exponent;
182
- return new BigNumber__default["default"](tokens).multipliedBy(multiplier).toString(10);
255
+ return new BigNumber__default.default(tokens).multipliedBy(multiplier).toString(10);
183
256
  }
184
257
 
185
258
  const bitLength = 64;
186
259
  function twox64Concat(input) {
187
- 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)));
188
262
  }
189
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
+
190
306
  exports.BigMath = BigMath;
191
307
  exports.Deferred = Deferred;
192
308
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
309
+ exports.addTrailingSlash = addTrailingSlash;
193
310
  exports.blake2Concat = blake2Concat;
194
311
  exports.classNames = classNames;
312
+ exports.convertAddress = convertAddress;
195
313
  exports.decodeAnyAddress = decodeAnyAddress;
314
+ exports.decodeSs58Format = decodeSs58Format;
196
315
  exports.encodeAnyAddress = encodeAnyAddress;
316
+ exports.firstThenDebounce = firstThenDebounce;
197
317
  exports.formatDecimals = formatDecimals;
318
+ exports.formatPrice = formatPrice;
198
319
  exports.hasOwnProperty = hasOwnProperty;
320
+ exports.isAddressEqual = isAddressEqual;
199
321
  exports.isArrayOf = isArrayOf;
322
+ exports.isAscii = isAscii;
200
323
  exports.isBigInt = isBigInt;
324
+ exports.isBooleanTrue = isBooleanTrue;
201
325
  exports.isEthereumAddress = isEthereumAddress;
326
+ exports.isValidSubstrateAddress = isValidSubstrateAddress;
327
+ exports.normalizeAddress = normalizeAddress;
202
328
  exports.planckToTokens = planckToTokens;
203
329
  exports.sleep = sleep;
204
330
  exports.throwAfter = throwAfter;
205
331
  exports.tokensToPlanck = tokensToPlanck;
206
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;
@@ -160,17 +207,43 @@ function isArrayOf(array, func) {
160
207
 
161
208
  const isBigInt = value => typeof value === "bigint";
162
209
 
210
+ const isBooleanTrue = x => !!x;
211
+
163
212
  const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
164
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
+ };
235
+
165
236
  function planckToTokens(planck, tokenDecimals) {
166
237
  if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
167
238
  const base = 10;
168
239
  const exponent = -1 * tokenDecimals;
169
240
  const multiplier = base ** exponent;
170
- return new BigNumber__default["default"](planck).multipliedBy(multiplier).toString(10);
241
+ return new BigNumber__default.default(planck).multipliedBy(multiplier).toString(10);
171
242
  }
172
243
 
173
- const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
244
+ const sleep = ms => new Promise(resolve => {
245
+ setTimeout(resolve, ms);
246
+ });
174
247
 
175
248
  const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
176
249
 
@@ -179,28 +252,82 @@ function tokensToPlanck(tokens, tokenDecimals) {
179
252
  const base = 10;
180
253
  const exponent = tokenDecimals;
181
254
  const multiplier = base ** exponent;
182
- return new BigNumber__default["default"](tokens).multipliedBy(multiplier).toString(10);
255
+ return new BigNumber__default.default(tokens).multipliedBy(multiplier).toString(10);
183
256
  }
184
257
 
185
258
  const bitLength = 64;
186
259
  function twox64Concat(input) {
187
- 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)));
188
262
  }
189
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
+
190
306
  exports.BigMath = BigMath;
191
307
  exports.Deferred = Deferred;
192
308
  exports.MAX_DECIMALS_FORMAT = MAX_DECIMALS_FORMAT;
309
+ exports.addTrailingSlash = addTrailingSlash;
193
310
  exports.blake2Concat = blake2Concat;
194
311
  exports.classNames = classNames;
312
+ exports.convertAddress = convertAddress;
195
313
  exports.decodeAnyAddress = decodeAnyAddress;
314
+ exports.decodeSs58Format = decodeSs58Format;
196
315
  exports.encodeAnyAddress = encodeAnyAddress;
316
+ exports.firstThenDebounce = firstThenDebounce;
197
317
  exports.formatDecimals = formatDecimals;
318
+ exports.formatPrice = formatPrice;
198
319
  exports.hasOwnProperty = hasOwnProperty;
320
+ exports.isAddressEqual = isAddressEqual;
199
321
  exports.isArrayOf = isArrayOf;
322
+ exports.isAscii = isAscii;
200
323
  exports.isBigInt = isBigInt;
324
+ exports.isBooleanTrue = isBooleanTrue;
201
325
  exports.isEthereumAddress = isEthereumAddress;
326
+ exports.isValidSubstrateAddress = isValidSubstrateAddress;
327
+ exports.normalizeAddress = normalizeAddress;
202
328
  exports.planckToTokens = planckToTokens;
203
329
  exports.sleep = sleep;
204
330
  exports.throwAfter = throwAfter;
205
331
  exports.tokensToPlanck = tokensToPlanck;
206
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;
@@ -152,8 +201,32 @@ function isArrayOf(array, func) {
152
201
 
153
202
  const isBigInt = value => typeof value === "bigint";
154
203
 
204
+ const isBooleanTrue = x => !!x;
205
+
155
206
  const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
156
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
+ };
229
+
157
230
  function planckToTokens(planck, tokenDecimals) {
158
231
  if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
159
232
  const base = 10;
@@ -162,7 +235,9 @@ function planckToTokens(planck, tokenDecimals) {
162
235
  return new BigNumber(planck).multipliedBy(multiplier).toString(10);
163
236
  }
164
237
 
165
- 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
+ });
166
241
 
167
242
  const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
168
243
 
@@ -176,7 +251,50 @@ function tokensToPlanck(tokens, tokenDecimals) {
176
251
 
177
252
  const bitLength = 64;
178
253
  function twox64Concat(input) {
179
- 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)));
180
256
  }
181
257
 
182
- export { BigMath, Deferred, MAX_DECIMALS_FORMAT, blake2Concat, classNames, decodeAnyAddress, encodeAnyAddress, formatDecimals, hasOwnProperty, isArrayOf, isBigInt, 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.1",
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
24
  "bignumber.js": "^9.1.2",
30
- "tailwind-merge": "^1.14.0"
25
+ "rxjs": "^7.8.1",
26
+ "tailwind-merge": "^2.5.4"
31
27
  },
32
28
  "devDependencies": {
33
- "@polkadot/keyring": "^12.3.2",
34
- "@polkadot/util": "^12.3.2",
35
- "@polkadot/util-crypto": "^12.3.2",
36
- "@talismn/eslint-config": "0.0.3",
37
- "@talismn/tsconfig": "0.0.2",
38
- "@types/jest": "^27.5.1",
39
- "eslint": "^8.52.0",
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",
40
34
  "jest": "^29.7.0",
41
- "ts-jest": "^29.1.1",
42
- "typescript": "^5.2.2"
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,85 +0,0 @@
1
- # @talismn/util
2
-
3
- ## 0.2.1
4
-
5
- ### Patch Changes
6
-
7
- - 372f995: replace ethers by viem
8
- - c4d5967: bump typescript version
9
- - e0eb84a: Error handling improvements
10
- - 620b7eb: Dependency updates
11
-
12
- ## 0.2.0
13
-
14
- ### Minor Changes
15
-
16
- - b920ab98: Added GPL licence
17
- - 7573864f: chore: remove dead code
18
-
19
- ### Patch Changes
20
-
21
- - 2d0ae30b: fix: non-conflict tw class merging for classNames via twMerge
22
-
23
- ## 0.1.9
24
-
25
- ### Patch Changes
26
-
27
- - f7aca48b: eslint rules
28
- - 01bf239b: feat: crowdloan and nom pool balances
29
- - 48f0222e: fix: removed some explicit `any`s
30
- - 01bf239b: fix: packages publishing with incorrect interdependency versions
31
-
32
- ## 0.1.8
33
-
34
- ### Patch Changes
35
-
36
- - 3068bd60: feat: stale balances and exponential rpc backoff
37
-
38
- ## 0.1.7
39
-
40
- ### Patch Changes
41
-
42
- - c651551c: build: move `@polkadot` dependencies to `peerDependencies`
43
-
44
- ## 0.1.6
45
-
46
- ### Patch Changes
47
-
48
- - 8adc7f06: feat: switched build tool to preconstruct
49
-
50
- ## 0.1.5
51
-
52
- ### Patch Changes
53
-
54
- - 4aa691d: feat: new balance modules
55
- - a63dbb3: feat: formatDecimal allow for BigNumber amount
56
-
57
- ## 0.1.4
58
-
59
- ### Patch Changes
60
-
61
- - fix: a variety of improvements from the wallet integration
62
-
63
- ## 0.1.3
64
-
65
- ### Patch Changes
66
-
67
- - 5af305c: switched build output from esm to commonjs for ecosystem compatibility
68
-
69
- ## 0.1.2
70
-
71
- ### Patch Changes
72
-
73
- - 2ccd51b: feat: implemented @talismn/balances-substrate-native
74
-
75
- ## 0.1.1
76
-
77
- ### Patch Changes
78
-
79
- - Fixed publish config
80
-
81
- ## 0.1.0
82
-
83
- ### Minor Changes
84
-
85
- - 43c1a3a: Initial release