@shapeshiftoss/utils 1.0.5 → 1.1.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.
- package/LICENSE.md +21 -0
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/assetData/baseAssets.d.ts +24 -1
- package/dist/cjs/assetData/baseAssets.js +383 -17
- package/dist/cjs/assetData/decodeAssetData.js +2 -2
- package/dist/cjs/assetData/getBaseAsset.js +48 -2
- package/dist/cjs/bigAmount/bigAmount.d.ts +82 -0
- package/dist/cjs/bigAmount/bigAmount.js +276 -0
- package/dist/cjs/bigAmount/bigAmount.test.d.ts +1 -0
- package/dist/cjs/bigAmount/bigAmount.test.js +1194 -0
- package/dist/cjs/chainIdToFeeAssetId.js +48 -2
- package/dist/cjs/encoding.d.ts +2 -0
- package/dist/cjs/encoding.js +13 -0
- package/dist/cjs/getAssetNamespaceFromChainId.js +25 -1
- package/dist/cjs/getChainShortName.d.ts +1 -1
- package/dist/cjs/getChainShortName.js +48 -2
- package/dist/cjs/getNativeFeeAssetReference.js +53 -2
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +7 -3
- package/dist/cjs/treasury.d.ts +10 -6
- package/dist/cjs/treasury.js +13 -6
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/assetData/baseAssets.d.ts +24 -1
- package/dist/esm/assetData/baseAssets.js +381 -16
- package/dist/esm/assetData/decodeAssetData.js +1 -1
- package/dist/esm/assetData/getBaseAsset.js +49 -3
- package/dist/esm/bigAmount/bigAmount.d.ts +82 -0
- package/dist/esm/bigAmount/bigAmount.js +272 -0
- package/dist/esm/bigAmount/bigAmount.test.d.ts +1 -0
- package/dist/esm/bigAmount/bigAmount.test.js +1192 -0
- package/dist/esm/chainIdToFeeAssetId.js +49 -3
- package/dist/esm/encoding.d.ts +2 -0
- package/dist/esm/encoding.js +8 -0
- package/dist/esm/getAssetNamespaceFromChainId.js +25 -1
- package/dist/esm/getChainShortName.d.ts +1 -1
- package/dist/esm/getChainShortName.js +48 -2
- package/dist/esm/getNativeFeeAssetReference.js +53 -2
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +4 -1
- package/dist/esm/treasury.d.ts +10 -6
- package/dist/esm/treasury.js +12 -5
- package/package.json +15 -16
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { bn } from '../bignumber/bignumber.js';
|
|
3
|
+
import { BigAmount } from './bigAmount.js';
|
|
4
|
+
describe('BigAmount', () => {
|
|
5
|
+
describe('fromBaseUnit', () => {
|
|
6
|
+
it('converts base unit to precision scale for BTC (8 decimals)', () => {
|
|
7
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
8
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
9
|
+
expect(amount.precision).toBe(8);
|
|
10
|
+
});
|
|
11
|
+
it('converts base unit to precision scale for ETH (18 decimals)', () => {
|
|
12
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
13
|
+
expect(amount.toPrecision()).toBe('1');
|
|
14
|
+
expect(amount.precision).toBe(18);
|
|
15
|
+
});
|
|
16
|
+
it('converts base unit to precision scale for USDC (6 decimals)', () => {
|
|
17
|
+
const amount = BigAmount.fromBaseUnit({ value: '1500000', precision: 6 });
|
|
18
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
19
|
+
expect(amount.precision).toBe(6);
|
|
20
|
+
});
|
|
21
|
+
it('handles dust amounts', () => {
|
|
22
|
+
const amount = BigAmount.fromBaseUnit({ value: '1', precision: 18 });
|
|
23
|
+
expect(amount.toPrecision()).toBe('0.000000000000000001');
|
|
24
|
+
});
|
|
25
|
+
it('handles large numbers', () => {
|
|
26
|
+
const amount = BigAmount.fromBaseUnit({ value: '999999999999999999', precision: 18 });
|
|
27
|
+
expect(amount.toPrecision()).toBe('0.999999999999999999');
|
|
28
|
+
});
|
|
29
|
+
it('handles zero', () => {
|
|
30
|
+
const amount = BigAmount.fromBaseUnit({ value: '0', precision: 8 });
|
|
31
|
+
expect(amount.toPrecision()).toBe('0');
|
|
32
|
+
expect(amount.isZero()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe('fromPrecision', () => {
|
|
36
|
+
it('stores precision-scale value directly', () => {
|
|
37
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
38
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
39
|
+
expect(amount.precision).toBe(8);
|
|
40
|
+
});
|
|
41
|
+
it('stores ETH amount directly', () => {
|
|
42
|
+
const amount = BigAmount.fromPrecision({ value: '1', precision: 18 });
|
|
43
|
+
expect(amount.toPrecision()).toBe('1');
|
|
44
|
+
});
|
|
45
|
+
it('accepts number inputs', () => {
|
|
46
|
+
const amount = BigAmount.fromPrecision({ value: 1.5, precision: 8 });
|
|
47
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('nullish-safe construction', () => {
|
|
51
|
+
it('fromBaseUnit treats undefined as zero', () => {
|
|
52
|
+
expect(BigAmount.fromBaseUnit({ value: undefined, precision: 8 }).isZero()).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it('fromBaseUnit treats null as zero', () => {
|
|
55
|
+
expect(BigAmount.fromBaseUnit({ value: null, precision: 8 }).isZero()).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
it('fromPrecision treats undefined as zero', () => {
|
|
58
|
+
expect(BigAmount.fromPrecision({ value: undefined, precision: 8 }).isZero()).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it('fromPrecision treats null as zero', () => {
|
|
61
|
+
expect(BigAmount.fromPrecision({ value: null, precision: 8 }).isZero()).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it('fromPrecision treats empty string as zero', () => {
|
|
64
|
+
expect(BigAmount.fromPrecision({ value: '', precision: 8 }).isZero()).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
it('fromPrecision treats NaN as zero', () => {
|
|
67
|
+
expect(BigAmount.fromPrecision({ value: NaN, precision: 8 }).isZero()).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
it('fromBaseUnit treats NaN as zero', () => {
|
|
70
|
+
expect(BigAmount.fromBaseUnit({ value: NaN, precision: 18 }).isZero()).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe('zero', () => {
|
|
74
|
+
it('creates a zero amount at given precision', () => {
|
|
75
|
+
const amount = BigAmount.zero({ precision: 18 });
|
|
76
|
+
expect(amount.isZero()).toBe(true);
|
|
77
|
+
expect(amount.precision).toBe(18);
|
|
78
|
+
expect(amount.toBaseUnit()).toBe('0');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('fromBN', () => {
|
|
82
|
+
it('wraps an existing BigNumber (precision-scale)', () => {
|
|
83
|
+
const amount = BigAmount.fromBN({ value: bn('1.5'), precision: 8 });
|
|
84
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
85
|
+
expect(amount.precision).toBe(8);
|
|
86
|
+
});
|
|
87
|
+
it('treats non-finite BigNumber as zero', () => {
|
|
88
|
+
const amount = BigAmount.fromBN({ value: bn(Infinity), precision: 8 });
|
|
89
|
+
expect(amount.isZero()).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('base unit / precision safety', () => {
|
|
93
|
+
it('fromBaseUnit and fromPrecision produce the same BigAmount for equivalent values', () => {
|
|
94
|
+
const fromBase = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
95
|
+
const fromPrec = BigAmount.fromPrecision({ value: '1', precision: 18 });
|
|
96
|
+
expect(fromBase.eq(fromPrec)).toBe(true);
|
|
97
|
+
expect(fromBase.toPrecision()).toBe(fromPrec.toPrecision());
|
|
98
|
+
});
|
|
99
|
+
it('normalizes both to base unit scale so operations are always safe', () => {
|
|
100
|
+
const a = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
101
|
+
const b = BigAmount.fromPrecision({ value: '0.5', precision: 8 });
|
|
102
|
+
const sum = a.plus(b);
|
|
103
|
+
expect(sum.toPrecision()).toBe('2');
|
|
104
|
+
expect(sum.toBaseUnit()).toBe('200000000');
|
|
105
|
+
});
|
|
106
|
+
it('prevents the classic bug: accidentally adding wei to ETH', () => {
|
|
107
|
+
const oneEthFromWei = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
108
|
+
const oneEthFromPrecision = BigAmount.fromPrecision({ value: '1', precision: 18 });
|
|
109
|
+
expect(oneEthFromWei.plus(oneEthFromPrecision).toPrecision()).toBe('2');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('round-trip', () => {
|
|
113
|
+
it.each([
|
|
114
|
+
{ baseUnit: '150000000', precision: 8, label: 'BTC' },
|
|
115
|
+
{ baseUnit: '1000000000000000000', precision: 18, label: 'ETH' },
|
|
116
|
+
{ baseUnit: '1500000', precision: 6, label: 'USDC' },
|
|
117
|
+
{ baseUnit: '1', precision: 18, label: 'dust ETH' },
|
|
118
|
+
{ baseUnit: '999999999999999999', precision: 18, label: 'large ETH' },
|
|
119
|
+
{ baseUnit: '0', precision: 8, label: 'zero' },
|
|
120
|
+
{ baseUnit: '100000000000', precision: 8, label: '1000 BTC' },
|
|
121
|
+
])('$label: fromBaseUnit($baseUnit, $precision) → toBaseUnit() === $baseUnit', ({ baseUnit, precision }) => {
|
|
122
|
+
const amount = BigAmount.fromBaseUnit({ value: baseUnit, precision });
|
|
123
|
+
expect(amount.toBaseUnit()).toBe(baseUnit);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('plus', () => {
|
|
127
|
+
it('adds two BigAmounts with same precision', () => {
|
|
128
|
+
const a = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
129
|
+
const b = BigAmount.fromPrecision({ value: '2.5', precision: 8 });
|
|
130
|
+
expect(a.plus(b).toPrecision()).toBe('4');
|
|
131
|
+
});
|
|
132
|
+
it('adds a scalar to a BigAmount (scalar is precision-scale)', () => {
|
|
133
|
+
const a = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
134
|
+
expect(a.plus('0.5').toPrecision()).toBe('2');
|
|
135
|
+
});
|
|
136
|
+
it('throws when adding BigAmounts with different precisions', () => {
|
|
137
|
+
const btc = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
138
|
+
const eth = BigAmount.fromPrecision({ value: '1', precision: 18 });
|
|
139
|
+
expect(() => btc.plus(eth)).toThrow('different precisions: 8 vs 18');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('minus', () => {
|
|
143
|
+
it('subtracts two BigAmounts with same precision', () => {
|
|
144
|
+
const a = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
145
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
146
|
+
expect(a.minus(b).toPrecision()).toBe('3');
|
|
147
|
+
});
|
|
148
|
+
it('subtracts a scalar from a BigAmount (scalar is precision-scale)', () => {
|
|
149
|
+
const a = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
150
|
+
expect(a.minus('2').toPrecision()).toBe('3');
|
|
151
|
+
});
|
|
152
|
+
it('throws when subtracting BigAmounts with different precisions', () => {
|
|
153
|
+
const btc = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
154
|
+
const eth = BigAmount.fromPrecision({ value: '1', precision: 18 });
|
|
155
|
+
expect(() => btc.minus(eth)).toThrow('different precisions: 8 vs 18');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('times', () => {
|
|
159
|
+
it('multiplies by a dimensionless scalar', () => {
|
|
160
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
161
|
+
expect(amount.times(2).toPrecision()).toBe('3');
|
|
162
|
+
expect(amount.times(2).toBaseUnit()).toBe('300000000');
|
|
163
|
+
});
|
|
164
|
+
it('multiplies by a string scalar', () => {
|
|
165
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
166
|
+
expect(amount.times('2').toBaseUnit()).toBe('300000000');
|
|
167
|
+
});
|
|
168
|
+
it('handles null scalar as zero', () => {
|
|
169
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
170
|
+
expect(amount.times(null).isZero()).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
it('handles undefined scalar as zero', () => {
|
|
173
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
174
|
+
expect(amount.times(undefined).isZero()).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
it('handles empty string scalar as zero', () => {
|
|
177
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
178
|
+
expect(amount.times('').isZero()).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
it('throws if passed a BigAmount (dimensionally invalid)', () => {
|
|
181
|
+
const a = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
182
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
183
|
+
expect(() => a.times(b)).toThrow('does not accept BigAmount');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe('div', () => {
|
|
187
|
+
it('divides by a scalar', () => {
|
|
188
|
+
const amount = BigAmount.fromPrecision({ value: '10', precision: 8 });
|
|
189
|
+
expect(amount.div(2).toPrecision()).toBe('5');
|
|
190
|
+
});
|
|
191
|
+
it('handles fractional division', () => {
|
|
192
|
+
const amount = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
193
|
+
expect(amount.div(3).toFixed(8)).toBe('0.33333333');
|
|
194
|
+
});
|
|
195
|
+
it('throws if passed a BigAmount (dimensionally invalid)', () => {
|
|
196
|
+
const a = BigAmount.fromPrecision({ value: '10', precision: 8 });
|
|
197
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
198
|
+
expect(() => a.div(b)).toThrow('does not accept BigAmount');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('abs', () => {
|
|
202
|
+
it('returns absolute value', () => {
|
|
203
|
+
const amount = BigAmount.fromPrecision({ value: '-5', precision: 8 });
|
|
204
|
+
expect(amount.abs().toPrecision()).toBe('5');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe('negated', () => {
|
|
208
|
+
it('negates the value', () => {
|
|
209
|
+
const amount = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
210
|
+
expect(amount.negated().toPrecision()).toBe('-5');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe('positiveOrZero', () => {
|
|
214
|
+
it('returns self for positive amounts', () => {
|
|
215
|
+
const amount = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
216
|
+
expect(amount.positiveOrZero().toPrecision()).toBe('5');
|
|
217
|
+
});
|
|
218
|
+
it('returns zero for negative amounts', () => {
|
|
219
|
+
const amount = BigAmount.fromPrecision({ value: '-5', precision: 8 });
|
|
220
|
+
const result = amount.positiveOrZero();
|
|
221
|
+
expect(result.isZero()).toBe(true);
|
|
222
|
+
expect(result.precision).toBe(8);
|
|
223
|
+
});
|
|
224
|
+
it('returns self for zero', () => {
|
|
225
|
+
const amount = BigAmount.zero({ precision: 8 });
|
|
226
|
+
expect(amount.positiveOrZero().isZero()).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe('decimalPlaces', () => {
|
|
230
|
+
it('rounds to specified decimal places', () => {
|
|
231
|
+
const amount = BigAmount.fromPrecision({ value: '1.123456789', precision: 18 });
|
|
232
|
+
expect(amount.decimalPlaces(4).toPrecision()).toBe('1.1235');
|
|
233
|
+
});
|
|
234
|
+
it('rounds down when specified', () => {
|
|
235
|
+
const amount = BigAmount.fromPrecision({ value: '1.999', precision: 18 });
|
|
236
|
+
expect(amount.decimalPlaces(2, 1).toPrecision()).toBe('1.99'); // ROUND_DOWN = 1
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe('arithmetic chaining', () => {
|
|
240
|
+
it('chains multiple operations with precision', () => {
|
|
241
|
+
const result = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 })
|
|
242
|
+
.times(2500)
|
|
243
|
+
.div(1.1)
|
|
244
|
+
.toFixed(2);
|
|
245
|
+
expect(result).toBe('2272.72');
|
|
246
|
+
});
|
|
247
|
+
it('base unit → fiat via toPrecision', () => {
|
|
248
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
249
|
+
const fiat = bn(amount.toPrecision()).times(60000).toFixed(2);
|
|
250
|
+
expect(fiat).toBe('90000.00');
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('comparison', () => {
|
|
254
|
+
it('gt with BigAmount', () => {
|
|
255
|
+
const a = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
256
|
+
const b = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
257
|
+
expect(a.gt(b)).toBe(true);
|
|
258
|
+
expect(b.gt(a)).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
it('gt with scalar (scalar is precision-scale)', () => {
|
|
261
|
+
const a = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
262
|
+
expect(a.gt('1')).toBe(true);
|
|
263
|
+
expect(a.gt('3')).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
it('gte', () => {
|
|
266
|
+
const a = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
267
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
268
|
+
expect(a.gte(b)).toBe(true);
|
|
269
|
+
expect(a.gte('2')).toBe(true);
|
|
270
|
+
expect(a.gte('3')).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
it('lt', () => {
|
|
273
|
+
const a = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
274
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
275
|
+
expect(a.lt(b)).toBe(true);
|
|
276
|
+
expect(a.lt('0')).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
it('lte', () => {
|
|
279
|
+
const a = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
280
|
+
expect(a.lte('1')).toBe(true);
|
|
281
|
+
expect(a.lte('2')).toBe(true);
|
|
282
|
+
expect(a.lte('0')).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
it('eq', () => {
|
|
285
|
+
const a = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
286
|
+
const b = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
287
|
+
expect(a.eq(b)).toBe(true);
|
|
288
|
+
expect(a.eq('1.5')).toBe(true);
|
|
289
|
+
expect(a.eq('2')).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
it('throws on comparison with different precision BigAmount', () => {
|
|
292
|
+
const btc = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
293
|
+
const eth = BigAmount.fromPrecision({ value: '1', precision: 18 });
|
|
294
|
+
expect(() => btc.gt(eth)).toThrow('different precisions');
|
|
295
|
+
expect(() => btc.gte(eth)).toThrow('different precisions');
|
|
296
|
+
expect(() => btc.lt(eth)).toThrow('different precisions');
|
|
297
|
+
expect(() => btc.lte(eth)).toThrow('different precisions');
|
|
298
|
+
expect(() => btc.eq(eth)).toThrow('different precisions');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
describe('boolean checks', () => {
|
|
302
|
+
it('isZero', () => {
|
|
303
|
+
expect(BigAmount.fromPrecision({ value: '0', precision: 8 }).isZero()).toBe(true);
|
|
304
|
+
expect(BigAmount.fromPrecision({ value: '1', precision: 8 }).isZero()).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
it('isPositive', () => {
|
|
307
|
+
expect(BigAmount.fromPrecision({ value: '1', precision: 8 }).isPositive()).toBe(true);
|
|
308
|
+
expect(BigAmount.fromPrecision({ value: '-1', precision: 8 }).isPositive()).toBe(false);
|
|
309
|
+
expect(BigAmount.fromPrecision({ value: '0', precision: 8 }).isPositive()).toBe(true); // BigNumber considers 0 positive
|
|
310
|
+
});
|
|
311
|
+
it('isNegative', () => {
|
|
312
|
+
expect(BigAmount.fromPrecision({ value: '-1', precision: 8 }).isNegative()).toBe(true);
|
|
313
|
+
expect(BigAmount.fromPrecision({ value: '1', precision: 8 }).isNegative()).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
it('isFinite', () => {
|
|
316
|
+
expect(BigAmount.fromPrecision({ value: '1', precision: 8 }).isFinite()).toBe(true);
|
|
317
|
+
expect(BigAmount.fromPrecision({ value: undefined, precision: 8 }).isFinite()).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
describe('toBaseUnit', () => {
|
|
321
|
+
it('converts to integer base unit string', () => {
|
|
322
|
+
expect(BigAmount.fromPrecision({ value: '1.5', precision: 8 }).toBaseUnit()).toBe('150000000');
|
|
323
|
+
});
|
|
324
|
+
it('converts ETH to wei', () => {
|
|
325
|
+
expect(BigAmount.fromPrecision({ value: '1', precision: 18 }).toBaseUnit()).toBe('1000000000000000000');
|
|
326
|
+
});
|
|
327
|
+
it('converts USDC to micro-units', () => {
|
|
328
|
+
expect(BigAmount.fromPrecision({ value: '100.5', precision: 6 }).toBaseUnit()).toBe('100500000');
|
|
329
|
+
});
|
|
330
|
+
it('converts zero', () => {
|
|
331
|
+
expect(BigAmount.zero({ precision: 8 }).toBaseUnit()).toBe('0');
|
|
332
|
+
});
|
|
333
|
+
it('returns stored base unit value directly (identity for fromBaseUnit)', () => {
|
|
334
|
+
expect(BigAmount.fromBaseUnit({ value: '12345', precision: 8 }).toBaseUnit()).toBe('12345');
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
describe('toPrecision', () => {
|
|
338
|
+
it('returns full precision string', () => {
|
|
339
|
+
expect(BigAmount.fromBaseUnit({ value: '150000000', precision: 8 }).toPrecision()).toBe('1.5');
|
|
340
|
+
});
|
|
341
|
+
it('is an alias for toString', () => {
|
|
342
|
+
const amount = BigAmount.fromBaseUnit({ value: '123456789', precision: 8 });
|
|
343
|
+
expect(amount.toPrecision()).toBe(amount.toString());
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
describe('toFixed', () => {
|
|
347
|
+
it('returns fixed decimal places', () => {
|
|
348
|
+
expect(BigAmount.fromPrecision({ value: '1.5', precision: 8 }).toFixed(2)).toBe('1.50');
|
|
349
|
+
});
|
|
350
|
+
it('rounds down by default', () => {
|
|
351
|
+
expect(BigAmount.fromPrecision({ value: '1.999', precision: 8 }).toFixed(2)).toBe('1.99');
|
|
352
|
+
});
|
|
353
|
+
it('returns full precision when no decimals specified', () => {
|
|
354
|
+
expect(BigAmount.fromPrecision({ value: '1.5', precision: 8 }).toFixed()).toBe('1.5');
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
describe('toNumber', () => {
|
|
358
|
+
it('returns JS number', () => {
|
|
359
|
+
expect(BigAmount.fromPrecision({ value: '1.5', precision: 8 }).toNumber()).toBe(1.5);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
describe('toSignificant', () => {
|
|
363
|
+
it('handles integers', () => {
|
|
364
|
+
expect(BigAmount.fromPrecision({ value: '1234', precision: 8 }).toSignificant(3)).toBe('1234');
|
|
365
|
+
});
|
|
366
|
+
it('handles values with integer and decimal parts', () => {
|
|
367
|
+
expect(BigAmount.fromPrecision({ value: '1.5', precision: 8 }).toSignificant(3)).toBe('1.5');
|
|
368
|
+
});
|
|
369
|
+
it('trims trailing zeros from significant digits', () => {
|
|
370
|
+
expect(BigAmount.fromPrecision({ value: '1.500', precision: 8 }).toSignificant(4)).toBe('1.5');
|
|
371
|
+
});
|
|
372
|
+
it('handles dust amounts', () => {
|
|
373
|
+
expect(BigAmount.fromBaseUnit({ value: '1', precision: 18 }).toSignificant(4)).toBe('0.000000000000000001');
|
|
374
|
+
});
|
|
375
|
+
it('handles small amounts with leading zeros', () => {
|
|
376
|
+
expect(BigAmount.fromPrecision({ value: '0.00012345', precision: 18 }).toSignificant(4)).toBe('0.0001234');
|
|
377
|
+
});
|
|
378
|
+
it('handles zero', () => {
|
|
379
|
+
expect(BigAmount.fromPrecision({ value: '0', precision: 8 }).toSignificant(4)).toBe('0');
|
|
380
|
+
});
|
|
381
|
+
it('handles negative values', () => {
|
|
382
|
+
expect(BigAmount.fromPrecision({ value: '-0.00012345', precision: 18 }).toSignificant(4)).toBe('-0.0001234');
|
|
383
|
+
});
|
|
384
|
+
it('handles values where integer part exceeds requested digits', () => {
|
|
385
|
+
expect(BigAmount.fromPrecision({ value: '12345.6789', precision: 18 }).toSignificant(3)).toBe('12345');
|
|
386
|
+
});
|
|
387
|
+
it('handles values where integer part uses some significant digits', () => {
|
|
388
|
+
expect(BigAmount.fromPrecision({ value: '12.3456', precision: 18 }).toSignificant(4)).toBe('12.34');
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
describe('fromThorBaseUnit', () => {
|
|
392
|
+
it('creates BigAmount from THOR 8-decimal base unit', () => {
|
|
393
|
+
const amount = BigAmount.fromThorBaseUnit('150000000');
|
|
394
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
395
|
+
expect(amount.precision).toBe(8);
|
|
396
|
+
});
|
|
397
|
+
it('handles nullish as zero', () => {
|
|
398
|
+
expect(BigAmount.fromThorBaseUnit(undefined).isZero()).toBe(true);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
describe('toThorBaseUnit', () => {
|
|
402
|
+
it('converts ETH (18 decimals) human value to THOR base unit', () => {
|
|
403
|
+
const amount = BigAmount.fromBaseUnit({ value: '1500000000000000000', precision: 18 });
|
|
404
|
+
expect(amount.toThorBaseUnit()).toBe('150000000');
|
|
405
|
+
});
|
|
406
|
+
it('converts BTC (8 decimals) — no-op since BTC and THOR share precision', () => {
|
|
407
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
408
|
+
expect(amount.toThorBaseUnit()).toBe('150000000');
|
|
409
|
+
expect(amount.toThorBaseUnit()).toBe(amount.toBaseUnit());
|
|
410
|
+
});
|
|
411
|
+
it('converts USDC (6 decimals) human value to THOR base unit', () => {
|
|
412
|
+
const amount = BigAmount.fromBaseUnit({ value: '1500000', precision: 6 });
|
|
413
|
+
expect(amount.toThorBaseUnit()).toBe('150000000');
|
|
414
|
+
});
|
|
415
|
+
it('converts from fromPrecision() the same way', () => {
|
|
416
|
+
const fromBase = BigAmount.fromBaseUnit({ value: '1500000000000000000', precision: 18 });
|
|
417
|
+
const fromPrec = BigAmount.fromPrecision({ value: '1.5', precision: 18 });
|
|
418
|
+
expect(fromBase.toThorBaseUnit()).toBe(fromPrec.toThorBaseUnit());
|
|
419
|
+
expect(fromPrec.toThorBaseUnit()).toBe('150000000');
|
|
420
|
+
});
|
|
421
|
+
it('handles zero', () => {
|
|
422
|
+
expect(BigAmount.zero({ precision: 18 }).toThorBaseUnit()).toBe('0');
|
|
423
|
+
});
|
|
424
|
+
it('replaces the old toThorBaseUnit({ valueCryptoBaseUnit, asset }) pattern', () => {
|
|
425
|
+
const nativeBaseUnit = '1500000000000000000';
|
|
426
|
+
const assetPrecision = 18;
|
|
427
|
+
const thorBaseUnit = BigAmount.fromBaseUnit({
|
|
428
|
+
value: nativeBaseUnit,
|
|
429
|
+
precision: assetPrecision,
|
|
430
|
+
}).toThorBaseUnit();
|
|
431
|
+
expect(thorBaseUnit).toBe('150000000');
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
describe('fromThorBaseUnit + toThorBaseUnit round-trip', () => {
|
|
435
|
+
it('round-trips THOR base unit → human → THOR base unit', () => {
|
|
436
|
+
const original = '150000000';
|
|
437
|
+
const result = BigAmount.fromThorBaseUnit(original).toThorBaseUnit();
|
|
438
|
+
expect(result).toBe(original);
|
|
439
|
+
});
|
|
440
|
+
it('round-trips with toBaseUnit (same as toThorBaseUnit for precision 8)', () => {
|
|
441
|
+
const original = BigAmount.fromThorBaseUnit('150000000');
|
|
442
|
+
expect(original.toBaseUnit()).toBe('150000000');
|
|
443
|
+
expect(original.toThorBaseUnit()).toBe('150000000');
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
describe('JSON serialization', () => {
|
|
447
|
+
it('round-trips through toJSON/fromJSON', () => {
|
|
448
|
+
const original = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
449
|
+
const json = original.toJSON();
|
|
450
|
+
expect(json).toEqual({ value: '150000000', precision: 8 });
|
|
451
|
+
const restored = BigAmount.fromJSON(json);
|
|
452
|
+
expect(restored.eq(original)).toBe(true);
|
|
453
|
+
expect(restored.toBaseUnit()).toBe('150000000');
|
|
454
|
+
});
|
|
455
|
+
it('round-trips ETH through toJSON/fromJSON', () => {
|
|
456
|
+
const original = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
457
|
+
const json = original.toJSON();
|
|
458
|
+
const restored = BigAmount.fromJSON(json);
|
|
459
|
+
expect(restored.toBaseUnit()).toBe('1000000000000000000');
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
describe('min', () => {
|
|
463
|
+
it('returns the smallest amount', () => {
|
|
464
|
+
const a = BigAmount.fromPrecision({ value: '3', precision: 8 });
|
|
465
|
+
const b = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
466
|
+
const c = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
467
|
+
expect(BigAmount.min(a, b, c).toPrecision()).toBe('1');
|
|
468
|
+
});
|
|
469
|
+
it('works with two amounts', () => {
|
|
470
|
+
const a = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
471
|
+
const b = BigAmount.fromPrecision({ value: '3', precision: 8 });
|
|
472
|
+
expect(BigAmount.min(a, b).toPrecision()).toBe('3');
|
|
473
|
+
});
|
|
474
|
+
it('throws with different precisions', () => {
|
|
475
|
+
const a = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
476
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 18 });
|
|
477
|
+
expect(() => BigAmount.min(a, b)).toThrow('different precisions');
|
|
478
|
+
});
|
|
479
|
+
it('throws with no arguments', () => {
|
|
480
|
+
expect(() => BigAmount.min()).toThrow('at least one argument');
|
|
481
|
+
});
|
|
482
|
+
it('returns the single amount when given one', () => {
|
|
483
|
+
const a = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
484
|
+
expect(BigAmount.min(a).toPrecision()).toBe('5');
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
describe('max', () => {
|
|
488
|
+
it('returns the largest amount', () => {
|
|
489
|
+
const a = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
490
|
+
const b = BigAmount.fromPrecision({ value: '3', precision: 8 });
|
|
491
|
+
const c = BigAmount.fromPrecision({ value: '2', precision: 8 });
|
|
492
|
+
expect(BigAmount.max(a, b, c).toPrecision()).toBe('3');
|
|
493
|
+
});
|
|
494
|
+
it('works with two amounts', () => {
|
|
495
|
+
const a = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
496
|
+
const b = BigAmount.fromPrecision({ value: '3', precision: 8 });
|
|
497
|
+
expect(BigAmount.max(a, b).toPrecision()).toBe('5');
|
|
498
|
+
});
|
|
499
|
+
it('throws with different precisions', () => {
|
|
500
|
+
const a = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
501
|
+
const b = BigAmount.fromPrecision({ value: '2', precision: 18 });
|
|
502
|
+
expect(() => BigAmount.max(a, b)).toThrow('different precisions');
|
|
503
|
+
});
|
|
504
|
+
it('throws with no arguments', () => {
|
|
505
|
+
expect(() => BigAmount.max()).toThrow('at least one argument');
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
describe('isBigAmount', () => {
|
|
509
|
+
it('returns true for BigAmount instances', () => {
|
|
510
|
+
expect(BigAmount.isBigAmount(BigAmount.fromPrecision({ value: '1', precision: 8 }))).toBe(true);
|
|
511
|
+
});
|
|
512
|
+
it('returns false for non-BigAmount values', () => {
|
|
513
|
+
expect(BigAmount.isBigAmount('1.5')).toBe(false);
|
|
514
|
+
expect(BigAmount.isBigAmount(1.5)).toBe(false);
|
|
515
|
+
expect(BigAmount.isBigAmount(null)).toBe(false);
|
|
516
|
+
expect(BigAmount.isBigAmount(undefined)).toBe(false);
|
|
517
|
+
expect(BigAmount.isBigAmount(bn('1.5'))).toBe(false);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
describe('immutability', () => {
|
|
521
|
+
it('arithmetic operations return new instances', () => {
|
|
522
|
+
const a = BigAmount.fromPrecision({ value: '1', precision: 8 });
|
|
523
|
+
const b = a.plus('1');
|
|
524
|
+
expect(a.toPrecision()).toBe('1');
|
|
525
|
+
expect(b.toPrecision()).toBe('2');
|
|
526
|
+
});
|
|
527
|
+
it('abs returns new instance', () => {
|
|
528
|
+
const a = BigAmount.fromPrecision({ value: '-5', precision: 8 });
|
|
529
|
+
const b = a.abs();
|
|
530
|
+
expect(a.toPrecision()).toBe('-5');
|
|
531
|
+
expect(b.toPrecision()).toBe('5');
|
|
532
|
+
});
|
|
533
|
+
it('negated returns new instance', () => {
|
|
534
|
+
const a = BigAmount.fromPrecision({ value: '5', precision: 8 });
|
|
535
|
+
const b = a.negated();
|
|
536
|
+
expect(a.toPrecision()).toBe('5');
|
|
537
|
+
expect(b.toPrecision()).toBe('-5');
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
describe('real-world patterns', () => {
|
|
541
|
+
it('base unit → fiat display via toPrecision()', () => {
|
|
542
|
+
const balance = '150000000';
|
|
543
|
+
const price = 60000;
|
|
544
|
+
const fiat = bn(BigAmount.fromBaseUnit({ value: balance, precision: 8 }).toPrecision())
|
|
545
|
+
.times(price)
|
|
546
|
+
.toFixed(2);
|
|
547
|
+
expect(fiat).toBe('90000.00');
|
|
548
|
+
});
|
|
549
|
+
it('user input → chain storage', () => {
|
|
550
|
+
const userInput = '0.5';
|
|
551
|
+
const baseUnit = BigAmount.fromPrecision({ value: userInput, precision: 8 }).toBaseUnit();
|
|
552
|
+
expect(baseUnit).toBe('50000000');
|
|
553
|
+
});
|
|
554
|
+
it('guard check: balance is zero', () => {
|
|
555
|
+
expect(BigAmount.fromPrecision({ value: '0', precision: 8 }).isZero()).toBe(true);
|
|
556
|
+
});
|
|
557
|
+
it('dimensionless scalar multiplication (rate, distribution, etc.)', () => {
|
|
558
|
+
const cryptoBaseUnit = '500000000';
|
|
559
|
+
const distributionRate = 0.5;
|
|
560
|
+
const result = BigAmount.fromBaseUnit({ value: cryptoBaseUnit, precision: 8 }).times(distributionRate);
|
|
561
|
+
expect(result.toPrecision()).toBe('2.5');
|
|
562
|
+
expect(result.toBaseUnit()).toBe('250000000');
|
|
563
|
+
});
|
|
564
|
+
it('comparison with maximum amount', () => {
|
|
565
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
566
|
+
expect(amount.gt('1.0')).toBe(true);
|
|
567
|
+
});
|
|
568
|
+
it('contract interaction: BigAmount → BigInt via BigInt(toBaseUnit())', () => {
|
|
569
|
+
const userInput = '1.5';
|
|
570
|
+
expect(BigInt(BigAmount.fromPrecision({ value: userInput, precision: 18 }).toBaseUnit())).toBe(1500000000000000000n);
|
|
571
|
+
});
|
|
572
|
+
it('ETH gas calculation', () => {
|
|
573
|
+
const gasLimit = '21000';
|
|
574
|
+
const gasPriceGwei = '30';
|
|
575
|
+
const gasCostWei = bn(gasLimit).times(bn(gasPriceGwei).times(1e9)).toFixed(0);
|
|
576
|
+
const gasCostEth = BigAmount.fromBaseUnit({ value: gasCostWei, precision: 18 });
|
|
577
|
+
expect(gasCostEth.toPrecision()).toBe('0.00063');
|
|
578
|
+
});
|
|
579
|
+
it('ERC-20 approval with exact BigInt (viem pattern)', () => {
|
|
580
|
+
const amount = BigAmount.fromPrecision({ value: '100', precision: 6 });
|
|
581
|
+
const bigIntForContract = BigInt(amount.toBaseUnit());
|
|
582
|
+
expect(bigIntForContract).toBe(100000000n);
|
|
583
|
+
expect(typeof bigIntForContract).toBe('bigint');
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
describe('base-unit internal storage', () => {
|
|
587
|
+
it('fromBaseUnit stores value directly (no division)', () => {
|
|
588
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
589
|
+
expect(amount.toBaseUnit()).toBe('150000000');
|
|
590
|
+
});
|
|
591
|
+
it('fromPrecision converts to base units for storage', () => {
|
|
592
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
593
|
+
expect(amount.toBaseUnit()).toBe('150000000');
|
|
594
|
+
});
|
|
595
|
+
it('.times() operates on base units (dimensionless scalar)', () => {
|
|
596
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
597
|
+
expect(amount.times(2).toBaseUnit()).toBe('200');
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
describe('assetId', () => {
|
|
601
|
+
const mockConfig = {
|
|
602
|
+
resolvePrecision: (assetId) => {
|
|
603
|
+
if (assetId === 'bip122:000000000019d6689c085ae165831e93/slip44:0')
|
|
604
|
+
return 8;
|
|
605
|
+
if (assetId === 'test-asset')
|
|
606
|
+
return 8;
|
|
607
|
+
return 0;
|
|
608
|
+
},
|
|
609
|
+
resolvePrice: () => '0',
|
|
610
|
+
resolvePriceUsd: () => '0',
|
|
611
|
+
};
|
|
612
|
+
beforeAll(() => {
|
|
613
|
+
BigAmount.configure(mockConfig);
|
|
614
|
+
});
|
|
615
|
+
it('fromBaseUnit with assetId resolves precision and carries assetId', () => {
|
|
616
|
+
const amount = BigAmount.fromBaseUnit({
|
|
617
|
+
value: '150000000',
|
|
618
|
+
assetId: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
|
|
619
|
+
});
|
|
620
|
+
expect(amount.assetId).toBe('bip122:000000000019d6689c085ae165831e93/slip44:0');
|
|
621
|
+
expect(amount.precision).toBe(8);
|
|
622
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
623
|
+
});
|
|
624
|
+
it('fromPrecision with assetId resolves precision and carries assetId', () => {
|
|
625
|
+
const amount = BigAmount.fromPrecision({
|
|
626
|
+
value: '1.5',
|
|
627
|
+
assetId: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
|
|
628
|
+
});
|
|
629
|
+
expect(amount.assetId).toBe('bip122:000000000019d6689c085ae165831e93/slip44:0');
|
|
630
|
+
expect(amount.precision).toBe(8);
|
|
631
|
+
});
|
|
632
|
+
it('assetId is undefined when using precision variant', () => {
|
|
633
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
634
|
+
expect(amount.assetId).toBeUndefined();
|
|
635
|
+
});
|
|
636
|
+
it('assetId propagates through arithmetic', () => {
|
|
637
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', assetId: 'test-asset' });
|
|
638
|
+
expect(amount.plus('1').assetId).toBe('test-asset');
|
|
639
|
+
expect(amount.minus('1').assetId).toBe('test-asset');
|
|
640
|
+
expect(amount.times(2).assetId).toBe('test-asset');
|
|
641
|
+
expect(amount.div(2).assetId).toBe('test-asset');
|
|
642
|
+
expect(amount.abs().assetId).toBe('test-asset');
|
|
643
|
+
expect(amount.negated().assetId).toBe('test-asset');
|
|
644
|
+
});
|
|
645
|
+
it('zero carries optional assetId', () => {
|
|
646
|
+
const amount = BigAmount.zero({ precision: 8, assetId: 'test-asset' });
|
|
647
|
+
expect(amount.assetId).toBe('test-asset');
|
|
648
|
+
expect(amount.isZero()).toBe(true);
|
|
649
|
+
});
|
|
650
|
+
it('toJSON includes assetId when present', () => {
|
|
651
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', assetId: 'test-asset' });
|
|
652
|
+
expect(amount.toJSON()).toEqual({ value: '100', precision: 8, assetId: 'test-asset' });
|
|
653
|
+
});
|
|
654
|
+
it('toJSON omits assetId when not present', () => {
|
|
655
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
656
|
+
expect(amount.toJSON()).toEqual({ value: '100', precision: 8, assetId: undefined });
|
|
657
|
+
});
|
|
658
|
+
it('fromJSON round-trips value and precision', () => {
|
|
659
|
+
const original = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
660
|
+
const restored = BigAmount.fromJSON(original.toJSON());
|
|
661
|
+
expect(restored.toBaseUnit()).toBe('100');
|
|
662
|
+
expect(restored.precision).toBe(8);
|
|
663
|
+
});
|
|
664
|
+
it('discriminated union prevents passing both precision and assetId', () => {
|
|
665
|
+
// @ts-expect-error — discriminated union: precision and assetId are mutually exclusive
|
|
666
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8, assetId: 'test-asset' });
|
|
667
|
+
expect(amount).toBeDefined();
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
describe('configure', () => {
|
|
671
|
+
const mockConfig = {
|
|
672
|
+
resolvePrecision: (assetId) => {
|
|
673
|
+
if (assetId === 'btc')
|
|
674
|
+
return 8;
|
|
675
|
+
if (assetId === 'eth')
|
|
676
|
+
return 18;
|
|
677
|
+
if (assetId === 'usdc')
|
|
678
|
+
return 6;
|
|
679
|
+
return 0;
|
|
680
|
+
},
|
|
681
|
+
resolvePrice: () => '0',
|
|
682
|
+
resolvePriceUsd: () => '0',
|
|
683
|
+
};
|
|
684
|
+
beforeAll(() => {
|
|
685
|
+
BigAmount.configure(mockConfig);
|
|
686
|
+
});
|
|
687
|
+
it('fromBaseUnit with assetId resolves precision from config', () => {
|
|
688
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
689
|
+
expect(amount.precision).toBe(8);
|
|
690
|
+
expect(amount.assetId).toBe('btc');
|
|
691
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
692
|
+
});
|
|
693
|
+
it('fromPrecision with assetId resolves precision from config', () => {
|
|
694
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', assetId: 'btc' });
|
|
695
|
+
expect(amount.precision).toBe(8);
|
|
696
|
+
expect(amount.toBaseUnit()).toBe('150000000');
|
|
697
|
+
});
|
|
698
|
+
it('fromBaseUnit with assetId works for ETH (18 decimals)', () => {
|
|
699
|
+
const amount = BigAmount.fromBaseUnit({
|
|
700
|
+
value: '1000000000000000000',
|
|
701
|
+
assetId: 'eth',
|
|
702
|
+
});
|
|
703
|
+
expect(amount.precision).toBe(18);
|
|
704
|
+
expect(amount.toPrecision()).toBe('1');
|
|
705
|
+
});
|
|
706
|
+
it('precision variant has no assetId (discriminated union)', () => {
|
|
707
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 4 });
|
|
708
|
+
expect(amount.precision).toBe(4);
|
|
709
|
+
expect(amount.assetId).toBeUndefined();
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
describe('NullableScalar widening', () => {
|
|
713
|
+
const amount = BigAmount.fromPrecision({ value: '10', precision: 8 });
|
|
714
|
+
describe('times accepts null/undefined', () => {
|
|
715
|
+
it('times(null) returns zero', () => {
|
|
716
|
+
expect(amount.times(null).isZero()).toBe(true);
|
|
717
|
+
});
|
|
718
|
+
it('times(undefined) returns zero', () => {
|
|
719
|
+
expect(amount.times(undefined).isZero()).toBe(true);
|
|
720
|
+
});
|
|
721
|
+
it('times(string) still works', () => {
|
|
722
|
+
expect(amount.times('2').toPrecision()).toBe('20');
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
describe('div accepts null/undefined', () => {
|
|
726
|
+
it('div(null) returns zero (safe division)', () => {
|
|
727
|
+
expect(amount.div(null).isZero()).toBe(true);
|
|
728
|
+
});
|
|
729
|
+
it('div(undefined) returns zero (safe division)', () => {
|
|
730
|
+
expect(amount.div(undefined).isZero()).toBe(true);
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
describe('plus accepts null/undefined scalar', () => {
|
|
734
|
+
it('plus(null) is identity', () => {
|
|
735
|
+
expect(amount.plus(null).eq(amount)).toBe(true);
|
|
736
|
+
});
|
|
737
|
+
it('plus(undefined) is identity', () => {
|
|
738
|
+
expect(amount.plus(undefined).eq(amount)).toBe(true);
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
describe('minus accepts null/undefined scalar', () => {
|
|
742
|
+
it('minus(null) is identity', () => {
|
|
743
|
+
expect(amount.minus(null).eq(amount)).toBe(true);
|
|
744
|
+
});
|
|
745
|
+
it('minus(undefined) is identity', () => {
|
|
746
|
+
expect(amount.minus(undefined).eq(amount)).toBe(true);
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
describe('comparison accepts null/undefined scalar', () => {
|
|
750
|
+
it('gt(null) — positive amount is greater than zero', () => {
|
|
751
|
+
expect(amount.gt(null)).toBe(true);
|
|
752
|
+
});
|
|
753
|
+
it('gt(undefined) — positive amount is greater than zero', () => {
|
|
754
|
+
expect(amount.gt(undefined)).toBe(true);
|
|
755
|
+
});
|
|
756
|
+
it('gte(null) — positive amount is gte zero', () => {
|
|
757
|
+
expect(amount.gte(null)).toBe(true);
|
|
758
|
+
});
|
|
759
|
+
it('gte(undefined) — positive amount is gte zero', () => {
|
|
760
|
+
expect(amount.gte(undefined)).toBe(true);
|
|
761
|
+
});
|
|
762
|
+
it('lt(null) — positive amount is NOT less than zero', () => {
|
|
763
|
+
expect(amount.lt(null)).toBe(false);
|
|
764
|
+
});
|
|
765
|
+
it('lt(undefined) — positive amount is NOT less than zero', () => {
|
|
766
|
+
expect(amount.lt(undefined)).toBe(false);
|
|
767
|
+
});
|
|
768
|
+
it('lte(null) — positive amount is NOT lte zero', () => {
|
|
769
|
+
expect(amount.lte(null)).toBe(false);
|
|
770
|
+
});
|
|
771
|
+
it('lte(undefined) — positive amount is NOT lte zero', () => {
|
|
772
|
+
expect(amount.lte(undefined)).toBe(false);
|
|
773
|
+
});
|
|
774
|
+
it('eq(null) — non-zero amount is NOT equal to zero', () => {
|
|
775
|
+
expect(amount.eq(null)).toBe(false);
|
|
776
|
+
});
|
|
777
|
+
it('eq(undefined) — non-zero amount is NOT equal to zero', () => {
|
|
778
|
+
expect(amount.eq(undefined)).toBe(false);
|
|
779
|
+
});
|
|
780
|
+
it('zero eq(null) — zero IS equal to null-as-zero', () => {
|
|
781
|
+
expect(BigAmount.zero({ precision: 8 }).eq(null)).toBe(true);
|
|
782
|
+
});
|
|
783
|
+
it('zero eq(undefined) — zero IS equal to undefined-as-zero', () => {
|
|
784
|
+
expect(BigAmount.zero({ precision: 8 }).eq(undefined)).toBe(true);
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
describe('real-world pattern: optional chaining without bnOrZero', () => {
|
|
788
|
+
it('balance.times(marketData?.price) where price is undefined', () => {
|
|
789
|
+
const balance = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
790
|
+
const marketData = {};
|
|
791
|
+
const result = balance.times(marketData?.price);
|
|
792
|
+
expect(result.isZero()).toBe(true);
|
|
793
|
+
});
|
|
794
|
+
it('balance.times(marketData?.price) where price exists', () => {
|
|
795
|
+
const balance = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
796
|
+
const marketData = { price: '60000' };
|
|
797
|
+
const result = balance.times(marketData?.price);
|
|
798
|
+
expect(result.toPrecision()).toBe('90000');
|
|
799
|
+
});
|
|
800
|
+
it('amount.gt(threshold) where threshold is undefined', () => {
|
|
801
|
+
const balance = BigAmount.fromPrecision({ value: '100', precision: 8 });
|
|
802
|
+
const threshold = undefined;
|
|
803
|
+
expect(balance.gt(threshold)).toBe(true);
|
|
804
|
+
});
|
|
805
|
+
it('amount.lt(limit) where limit is null', () => {
|
|
806
|
+
const balance = BigAmount.fromPrecision({ value: '100', precision: 8 });
|
|
807
|
+
const limit = null;
|
|
808
|
+
expect(balance.lt(limit)).toBe(false);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
describe('div safety', () => {
|
|
813
|
+
it('div(0) returns zero instead of Infinity', () => {
|
|
814
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
815
|
+
expect(amount.div(0).isZero()).toBe(true);
|
|
816
|
+
expect(amount.div(0).isFinite()).toBe(true);
|
|
817
|
+
});
|
|
818
|
+
it('div(null) returns zero', () => {
|
|
819
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
820
|
+
expect(amount.div(null).isZero()).toBe(true);
|
|
821
|
+
});
|
|
822
|
+
it('div(undefined) returns zero', () => {
|
|
823
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
824
|
+
expect(amount.div(undefined).isZero()).toBe(true);
|
|
825
|
+
});
|
|
826
|
+
it('div("") returns zero', () => {
|
|
827
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
828
|
+
expect(amount.div('').isZero()).toBe(true);
|
|
829
|
+
});
|
|
830
|
+
it('div("0") returns zero', () => {
|
|
831
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
832
|
+
expect(amount.div('0').isZero()).toBe(true);
|
|
833
|
+
});
|
|
834
|
+
it('div on zero amount by zero returns zero', () => {
|
|
835
|
+
expect(BigAmount.zero({ precision: 18 }).div(0).isZero()).toBe(true);
|
|
836
|
+
});
|
|
837
|
+
it('div by valid divisor still works', () => {
|
|
838
|
+
const amount = BigAmount.fromPrecision({ value: '10', precision: 8 });
|
|
839
|
+
expect(amount.div(2).toPrecision()).toBe('5');
|
|
840
|
+
});
|
|
841
|
+
it('preserves precision', () => {
|
|
842
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
843
|
+
const result = amount.div(0);
|
|
844
|
+
expect(result.precision).toBe(8);
|
|
845
|
+
});
|
|
846
|
+
it('preserves assetId through div(0)', () => {
|
|
847
|
+
const amount = BigAmount.zero({ precision: 8, assetId: 'test-asset' }).plus(BigAmount.fromBaseUnit({ value: '100', precision: 8 }));
|
|
848
|
+
const result = amount.div(0);
|
|
849
|
+
expect(result.assetId).toBe('test-asset');
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
describe('unconfigured state', () => {
|
|
853
|
+
let savedConfig;
|
|
854
|
+
beforeAll(() => {
|
|
855
|
+
savedConfig = BigAmount.getConfig();
|
|
856
|
+
BigAmount.resetConfig();
|
|
857
|
+
});
|
|
858
|
+
afterAll(() => {
|
|
859
|
+
if (savedConfig)
|
|
860
|
+
BigAmount.configure(savedConfig);
|
|
861
|
+
});
|
|
862
|
+
it('throws when using assetId resolution without config', () => {
|
|
863
|
+
expect(() => BigAmount.fromBaseUnit({ value: '1000000', assetId: 'eip155:1/slip44:60' })).toThrow('BigAmount: not configured');
|
|
864
|
+
});
|
|
865
|
+
it('works fine with explicit precision when unconfigured', () => {
|
|
866
|
+
const amount = BigAmount.fromBaseUnit({ value: '1000000', precision: 6 });
|
|
867
|
+
expect(amount.toPrecision()).toBe('1');
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
describe('negative precision', () => {
|
|
871
|
+
it('handles negative precision mathematically (multiplies instead of divides)', () => {
|
|
872
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: -2 });
|
|
873
|
+
expect(amount.toPrecision()).toBe('10000');
|
|
874
|
+
});
|
|
875
|
+
it('round-trips with negative precision', () => {
|
|
876
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: -2 });
|
|
877
|
+
expect(amount.toBaseUnit()).toBe('100');
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
describe('toBN', () => {
|
|
881
|
+
it('returns precision-scale BigNumber', () => {
|
|
882
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
883
|
+
const result = amount.toBN();
|
|
884
|
+
expect(result.toString()).toBe('1.5');
|
|
885
|
+
});
|
|
886
|
+
it('can be used in BN arithmetic chains', () => {
|
|
887
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
888
|
+
expect(amount.toBN().times(60000).toFixed(2)).toBe('90000.00');
|
|
889
|
+
});
|
|
890
|
+
it('returns zero for zero amount', () => {
|
|
891
|
+
expect(BigAmount.zero({ precision: 8 }).toBN().isZero()).toBe(true);
|
|
892
|
+
});
|
|
893
|
+
it('handles negative values', () => {
|
|
894
|
+
const amount = BigAmount.fromPrecision({ value: '-1.5', precision: 8 });
|
|
895
|
+
expect(amount.toBN().toString()).toBe('-1.5');
|
|
896
|
+
});
|
|
897
|
+
it('avoids string round-trip vs manual conversion', () => {
|
|
898
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
899
|
+
const viaBN = amount.toBN();
|
|
900
|
+
const viaString = bn(amount.toPrecision());
|
|
901
|
+
expect(viaBN.eq(viaString)).toBe(true);
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
describe('discriminated union enforcement', () => {
|
|
905
|
+
const mockConfig = {
|
|
906
|
+
resolvePrecision: (assetId) => {
|
|
907
|
+
if (assetId === 'btc')
|
|
908
|
+
return 8;
|
|
909
|
+
if (assetId === 'eth')
|
|
910
|
+
return 18;
|
|
911
|
+
if (assetId === 'usdc')
|
|
912
|
+
return 6;
|
|
913
|
+
return 0;
|
|
914
|
+
},
|
|
915
|
+
resolvePrice: () => '0',
|
|
916
|
+
resolvePriceUsd: () => '0',
|
|
917
|
+
};
|
|
918
|
+
beforeAll(() => {
|
|
919
|
+
BigAmount.configure(mockConfig);
|
|
920
|
+
});
|
|
921
|
+
it('fromBaseUnit with precision only — no assetId', () => {
|
|
922
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
923
|
+
expect(amount.precision).toBe(8);
|
|
924
|
+
expect(amount.assetId).toBeUndefined();
|
|
925
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
926
|
+
});
|
|
927
|
+
it('fromBaseUnit with assetId only — resolves precision from config', () => {
|
|
928
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
929
|
+
expect(amount.precision).toBe(8);
|
|
930
|
+
expect(amount.assetId).toBe('btc');
|
|
931
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
932
|
+
});
|
|
933
|
+
it('fromPrecision with precision only — no assetId', () => {
|
|
934
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', precision: 8 });
|
|
935
|
+
expect(amount.precision).toBe(8);
|
|
936
|
+
expect(amount.assetId).toBeUndefined();
|
|
937
|
+
});
|
|
938
|
+
it('fromPrecision with assetId only — resolves precision from config', () => {
|
|
939
|
+
const amount = BigAmount.fromPrecision({ value: '1.5', assetId: 'btc' });
|
|
940
|
+
expect(amount.precision).toBe(8);
|
|
941
|
+
expect(amount.assetId).toBe('btc');
|
|
942
|
+
expect(amount.toBaseUnit()).toBe('150000000');
|
|
943
|
+
});
|
|
944
|
+
it('assetId variant resolves ETH precision 18 correctly', () => {
|
|
945
|
+
const amount = BigAmount.fromBaseUnit({
|
|
946
|
+
value: '1000000000000000000',
|
|
947
|
+
assetId: 'eth',
|
|
948
|
+
});
|
|
949
|
+
expect(amount.precision).toBe(18);
|
|
950
|
+
expect(amount.toPrecision()).toBe('1');
|
|
951
|
+
});
|
|
952
|
+
it('assetId variant resolves USDC precision 6 correctly', () => {
|
|
953
|
+
const amount = BigAmount.fromPrecision({ value: '100', assetId: 'usdc' });
|
|
954
|
+
expect(amount.precision).toBe(6);
|
|
955
|
+
expect(amount.toBaseUnit()).toBe('100000000');
|
|
956
|
+
});
|
|
957
|
+
it('passing both precision and assetId is a TypeScript error', () => {
|
|
958
|
+
// @ts-expect-error — discriminated union: precision and assetId are mutually exclusive
|
|
959
|
+
BigAmount.fromBaseUnit({ value: '100', precision: 8, assetId: 'btc' });
|
|
960
|
+
// @ts-expect-error — discriminated union: precision and assetId are mutually exclusive
|
|
961
|
+
BigAmount.fromPrecision({ value: '1', precision: 8, assetId: 'btc' });
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
describe('uint256-scale values', () => {
|
|
965
|
+
const maxUint256 = '115792089237316195423570985008687907853269984665640564039457584007913129639935';
|
|
966
|
+
it('handles max uint256', () => {
|
|
967
|
+
const amount = BigAmount.fromBaseUnit({ value: maxUint256, precision: 18 });
|
|
968
|
+
expect(amount.toBaseUnit()).toBe(maxUint256);
|
|
969
|
+
expect(amount.isFinite()).toBe(true);
|
|
970
|
+
expect(amount.isZero()).toBe(false);
|
|
971
|
+
});
|
|
972
|
+
it('round-trips uint256 through toJSON/fromJSON', () => {
|
|
973
|
+
const amount = BigAmount.fromBaseUnit({ value: maxUint256, precision: 18 });
|
|
974
|
+
const restored = BigAmount.fromJSON(amount.toJSON());
|
|
975
|
+
expect(restored.toBaseUnit()).toBe(maxUint256);
|
|
976
|
+
});
|
|
977
|
+
it('arithmetic on uint256 values', () => {
|
|
978
|
+
const large = '100000000000000000000000000000000000000';
|
|
979
|
+
const amount = BigAmount.fromBaseUnit({ value: large, precision: 18 });
|
|
980
|
+
expect(amount.times(2).div(2).toBaseUnit()).toBe(large);
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
describe('precision 0', () => {
|
|
984
|
+
it('constructs with precision 0', () => {
|
|
985
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 0 });
|
|
986
|
+
expect(amount.toPrecision()).toBe('100');
|
|
987
|
+
expect(amount.toBaseUnit()).toBe('100');
|
|
988
|
+
});
|
|
989
|
+
it('round-trips with precision 0', () => {
|
|
990
|
+
expect(BigAmount.fromBaseUnit({ value: '42', precision: 0 }).toBaseUnit()).toBe('42');
|
|
991
|
+
});
|
|
992
|
+
it('zero with precision 0', () => {
|
|
993
|
+
const z = BigAmount.zero({ precision: 0 });
|
|
994
|
+
expect(z.isZero()).toBe(true);
|
|
995
|
+
expect(z.toBaseUnit()).toBe('0');
|
|
996
|
+
expect(z.toPrecision()).toBe('0');
|
|
997
|
+
});
|
|
998
|
+
it('arithmetic with precision 0', () => {
|
|
999
|
+
const a = BigAmount.fromBaseUnit({ value: '10', precision: 0 });
|
|
1000
|
+
const b = BigAmount.fromBaseUnit({ value: '3', precision: 0 });
|
|
1001
|
+
expect(a.plus(b).toBaseUnit()).toBe('13');
|
|
1002
|
+
});
|
|
1003
|
+
it('fromPrecision with precision 0 is same as fromBaseUnit', () => {
|
|
1004
|
+
const a = BigAmount.fromBaseUnit({ value: '100', precision: 0 });
|
|
1005
|
+
const b = BigAmount.fromPrecision({ value: '100', precision: 0 });
|
|
1006
|
+
expect(a.eq(b)).toBe(true);
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
1009
|
+
describe('negative values in fromBaseUnit', () => {
|
|
1010
|
+
it('handles negative base unit values', () => {
|
|
1011
|
+
const amount = BigAmount.fromBaseUnit({ value: '-150000000', precision: 8 });
|
|
1012
|
+
expect(amount.toPrecision()).toBe('-1.5');
|
|
1013
|
+
expect(amount.isNegative()).toBe(true);
|
|
1014
|
+
expect(amount.toBaseUnit()).toBe('-150000000');
|
|
1015
|
+
});
|
|
1016
|
+
it('round-trips negative values', () => {
|
|
1017
|
+
const amount = BigAmount.fromBaseUnit({ value: '-1000000000000000000', precision: 18 });
|
|
1018
|
+
expect(amount.toBaseUnit()).toBe('-1000000000000000000');
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
describe('toBaseUnit rounding', () => {
|
|
1022
|
+
it('rounds correctly after fractional multiplication', () => {
|
|
1023
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
1024
|
+
expect(amount.times(0.333).toBaseUnit()).toBe('33');
|
|
1025
|
+
});
|
|
1026
|
+
it('rounds at .5 boundary (ROUND_HALF_UP)', () => {
|
|
1027
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
1028
|
+
expect(amount.times(0.005).toBaseUnit()).toBe('1');
|
|
1029
|
+
});
|
|
1030
|
+
it('rounds down below .5', () => {
|
|
1031
|
+
const amount = BigAmount.fromBaseUnit({ value: '100', precision: 8 });
|
|
1032
|
+
// 100 * 0.004 = 0.4 → rounds to 0
|
|
1033
|
+
expect(amount.times(0.004).toBaseUnit()).toBe('0');
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
describe('precision stability over chained operations', () => {
|
|
1037
|
+
it('maintains exact value through 20 times(2)/div(2) cycles', () => {
|
|
1038
|
+
let amount = BigAmount.fromBaseUnit({ value: '1000000000000000000', precision: 18 });
|
|
1039
|
+
for (let i = 0; i < 20; i++) {
|
|
1040
|
+
amount = amount.times(2).div(2);
|
|
1041
|
+
}
|
|
1042
|
+
expect(amount.toBaseUnit()).toBe('1000000000000000000');
|
|
1043
|
+
});
|
|
1044
|
+
it('maintains value through plus/minus cycles', () => {
|
|
1045
|
+
const original = BigAmount.fromPrecision({ value: '100', precision: 8 });
|
|
1046
|
+
let amount = original;
|
|
1047
|
+
for (let i = 0; i < 10; i++) {
|
|
1048
|
+
amount = amount.plus('50').minus('50');
|
|
1049
|
+
}
|
|
1050
|
+
expect(amount.eq(original)).toBe(true);
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
describe('toSignificant edge cases', () => {
|
|
1054
|
+
it('toSignificant(0) returns integer part only', () => {
|
|
1055
|
+
const amount = BigAmount.fromPrecision({ value: '12.345', precision: 18 });
|
|
1056
|
+
expect(amount.toSignificant(0)).toBe('12');
|
|
1057
|
+
});
|
|
1058
|
+
it('toSignificant(1) returns one significant digit after leading zeros', () => {
|
|
1059
|
+
const amount = BigAmount.fromPrecision({ value: '0.00456', precision: 18 });
|
|
1060
|
+
expect(amount.toSignificant(1)).toBe('0.004');
|
|
1061
|
+
});
|
|
1062
|
+
it('toSignificant(1) on value with integer part', () => {
|
|
1063
|
+
const amount = BigAmount.fromPrecision({ value: '5.678', precision: 18 });
|
|
1064
|
+
expect(amount.toSignificant(1)).toBe('5');
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
describe('decimalPlaces edge cases', () => {
|
|
1068
|
+
it('handles zero decimal places', () => {
|
|
1069
|
+
const amount = BigAmount.fromBaseUnit({ value: '1234567890', precision: 8 });
|
|
1070
|
+
expect(amount.decimalPlaces(0).toPrecision()).toBe('12');
|
|
1071
|
+
});
|
|
1072
|
+
it('handles decimal places larger than precision', () => {
|
|
1073
|
+
const amount = BigAmount.fromBaseUnit({ value: '1234567890', precision: 8 });
|
|
1074
|
+
expect(amount.decimalPlaces(20).toPrecision()).toBe('12.3456789');
|
|
1075
|
+
});
|
|
1076
|
+
it('handles dust amount with many leading zeros', () => {
|
|
1077
|
+
const amount = BigAmount.fromBaseUnit({ value: '1', precision: 18 });
|
|
1078
|
+
expect(amount.decimalPlaces(18).toPrecision()).toBe('0.000000000000000001');
|
|
1079
|
+
expect(amount.decimalPlaces(17).toPrecision()).toBe('0');
|
|
1080
|
+
});
|
|
1081
|
+
it('uses ROUND_HALF_UP by default', () => {
|
|
1082
|
+
const amount = BigAmount.fromBaseUnit({ value: '199999999', precision: 8 });
|
|
1083
|
+
expect(amount.decimalPlaces(2).toPrecision()).toBe('2');
|
|
1084
|
+
});
|
|
1085
|
+
});
|
|
1086
|
+
describe('toUserCurrency', () => {
|
|
1087
|
+
beforeAll(() => {
|
|
1088
|
+
BigAmount.configure({
|
|
1089
|
+
resolvePrecision: () => 8,
|
|
1090
|
+
resolvePrice: (assetId) => {
|
|
1091
|
+
if (assetId === 'btc')
|
|
1092
|
+
return '50000';
|
|
1093
|
+
return '0';
|
|
1094
|
+
},
|
|
1095
|
+
resolvePriceUsd: (assetId) => {
|
|
1096
|
+
if (assetId === 'btc')
|
|
1097
|
+
return '50000';
|
|
1098
|
+
return '0';
|
|
1099
|
+
},
|
|
1100
|
+
});
|
|
1101
|
+
});
|
|
1102
|
+
afterAll(() => {
|
|
1103
|
+
BigAmount.resetConfig();
|
|
1104
|
+
});
|
|
1105
|
+
it('converts to user currency with default 2 decimals', () => {
|
|
1106
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
1107
|
+
expect(amount.toUserCurrency()).toBe('75000.00');
|
|
1108
|
+
});
|
|
1109
|
+
it('converts to user currency with custom decimals', () => {
|
|
1110
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
1111
|
+
expect(amount.toUserCurrency(4)).toBe('75000.0000');
|
|
1112
|
+
});
|
|
1113
|
+
it('throws without assetId', () => {
|
|
1114
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
1115
|
+
expect(() => amount.toUserCurrency()).toThrow('requires assetId');
|
|
1116
|
+
});
|
|
1117
|
+
it('throws without config', () => {
|
|
1118
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
1119
|
+
BigAmount.resetConfig();
|
|
1120
|
+
expect(() => amount.toUserCurrency()).toThrow('not configured');
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
1123
|
+
describe('toUSD', () => {
|
|
1124
|
+
beforeAll(() => {
|
|
1125
|
+
BigAmount.configure({
|
|
1126
|
+
resolvePrecision: () => 8,
|
|
1127
|
+
resolvePrice: () => '0',
|
|
1128
|
+
resolvePriceUsd: (assetId) => {
|
|
1129
|
+
if (assetId === 'btc')
|
|
1130
|
+
return '50000';
|
|
1131
|
+
return '0';
|
|
1132
|
+
},
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
afterAll(() => {
|
|
1136
|
+
BigAmount.resetConfig();
|
|
1137
|
+
});
|
|
1138
|
+
it('converts to USD with default 2 decimals', () => {
|
|
1139
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
1140
|
+
expect(amount.toUSD()).toBe('75000.00');
|
|
1141
|
+
});
|
|
1142
|
+
it('converts to USD with custom decimals', () => {
|
|
1143
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
1144
|
+
expect(amount.toUSD(0)).toBe('75000');
|
|
1145
|
+
});
|
|
1146
|
+
it('throws without assetId', () => {
|
|
1147
|
+
const amount = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
1148
|
+
expect(() => amount.toUSD()).toThrow('requires assetId');
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
describe('fromBN with assetId', () => {
|
|
1152
|
+
beforeAll(() => {
|
|
1153
|
+
BigAmount.configure({
|
|
1154
|
+
resolvePrecision: () => 8,
|
|
1155
|
+
resolvePrice: () => '0',
|
|
1156
|
+
resolvePriceUsd: () => '0',
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
afterAll(() => {
|
|
1160
|
+
BigAmount.resetConfig();
|
|
1161
|
+
});
|
|
1162
|
+
it('carries assetId through fromBN', () => {
|
|
1163
|
+
const amount = BigAmount.fromBN({ value: bn('1.5'), precision: 8, assetId: 'btc' });
|
|
1164
|
+
expect(amount.assetId).toBe('btc');
|
|
1165
|
+
expect(amount.toPrecision()).toBe('1.5');
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
describe('toJSON/fromJSON with assetId', () => {
|
|
1169
|
+
it('round-trips assetId through JSON', () => {
|
|
1170
|
+
const original = BigAmount.fromBaseUnit({ value: '150000000', precision: 8 });
|
|
1171
|
+
const json = original.toJSON();
|
|
1172
|
+
expect(json.assetId).toBeUndefined();
|
|
1173
|
+
const restored = BigAmount.fromJSON(json);
|
|
1174
|
+
expect(restored.toPrecision()).toBe('1.5');
|
|
1175
|
+
expect(restored.assetId).toBeUndefined();
|
|
1176
|
+
});
|
|
1177
|
+
it('round-trips assetId when present', () => {
|
|
1178
|
+
BigAmount.configure({
|
|
1179
|
+
resolvePrecision: () => 8,
|
|
1180
|
+
resolvePrice: () => '0',
|
|
1181
|
+
resolvePriceUsd: () => '0',
|
|
1182
|
+
});
|
|
1183
|
+
const original = BigAmount.fromBaseUnit({ value: '150000000', assetId: 'btc' });
|
|
1184
|
+
const json = original.toJSON();
|
|
1185
|
+
expect(json.assetId).toBe('btc');
|
|
1186
|
+
const restored = BigAmount.fromJSON(json);
|
|
1187
|
+
expect(restored.toPrecision()).toBe('1.5');
|
|
1188
|
+
expect(restored.assetId).toBe('btc');
|
|
1189
|
+
BigAmount.resetConfig();
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
});
|