@swapkit/helpers 0.0.0-nightly-20240208140027

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,266 @@
1
+ import { BaseDecimal, Chain, ChainToChainId } from '@swapkit/types';
2
+
3
+ import type { CommonAssetString } from '../helpers/asset.ts';
4
+ import { getAssetType, getCommonAssetInfo, getDecimal, isGasAsset } from '../helpers/asset.ts';
5
+ import { validateIdentifier } from '../helpers/validators.ts';
6
+ import type { TokenNames, TokenTax } from '../types.ts';
7
+
8
+ import type { NumberPrimitives } from './bigIntArithmetics.ts';
9
+ import { BigIntArithmetics, formatBigIntToSafeValue } from './bigIntArithmetics.ts';
10
+ import type { SwapKitValueType } from './swapKitNumber.ts';
11
+
12
+ const staticTokensMap = new Map<
13
+ TokenNames,
14
+ { tax?: TokenTax; decimal: number; identifier: string }
15
+ >();
16
+
17
+ export class AssetValue extends BigIntArithmetics {
18
+ address?: string;
19
+ chain: Chain;
20
+ isGasAsset = false;
21
+ isSynthetic = false;
22
+ symbol: string;
23
+ tax?: TokenTax;
24
+ ticker: string;
25
+ type: ReturnType<typeof getAssetType>;
26
+
27
+ constructor({
28
+ value,
29
+ decimal,
30
+ tax,
31
+ chain,
32
+ symbol,
33
+ identifier,
34
+ }: { decimal: number; value: SwapKitValueType; tax?: TokenTax } & (
35
+ | { chain: Chain; symbol: string; identifier?: never }
36
+ | { identifier: string; chain?: never; symbol?: never }
37
+ )) {
38
+ super(typeof value === 'object' ? value : { decimal, value });
39
+
40
+ const assetInfo = getAssetInfo(identifier || `${chain}.${symbol}`);
41
+
42
+ this.type = getAssetType(assetInfo);
43
+ this.tax = tax;
44
+ this.chain = assetInfo.chain;
45
+ this.ticker = assetInfo.ticker;
46
+ this.symbol = assetInfo.symbol;
47
+ this.address = assetInfo.address;
48
+ this.isSynthetic = assetInfo.isSynthetic;
49
+ this.isGasAsset = assetInfo.isGasAsset;
50
+ }
51
+
52
+ toString() {
53
+ return this.isSynthetic ? this.symbol : `${this.chain}.${this.symbol}`;
54
+ }
55
+
56
+ toUrl() {
57
+ return this.isSynthetic ? `${this.chain}.${this.symbol.replace('/', '.')}` : this.toString();
58
+ }
59
+
60
+ eq({ chain, symbol }: { chain: Chain; symbol: string }) {
61
+ return this.chain === chain && this.symbol === symbol;
62
+ }
63
+
64
+ chainId() {
65
+ return ChainToChainId[this.chain];
66
+ }
67
+
68
+ // THOR.RUNE
69
+ // THOR.ETH.ETH
70
+ // ETH.THOR-0x1234567890
71
+ static fromUrl(urlAsset: string, value: NumberPrimitives = 0) {
72
+ const [chain, ticker, symbol] = urlAsset.split('.');
73
+ if (!chain || !ticker) throw new Error('Invalid asset url');
74
+
75
+ const assetString =
76
+ chain === Chain.THORChain && symbol ? `${chain}.${ticker}/${symbol}` : urlAsset;
77
+
78
+ return createAssetValue(assetString, value);
79
+ }
80
+
81
+ static fromString(assetString: string, value: NumberPrimitives = 0) {
82
+ return createAssetValue(assetString, value);
83
+ }
84
+ static fromIdentifier(
85
+ assetString:
86
+ | `${Chain}.${string}`
87
+ | `${Chain}/${string}`
88
+ | `${Chain}.${string}-${string}`
89
+ | TokenNames,
90
+ value: NumberPrimitives = 0,
91
+ ) {
92
+ return createAssetValue(assetString, value);
93
+ }
94
+
95
+ static fromStringSync(assetString: string, value: NumberPrimitives = 0) {
96
+ const { chain, isSynthetic } = getAssetInfo(assetString);
97
+ const tokenInfo = staticTokensMap.get(assetString.toUpperCase() as TokenNames);
98
+
99
+ if (isSynthetic) return createSyntheticAssetValue(assetString, value);
100
+ // TODO: write logger that will only run in dev mode with some flag
101
+ // if (!tokenInfo) {
102
+ // console.error(
103
+ // `Asset ${assetString} is not loaded. Use AssetValue.loadStaticAssets() to load it`,
104
+ // );
105
+ // }
106
+
107
+ const { tax, decimal, identifier } = tokenInfo || {
108
+ decimal: BaseDecimal[chain],
109
+ identifier: assetString,
110
+ };
111
+
112
+ return new AssetValue({
113
+ tax,
114
+ value: safeValue(value, decimal),
115
+ identifier: isSynthetic ? assetString : identifier,
116
+ decimal: isSynthetic ? 8 : decimal,
117
+ });
118
+ }
119
+
120
+ static fromIdentifierSync(assetString: TokenNames, value: NumberPrimitives = 0) {
121
+ const { chain, isSynthetic } = getAssetInfo(assetString);
122
+ const tokenInfo = staticTokensMap.get(assetString);
123
+
124
+ if (isSynthetic) return createSyntheticAssetValue(assetString, value);
125
+ // TODO: write logger that will only run in dev mode with some flag
126
+ // if (!tokenInfo) {
127
+ // console.error(
128
+ // `Asset ${assetString} is not loaded. - Loading with base Chain. Use AssetValue.loadStaticAssets() to load it`,
129
+ // );
130
+ // }
131
+
132
+ const { tax, decimal, identifier } = tokenInfo || {
133
+ decimal: BaseDecimal[chain],
134
+ identifier: assetString,
135
+ };
136
+ return new AssetValue({ tax, decimal, identifier, value: safeValue(value, decimal) });
137
+ }
138
+
139
+ static fromChainOrSignature(assetString: CommonAssetString, value: NumberPrimitives = 0) {
140
+ const { decimal, identifier } = getCommonAssetInfo(assetString);
141
+ return new AssetValue({ value: safeValue(value, decimal), decimal, identifier });
142
+ }
143
+
144
+ static loadStaticAssets() {
145
+ return new Promise<{ ok: true } | { ok: false; message: string; error: any }>(
146
+ async (resolve, reject) => {
147
+ try {
148
+ const tokenPackages = await import('@swapkit/tokens');
149
+
150
+ Object.values(tokenPackages).forEach((tokenList) => {
151
+ tokenList?.tokens?.forEach(({ identifier, chain, ...rest }) => {
152
+ staticTokensMap.set(identifier.toUpperCase() as TokenNames, {
153
+ identifier,
154
+ decimal: 'decimals' in rest ? rest.decimals : BaseDecimal[chain as Chain],
155
+ });
156
+ });
157
+ });
158
+
159
+ resolve({ ok: true });
160
+ } catch (error) {
161
+ console.error(error);
162
+ reject({
163
+ ok: false,
164
+ error,
165
+ message:
166
+ "Couldn't load static assets. Ensure you have installed @swapkit/tokens package",
167
+ });
168
+ }
169
+ },
170
+ );
171
+ }
172
+ }
173
+
174
+ export function getMinAmountByChain(chain: Chain) {
175
+ const asset = AssetValue.fromChainOrSignature(chain);
176
+
177
+ switch (chain) {
178
+ case Chain.Bitcoin:
179
+ case Chain.Litecoin:
180
+ case Chain.BitcoinCash:
181
+ return asset.set(0.00010001);
182
+
183
+ case Chain.Dogecoin:
184
+ return asset.set(1.00000001);
185
+
186
+ case Chain.Avalanche:
187
+ case Chain.Ethereum:
188
+ return asset.set(0.00000001);
189
+
190
+ case Chain.THORChain:
191
+ case Chain.Maya:
192
+ return asset.set(0);
193
+
194
+ case Chain.Cosmos:
195
+ return asset.set(0.000001);
196
+
197
+ default:
198
+ return asset.set(0.00000001);
199
+ }
200
+ }
201
+
202
+ async function createAssetValue(identifier: string, value: NumberPrimitives = 0) {
203
+ validateIdentifier(identifier);
204
+
205
+ const staticToken = staticTokensMap.get(identifier.toUpperCase() as TokenNames);
206
+ const decimal = staticToken?.decimal || (await getDecimal(getAssetInfo(identifier)));
207
+ if (!staticToken) {
208
+ staticTokensMap.set(identifier.toUpperCase() as TokenNames, { identifier, decimal });
209
+ }
210
+
211
+ return new AssetValue({ decimal, value: safeValue(value, decimal), identifier });
212
+ }
213
+
214
+ function createSyntheticAssetValue(identifier: string, value: NumberPrimitives = 0) {
215
+ const [synthChain, symbol] =
216
+ identifier.split('.')[0].toUpperCase() === Chain.THORChain
217
+ ? identifier.split('.').slice(1)!.join().split('/')
218
+ : identifier.split('/');
219
+
220
+ if (!synthChain || !symbol) throw new Error('Invalid asset identifier');
221
+
222
+ return new AssetValue({
223
+ decimal: 8,
224
+ value: safeValue(value, 8),
225
+ identifier: `${Chain.THORChain}.${synthChain}/${symbol}`,
226
+ });
227
+ }
228
+
229
+ function safeValue(value: NumberPrimitives, decimal: number) {
230
+ return typeof value === 'bigint'
231
+ ? formatBigIntToSafeValue({ value, bigIntDecimal: decimal, decimal })
232
+ : value;
233
+ }
234
+
235
+ // TODO refactor & split into smaller functions
236
+ function getAssetInfo(identifier: string) {
237
+ const isSynthetic = identifier.slice(0, 14).includes('/');
238
+
239
+ const [synthChain, synthSymbol] =
240
+ identifier.split('.')[0].toUpperCase() === Chain.THORChain
241
+ ? identifier.split('.').slice(1)!.join().split('/')
242
+ : identifier.split('/');
243
+
244
+ if (isSynthetic && (!synthChain || !synthSymbol)) throw new Error('Invalid asset identifier');
245
+
246
+ const adjustedIdentifier =
247
+ identifier.includes('.') && !isSynthetic ? identifier : `${Chain.THORChain}.${synthSymbol}`;
248
+
249
+ const [chain, ...rest] = adjustedIdentifier.split('.') as [Chain, string];
250
+ const [ticker, address] = (isSynthetic ? synthSymbol : rest.join('.')).split('-') as [
251
+ string,
252
+ string?,
253
+ ];
254
+ const symbol = isSynthetic ? synthSymbol : rest.join('.');
255
+
256
+ return {
257
+ address: address?.toLowerCase(),
258
+ chain,
259
+ isGasAsset: isGasAsset({ chain, symbol }),
260
+ isSynthetic,
261
+ symbol:
262
+ (isSynthetic ? `${synthChain}/` : '') +
263
+ (address ? `${ticker}-${address?.toLowerCase() ?? ''}` : symbol),
264
+ ticker,
265
+ };
266
+ }
@@ -0,0 +1,419 @@
1
+ import { BaseDecimal } from '@swapkit/types';
2
+
3
+ import type { SwapKitNumber } from './swapKitNumber.ts';
4
+
5
+ type NumberPrimitivesType = {
6
+ bigint: bigint;
7
+ number: number;
8
+ string: string;
9
+ };
10
+ export type NumberPrimitives = bigint | number | string;
11
+ type InitialisationValueType = NumberPrimitives | BigIntArithmetics | SwapKitNumber;
12
+
13
+ type SKBigIntParams = InitialisationValueType | { decimal?: number; value: number | string };
14
+ type AllowedNumberTypes = 'bigint' | 'number' | 'string';
15
+
16
+ const DEFAULT_DECIMAL = 8;
17
+ const toMultiplier = (decimal: number) => 10n ** BigInt(decimal);
18
+ const decimalFromMultiplier = (multiplier: bigint) => Math.log10(parseFloat(multiplier.toString()));
19
+
20
+ export function formatBigIntToSafeValue({
21
+ value,
22
+ bigIntDecimal = DEFAULT_DECIMAL,
23
+ decimal = DEFAULT_DECIMAL,
24
+ }: {
25
+ value: bigint;
26
+ bigIntDecimal?: number;
27
+ decimal?: number;
28
+ }) {
29
+ if (decimal === 0) return value.toString();
30
+ const isNegative = value < 0n;
31
+ let valueString = value.toString().substring(isNegative ? 1 : 0);
32
+
33
+ const padLength = decimal - (valueString.length - 1);
34
+
35
+ if (padLength > 0) {
36
+ valueString = '0'.repeat(padLength) + valueString;
37
+ }
38
+
39
+ const decimalIndex = valueString.length - decimal;
40
+ let decimalString = valueString.slice(-decimal);
41
+
42
+ // Check if we need to round up
43
+ if (parseInt(decimalString[bigIntDecimal]) >= 5) {
44
+ // Increment the last decimal place and slice off the rest
45
+ decimalString = `${decimalString.substring(0, bigIntDecimal - 1)}${(
46
+ parseInt(decimalString[bigIntDecimal - 1]) + 1
47
+ ).toString()}`;
48
+ } else {
49
+ // Just slice off the extra digits
50
+ decimalString = decimalString.substring(0, bigIntDecimal);
51
+ }
52
+
53
+ return `${isNegative ? '-' : ''}${valueString.slice(0, decimalIndex)}.${decimalString}`.replace(
54
+ /\.?0*$/,
55
+ '',
56
+ );
57
+ }
58
+
59
+ export class BigIntArithmetics {
60
+ decimalMultiplier: bigint = 10n ** 8n;
61
+ bigIntValue: bigint = 0n;
62
+ decimal?: number;
63
+
64
+ static fromBigInt(value: bigint, decimal?: number) {
65
+ return new BigIntArithmetics({
66
+ decimal,
67
+ value: formatBigIntToSafeValue({ value, bigIntDecimal: decimal, decimal }),
68
+ });
69
+ }
70
+
71
+ static shiftDecimals({
72
+ value,
73
+ from,
74
+ to,
75
+ }: {
76
+ value: InstanceType<typeof SwapKitNumber>;
77
+ from: number;
78
+ to: number;
79
+ }) {
80
+ return this.fromBigInt(
81
+ (value.getBaseValue('bigint') * toMultiplier(to)) / toMultiplier(from),
82
+ to,
83
+ );
84
+ }
85
+
86
+ constructor(params: SKBigIntParams) {
87
+ const value = getStringValue(params);
88
+ const isComplex = typeof params === 'object';
89
+ this.decimal = isComplex ? params.decimal : undefined;
90
+
91
+ // use the multiplier to keep track of decimal point - defaults to 8 if lower than 8
92
+ this.decimalMultiplier =
93
+ isComplex && 'decimalMultiplier' in params
94
+ ? params.decimalMultiplier
95
+ : toMultiplier(Math.max(getFloatDecimals(toSafeValue(value)), this.decimal || 0));
96
+ this.#setValue(value);
97
+ }
98
+
99
+ set(value: SKBigIntParams): this {
100
+ // @ts-expect-error False positive
101
+ return new this.constructor({ decimal: this.decimal, value, identifier: this.toString() });
102
+ }
103
+ add(...args: InitialisationValueType[]) {
104
+ return this.#arithmetics('add', ...args);
105
+ }
106
+ sub(...args: InitialisationValueType[]) {
107
+ return this.#arithmetics('sub', ...args);
108
+ }
109
+ mul(...args: InitialisationValueType[]) {
110
+ return this.#arithmetics('mul', ...args);
111
+ }
112
+ div(...args: InitialisationValueType[]) {
113
+ return this.#arithmetics('div', ...args);
114
+ }
115
+ gt(value: InitialisationValueType) {
116
+ return this.#comparison('gt', value);
117
+ }
118
+ gte(value: InitialisationValueType) {
119
+ return this.#comparison('gte', value);
120
+ }
121
+ lt(value: InitialisationValueType) {
122
+ return this.#comparison('lt', value);
123
+ }
124
+ lte(value: InitialisationValueType) {
125
+ return this.#comparison('lte', value);
126
+ }
127
+ eqValue(value: InitialisationValueType) {
128
+ return this.#comparison('eqValue', value);
129
+ }
130
+
131
+ // @ts-expect-error False positive
132
+ getValue<T extends AllowedNumberTypes>(type: T): NumberPrimitivesType[T] {
133
+ const value = this.formatBigIntToSafeValue(
134
+ this.bigIntValue,
135
+ this.decimal || decimalFromMultiplier(this.decimalMultiplier),
136
+ );
137
+
138
+ switch (type) {
139
+ case 'number':
140
+ return Number(value) as NumberPrimitivesType[T];
141
+ case 'string':
142
+ return value as NumberPrimitivesType[T];
143
+ case 'bigint':
144
+ return ((this.bigIntValue * 10n ** BigInt(this.decimal || 8n)) /
145
+ this.decimalMultiplier) as NumberPrimitivesType[T];
146
+ }
147
+ }
148
+
149
+ // @ts-expect-error
150
+ getBaseValue<T extends AllowedNumberTypes>(type: T): NumberPrimitivesType[T] {
151
+ const divisor = this.decimalMultiplier / toMultiplier(this.decimal || BaseDecimal.THOR);
152
+ const baseValue = this.bigIntValue / divisor;
153
+
154
+ switch (type) {
155
+ case 'number':
156
+ return Number(baseValue) as NumberPrimitivesType[T];
157
+ case 'string':
158
+ return baseValue.toString() as NumberPrimitivesType[T];
159
+ case 'bigint':
160
+ return baseValue as NumberPrimitivesType[T];
161
+ }
162
+ }
163
+
164
+ getBigIntValue(value: InitialisationValueType, decimal?: number) {
165
+ if (!decimal && typeof value === 'object') return value.bigIntValue;
166
+
167
+ const stringValue = getStringValue(value);
168
+ const safeValue = toSafeValue(stringValue);
169
+
170
+ if (safeValue === '0' || safeValue === 'undefined') return 0n;
171
+ return this.#toBigInt(safeValue, decimal);
172
+ }
173
+
174
+ toSignificant(significantDigits: number = 6) {
175
+ const [int, dec] = this.getValue('string').split('.');
176
+ const integer = int || '';
177
+ const decimal = dec || '';
178
+ const valueLength = parseInt(integer) ? integer.length + decimal.length : decimal.length;
179
+
180
+ if (valueLength <= significantDigits) {
181
+ return this.getValue('string');
182
+ }
183
+
184
+ if (integer.length >= significantDigits) {
185
+ return integer.slice(0, significantDigits).padEnd(integer.length, '0');
186
+ }
187
+
188
+ if (parseInt(integer)) {
189
+ return `${integer}.${decimal.slice(0, significantDigits - integer.length)}`.padEnd(
190
+ significantDigits - integer.length,
191
+ '0',
192
+ );
193
+ }
194
+
195
+ const trimmedDecimal = parseInt(decimal);
196
+ const slicedDecimal = `${trimmedDecimal}`.slice(0, significantDigits);
197
+
198
+ return `0.${slicedDecimal.padStart(
199
+ decimal.length - `${trimmedDecimal}`.length + slicedDecimal.length,
200
+ '0',
201
+ )}`;
202
+ }
203
+
204
+ toFixed(fixedDigits: number = 6) {
205
+ const [int, dec] = this.getValue('string').split('.');
206
+ const integer = int || '';
207
+ const decimal = dec || '';
208
+
209
+ if (parseInt(integer)) {
210
+ return `${integer}.${decimal.slice(0, fixedDigits)}`.padEnd(fixedDigits, '0');
211
+ }
212
+
213
+ const trimmedDecimal = parseInt(decimal);
214
+ const slicedDecimal = `${trimmedDecimal}`.slice(0, fixedDigits);
215
+
216
+ return `0.${slicedDecimal.padStart(
217
+ decimal.length - `${trimmedDecimal}`.length + slicedDecimal.length,
218
+ '0',
219
+ )}`;
220
+ }
221
+
222
+ toAbbreviation(digits = 2) {
223
+ const value = this.getValue('number');
224
+ const abbreviations = ['', 'K', 'M', 'B', 'T', 'Q', 'Qi', 'S'];
225
+ const tier = Math.floor(Math.log10(Math.abs(value)) / 3);
226
+ const suffix = abbreviations[tier];
227
+
228
+ if (!suffix) return this.getValue('string');
229
+
230
+ const scale = 10 ** (tier * 3);
231
+ const scaled = value / scale;
232
+
233
+ return `${scaled.toFixed(digits)}${suffix}`;
234
+ }
235
+
236
+ toCurrency(
237
+ currency = '$',
238
+ {
239
+ currencyPosition = 'start',
240
+ decimal = 2,
241
+ decimalSeparator = '.',
242
+ thousandSeparator = ',',
243
+ } = {},
244
+ ) {
245
+ const value = this.getValue('number');
246
+ const [int, dec = ''] = value.toFixed(6).split('.');
247
+ const integer = int.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator);
248
+
249
+ const parsedValue =
250
+ !int && !dec
251
+ ? '0.00'
252
+ : int === '0'
253
+ ? `${parseFloat(`0.${dec}`)}`.replace('.', decimalSeparator)
254
+ : `${integer}${parseInt(dec) ? `${decimalSeparator}${dec.slice(0, decimal)}` : ''}`;
255
+
256
+ return `${currencyPosition === 'start' ? currency : ''}${parsedValue}${
257
+ currencyPosition === 'end' ? currency : ''
258
+ }`;
259
+ }
260
+
261
+ formatBigIntToSafeValue(value: bigint, decimal?: number) {
262
+ const bigIntDecimal = decimal || this.decimal || DEFAULT_DECIMAL;
263
+ const decimalToUseForConversion = Math.max(
264
+ bigIntDecimal,
265
+ decimalFromMultiplier(this.decimalMultiplier),
266
+ );
267
+ const isNegative = value < 0n;
268
+
269
+ const valueString = value.toString().substring(isNegative ? 1 : 0);
270
+ const padLength = decimalToUseForConversion - (valueString.length - 1);
271
+
272
+ const parsedValueString = padLength > 0 ? '0'.repeat(padLength) + valueString : valueString;
273
+
274
+ const decimalIndex = parsedValueString.length - decimalToUseForConversion;
275
+ let decimalString = parsedValueString.slice(-decimalToUseForConversion);
276
+
277
+ // Check if we need to round up
278
+ if (parseInt(decimalString[bigIntDecimal]) >= 5) {
279
+ // Increment the last decimal place and slice off the rest
280
+ decimalString = `${decimalString.substring(0, bigIntDecimal - 1)}${(
281
+ parseInt(decimalString[bigIntDecimal - 1]) + 1
282
+ ).toString()}`;
283
+ } else {
284
+ // Just slice off the extra digits
285
+ decimalString = decimalString.substring(0, bigIntDecimal);
286
+ }
287
+
288
+ return `${isNegative ? '-' : ''}${parsedValueString.slice(
289
+ 0,
290
+ decimalIndex,
291
+ )}.${decimalString}`.replace(/\.?0*$/, '');
292
+ }
293
+
294
+ #arithmetics(method: 'add' | 'sub' | 'mul' | 'div', ...args: InitialisationValueType[]): this {
295
+ const precisionDecimal = this.#retrievePrecisionDecimal(this, ...args);
296
+ const decimal = Math.max(precisionDecimal, decimalFromMultiplier(this.decimalMultiplier));
297
+ const precisionDecimalMultiplier = toMultiplier(decimal);
298
+
299
+ const result = args.reduce(
300
+ (acc: bigint, arg) => {
301
+ const value = this.getBigIntValue(arg, decimal);
302
+
303
+ switch (method) {
304
+ case 'add':
305
+ return acc + value;
306
+ case 'sub':
307
+ return acc - value;
308
+ /**
309
+ * Multiplication & division would end up with wrong result if we don't adjust the value
310
+ * 200000000n * 200000000n => 40000000000000000n
311
+ * 200000000n / 200000000n => 1n
312
+ * So we do the following:
313
+ * 200000000n * 200000000n = 40000000000000000n / 100000000n (decimals) => 400000000n
314
+ * (200000000n * 100000000n (decimals)) / 200000000n => 100000000n
315
+ */
316
+ case 'mul':
317
+ return (acc * value) / precisionDecimalMultiplier;
318
+ case 'div': {
319
+ if (value === 0n) throw new RangeError('Division by zero');
320
+ return (acc * precisionDecimalMultiplier) / value;
321
+ }
322
+ default:
323
+ return acc;
324
+ }
325
+ },
326
+ //normalize is to precision multiplier base
327
+ (this.bigIntValue * precisionDecimalMultiplier) / this.decimalMultiplier,
328
+ );
329
+
330
+ const value = formatBigIntToSafeValue({
331
+ bigIntDecimal: decimal,
332
+ decimal,
333
+ value: result,
334
+ });
335
+
336
+ // @ts-expect-error False positive
337
+ return new this.constructor({
338
+ decimalMultiplier: toMultiplier(decimal),
339
+ decimal: this.decimal,
340
+ value,
341
+ identifier: this.toString(),
342
+ });
343
+ }
344
+
345
+ #comparison(method: 'gt' | 'gte' | 'lt' | 'lte' | 'eqValue', ...args: InitialisationValueType[]) {
346
+ const decimal = this.#retrievePrecisionDecimal(this, ...args);
347
+ const value = this.getBigIntValue(args[0], decimal);
348
+ const compareToValue = this.getBigIntValue(this, decimal);
349
+
350
+ switch (method) {
351
+ case 'gt':
352
+ return compareToValue > value;
353
+ case 'gte':
354
+ return compareToValue >= value;
355
+ case 'lt':
356
+ return compareToValue < value;
357
+ case 'lte':
358
+ return compareToValue <= value;
359
+ case 'eqValue':
360
+ return compareToValue === value;
361
+ }
362
+ }
363
+
364
+ #setValue(value: InitialisationValueType) {
365
+ const safeValue = toSafeValue(value) || '0';
366
+ this.bigIntValue = this.#toBigInt(safeValue);
367
+ }
368
+
369
+ #retrievePrecisionDecimal(...args: InitialisationValueType[]) {
370
+ const decimals = args
371
+ .map((arg) => {
372
+ const isObject = typeof arg === 'object';
373
+ const value = isObject
374
+ ? arg.decimal || decimalFromMultiplier(arg.decimalMultiplier)
375
+ : getFloatDecimals(toSafeValue(arg));
376
+
377
+ return value;
378
+ })
379
+ .filter(Boolean) as number[];
380
+
381
+ return Math.max(...decimals, DEFAULT_DECIMAL);
382
+ }
383
+
384
+ #toBigInt(value: string, decimal?: number) {
385
+ const multiplier = decimal ? toMultiplier(decimal) : this.decimalMultiplier;
386
+ const padDecimal = decimalFromMultiplier(multiplier);
387
+ const [integerPart = '', decimalPart = ''] = value.split('.');
388
+
389
+ return BigInt(`${integerPart}${decimalPart.padEnd(padDecimal, '0')}`);
390
+ }
391
+ }
392
+
393
+ const numberFormatter = Intl.NumberFormat('fullwide', {
394
+ useGrouping: false,
395
+ maximumFractionDigits: 20,
396
+ });
397
+
398
+ function toSafeValue(value: InitialisationValueType) {
399
+ const parsedValue =
400
+ typeof value === 'number' ? numberFormatter.format(value) : getStringValue(value);
401
+ const splitValue = `${parsedValue}`.replaceAll(',', '.').split('.');
402
+
403
+ return splitValue.length > 1
404
+ ? `${splitValue.slice(0, -1).join('')}.${splitValue.at(-1)}`
405
+ : splitValue[0];
406
+ }
407
+
408
+ function getFloatDecimals(value: string) {
409
+ const decimals = value.split('.')[1]?.length || 0;
410
+ return Math.max(decimals, DEFAULT_DECIMAL);
411
+ }
412
+
413
+ function getStringValue(param: SKBigIntParams) {
414
+ return typeof param === 'object'
415
+ ? 'getValue' in param
416
+ ? param.getValue('string')
417
+ : param.value
418
+ : param;
419
+ }