@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.
- package/LICENSE +201 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +490 -0
- package/dist/index.es.js +1241 -0
- package/dist/index.es.js.map +1 -0
- package/package.json +53 -0
- package/src/helpers/__tests__/asset.test.ts +157 -0
- package/src/helpers/__tests__/memo.test.ts +79 -0
- package/src/helpers/__tests__/others.test.ts +59 -0
- package/src/helpers/asset.ts +227 -0
- package/src/helpers/liquidity.ts +180 -0
- package/src/helpers/memo.ts +93 -0
- package/src/helpers/others.ts +20 -0
- package/src/helpers/validators.ts +18 -0
- package/src/index.ts +17 -0
- package/src/modules/__tests__/assetValue.test.ts +409 -0
- package/src/modules/__tests__/bigIntArithmetics.test.ts +30 -0
- package/src/modules/__tests__/swapKitNumber.test.ts +533 -0
- package/src/modules/assetValue.ts +266 -0
- package/src/modules/bigIntArithmetics.ts +419 -0
- package/src/modules/swapKitError.ts +79 -0
- package/src/modules/swapKitNumber.ts +16 -0
- package/src/types.ts +30 -0
|
@@ -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
|
+
}
|