@lightsparkdev/core 1.2.6 → 1.2.8

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.
@@ -20,6 +20,7 @@ export const CurrencyUnit = {
20
20
  MILLIBITCOIN: "MILLIBITCOIN",
21
21
  USD: "USD",
22
22
  MXN: "MXN",
23
+ PHP: "PHP",
23
24
 
24
25
  Bitcoin: "BITCOIN",
25
26
  Microbitcoin: "MICROBITCOIN",
@@ -29,6 +30,7 @@ export const CurrencyUnit = {
29
30
  Satoshi: "SATOSHI",
30
31
  Usd: "USD",
31
32
  Mxn: "MXN",
33
+ Php: "PHP",
32
34
  } as const;
33
35
 
34
36
  export type CurrencyUnitType = (typeof CurrencyUnit)[keyof typeof CurrencyUnit];
@@ -56,6 +58,7 @@ const standardUnitConversionObj = {
56
58
  /* Converting between two different fiat types is not currently supported */
57
59
  [CurrencyUnit.USD]: (v: number) => v,
58
60
  [CurrencyUnit.MXN]: (v: number) => v,
61
+ [CurrencyUnit.PHP]: (v: number) => v,
59
62
  };
60
63
 
61
64
  /* Round without decimals since we're returning cents: */
@@ -82,6 +85,7 @@ const CONVERSION_MAP = {
82
85
  [CurrencyUnit.SATOSHI]: (v: number) => v * 100_000_000,
83
86
  [CurrencyUnit.USD]: toBitcoinConversion,
84
87
  [CurrencyUnit.MXN]: toBitcoinConversion,
88
+ [CurrencyUnit.PHP]: toBitcoinConversion,
85
89
  },
86
90
  [CurrencyUnit.MICROBITCOIN]: {
87
91
  [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000,
@@ -92,6 +96,7 @@ const CONVERSION_MAP = {
92
96
  [CurrencyUnit.SATOSHI]: (v: number) => v * 100,
93
97
  [CurrencyUnit.USD]: toMicrobitcoinConversion,
94
98
  [CurrencyUnit.MXN]: toMicrobitcoinConversion,
99
+ [CurrencyUnit.PHP]: toMicrobitcoinConversion,
95
100
  },
96
101
  [CurrencyUnit.MILLIBITCOIN]: {
97
102
  [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000,
@@ -102,6 +107,7 @@ const CONVERSION_MAP = {
102
107
  [CurrencyUnit.SATOSHI]: (v: number) => v * 100_000,
103
108
  [CurrencyUnit.USD]: toMillibitcoinConversion,
104
109
  [CurrencyUnit.MXN]: toMillibitcoinConversion,
110
+ [CurrencyUnit.PHP]: toMillibitcoinConversion,
105
111
  },
106
112
  [CurrencyUnit.MILLISATOSHI]: {
107
113
  [CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000_000,
@@ -112,6 +118,7 @@ const CONVERSION_MAP = {
112
118
  [CurrencyUnit.SATOSHI]: (v: number) => v / 1000,
113
119
  [CurrencyUnit.USD]: toMillisatoshiConversion,
114
120
  [CurrencyUnit.MXN]: toMillisatoshiConversion,
121
+ [CurrencyUnit.PHP]: toMillisatoshiConversion,
115
122
  },
116
123
  [CurrencyUnit.NANOBITCOIN]: {
117
124
  [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000_000,
@@ -122,6 +129,7 @@ const CONVERSION_MAP = {
122
129
  [CurrencyUnit.SATOSHI]: (v: number) => v / 10,
123
130
  [CurrencyUnit.USD]: toNanobitcoinConversion,
124
131
  [CurrencyUnit.MXN]: toNanobitcoinConversion,
132
+ [CurrencyUnit.PHP]: toNanobitcoinConversion,
125
133
  },
126
134
  [CurrencyUnit.SATOSHI]: {
127
135
  [CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000,
@@ -132,9 +140,11 @@ const CONVERSION_MAP = {
132
140
  [CurrencyUnit.SATOSHI]: (v: number) => v,
133
141
  [CurrencyUnit.USD]: toSatoshiConversion,
134
142
  [CurrencyUnit.MXN]: toSatoshiConversion,
143
+ [CurrencyUnit.PHP]: toSatoshiConversion,
135
144
  },
136
145
  [CurrencyUnit.USD]: standardUnitConversionObj,
137
146
  [CurrencyUnit.MXN]: standardUnitConversionObj,
147
+ [CurrencyUnit.PHP]: standardUnitConversionObj,
138
148
  };
139
149
 
140
150
  export function convertCurrencyAmountValue(
@@ -146,7 +156,7 @@ export function convertCurrencyAmountValue(
146
156
  units to provide value estimates where needed where a backend value is not available, eg
147
157
  previewing the approximate value of an amount to send. */
148
158
  unitsPerBtc = 1,
149
- ): number {
159
+ ) {
150
160
  if (
151
161
  fromUnit === CurrencyUnit.FUTURE_VALUE ||
152
162
  toUnit === CurrencyUnit.FUTURE_VALUE
@@ -198,6 +208,7 @@ export type CurrencyMap = {
198
208
  [CurrencyUnit.NANOBITCOIN]: number;
199
209
  [CurrencyUnit.USD]: number;
200
210
  [CurrencyUnit.MXN]: number;
211
+ [CurrencyUnit.PHP]: number;
201
212
  [CurrencyUnit.FUTURE_VALUE]: number;
202
213
  formatted: {
203
214
  sats: string;
@@ -211,6 +222,7 @@ export type CurrencyMap = {
211
222
  [CurrencyUnit.NANOBITCOIN]: string;
212
223
  [CurrencyUnit.USD]: string;
213
224
  [CurrencyUnit.MXN]: string;
225
+ [CurrencyUnit.PHP]: string;
214
226
  [CurrencyUnit.FUTURE_VALUE]: string;
215
227
  };
216
228
  isZero: boolean;
@@ -220,34 +232,47 @@ export type CurrencyMap = {
220
232
  type: "CurrencyMap";
221
233
  };
222
234
 
235
+ /* GQL CurrencyAmountInputs have this shape as well as client side CurrencyAmount objects.
236
+ * Technically value is always a number for GQL inputs. This is enforced by mutation input
237
+ * types. For client side utils we can have slightly more forgiving input and coerce with
238
+ * asNumber. */
239
+ export type CurrencyAmountInputObj = {
240
+ value: number | string | null;
241
+ unit: CurrencyUnitType;
242
+ };
243
+
244
+ /* Persisted CurrencyAmount objects may have this shape if queried from GQL in this format
245
+ but the fields are deprecated and original_unit and original_value should be used instead: */
223
246
  export type DeprecatedCurrencyAmountObj = {
224
- /* Technically the generated graphql schema has value as `any` but it's always a number.
225
- * We are intentionally widening the type here to allow for more forgiving input: */
226
- value?: number | string | null;
247
+ /* Technically the generated graphql schema has value as `any` but it's always a number: */
248
+ value?: number;
227
249
  /* assume satoshi if not provided */
228
250
  unit?: CurrencyUnitType;
229
- __typename?: "CurrencyAmount" | undefined;
251
+ __typename?: "CurrencyAmount";
230
252
  };
231
253
 
232
254
  export type CurrencyAmountObj = {
233
- /* Technically the generated graphql schema has value as `any` but it's always a number.
234
- * We are intentionally widening the type here to allow for more forgiving input: */
235
- original_value?: number | string | null;
255
+ /* Technically the generated graphql schema has value as `any` but it's always a number: */
256
+ original_value?: number;
236
257
  /* assume satoshi if not provided */
237
258
  original_unit?: CurrencyUnitType;
238
- __typename?: "CurrencyAmount" | undefined;
259
+ __typename?: "CurrencyAmount";
239
260
  };
240
261
 
241
262
  export type CurrencyAmountPreferenceObj = {
242
- /* Technically the generated graphql schema has value as `any` but it's always a number.
243
- * We are intentionally widening the type here to allow for more forgiving input: */
244
- preferred_currency_unit?: CurrencyUnitType;
245
- /* assume satoshi if not provided */
246
- preferred_currency_value_rounded?: number | string | null;
247
- __typename?: "CurrencyAmount" | undefined;
263
+ /* unit and value, along with original unit and value are all required for
264
+ * CurrencyAmountPreferenceObj - the preferred value is used for the corresponding unit
265
+ * but the original unit/value are also needed to ensure accurate conversion to other units */
266
+ original_value: number;
267
+ original_unit: CurrencyUnitType;
268
+ preferred_currency_unit: CurrencyUnitType;
269
+ /* Technically the generated graphql schema has value as `any` but it's always a number: */
270
+ preferred_currency_value_approx: number;
271
+ __typename?: "CurrencyAmount";
248
272
  };
249
273
 
250
274
  export type CurrencyAmountArg =
275
+ | CurrencyAmountInputObj
251
276
  | DeprecatedCurrencyAmountObj
252
277
  | CurrencyAmountObj
253
278
  | CurrencyAmountPreferenceObj
@@ -255,6 +280,21 @@ export type CurrencyAmountArg =
255
280
  | undefined
256
281
  | null;
257
282
 
283
+ export function isCurrencyAmountInputObj(
284
+ arg: unknown,
285
+ ): arg is CurrencyAmountInputObj {
286
+ return (
287
+ typeof arg === "object" &&
288
+ arg !== null &&
289
+ "value" in arg &&
290
+ (typeof arg.value === "number" ||
291
+ typeof arg.value === "string" ||
292
+ arg.value === null) &&
293
+ "unit" in arg &&
294
+ typeof arg.unit === "string"
295
+ );
296
+ }
297
+
258
298
  export function isDeprecatedCurrencyAmountObj(
259
299
  arg: unknown,
260
300
  ): arg is DeprecatedCurrencyAmountObj {
@@ -278,8 +318,14 @@ export function isCurrencyAmountPreferenceObj(
278
318
  return (
279
319
  typeof arg === "object" &&
280
320
  arg !== null &&
321
+ "original_unit" in arg &&
322
+ typeof arg.original_unit === "string" &&
323
+ "original_value" in arg &&
324
+ typeof arg.original_value === "number" &&
281
325
  "preferred_currency_unit" in arg &&
282
- "preferred_currency_value_rounded" in arg
326
+ typeof arg.preferred_currency_unit === "string" &&
327
+ "preferred_currency_value_approx" in arg &&
328
+ typeof arg.preferred_currency_value_approx === "number"
283
329
  );
284
330
  }
285
331
 
@@ -304,20 +350,20 @@ function asNumber(value: string | number | null | undefined) {
304
350
  return value || 0;
305
351
  }
306
352
 
307
- function getCurrencyAmount(currencyAmountArg: CurrencyAmountArg) {
353
+ export function getCurrencyAmount(currencyAmountArg: CurrencyAmountArg) {
308
354
  let value = 0;
309
355
  let unit = undefined;
310
356
 
311
357
  if (isSDKCurrencyAmount(currencyAmountArg)) {
312
358
  value = currencyAmountArg.originalValue;
313
359
  unit = currencyAmountArg.originalUnit;
314
- } else if (isCurrencyAmountPreferenceObj(currencyAmountArg)) {
315
- value = asNumber(currencyAmountArg.preferred_currency_value_rounded);
316
- unit = currencyAmountArg.preferred_currency_unit;
317
360
  } else if (isCurrencyAmountObj(currencyAmountArg)) {
318
361
  value = asNumber(currencyAmountArg.original_value);
319
362
  unit = currencyAmountArg.original_unit;
320
- } else if (isDeprecatedCurrencyAmountObj(currencyAmountArg)) {
363
+ } else if (
364
+ isCurrencyAmountInputObj(currencyAmountArg) ||
365
+ isDeprecatedCurrencyAmountObj(currencyAmountArg)
366
+ ) {
321
367
  value = asNumber(currencyAmountArg.value);
322
368
  unit = currencyAmountArg.unit;
323
369
  }
@@ -328,21 +374,71 @@ function getCurrencyAmount(currencyAmountArg: CurrencyAmountArg) {
328
374
  };
329
375
  }
330
376
 
377
+ function convertCurrencyAmountValues(
378
+ fromUnit: CurrencyUnitType,
379
+ amount: number,
380
+ unitsPerBtc = 1,
381
+ conversionOverride?: { unit: CurrencyUnitType; convertedValue: number },
382
+ ) {
383
+ const convert = convertCurrencyAmountValue;
384
+ const namesToUnits = {
385
+ sats: CurrencyUnit.SATOSHI,
386
+ btc: CurrencyUnit.BITCOIN,
387
+ msats: CurrencyUnit.MILLISATOSHI,
388
+ usd: CurrencyUnit.USD,
389
+ mxn: CurrencyUnit.MXN,
390
+ php: CurrencyUnit.PHP,
391
+ mibtc: CurrencyUnit.MICROBITCOIN,
392
+ mlbtc: CurrencyUnit.MILLIBITCOIN,
393
+ nbtc: CurrencyUnit.NANOBITCOIN,
394
+ };
395
+ return Object.entries(namesToUnits).reduce(
396
+ (acc, [name, unit]) => {
397
+ if (conversionOverride && unit === conversionOverride.unit) {
398
+ acc[name as keyof typeof namesToUnits] =
399
+ conversionOverride.convertedValue;
400
+ } else {
401
+ acc[name as keyof typeof namesToUnits] = convert(
402
+ fromUnit,
403
+ unit,
404
+ amount,
405
+ unitsPerBtc,
406
+ );
407
+ }
408
+ return acc;
409
+ },
410
+ {} as Record<keyof typeof namesToUnits, number>,
411
+ );
412
+ }
413
+
414
+ function getPreferredConversionOverride(currencyAmountArg: CurrencyAmountArg) {
415
+ if (isCurrencyAmountPreferenceObj(currencyAmountArg)) {
416
+ return {
417
+ unit: currencyAmountArg.preferred_currency_unit,
418
+ convertedValue: currencyAmountArg.preferred_currency_value_approx,
419
+ };
420
+ } else if (isSDKCurrencyAmount(currencyAmountArg)) {
421
+ return {
422
+ unit: currencyAmountArg.preferredCurrencyUnit,
423
+ convertedValue: currencyAmountArg.preferredCurrencyValueApprox,
424
+ };
425
+ }
426
+ return undefined;
427
+ }
428
+
331
429
  export function mapCurrencyAmount(
332
430
  currencyAmountArg: CurrencyAmountArg,
333
431
  unitsPerBtc = 1,
334
432
  ): CurrencyMap {
335
433
  const { value, unit } = getCurrencyAmount(currencyAmountArg);
336
434
 
337
- const convert = convertCurrencyAmountValue;
338
- const sats = convert(unit, CurrencyUnit.SATOSHI, value, unitsPerBtc);
339
- const btc = convert(unit, CurrencyUnit.BITCOIN, value, unitsPerBtc);
340
- const msats = convert(unit, CurrencyUnit.MILLISATOSHI, value, unitsPerBtc);
341
- const usd = convert(unit, CurrencyUnit.USD, value, unitsPerBtc);
342
- const mxn = convert(unit, CurrencyUnit.MXN, value, unitsPerBtc);
343
- const mibtc = convert(unit, CurrencyUnit.MICROBITCOIN, value, unitsPerBtc);
344
- const mlbtc = convert(unit, CurrencyUnit.MILLIBITCOIN, value, unitsPerBtc);
345
- const nbtc = convert(unit, CurrencyUnit.NANOBITCOIN, value, unitsPerBtc);
435
+ /* Prefer approximation from backend for corresponding unit if specified on currencyAmountArg.
436
+ * This will always be at most for one single unit type since there's only one
437
+ * preferred_currency_unit on CurrencyAmount types: */
438
+ const conversionOverride = getPreferredConversionOverride(currencyAmountArg);
439
+
440
+ const { sats, msats, btc, usd, mxn, php, mibtc, mlbtc, nbtc } =
441
+ convertCurrencyAmountValues(unit, value, unitsPerBtc, conversionOverride);
346
442
 
347
443
  const mapWithCurrencyUnits = {
348
444
  [CurrencyUnit.BITCOIN]: btc,
@@ -350,6 +446,7 @@ export function mapCurrencyAmount(
350
446
  [CurrencyUnit.MILLISATOSHI]: msats,
351
447
  [CurrencyUnit.USD]: usd,
352
448
  [CurrencyUnit.MXN]: mxn,
449
+ [CurrencyUnit.PHP]: php,
353
450
  [CurrencyUnit.MICROBITCOIN]: mibtc,
354
451
  [CurrencyUnit.MILLIBITCOIN]: mlbtc,
355
452
  [CurrencyUnit.NANOBITCOIN]: nbtc,
@@ -387,6 +484,10 @@ export function mapCurrencyAmount(
387
484
  value: mxn,
388
485
  unit: CurrencyUnit.MXN,
389
486
  }),
487
+ [CurrencyUnit.PHP]: formatCurrencyStr({
488
+ value: php,
489
+ unit: CurrencyUnit.PHP,
490
+ }),
390
491
  [CurrencyUnit.FUTURE_VALUE]: "-",
391
492
  },
392
493
  };
@@ -451,8 +552,16 @@ export const abbrCurrencyUnit = (unit: CurrencyUnitType) => {
451
552
  return "SAT";
452
553
  case CurrencyUnit.MILLISATOSHI:
453
554
  return "MSAT";
555
+ case CurrencyUnit.MILLIBITCOIN:
556
+ return "mBTC";
557
+ case CurrencyUnit.MICROBITCOIN:
558
+ return "μBTC";
454
559
  case CurrencyUnit.USD:
455
560
  return "USD";
561
+ case CurrencyUnit.MXN:
562
+ return "MXN";
563
+ case CurrencyUnit.PHP:
564
+ return "PHP";
456
565
  }
457
566
  return "Unsupported CurrencyUnit";
458
567
  };
@@ -462,6 +571,15 @@ const defaultOptions = {
462
571
  precision: undefined,
463
572
  compact: false,
464
573
  showBtcSymbol: false,
574
+ append: undefined,
575
+ };
576
+
577
+ export type AppendUnitsOptions = {
578
+ plural?: boolean | undefined;
579
+ lowercase?: boolean | undefined;
580
+ /* Default behavior for built in toLocaleString is to not show the unit when it's
581
+ the default unit in the locale (when using default currencyDisplay). We'll do the same. */
582
+ showForCurrentLocaleUnit?: boolean | undefined;
465
583
  };
466
584
 
467
585
  type FormatCurrencyStrOptions = {
@@ -469,6 +587,7 @@ type FormatCurrencyStrOptions = {
469
587
  precision?: number | "full" | undefined;
470
588
  compact?: boolean | undefined;
471
589
  showBtcSymbol?: boolean | undefined;
590
+ appendUnits?: AppendUnitsOptions | undefined;
472
591
  };
473
592
 
474
593
  export function formatCurrencyStr(
@@ -484,8 +603,14 @@ export function formatCurrencyStr(
484
603
  let { value: num } = currencyAmount;
485
604
  const { unit } = currencyAmount;
486
605
 
487
- /* Currencies should always be represented in the smallest unit, e.g. cents for USD: */
488
- if (unit === CurrencyUnit.USD) {
606
+ const centCurrencies = [
607
+ CurrencyUnit.USD,
608
+ CurrencyUnit.MXN,
609
+ CurrencyUnit.PHP,
610
+ ] as string[];
611
+ /* centCurrencies are always provided in the smallest unit, e.g. cents for USD. These should be
612
+ * divided by 100 for proper display format: */
613
+ if (centCurrencies.includes(unit)) {
489
614
  num = num / 100;
490
615
  }
491
616
 
@@ -515,39 +640,67 @@ export function formatCurrencyStr(
515
640
 
516
641
  const currentLocale = getCurrentLocale();
517
642
 
643
+ let formattedStr = "";
518
644
  switch (unit) {
645
+ case CurrencyUnit.MXN:
646
+ case CurrencyUnit.USD:
647
+ case CurrencyUnit.PHP:
648
+ formattedStr = num.toLocaleString(currentLocale, {
649
+ style: "currency",
650
+ currency: unit,
651
+ currencyDisplay: "narrowSymbol",
652
+ notation: compact ? ("compact" as const) : undefined,
653
+ maximumFractionDigits: getDefaultMaxFractionDigits(2, 2),
654
+ });
655
+ break;
519
656
  case CurrencyUnit.BITCOIN:
520
657
  /* In most cases product prefers 4 precision digtis for BTC. In a few places
521
658
  full precision (8 digits) are preferred, e.g. for a transaction details page: */
522
- return `${symbol}${num.toLocaleString(currentLocale, {
659
+ formattedStr = `${symbol}${num.toLocaleString(currentLocale, {
523
660
  notation: compact ? ("compact" as const) : undefined,
524
661
  maximumFractionDigits: getDefaultMaxFractionDigits(4, 8),
525
662
  })}`;
663
+ break;
526
664
  case CurrencyUnit.SATOSHI:
527
665
  /* In most cases product prefers hiding sub sat precision (msats). In a few
528
666
  places full precision (3 digits) are preferred, e.g. for Lightning fees
529
667
  paid on a transaction details page: */
530
- return `${symbol}${num.toLocaleString(currentLocale, {
668
+ formattedStr = `${symbol}${num.toLocaleString(currentLocale, {
531
669
  notation: compact ? ("compact" as const) : undefined,
532
670
  maximumFractionDigits: getDefaultMaxFractionDigits(0, 3),
533
671
  })}`;
672
+ break;
534
673
  case CurrencyUnit.MILLISATOSHI:
535
674
  case CurrencyUnit.MICROBITCOIN:
536
675
  case CurrencyUnit.MILLIBITCOIN:
537
676
  case CurrencyUnit.NANOBITCOIN:
538
677
  default:
539
- return `${symbol}${num.toLocaleString(currentLocale, {
678
+ formattedStr = `${symbol}${num.toLocaleString(currentLocale, {
540
679
  notation: compact ? ("compact" as const) : undefined,
541
680
  maximumFractionDigits: getDefaultMaxFractionDigits(0, 0),
542
681
  })}`;
543
- case CurrencyUnit.USD:
544
- return num.toLocaleString(currentLocale, {
545
- style: "currency",
546
- currency: defaultCurrencyCode,
547
- notation: compact ? ("compact" as const) : undefined,
548
- maximumFractionDigits: getDefaultMaxFractionDigits(2, 2),
549
- });
550
682
  }
683
+
684
+ if (options?.appendUnits) {
685
+ const localeCurrencyCode = localeToCurrencyCode(currentLocale);
686
+ if (
687
+ unit === localeCurrencyCode &&
688
+ !options.appendUnits.showForCurrentLocaleUnit
689
+ ) {
690
+ return formattedStr;
691
+ }
692
+
693
+ const unitStr = abbrCurrencyUnit(unit);
694
+ const unitSuffix = options.appendUnits.plural && num > 1 ? "s" : "";
695
+ const unitStrWithSuffix = `${unitStr}${unitSuffix}`;
696
+ formattedStr += ` ${
697
+ options.appendUnits.lowercase
698
+ ? unitStrWithSuffix.toLowerCase()
699
+ : unitStrWithSuffix
700
+ }`;
701
+ }
702
+
703
+ return formattedStr;
551
704
  }
552
705
 
553
706
  export function separateCurrencyStrParts(currencyStr: string) {
@@ -1,3 +1,5 @@
1
+ /* locale utils for both NodeJS and browser contexts */
2
+
1
3
  export function getCurrentLocale() {
2
4
  return Intl.NumberFormat().resolvedOptions().locale;
3
5
  }
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  convertCurrencyAmountValue,
5
5
  CurrencyUnit,
6
+ formatCurrencyStr,
6
7
  localeToCurrencySymbol,
7
8
  mapCurrencyAmount,
8
9
  separateCurrencyStrParts,
@@ -138,7 +139,7 @@ describe("convertCurrencyAmountValue", () => {
138
139
  });
139
140
 
140
141
  describe("mapCurrencyAmount", () => {
141
- it("should return the expected value for a CurrencyAmountObj", () => {
142
+ it("should return the expected value for a CurrencyAmountInputObj with number value", () => {
142
143
  const currencyMap = mapCurrencyAmount(
143
144
  {
144
145
  value: 100_000_000,
@@ -170,6 +171,113 @@ describe("mapCurrencyAmount", () => {
170
171
  expect(smallerCurrencyMap.isGreaterThan(currencyMap)).toBe(false);
171
172
  expect(smallerCurrencyMap.isLessThan(currencyMap)).toBe(true);
172
173
  });
174
+
175
+ it("should return the expected value for a CurrencyAmountInputObj with string value", () => {
176
+ const currencyMap = mapCurrencyAmount(
177
+ {
178
+ value: "100000000",
179
+ unit: CurrencyUnit.SATOSHI,
180
+ },
181
+ 25_000_00,
182
+ );
183
+ expect(currencyMap.btc).toBe(1);
184
+ expect(currencyMap.USD).toBe(25_000_00);
185
+ expect(currencyMap.sats).toBe(100_000_000);
186
+ expect(currencyMap.msats).toBe(100_000_000_000);
187
+ expect(currencyMap.formatted.btc).toBe("1");
188
+ expect(currencyMap.formatted.USD).toBe("$25,000.00");
189
+ expect(currencyMap.formatted.sats).toBe("100,000,000");
190
+ expect(currencyMap.formatted.msats).toBe("100,000,000,000");
191
+ });
192
+
193
+ it("should return the expected value for a CurrencyAmountInputObj with null value", () => {
194
+ const currencyMap = mapCurrencyAmount(
195
+ {
196
+ value: null,
197
+ unit: CurrencyUnit.SATOSHI,
198
+ },
199
+ 25_000_00,
200
+ );
201
+ expect(currencyMap.btc).toBe(0);
202
+ expect(currencyMap.USD).toBe(0);
203
+ expect(currencyMap.sats).toBe(0);
204
+ expect(currencyMap.msats).toBe(0);
205
+ expect(currencyMap.formatted.btc).toBe("0");
206
+ expect(currencyMap.formatted.USD).toBe("$0.00");
207
+ expect(currencyMap.formatted.sats).toBe("0");
208
+ expect(currencyMap.formatted.msats).toBe("0");
209
+ });
210
+
211
+ it("should return the expected value for a CurrencyAmountObj", () => {
212
+ const currencyMap = mapCurrencyAmount(
213
+ {
214
+ original_value: 147,
215
+ original_unit: CurrencyUnit.SATOSHI,
216
+ },
217
+ 25_000_00,
218
+ );
219
+ expect(currencyMap.btc).toBe(0.00000147);
220
+ expect(currencyMap.USD).toBe(4); // 0.03675 should round to 4 cents
221
+ expect(currencyMap.sats).toBe(147);
222
+ expect(currencyMap.msats).toBe(147_000);
223
+ expect(currencyMap.formatted.btc).toBe("0");
224
+ expect(currencyMap.formatted.USD).toBe("$0.04");
225
+ expect(currencyMap.formatted.sats).toBe("147");
226
+ expect(currencyMap.formatted.msats).toBe("147,000");
227
+ });
228
+
229
+ it("should have a type error when extra fields are provided as CurrencyAmountArg", () => {
230
+ mapCurrencyAmount(
231
+ {
232
+ original_value: 147,
233
+ original_unit: CurrencyUnit.SATOSHI,
234
+ /* @ts-expect-error `value` cannot be provided with `original_value` */
235
+ value: 100_000_000,
236
+ },
237
+ 25_000_00,
238
+ );
239
+ });
240
+
241
+ it("should use the backend approximation for the corresponding unit only when provided via CurrencyAmountPreferenceObj", () => {
242
+ const currencyMap = mapCurrencyAmount(
243
+ {
244
+ original_value: 147,
245
+ original_unit: CurrencyUnit.SATOSHI,
246
+ preferred_currency_unit: CurrencyUnit.USD,
247
+ preferred_currency_value_approx: 1_234_56,
248
+ },
249
+ 25_000_00,
250
+ );
251
+ expect(currencyMap.btc).toBe(0.00000147);
252
+ expect(currencyMap.USD).toBe(1_234_56);
253
+ expect(currencyMap.sats).toBe(147);
254
+ expect(currencyMap.msats).toBe(147_000);
255
+ expect(currencyMap.formatted.btc).toBe("0");
256
+ expect(currencyMap.formatted.USD).toBe("$1,234.56");
257
+ expect(currencyMap.formatted.sats).toBe("147");
258
+ expect(currencyMap.formatted.msats).toBe("147,000");
259
+ });
260
+
261
+ it("should return the expected value for a SDKCurrencyAmountType and use backend approximation for the corresponding unit", () => {
262
+ const currencyMap = mapCurrencyAmount(
263
+ {
264
+ originalValue: 147,
265
+ originalUnit: CurrencyUnit.SATOSHI,
266
+ preferredCurrencyUnit: CurrencyUnit.USD,
267
+ preferredCurrencyValueApprox: 1_234_56,
268
+ preferredCurrencyValueRounded: 1_234_56,
269
+ },
270
+ 25_000_00,
271
+ );
272
+ expect(currencyMap.btc).toBe(0.00000147);
273
+ expect(currencyMap.USD).toBe(1_234_56);
274
+ expect(currencyMap.sats).toBe(147);
275
+ expect(currencyMap.msats).toBe(147_000);
276
+ expect(currencyMap.formatted.btc).toBe("0");
277
+ expect(currencyMap.formatted.USD).toBe("$1,234.56");
278
+ expect(currencyMap.formatted.sats).toBe("147");
279
+ expect(currencyMap.formatted.msats).toBe("147,000");
280
+ });
173
281
  });
174
282
 
175
283
  describe("localeToCurrencySymbol", () => {
@@ -254,3 +362,77 @@ describe("separateCurrencyStrParts", () => {
254
362
  symbol: "$",
255
363
  });
256
364
  });
365
+
366
+ describe("formatCurrencyStr", () => {
367
+ it("should return the expected currency string", () => {
368
+ expect(
369
+ formatCurrencyStr({
370
+ value: 5000,
371
+ unit: CurrencyUnit.USD,
372
+ }),
373
+ ).toBe("$50.00");
374
+ });
375
+
376
+ it("should return the expected currency string with precision 1", () => {
377
+ expect(
378
+ formatCurrencyStr(
379
+ { value: 5000.245235323, unit: CurrencyUnit.Bitcoin },
380
+ { precision: 1 },
381
+ ),
382
+ ).toBe("5,000.2");
383
+ });
384
+
385
+ it("should return the expected currency string with precision full", () => {
386
+ expect(
387
+ formatCurrencyStr(
388
+ { value: 5000.245235323, unit: CurrencyUnit.Bitcoin },
389
+ { precision: "full" },
390
+ ),
391
+ ).toBe("5,000.24523532");
392
+ });
393
+
394
+ it("should return the expected currency string with compact", () => {
395
+ expect(
396
+ formatCurrencyStr(
397
+ { value: 5000.245235323, unit: CurrencyUnit.Bitcoin },
398
+ { compact: true },
399
+ ),
400
+ ).toBe("5K");
401
+ });
402
+
403
+ it("should return the expected currency string with appendUnits", () => {
404
+ expect(
405
+ formatCurrencyStr(
406
+ { value: 100000, unit: CurrencyUnit.Satoshi },
407
+ { appendUnits: { plural: true, lowercase: true } },
408
+ ),
409
+ ).toBe("100,000 sats");
410
+ });
411
+
412
+ it("should return the expected currency string with appendUnits plural and lowercase", () => {
413
+ expect(
414
+ formatCurrencyStr(
415
+ { value: 100000, unit: CurrencyUnit.Satoshi },
416
+ { appendUnits: { plural: true, lowercase: true } },
417
+ ),
418
+ ).toBe("100,000 sats");
419
+ });
420
+
421
+ it("should return the expected currency string with appendUnits plural and lowercase with value < 2", () => {
422
+ expect(
423
+ formatCurrencyStr(
424
+ { value: 1, unit: CurrencyUnit.Satoshi },
425
+ { appendUnits: { plural: true, lowercase: true } },
426
+ ),
427
+ ).toBe("1 sat");
428
+ });
429
+
430
+ it("should return the expected currency string with appendUnits plural and lowercase with value < 2", () => {
431
+ expect(
432
+ formatCurrencyStr(
433
+ { value: 100012, unit: CurrencyUnit.Mxn },
434
+ { appendUnits: { plural: false } },
435
+ ),
436
+ ).toBe("$1,000.12 MXN");
437
+ });
438
+ });