@talismn/util 0.5.0 → 0.5.1

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.
@@ -1,8 +1,5 @@
1
- import { u8aToHex, u8aConcat, u8aToU8a, hexToU8a } from '@polkadot/util';
2
- import { blake2AsU8a, isEthereumAddress as isEthereumAddress$1, ethereumEncode, decodeAddress as decodeAddress$1, base58Decode, checkAddressChecksum, xxhashAsU8a } from '@polkadot/util-crypto';
3
1
  import { twMerge } from 'tailwind-merge';
4
- import { encodeAddress, decodeAddress } from '@polkadot/keyring';
5
- import { concat, take, skip, debounceTime, shareReplay, tap } from 'rxjs';
2
+ import { concat, take, skip, debounceTime, shareReplay, Subject, tap, ReplaySubject, timer, switchMap, from, startWith, map, catchError, of } from 'rxjs';
6
3
  import BigNumber from 'bignumber.js';
7
4
 
8
5
  const addTrailingSlash = url => {
@@ -39,54 +36,8 @@ const BigMath = {
39
36
  }
40
37
  };
41
38
 
42
- const bitLength$1 = 128;
43
- function blake2Concat(input) {
44
- return u8aToHex(u8aConcat(blake2AsU8a(input, bitLength$1), u8aToU8a(input)));
45
- }
46
-
47
39
  const classNames = twMerge;
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
-
69
- function decodeAnyAddress(encoded, ignoreChecksum, ss58Format) {
70
- try {
71
- return decodeAddress(encoded, ignoreChecksum, ss58Format);
72
- } catch (error) {
73
- if (typeof encoded !== "string") throw error;
74
- if (!isEthereumAddress$1(encoded)) throw error;
75
- return hexToU8a(encoded.slice("0x".length));
76
- }
77
- }
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
- };
40
+ const cn = twMerge;
90
41
 
91
42
  /**
92
43
  * In TypeScript, a deferred promise refers to a pattern that involves creating a promise that can be
@@ -189,113 +140,57 @@ const formatPrice = (price, currency, compact) => {
189
140
  }).format(price);
190
141
  };
191
142
 
143
+ const CACHE = new Map();
144
+
145
+ /**
146
+ * When using react-rxjs hooks and state observables, the options are used as weak map keys.
147
+ * This means that if the options object is recreated on each render, the observable will be recreated as well.
148
+ * This utility function allows you to create a shared observable based on a namespace and arguments that, so react-rxjs can reuse the same observables
149
+ *
150
+ * @param namespace
151
+ * @param args
152
+ * @param createObservable
153
+ * @param serializer
154
+ * @returns
155
+ */
156
+ const getSharedObservable = (namespace, args, createObservable, serializer = args => JSON.stringify(args)) => {
157
+ const cacheKey = `${namespace}:${serializer(args)}`;
158
+ if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
159
+ const obs = createObservable(args);
160
+ const sharedObs = obs.pipe(shareReplay({
161
+ bufferSize: 1,
162
+ refCount: true
163
+ }));
164
+ CACHE.set(cacheKey, sharedObs);
165
+ return sharedObs;
166
+ };
167
+
192
168
  function hasOwnProperty(obj, prop) {
193
169
  if (typeof obj !== "object") return false;
194
170
  if (obj === null) return false;
195
171
  return prop in obj;
196
172
  }
197
173
 
174
+ const isAbortError = error => {
175
+ return error instanceof Error && error.name === "AbortError";
176
+ };
177
+
198
178
  function isArrayOf(array, func) {
199
179
  if (array.length > 0 && array[0] instanceof func) return true;
200
180
  return false;
201
181
  }
202
182
 
203
- const isBigInt = value => typeof value === "bigint";
204
-
205
- const isBooleanTrue = x => !!x;
206
-
207
- const isEthereumAddress = address => !!address && address.startsWith("0x") && address.length === 42;
208
-
209
- /**
210
- * Similar to isValidAddress but will not call isEthereumAddress under the hood
211
- * @param address
212
- * @param prefix if supplied, the method will also check the prefix
213
- * @returns true if valid substrate (SS58) address, false otherwise
214
- */
215
- const isValidSubstrateAddress = (address, prefix) => {
216
- try {
217
- // attempt to encode, it will throw an error if address is invalid
218
- const encoded = encodeAddress(address, prefix ?? undefined);
219
-
220
- //if a prefix is supplied, check that reencoding using this prefix matches the input address
221
- if (prefix !== undefined) return address === encoded;
222
-
223
- //if no prefix supplied, the fact that decoding + encoding succeded indicates that the address is valid
224
- return true;
225
- } catch (error) {
226
- // input is not a substrate (SS58) address
227
- return false;
228
- }
229
- };
230
-
231
- function planckToTokens(planck, tokenDecimals) {
232
- if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
233
- const base = 10;
234
- const exponent = -1 * tokenDecimals;
235
- const multiplier = base ** exponent;
236
- return new BigNumber(planck).multipliedBy(multiplier).toString(10);
237
- }
238
-
239
- const sleep = ms => new Promise(resolve => {
240
- if (process.env.NODE_ENV === "test") resolve();else setTimeout(resolve, ms);
241
- });
242
-
243
- const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
244
-
245
- function tokensToPlanck(tokens, tokenDecimals) {
246
- if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
247
- const base = 10;
248
- const exponent = tokenDecimals;
249
- const multiplier = base ** exponent;
250
- return new BigNumber(tokens).multipliedBy(multiplier).toString(10);
251
- }
252
-
253
- const bitLength = 64;
254
- function twox64Concat(input) {
255
- const inputAsU8a = typeof input === "string" ? input : new Uint8Array(input);
256
- return u8aToHex(u8aConcat(xxhashAsU8a(inputAsU8a, bitLength), u8aToU8a(inputAsU8a)));
257
- }
258
-
259
- /**
260
- * @name validateHexString
261
- * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
262
- * @param {string} str - string to check
263
- * @returns {HexString} - boolean
264
- * @example
265
- * validateHexString("0x1234") // "0x1234"
266
- * validateHexString("1234") // Error: Expected a hex string
267
- * validateHexString(1234) // Error: Expected a string
268
- **/
269
- const validateHexString = str => {
270
- if (typeof str !== "string") {
271
- throw new Error("Expected a string");
272
- }
273
- if (str.startsWith("0x")) {
274
- return str;
275
- }
276
- throw new Error("Expected a hex string");
183
+ const isAscii = str => {
184
+ return [...str].every(char => char.charCodeAt(0) <= 127);
277
185
  };
278
186
 
279
- const CACHE$1 = new Map();
280
-
281
- // Normalize an address in a way that it can be compared to other addresses that have also been normalized
282
- const normalizeAddress = address => {
283
- try {
284
- if (!CACHE$1.has(address)) CACHE$1.set(address, encodeAnyAddress(address));
285
- return CACHE$1.get(address);
286
- } catch (cause) {
287
- throw new Error(`Unable to normalize address: ${address}`, {
288
- cause
289
- });
290
- }
291
- };
187
+ const isBigInt = value => typeof value === "bigint";
292
188
 
293
- const isAddressEqual = (address1, address2) => {
294
- return normalizeAddress(address1) === normalizeAddress(address2);
295
- };
189
+ const isBooleanTrue = x => !!x;
296
190
 
297
- const isAscii = str => {
298
- return [...str].every(char => char.charCodeAt(0) <= 127);
191
+ const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
192
+ const isHexString = value => {
193
+ return typeof value === "string" && REGEX_HEX_STRING.test(value);
299
194
  };
300
195
 
301
196
  /**
@@ -308,36 +203,33 @@ const isAscii = str => {
308
203
  */
309
204
  const isNotNil = value => value !== null && value !== undefined;
310
205
 
311
- const isTruthy = value => Boolean(value);
206
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
207
+ const isPromise = value => !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
312
208
 
313
- const isAbortError = error => {
314
- return error instanceof Error && error.name === "AbortError";
315
- };
209
+ /**
210
+ * Tests to see if an object is an RxJS {@link Subject}.
211
+ */
212
+ function isSubject(object) {
213
+ if (!object) return false;
214
+ if (object instanceof Subject) return true;
215
+ return "asObservable" in object && isFn(object.asObservable) && "complete" in object && isFn(object.complete) && "error" in object && isFn(object.error) && "forEach" in object && isFn(object.forEach) && "next" in object && isFn(object.next) && "pipe" in object && isFn(object.pipe) && "subscribe" in object && isFn(object.subscribe) && "unsubscribe" in object && isFn(object.unsubscribe) && "closed" in object && isBool(object.closed) && "observed" in object && isBool(object.observed);
216
+ }
316
217
 
317
- const CACHE = new Map();
218
+ /**
219
+ * Returns `true` if `value` is a function.
220
+ */
221
+ function isFn(value) {
222
+ return typeof value === "function";
223
+ }
318
224
 
319
225
  /**
320
- * When using react-rxjs hooks and state observables, the options are used as weak map keys.
321
- * This means that if the options object is recreated on each render, the observable will be recreated as well.
322
- * This utility function allows you to create a shared observable based on a namespace and arguments that, so react-rxjs can reuse the same observables
323
- *
324
- * @param namespace
325
- * @param args
326
- * @param createObservable
327
- * @param serializer
328
- * @returns
226
+ * Returns `true` if `value` is a boolean.
329
227
  */
330
- const getSharedObservable = (namespace, args, createObservable, serializer = args => JSON.stringify(args)) => {
331
- const cacheKey = `${namespace}:${serializer(args)}`;
332
- if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
333
- const obs = createObservable(args);
334
- const sharedObs = obs.pipe(shareReplay({
335
- bufferSize: 1,
336
- refCount: true
337
- }));
338
- CACHE.set(cacheKey, sharedObs);
339
- return sharedObs;
340
- };
228
+ function isBool(value) {
229
+ return typeof value === "boolean";
230
+ }
231
+
232
+ const isTruthy = value => Boolean(value);
341
233
 
342
234
  /**
343
235
  * An RxJS operator that keeps the source observable alive for a specified duration
@@ -373,9 +265,120 @@ const keepSourceSubscribed = (observable, ms) => {
373
265
  return () => setTimeout(() => sub.unsubscribe(), ms);
374
266
  };
375
267
 
376
- const REGEX_HEX_STRING = /^0x[0-9a-fA-F]*$/;
377
- const isHexString = value => {
378
- return typeof value === "string" && REGEX_HEX_STRING.test(value);
268
+ function planckToTokens(planck, tokenDecimals) {
269
+ if (typeof planck !== "string" || typeof tokenDecimals !== "number") return;
270
+ const base = 10;
271
+ const exponent = -1 * tokenDecimals;
272
+ const multiplier = base ** exponent;
273
+ return new BigNumber(planck).multipliedBy(multiplier).toString(10);
274
+ }
275
+
276
+ /**
277
+ * Turns a value into a {@link ReplaySubject} of size 1.
278
+ *
279
+ * If the value is already a {@link ReplaySubject}, it will be returned as-is.
280
+ *
281
+ * If the value is a {@link Promise}, it will be awaited,
282
+ * and the awaited value will be published into the {@link ReplaySubject} when it becomes available.
283
+ *
284
+ * For any other type of value, it will be immediately published into the {@link ReplaySubject}.
285
+ */
286
+ const replaySubjectFrom = initialValue => {
287
+ if (initialValue instanceof ReplaySubject) return initialValue;
288
+ const subject = new ReplaySubject(1);
289
+
290
+ // if initialValue is a promise, await it and then call `subject.next()` with the awaited value
291
+ if (isPromise(initialValue)) {
292
+ initialValue.then(value => subject.next(value), error => subject.error(error));
293
+ return subject;
294
+ }
295
+
296
+ // if initialValue is not a promise, immediately call `subject.next()` with the value
297
+ subject.next(initialValue);
298
+ return subject;
299
+ };
300
+
301
+ const sleep = ms => new Promise(resolve => {
302
+ if (process.env.NODE_ENV === "test") resolve();else setTimeout(resolve, ms);
303
+ });
304
+
305
+ /**
306
+ * Takes a subject and splits it into two parts:
307
+ *
308
+ * 1. A function to submit new values into the subject.
309
+ * 2. An observable for subscribing to new values from the subject.
310
+ *
311
+ * This can be helpful when, to avoid bugs, you want to expose only one
312
+ * of these parts to external code and keep the other part private.
313
+ */
314
+ function splitSubject(subject) {
315
+ const next = value => subject.next(value);
316
+ const observable = subject.asObservable();
317
+ return [next, observable];
318
+ }
319
+
320
+ const throwAfter = (ms, reason) => new Promise((_, reject) => setTimeout(() => reject(new Error(reason)), ms));
321
+
322
+ function tokensToPlanck(tokens, tokenDecimals) {
323
+ if (typeof tokens !== "string" || typeof tokenDecimals !== "number") return;
324
+ const base = 10;
325
+ const exponent = tokenDecimals;
326
+ const multiplier = base ** exponent;
327
+ return new BigNumber(tokens).multipliedBy(multiplier).toString(10);
328
+ }
329
+
330
+ /**
331
+ * @name validateHexString
332
+ * @description Checks if a string is a hex string. Required to account for type differences between different polkadot libraries
333
+ * @param {string} str - string to check
334
+ * @returns {`0x${string}`} - boolean
335
+ * @example
336
+ * validateHexString("0x1234") // "0x1234"
337
+ * validateHexString("1234") // Error: Expected a hex string
338
+ * validateHexString(1234) // Error: Expected a string
339
+ **/
340
+ const validateHexString = str => {
341
+ if (typeof str !== "string") {
342
+ throw new Error("Expected a string");
343
+ }
344
+ if (str.startsWith("0x")) {
345
+ return str;
346
+ }
347
+ throw new Error("Expected a hex string");
348
+ };
349
+
350
+ // Designed to be serializable as it can be sent to the frontend
351
+
352
+ function getLoadable$(factory, options = {}) {
353
+ const {
354
+ getError,
355
+ refreshInterval
356
+ } = options;
357
+ const createLoadableStream = () => from(factory()).pipe(map(data => ({
358
+ status: "success",
359
+ data
360
+ })), catchError(error => of({
361
+ status: "error",
362
+ error: getError ? getError(error) : getGenericError(error)
363
+ })));
364
+ const source$ = refreshInterval ? timer(0, refreshInterval).pipe(switchMap(() => createLoadableStream())) : createLoadableStream();
365
+ return source$.pipe(startWith({
366
+ status: "loading"
367
+ }));
368
+ }
369
+ const getGenericError = error => ({
370
+ name: "Error",
371
+ message: getGenericErrorMessage(error)
372
+ });
373
+ const getGenericErrorMessage = error => {
374
+ if (typeof error === "string") {
375
+ return error;
376
+ } else if (error instanceof Error) {
377
+ return error.message;
378
+ } else if (error && typeof error === "object" && "message" in error) {
379
+ return error.message;
380
+ }
381
+ return String(error) || "Unknown error";
379
382
  };
380
383
 
381
- export { BigMath, Deferred, MAX_DECIMALS_FORMAT, REGEX_HEX_STRING, addTrailingSlash, blake2Concat, classNames, convertAddress, decodeAnyAddress, decodeSs58Format, encodeAnyAddress, firstThenDebounce, formatDecimals, formatPrice, getSharedObservable, hasOwnProperty, isAbortError, isAddressEqual, isArrayOf, isAscii, isBigInt, isBooleanTrue, isEthereumAddress, isHexString, isNotNil, isTruthy, isValidSubstrateAddress, keepAlive, normalizeAddress, planckToTokens, sleep, throwAfter, tokensToPlanck, twox64Concat, validateHexString };
384
+ export { BigMath, Deferred, MAX_DECIMALS_FORMAT, REGEX_HEX_STRING, addTrailingSlash, classNames, cn, firstThenDebounce, formatDecimals, formatPrice, getLoadable$, getSharedObservable, hasOwnProperty, isAbortError, isArrayOf, isAscii, isBigInt, isBooleanTrue, isHexString, isNotNil, isPromise, isSubject, isTruthy, keepAlive, planckToTokens, replaySubjectFrom, sleep, splitSubject, throwAfter, tokensToPlanck, validateHexString };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/util",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -26,9 +26,6 @@
26
26
  "tailwind-merge": "^2.5.4"
27
27
  },
28
28
  "devDependencies": {
29
- "@polkadot/keyring": "13.5.1",
30
- "@polkadot/util": "13.5.1",
31
- "@polkadot/util-crypto": "13.5.1",
32
29
  "@types/jest": "^29.5.14",
33
30
  "eslint": "^8.57.1",
34
31
  "jest": "^29.7.0",
@@ -38,9 +35,6 @@
38
35
  "@talismn/eslint-config": "0.0.3"
39
36
  },
40
37
  "peerDependencies": {
41
- "@polkadot/keyring": "*",
42
- "@polkadot/util": "*",
43
- "@polkadot/util-crypto": "*",
44
38
  "rxjs": ">= 7.8.1"
45
39
  },
46
40
  "eslintConfig": {
@@ -1 +0,0 @@
1
- export declare function blake2Concat(input: Uint8Array): `0x${string}`;
@@ -1,7 +0,0 @@
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;
@@ -1 +0,0 @@
1
- export declare function decodeAnyAddress(encoded?: string | Uint8Array | null | undefined, ignoreChecksum?: boolean | undefined, ss58Format?: number | undefined): Uint8Array;
@@ -1 +0,0 @@
1
- export declare const decodeSs58Format: (address?: string) => number | undefined;
@@ -1 +0,0 @@
1
- export declare function encodeAnyAddress(key: string | Uint8Array, ss58Format?: number | undefined): string;
@@ -1 +0,0 @@
1
- export declare const isAddressEqual: (address1: string, address2: string) => boolean;
@@ -1 +0,0 @@
1
- export declare const isEthereumAddress: (address: string | undefined | null) => address is `0x${string}`;
@@ -1,7 +0,0 @@
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;
@@ -1 +0,0 @@
1
- export declare const normalizeAddress: (address: string) => string;
@@ -1 +0,0 @@
1
- export declare function twox64Concat(input: string | Buffer | Uint8Array): `0x${string}`;