Skip to content

Commit 9a7776b

Browse files
github-actions[bot]mhrheaumebsiaotickchongAaryamanBhutematthappens
authored
[trivial] Updated description for tasks.site.
## Reason This was copied from (#490) * [trivial] Updated description for `tasks.site`. (#22343) ## Reason This was copied from minikube so they were rendering on the same line in shell completion. ## Test Plan N/A. GitOrigin-RevId: ba66c0264c979d30eea5a1ee8ae14477b0b278d1 * [umaaas] add send and receive failure reason filters for operations (#22309) ## Reason Sofi wants the ability to filter operations by failure reason ## Overview - since we have separate enums for send and receive operation failure reasons, I split it into two filters - makes send/receive specific queries if send/receive failure reason filter is present - common filter logic is abstracted - adds callback to DataManagerTable to handle reacting to filter state changes - makes "type" and failure reason filters mutually exclusive since it doesn't make sense to have multiple applied at the same time - updates unit tests ## Test Plan - unit tests - works locally ![Screenshot 2025-12-05 at 5.28.58 PM.png](https://app.graphite.com/user-attachments/assets/a686b759-0037-45b3-b54e-b3debc6030b8.png) ![Screenshot 2025-12-05 at 5.29.02 PM.png](https://app.graphite.com/user-attachments/assets/1cbbcc5f-89e6-4b32-aeb9-9487c397108c.png) GitOrigin-RevId: f74ca29c2593d95f55b52ce518cc26e9ad8cf1a3 * [Bridge] Update phone number regex to accept 7-15 digits (#22382) ## Reason The current phone number validation regex is too restrictive, only accepting US 10-digit phone numbers that start with digits 2-9. This prevents international phone numbers and other valid formats from being accepted. ## Overview Updated the phone number validation regex to accept phone numbers with 7-15 digits, accommodating international phone number formats and a wider range of valid phone numbers. ## Test Plan Tested by verifying that: - Valid international phone numbers with varying lengths (7-15 digits) are now accepted - Phone numbers with fewer than 7 or more than 15 digits are rejected - The form validation works correctly with the new regex pattern GitOrigin-RevId: 4ca3c2f5cd975b5760cf67dcd8677c88a0594c10 * Added `failHtlcs` to `LightsparkClient`. (#22393) ## Reason Shakepay needs this for remote signing so that they can fail back HTLCs that don't wish to claim. ## Test Plan Unit tests. GitOrigin-RevId: 846ac06b9ceb53bb9512dd2025219e3006f2be3e * [ops] add hardcore mode easter egg (#22425) ## Reason No reason [https://app.graphite.com/github/pr/lightsparkdev/webdev/22418/[ops]-add-core-service-select-in-header-instead-of-toggles#comment-PRRC_kwDOHaFZpc6bdfkz](https://app.graphite.com/github/pr/lightsparkdev/webdev/22418/%5Bops%5D-add-core-service-select-in-header-instead-of-toggles#comment-PRRC_kwDOHaFZpc6bdfkz) ![Screenshot 2025-12-10 at 2.10.30 PM.png](https://app.graphite.com/user-attachments/assets/aba43d46-4610-40c8-9e46-5aa43deaf8f2.png) ![Screenshot 2025-12-10 at 2.10.57 PM.png](https://app.graphite.com/user-attachments/assets/2558a738-ccdc-4f2b-82b8-d13ce555095e.png) GitOrigin-RevId: b23d54f17da86fbe14386c1d3b7e554caaa261e8 * [striga] fix verify-identity modal not being scrollable (#22469) ## Reason sumsub kyc modal was overflowing and wasn't scrollable completely ## Overview - makes Drawer more scrollable and fixes paddings (unrelated) - fixes kycsumsubmodal scrolling ## Test Plan ![Screenshot 2025-12-12 at 11.12.14 AM.png](https://app.graphite.com/user-attachments/assets/567e1867-1c78-406a-bd19-9d5d6cadaa23.png) GitOrigin-RevId: 5f1f9e30d7bca379c65fe1414722d0bd9de418ff * Add new currencies, mwk, xof, xaf, and rwf (#22501) ## Reason Add 4 new African currencies GitOrigin-RevId: a5d4983b76a0522ebca873576b349ee047a0cb60 * [uma-bridge] fix overflowing email verification button text (#22532) ## Reason we were seeing long button text stretch the button past modal screens ## Overview - adds overflow hidden to button so that ellipsis works properly for full width - updates translations for "Open email app" so that it fits ## Test Plan ![Screenshot 2025-12-15 at 3.00.30 PM.png](https://app.graphite.com/user-attachments/assets/ec7f3f35-f588-402a-a93c-8d59fd8bc03a.png) GitOrigin-RevId: 3426847249ce63cc99bab48c8a4d41036140e6bb * [striga] update kycsumsubmodal scroll button (#22534) ## Reason Updated design to be clearer ## Overview - adds scroll up button - adds text - adds new icons ## Test Plan ![Screenshot 2025-12-15 at 3.36.03 PM.png](https://app.graphite.com/user-attachments/assets/751da12c-5ad1-4967-a855-f6c5c4472231.png) ![Screenshot 2025-12-15 at 3.36.09 PM.png](https://app.graphite.com/user-attachments/assets/8e8fc348-9859-48e7-ad12-c39ff21bbdcd.png) GitOrigin-RevId: 61a0ed2d6f115e014456a414fd4f36a424f10e34 * [striga] link bank UI fixes (#22577) ## Overview - minor fixes for the link bank UI and copy changes ## Test Plan ![Screenshot 2025-12-16 at 2.59.21 PM.png](https://app.graphite.com/user-attachments/assets/cbbe3bd3-a8ce-4a20-9896-e0370508ecb3.png) ![Screenshot 2025-12-16 at 2.59.09 PM.png](https://app.graphite.com/user-attachments/assets/9be54352-cfb0-4b07-bba7-446a934d30de.png) ![Screenshot 2025-12-16 at 3.05.17 PM.png](https://app.graphite.com/user-attachments/assets/b440d147-4446-4bbd-af36-b4fa28534aff.png) ![Screenshot 2025-12-16 at 3.00.47 PM.png](https://app.graphite.com/user-attachments/assets/51ccb0ac-d951-4648-bbff-269eeb46dce9.png) GitOrigin-RevId: 7c380f30fe882496802e22fee1f1480348c20f35 * [uma-bridge] fix mobile drawer scroll bleed (#22593) ## Reason On iOS there's a usability issue with Drawers where there's "scroll bleed" causing - scroll to go below the modal overlay/background - the drawer starts too high ## Overview - fixes the scroll bleed with a new hook that locks scroll - moves drawer to be above keyboard with another hook that listens for the offset - note: top overrides bottom set by the keyboard offset so modals with top will stick to the top which is intentional - refactors NationalitySelector so that VerifyIdentityModal can show its contents within the same modal - various other fixes downside is you can no longer touch move the outer drawer container so if it's too large the user won't be able to see the top ## Test Plan [Screen Recording 2025-12-16 at 7.06.59 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/1e9de009-90f5-4f2e-9cd9-c03bf055601f.mov" />](https://app.graphite.com/user-attachments/video/1e9de009-90f5-4f2e-9cd9-c03bf055601f.mov) GitOrigin-RevId: 24e1bdeb942ac5138d027b82069f90d312c09234 * Striga design updates / kyc fixes (#22603) ## Reason Several design / functionality issues came up during Striga's recent design review, here are a few of them | Change | Appearance | |----------|----------| | new icon for proof of address & secondary text color for subtext | ![Screenshot 2025-12-17 at 11.24.31 AM.png](https://app.graphite.com/user-attachments/assets/e61da014-a21d-496f-b784-fe39819e1718.png) | | remove us error subtext | ![Screenshot 2025-12-17 at 11.06.38 AM.png](https://app.graphite.com/user-attachments/assets/dd5120b1-015d-4bd8-a1bd-acee8afdd20c.png) | | check all banned countries when determining eligibility requirements | previously we relied on IP check - this update makes it so that we block countries like Russia even when they do not have russian vpns. | | Fix rejected final icon & text centering | ![Screenshot 2025-12-17 at 11.28.57 AM.png](https://app.graphite.com/user-attachments/assets/b410f418-be18-4c8f-b3eb-268beb790bc4.png) | |add loading state and spinner to continue | | GitOrigin-RevId: 5fd335913073ab0d07214666beb4de0dcf2a5d86 * Add dedicated name match failed page for Nigeria (#22653) ## Reason closes PX-1305 Nigeria has a dedicated "can't link your bank" page for name match failures This PR adds that page ## Changes * Add a "yellowcard error" enum which is included as part of the bank info output. For now, it only indicates a failure to match names * if the client sees this error, display the new page, which will redirect back to link bank in case the user wants to try again ![Screenshot 2025-12-18 at 10.04.00 AM.png](https://app.graphite.com/user-attachments/assets/a4af0dc4-e622-47d4-8f6b-93b9c5b24925.png) GitOrigin-RevId: c0662e2cbd878da72d07f75654e48b9ee3a54714 * [AI tools] Several small tweaks to make cursor work better. (#22683) - Add symlinks from all CLAUDE.md files to AGENTS.md so that they get seen by all agent types (including cursor). - Switch the cursor rule structure to the new file format: https://cursor.com/docs/context/rules#project-rules - Clean up a few of the rules for formatting and details. GitOrigin-RevId: 5b0bb0ed7e60248fd7c83be92b6a5b24b5c71df8 * [uma-bridge] add segment events for all flows, update event name format (#22727) ## Reason We mainly only had events for the zerohash onboarding flow and were generally missing a lot of events. This adds events from https://docs.google.com/document/d/1jxW4cKvoyK8k-L7eDEMi_xg65iYqc6pQl1RdyAvLwGA/edit?tab=t.uzinptq1apsn and more ## Overview - adds events - rather than region-specific event names, have generic events and have accountType be a parameter in certain cases - reformats existing events to follow the same pattern (no colon prefix, and action properties) ## Test plan tested locally GitOrigin-RevId: 40fac5de2b9af8072c2ff44d1d156b448d9875ab * fix double display of currency for currencies without a narrow symbol (#22779) ## Reason closes PQA-394 XOF doesn't have a narrow symbol (like $) so our currency rendering libraries render twice This change checks for such currencies (probably a few in africa) and prevents them from rendering twice GitOrigin-RevId: ba506dcf9d9583ae0fcf8aad36b1b5246eca9968 * [uma-bridge] add new 2fa code input design (#22794) ## Reason Long overdue design for 2fa code input ## Overview - adds "unified" variant for the CodeInput component ## Test Plan ![Screenshot 2025-12-23 at 3.50.45 PM.png](https://app.graphite.com/user-attachments/assets/0d0a5fda-2a55-4f30-819a-ac422e9c19aa.png) ![Screenshot 2025-12-23 at 3.50.58 PM.png](https://app.graphite.com/user-attachments/assets/9a0749a4-bb1f-4608-960a-22a51df8b5e0.png) GitOrigin-RevId: bfe4340728490a7e8d88fc36d919cd46c244146b * feat: adding idempotency to JS withdrawal request (#22827) ## Reason Added support for idempotency keys in withdrawal requests to prevent duplicate withdrawals when network issues occur. ## Overview This change adds an optional `idempotencyKey` parameter to the `requestWithdrawal` method in the Lightspark SDK. The parameter is passed to the GraphQL mutation, allowing clients to safely retry withdrawal requests without risking duplicate transactions. GitOrigin-RevId: 174bd753d4b2e591a1c36fd26288edf0b2cd5bc8 * adding changesets --------- Co-authored-by: Matthew Rheaume <mhr@lightspark.com> Co-authored-by: Brian Siao Tick Chong <bsiaotickchong@gmail.com> Co-authored-by: Aaryaman Bhute <35084309+AaryamanBhute@users.noreply.github.com> Co-authored-by: Matt Davis <matthappens@gmail.com> Co-authored-by: Jeremy Klein <jklein24@gmail.com> Co-authored-by: Peng Ying <peng@lightspark.com>
1 parent cdb123b commit 9a7776b

34 files changed

Lines changed: 1269 additions & 137 deletions

.changeset/curvy-experts-fall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lightsparkdev/core": patch
3+
---
4+
5+
Adding currency support for XOF, XAF, MWK, RWF

.changeset/little-hands-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lightsparkdev/ui": patch
3+
---
4+
5+
UI updates and fixes for bank linking, kyc, 2fa, filters

.changeset/nice-pumas-rhyme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lightsparkdev/lightspark-sdk": patch
3+
---
4+
5+
Adding ability to fail HTLCs and add an idempotency key to requestWithdrawal

.mise.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ description = "Start ops frontend connected to minikube"
4545
run = "VITE_PROXY_TARGET=http://app.minikube.local yarn start ops"
4646

4747
[tasks.site]
48-
description = "Start CN2 frontend connected to minikube"
48+
description = "Start CN2 frontend development server"
4949
run = "yarn start site"
5050

5151
[tasks.site-minikube]

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CLAUDE.md

packages/core/src/utils/currency.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ export const CurrencyUnit = {
3939
TZS: "TZS",
4040
UGX: "UGX",
4141
BWP: "BWP",
42+
XOF: "XOF",
43+
XAF: "XAF",
44+
MWK: "MWK",
45+
RWF: "RWF",
4246
USDT: "USDT",
4347
USDC: "USDC",
4448

@@ -102,6 +106,10 @@ const standardUnitConversionObj = {
102106
[CurrencyUnit.TZS]: (v: number) => v,
103107
[CurrencyUnit.UGX]: (v: number) => v,
104108
[CurrencyUnit.BWP]: (v: number) => v,
109+
[CurrencyUnit.XOF]: (v: number) => v,
110+
[CurrencyUnit.XAF]: (v: number) => v,
111+
[CurrencyUnit.MWK]: (v: number) => v,
112+
[CurrencyUnit.RWF]: (v: number) => v,
105113
[CurrencyUnit.USDT]: (v: number) => v,
106114
[CurrencyUnit.USDC]: (v: number) => v,
107115
};
@@ -149,6 +157,10 @@ const CONVERSION_MAP = {
149157
[CurrencyUnit.TZS]: toBitcoinConversion,
150158
[CurrencyUnit.UGX]: toBitcoinConversion,
151159
[CurrencyUnit.BWP]: toBitcoinConversion,
160+
[CurrencyUnit.XOF]: toBitcoinConversion,
161+
[CurrencyUnit.XAF]: toBitcoinConversion,
162+
[CurrencyUnit.MWK]: toBitcoinConversion,
163+
[CurrencyUnit.RWF]: toBitcoinConversion,
152164
[CurrencyUnit.USDT]: toBitcoinConversion,
153165
[CurrencyUnit.USDC]: toBitcoinConversion,
154166
},
@@ -180,6 +192,10 @@ const CONVERSION_MAP = {
180192
[CurrencyUnit.TZS]: toMicrobitcoinConversion,
181193
[CurrencyUnit.UGX]: toMicrobitcoinConversion,
182194
[CurrencyUnit.BWP]: toMicrobitcoinConversion,
195+
[CurrencyUnit.XOF]: toMicrobitcoinConversion,
196+
[CurrencyUnit.XAF]: toMicrobitcoinConversion,
197+
[CurrencyUnit.MWK]: toMicrobitcoinConversion,
198+
[CurrencyUnit.RWF]: toMicrobitcoinConversion,
183199
[CurrencyUnit.USDT]: toMicrobitcoinConversion,
184200
[CurrencyUnit.USDC]: toMicrobitcoinConversion,
185201
},
@@ -211,6 +227,10 @@ const CONVERSION_MAP = {
211227
[CurrencyUnit.TZS]: toMillibitcoinConversion,
212228
[CurrencyUnit.UGX]: toMillibitcoinConversion,
213229
[CurrencyUnit.BWP]: toMillibitcoinConversion,
230+
[CurrencyUnit.XOF]: toMillibitcoinConversion,
231+
[CurrencyUnit.XAF]: toMillibitcoinConversion,
232+
[CurrencyUnit.MWK]: toMillibitcoinConversion,
233+
[CurrencyUnit.RWF]: toMillibitcoinConversion,
214234
[CurrencyUnit.USDT]: toMillibitcoinConversion,
215235
[CurrencyUnit.USDC]: toMillibitcoinConversion,
216236
},
@@ -242,6 +262,10 @@ const CONVERSION_MAP = {
242262
[CurrencyUnit.TZS]: toMillisatoshiConversion,
243263
[CurrencyUnit.UGX]: toMillisatoshiConversion,
244264
[CurrencyUnit.BWP]: toMillisatoshiConversion,
265+
[CurrencyUnit.XOF]: toMillisatoshiConversion,
266+
[CurrencyUnit.XAF]: toMillisatoshiConversion,
267+
[CurrencyUnit.MWK]: toMillisatoshiConversion,
268+
[CurrencyUnit.RWF]: toMillisatoshiConversion,
245269
[CurrencyUnit.USDT]: toMillisatoshiConversion,
246270
[CurrencyUnit.USDC]: toMillisatoshiConversion,
247271
},
@@ -273,6 +297,10 @@ const CONVERSION_MAP = {
273297
[CurrencyUnit.TZS]: toNanobitcoinConversion,
274298
[CurrencyUnit.UGX]: toNanobitcoinConversion,
275299
[CurrencyUnit.BWP]: toNanobitcoinConversion,
300+
[CurrencyUnit.XOF]: toNanobitcoinConversion,
301+
[CurrencyUnit.XAF]: toNanobitcoinConversion,
302+
[CurrencyUnit.MWK]: toNanobitcoinConversion,
303+
[CurrencyUnit.RWF]: toNanobitcoinConversion,
276304
[CurrencyUnit.USDT]: toNanobitcoinConversion,
277305
[CurrencyUnit.USDC]: toNanobitcoinConversion,
278306
},
@@ -304,6 +332,10 @@ const CONVERSION_MAP = {
304332
[CurrencyUnit.TZS]: toSatoshiConversion,
305333
[CurrencyUnit.UGX]: toSatoshiConversion,
306334
[CurrencyUnit.BWP]: toSatoshiConversion,
335+
[CurrencyUnit.XOF]: toSatoshiConversion,
336+
[CurrencyUnit.XAF]: toSatoshiConversion,
337+
[CurrencyUnit.MWK]: toSatoshiConversion,
338+
[CurrencyUnit.RWF]: toSatoshiConversion,
307339
[CurrencyUnit.USDT]: toSatoshiConversion,
308340
[CurrencyUnit.USDC]: toSatoshiConversion,
309341
},
@@ -328,6 +360,10 @@ const CONVERSION_MAP = {
328360
[CurrencyUnit.TZS]: standardUnitConversionObj,
329361
[CurrencyUnit.UGX]: standardUnitConversionObj,
330362
[CurrencyUnit.BWP]: standardUnitConversionObj,
363+
[CurrencyUnit.XOF]: standardUnitConversionObj,
364+
[CurrencyUnit.XAF]: standardUnitConversionObj,
365+
[CurrencyUnit.MWK]: standardUnitConversionObj,
366+
[CurrencyUnit.RWF]: standardUnitConversionObj,
331367
[CurrencyUnit.USDT]: standardUnitConversionObj,
332368
[CurrencyUnit.USDC]: standardUnitConversionObj,
333369
};
@@ -412,6 +448,10 @@ export type CurrencyMap = {
412448
[CurrencyUnit.TZS]: number;
413449
[CurrencyUnit.UGX]: number;
414450
[CurrencyUnit.BWP]: number;
451+
[CurrencyUnit.XOF]: number;
452+
[CurrencyUnit.XAF]: number;
453+
[CurrencyUnit.MWK]: number;
454+
[CurrencyUnit.RWF]: number;
415455
[CurrencyUnit.USDT]: number;
416456
[CurrencyUnit.USDC]: number;
417457
[CurrencyUnit.FUTURE_VALUE]: number;
@@ -446,6 +486,10 @@ export type CurrencyMap = {
446486
[CurrencyUnit.TZS]: string;
447487
[CurrencyUnit.UGX]: string;
448488
[CurrencyUnit.BWP]: string;
489+
[CurrencyUnit.XOF]: string;
490+
[CurrencyUnit.XAF]: string;
491+
[CurrencyUnit.MWK]: string;
492+
[CurrencyUnit.RWF]: string;
449493
[CurrencyUnit.USDT]: string;
450494
[CurrencyUnit.USDC]: string;
451495
[CurrencyUnit.FUTURE_VALUE]: string;
@@ -661,6 +705,10 @@ function convertCurrencyAmountValues(
661705
tzs: CurrencyUnit.TZS,
662706
ugx: CurrencyUnit.UGX,
663707
bwp: CurrencyUnit.BWP,
708+
xof: CurrencyUnit.XOF,
709+
xaf: CurrencyUnit.XAF,
710+
mwk: CurrencyUnit.MWK,
711+
rwf: CurrencyUnit.RWF,
664712
mibtc: CurrencyUnit.MICROBITCOIN,
665713
mlbtc: CurrencyUnit.MILLIBITCOIN,
666714
nbtc: CurrencyUnit.NANOBITCOIN,
@@ -740,6 +788,10 @@ export function mapCurrencyAmount(
740788
tzs,
741789
ugx,
742790
bwp,
791+
xof,
792+
xaf,
793+
mwk,
794+
rwf,
743795
usdt,
744796
usdc,
745797
} = convertCurrencyAmountValues(unit, value, unitsPerBtc, conversionOverride);
@@ -769,6 +821,10 @@ export function mapCurrencyAmount(
769821
[CurrencyUnit.TZS]: tzs,
770822
[CurrencyUnit.UGX]: ugx,
771823
[CurrencyUnit.BWP]: bwp,
824+
[CurrencyUnit.XOF]: xof,
825+
[CurrencyUnit.XAF]: xaf,
826+
[CurrencyUnit.MWK]: mwk,
827+
[CurrencyUnit.RWF]: rwf,
772828
[CurrencyUnit.MICROBITCOIN]: mibtc,
773829
[CurrencyUnit.MILLIBITCOIN]: mlbtc,
774830
[CurrencyUnit.NANOBITCOIN]: nbtc,
@@ -884,6 +940,22 @@ export function mapCurrencyAmount(
884940
value: bwp,
885941
unit: CurrencyUnit.BWP,
886942
}),
943+
[CurrencyUnit.XOF]: formatCurrencyStr({
944+
value: xof,
945+
unit: CurrencyUnit.XOF,
946+
}),
947+
[CurrencyUnit.XAF]: formatCurrencyStr({
948+
value: xaf,
949+
unit: CurrencyUnit.XAF,
950+
}),
951+
[CurrencyUnit.MWK]: formatCurrencyStr({
952+
value: mwk,
953+
unit: CurrencyUnit.MWK,
954+
}),
955+
[CurrencyUnit.RWF]: formatCurrencyStr({
956+
value: rwf,
957+
unit: CurrencyUnit.RWF,
958+
}),
887959
[CurrencyUnit.USDT]: formatCurrencyStr({
888960
value: usdt,
889961
unit: CurrencyUnit.USDT,
@@ -1006,6 +1078,14 @@ export const abbrCurrencyUnit = (unit: CurrencyUnitType) => {
10061078
return "UGX";
10071079
case CurrencyUnit.BWP:
10081080
return "BWP";
1081+
case CurrencyUnit.XOF:
1082+
return "XOF";
1083+
case CurrencyUnit.XAF:
1084+
return "XAF";
1085+
case CurrencyUnit.MWK:
1086+
return "MWK";
1087+
case CurrencyUnit.RWF:
1088+
return "RWF";
10091089
}
10101090
return "Unsupported CurrencyUnit";
10111091
};
@@ -1175,6 +1255,13 @@ export function formatCurrencyStr(
11751255
const unitStr = isUmaCurrencyAmount(amount)
11761256
? amount.currency.code
11771257
: abbrCurrencyUnit(unit as CurrencyUnitType);
1258+
1259+
// Skip appending if the formatted string already contains the currency code
1260+
// This happens for currencies like XOF where Intl.NumberFormat uses the code as the symbol
1261+
if (formattedStr.includes(unitStr)) {
1262+
return formattedStr;
1263+
}
1264+
11781265
const unitSuffix = options?.appendUnits?.plural && num > 1 ? "s" : "";
11791266
const unitStrWithSuffix = `${unitStr}${unitSuffix}`;
11801267
formattedStr += ` ${

packages/core/src/utils/tests/currency.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,22 @@ describe("formatCurrencyStr", () => {
436436
).toBe("$1,000.12 MXN");
437437
});
438438
});
439+
440+
it("should not append XOF if it's already in the formatted string", () => {
441+
/* This test verifies the fix for PQA-394 where XOF was appearing twice.
442+
* If Intl.NumberFormat formats with currencyDisplay: 'code', it will include
443+
* XOF in the output, and we shouldn't append it again. */
444+
const formatted = formatCurrencyStr(
445+
{ value: 221900, unit: CurrencyUnit.XOF },
446+
{ appendUnits: { plural: false, lowercase: false } },
447+
);
448+
449+
/* Count occurrences of 'XOF' or 'CFA' in the result */
450+
const xofCount = (formatted.match(/XOF/g) || []).length;
451+
const cfaCount = (formatted.match(/CFA/g) || []).length;
452+
453+
/* Should have either XOF or CFA, but not both, and only once */
454+
expect(xofCount + cfaCount).toBeGreaterThan(0);
455+
expect(xofCount).toBeLessThanOrEqual(1);
456+
expect(cfaCount).toBeLessThanOrEqual(1);
457+
});

packages/lightspark-sdk/src/client.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { CreateUmaInvitationWithPayment } from "./graphql/CreateUmaInvitationWit
4545
import { CreateUmaInvoice } from "./graphql/CreateUmaInvoice.js";
4646
import { DecodeInvoice } from "./graphql/DecodeInvoice.js";
4747
import { DeleteApiToken } from "./graphql/DeleteApiToken.js";
48+
import { FailHtlcs } from "./graphql/FailHtlcs.js";
4849
import { FetchUmaInvitation } from "./graphql/FetchUmaInvitation.js";
4950
import { FundNode } from "./graphql/FundNode.js";
5051
import { IncomingPaymentsForInvoice } from "./graphql/IncomingPaymentsForInvoice.js";
@@ -76,6 +77,8 @@ import type ComplianceProvider from "./objects/ComplianceProvider.js";
7677
import type CreateApiTokenOutput from "./objects/CreateApiTokenOutput.js";
7778
import type CurrencyAmount from "./objects/CurrencyAmount.js";
7879
import { CurrencyAmountFromJson } from "./objects/CurrencyAmount.js";
80+
import type FailHtlcsOutput from "./objects/FailHtlcsOutput.js";
81+
import { FailHtlcsOutputFromJson } from "./objects/FailHtlcsOutput.js";
7982
import type FeeEstimate from "./objects/FeeEstimate.js";
8083
import { FeeEstimateFromJson } from "./objects/FeeEstimate.js";
8184
import type IncomingPayment from "./objects/IncomingPayment.js";
@@ -736,6 +739,28 @@ class LightsparkClient {
736739
return InvoiceFromJson(invoiceJson);
737740
}
738741

742+
/**
743+
* Fails all pending HTLCs (Hash Time Locked Contracts) for a given invoice.
744+
*
745+
* @param invoiceId The ID of the invoice for which to fail HTLCs.
746+
* @param cancelInvoice Whether to also cancel the invoice after failing the HTLCs.
747+
* @returns The output containing the invoice ID, or undefined if the operation failed.
748+
*/
749+
public async failHtlcs(
750+
invoiceId: string,
751+
cancelInvoice: boolean,
752+
): Promise<FailHtlcsOutput | undefined> {
753+
const response = await this.requester.makeRawRequest(FailHtlcs, {
754+
invoice_id: invoiceId,
755+
cancel_invoice: cancelInvoice,
756+
});
757+
const output = response.fail_htlcs;
758+
if (!output) {
759+
return undefined;
760+
}
761+
return FailHtlcsOutputFromJson(output);
762+
}
763+
739764
/**
740765
* Decodes an encoded lightning invoice string.
741766
*
@@ -1206,15 +1231,20 @@ class LightsparkClient {
12061231
amountSats: number,
12071232
bitcoinAddress: string,
12081233
mode: WithdrawalMode,
1234+
idempotencyKey: string | undefined = undefined,
12091235
): Promise<WithdrawalRequest> {
1236+
const variables: Record<string, string | number> = {
1237+
node_id: nodeId,
1238+
amount_sats: amountSats,
1239+
bitcoin_address: bitcoinAddress,
1240+
withdrawal_mode: mode,
1241+
};
1242+
if (idempotencyKey !== undefined) {
1243+
variables.idempotency_key = idempotencyKey;
1244+
}
12101245
const response = await this.requester.makeRawRequest(
12111246
RequestWithdrawal,
1212-
{
1213-
node_id: nodeId,
1214-
amount_sats: amountSats,
1215-
bitcoin_address: bitcoinAddress,
1216-
withdrawal_mode: mode,
1217-
},
1247+
variables,
12181248
nodeId,
12191249
);
12201250
return WithdrawalRequestFromJson(response.request_withdrawal.request);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved
2+
3+
import { FRAGMENT as FailHtlcsOutputFragment } from "../objects/FailHtlcsOutput.js";
4+
5+
export const FailHtlcs = `
6+
mutation FailHtlcs(
7+
$invoice_id: ID!,
8+
$cancel_invoice: Boolean!
9+
) {
10+
fail_htlcs(input: {
11+
invoice_id: $invoice_id,
12+
cancel_invoice: $cancel_invoice
13+
}) {
14+
...FailHtlcsOutputFragment
15+
}
16+
}
17+
18+
${FailHtlcsOutputFragment}
19+
`;

packages/lightspark-sdk/src/graphql/RequestWithdrawal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ export const RequestWithdrawal = `
88
$bitcoin_address: String!
99
$amount_sats: Long!
1010
$withdrawal_mode: WithdrawalMode!
11+
$idempotency_key: String
1112
) {
1213
request_withdrawal(input: {
1314
node_id: $node_id
1415
bitcoin_address: $bitcoin_address
1516
amount_sats: $amount_sats
1617
withdrawal_mode: $withdrawal_mode
18+
idempotency_key: $idempotency_key
1719
}) {
1820
request {
1921
...WithdrawalRequestFragment

0 commit comments

Comments
 (0)