Skip to content

Commit 8fd199a

Browse files
github-actions[bot]exs6350bsiaotickchongJasonCWangLightspark Eng
authored
Updates to core, lightspark-sdk, ui (#482)
* [UmaProber] Add Tether as a currency unit (#20124) ## Reason UmaProber does a "probing" against vasps to try to figure out fx exchange rates. Currently when we probe Coins.ph in the Philippines they only have a preferred currency as Tether but we keep mangling the response because we don't have a representation of tether as a currency unit. GitOrigin-RevId: 591ec8a37b23e852e3ec69c32dddc5750a19da3a * [nage] add feature to create api tokens (#20167) - adds mutation to create new umaaas api tokens - shows uma permissions when creating tokens - shows error toasts and state when token info not provided - adds NageModal to be used for token created - updates Modal to use overlayBackground prop instead of voidOverlayBackground - adds CopyInput component for nage - fixes CentralLoadingCircle color - fixes Checkbox background theme color (theme.info) ![Screenshot 2025-08-28 at 5.53.33 PM.png](https://app.graphite.dev/user-attachments/assets/ca762d4b-2034-416b-8a75-24743441560d.png) ![Screenshot 2025-08-28 at 5.40.12 PM.png](https://app.graphite.dev/user-attachments/assets/4e9942a5-4b80-47a8-be34-2014c9ce000d.png) GitOrigin-RevId: 3ff84388c5cb04bb85265067868e7a025763f857 * [nage] add delete api token feature (#20186) - adds feature to delete api tokens - updates Table to handle triple dot button columns - updates dropdown component to handle toReactNodesArgs - adds cancel button to NageModal - adds theme for danger button (delete button) - adds card for token display ![Screenshot 2025-08-28 at 6.55.17 PM.png](https://app.graphite.dev/user-attachments/assets/3288bd01-c4a9-4d9c-8610-95f3f3c93db6.png) ![Screenshot 2025-08-28 at 7.15.39 PM.png](https://app.graphite.dev/user-attachments/assets/5744aefc-2fdc-414a-8afb-220346a869ff.png) GitOrigin-RevId: fc011969714490d3541b0b217abe081dca62cee5 * [Nage] Fix command center typography and add auto scroll (#19868) ![Screenshot 2025-08-18 at 3.16.41 PM.png](https://app.graphite.dev/user-attachments/assets/a558cbf4-8361-4b46-a7fd-3275f7a9503e.png) GitOrigin-RevId: fec8ca6cdaae37018643f176f834d464492cc424 * [Nage] Add update name / start of invitations into nage settings (#20194) ## Overview - Implement update name path - Add current user query - Rename update name to update profile per designs GitOrigin-RevId: 6a08f2bc9e63972bd317cda7298fe32b0490e6b2 * CI update lock file for PR * [nage] add filter query params for DataManagerTable (#20270) - adds option to DataManagerTable to save/load filters from URL query params - this allows refreshing the page and persisting filter state - also will be used for the CommandCenter in the future - must be only enabled via boolean because ops pages can have multiple DataManagerTables and we don't want the same filters applying to every table; will need to figure out a solution for that later - fixes date range filter for transactions table GitOrigin-RevId: 0b7220cefe53c79b1eceab0282b95c4d6db965b2 * [site] route to umaaas routes depending on user role (#20317) - since umaaas/nage routes live in site, we need to reroute to /umaaas when the user tries accessing any normal cn2 routes - also route to cn2 if the user's roles are not nage related - small fix for zIndex in filter dropdowns GitOrigin-RevId: bfb40937ff165181049b1e877fb49a32608deb80 * [Nage] Add sandbox platform creation flow (#20257) ![Screenshot 2025-09-03 at 2.38.02 PM.png](https://app.graphite.dev/user-attachments/assets/0b992a5a-bfa3-49e8-81f2-2ecf9bbc1b46.png) ![Screenshot 2025-09-03 at 2.38.07 PM.png](https://app.graphite.dev/user-attachments/assets/c69bbaaa-9147-4e9e-8ebd-97da96fa03ed.png) Contributes to PX-834 GitOrigin-RevId: 450a09f8bb6befea49d93270fdeb5d4b953415f9 * [nage] show nav items depending on user role (#20318) - shows nav items depending on umaaas role - fixes console error due to svg prop - removes notification bell and unused menu items - fixes docs link Admin and developer: ![Screenshot 2025-09-05 at 5.44.15 PM.png](https://app.graphite.dev/user-attachments/assets/701d9d7a-0cf8-4f88-9131-466d0f1a3480.png) Compliance: ![Screenshot 2025-09-05 at 5.44.25 PM.png](https://app.graphite.dev/user-attachments/assets/c87495e0-38a7-4209-8f3d-24f38a0126d5.png) Support: ![Screenshot 2025-09-05 at 5.44.03 PM.png](https://app.graphite.dev/user-attachments/assets/27b48b7f-14df-4efa-bc33-201101aa1340.png) GitOrigin-RevId: 6435f3e38ff44d0ae9f888bc9baa035a9bd9af08 * [nage] fix nav button and command center search button styling (#20319) - use transparent button kind instead of links - add option to Button for justifyContent - add themeable prop for button gap - auto navigate to transactions page if landing on home / base route ![Screenshot 2025-09-05 at 6.10.04 PM.png](https://app.graphite.dev/user-attachments/assets/7439eb79-882b-40e7-9c1b-b15cb90948a0.png) GitOrigin-RevId: bb79b901ef7b06f4bbe538c64fce099771913751 * [ui] default button justifyContent to center (#20339) - this was a regression when the justifyContent prop was added - it should default to center when not provided GitOrigin-RevId: 3a40a61007ca75b224a4df7f58c1b613a9960215 * [Nage] Update settings styling, update nage select, and implement user invites (#20341) ![Screenshot 2025-09-09 at 11.32.57 AM.png](https://app.graphite.dev/user-attachments/assets/188b53c1-19c0-4b24-8525-72be634f71b3.png) GitOrigin-RevId: 5bb91f6ea9c876618ee399e3558e1a54a025949e * [nage] refresh table data on platform change (#20376) GitOrigin-RevId: 9a3ee6ce8d5f395dae9e3fc620774f8c05e8209b * [Nage] Add sandbox deletion functionality and separate prod/sandbox config (#20378) ![Screenshot 2025-09-09 at 4.12.30 PM.png](https://app.graphite.dev/user-attachments/assets/67f6d258-b1b7-4c8b-8052-4d0814e6c854.png) ![Screenshot 2025-09-09 at 4.12.26 PM.png](https://app.graphite.dev/user-attachments/assets/dd81b73b-551e-44c3-b9ca-b23243c30548.png) GitOrigin-RevId: 88c4bd93d79ae47d18450e4be416d846f4be4efb * [nage] add progress bar loading style to data manager tables (#20398) - adds progress bar loading style to data manager table - adds option to show no loader or progress bar - adds ability to stretch table to match parent component (fullHeight) so that the pagination is at the bottom of the screen and the loader is properly centered when there are no results [Screen Recording 2025-09-10 at 2.40.06 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/user-attachments/thumbnails/f2b95a15-0056-4c63-a0b7-47a297c22bf9.mov" />](https://app.graphite.dev/user-attachments/video/f2b95a15-0056-4c63-a0b7-47a297c22bf9.mov) GitOrigin-RevId: 734257f091069d6cd1d7b3ac3a456d95920ab23c * add blocked ip screen to add credentials component (#20034) ## Reason This change provides an "uma not allowed in your region" screen if the user's ip is banned when selecting europe during the striga onboarding flow ## Overview * adds a new query to check the current user's ip address against a sanctioned list * modifies the `AddCredentials.tsx` to show a 'uma not allowed' screen as a result of this query * modifies AddCredentials to introduce a more robust state management mechanism ## Test Plan During onboarding of Striga users, set "is ip valid" to false by default on backend, ensure we see the "uma not allowed" screen GitOrigin-RevId: e23f3e750360563e7e1433f4afcf101aec9de1e8 * Make bobby tables textarea resizable (#20476) Make the SQL textarea resizable in bobby tables. GitOrigin-RevId: 6d4d64521e3a3a3ff171d243a55820653053d67c * [lightspark-sdk] Allow RemoteSigningWebhookHandler validator to be async (#20536) ## Reason Explain *why* this change is being made. ## Overview For large or complex changes, describe what is being changed. ## Test Plan Explain how you tested the change. GitOrigin-RevId: d4a54c0b0cfff6e1eaf7481829e70e41c05f84df * [Nage] Add basic uuid frontend to command center (#20475) ![Screenshot 2025-09-17 at 3.15.56 PM.png](https://app.graphite.dev/user-attachments/assets/e70032ae-57b5-4a03-bc3c-bdf254fac9e9.png) ![Screenshot 2025-09-17 at 3.16.00 PM.png](https://app.graphite.dev/user-attachments/assets/ac35d88f-e4cc-4336-af69-728b3da1093b.png) GitOrigin-RevId: 13658f8a6e0e93fea745d4343ab5ba8dad1bb765 * [nage] add filter pill redesign to DataManagerTable GitOrigin-RevId: d3a034f534cfe94103d7c79dadab1d747a7280f2 * CI update lock file for PR * [TazaPay] Add UPI bank linking interface for INR (#20145) ## - Created a new UI for the LinkBankINR component with a form for users to enter their UPI ID - Added necessary translations for UPI-related text in English, Spanish, and Portuguese - Implemented validation for the UPI ID field - Added conditional rendering based on Tazapay integration status and user onboarding state GitOrigin-RevId: 6f91c0bc5c3eb85d5da26bbe5f963fc3cd430a9e * add page to update iban and standing order for striga users (#20491) ## Reason closes PX-776 Implement update iban page for Striga users This flow is for users who have already provided an IBAN and need to change it. This flow is somewhat complex, as it requires users to first cancel an existing standing order * add UpdateBankEu component for remove/update iban * connect to cancel standing order mutation * link the settings page update/remove bank account buttons to this new component * link the received failed indicators to UpdateBankEu This code shares quite a bit of functionality with link bank eu, so this PR also extracts common functionality into shared components GitOrigin-RevId: 075eadae3e36ec6d65a2a99500beb9e218049613 * [nage] add Apply button to date filter and fix change handling (#20602) - fixes wonky auto date application logic with Apply button - use shouldClose={false} to avoid styling issues when clicking a date range - fixes typography of calendar text ![Screenshot 2025-09-19 at 2.53.08 PM.png](https://app.graphite.dev/user-attachments/assets/eefdc0ab-bc10-4d39-a140-8d42dfc79c18.png) GitOrigin-RevId: dd26ad67bbd061ae1ba9b5ccd4adf249d5f3cdf5 * Update from public js-sdk main branch (#20119) Update public `js` sources with the latest code from the [public repository](https://github.com/lightsparkdev/js-sdk) main branch. This typically happens when new versions of the SDK are released and version updates need to be synced. The PR should be merged as soon as possible to avoid updates to webdev overwriting the changes in the js-sdk develop branch. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Davis <[email protected]> Co-authored-by: Peng Ying <[email protected]> Co-authored-by: lightspark-ci-js-sdk[bot] <134011073+lightspark-ci-js-sdk[bot]@users.noreply.github.com> Co-authored-by: Lightspark Eng <[email protected]> Co-authored-by: Corey Martin <[email protected]> GitOrigin-RevId: 6578b59e40e43022573e22ffa9d57bb1358262cf * CI update lock file for PR * chore: adding changesets * [nage] fix string and id filters, wrap pill filters in header (#20604) - fixes enum rendering in menu and pill filters - wraps filter pills in header - fixes string and id filters getting reset upon adding the filter again ![Screenshot 2025-09-19 at 3.33.12 PM.png](https://app.graphite.dev/user-attachments/assets/856eca6b-78a5-480d-be8c-e0acbd1dde62.png) ![Screenshot 2025-09-19 at 3.16.31 PM.png](https://app.graphite.dev/user-attachments/assets/5c02fef5-3c4f-4e42-83c9-69d7ce469985.png) ![Screenshot 2025-09-19 at 3.16.35 PM.png](https://app.graphite.dev/user-attachments/assets/70b44ed7-afda-469b-af8f-b49ea3c81ef3.png) GitOrigin-RevId: 2228ef78b37309d968b5d3b931b909f65a4cd77a * fix changesets --------- Co-authored-by: Ernesto Soltero <[email protected]> Co-authored-by: Brian Siao Tick Chong <[email protected]> Co-authored-by: Jason Wang <[email protected]> Co-authored-by: Lightspark Eng <[email protected]> Co-authored-by: Matt Davis <[email protected]> Co-authored-by: Kevin Zhang <[email protected]> Co-authored-by: Corey Martin <[email protected]> Co-authored-by: Aaryaman Bhute <[email protected]> Co-authored-by: lightspark-ci-js-sdk[bot] <134011073+lightspark-ci-js-sdk[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Peng Ying <[email protected]>
1 parent 57b7200 commit 8fd199a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1957
-236
lines changed

.changeset/better-turkeys-feel.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+
- Add USDT to supported currencies

.changeset/petite-pans-post.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 async validator for remote signing

.changeset/poor-rules-invent.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+
- Component and theme updates

apps/examples/remote-signing-server/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ async function handleRemoteSigningWebhook(
104104
signatureHeader: string,
105105
) {
106106
const validator = {
107-
should_sign: (webhook: WebhookEvent) => true,
107+
should_sign: async (webhook: WebhookEvent) => {
108+
// Simulate async policy, e.g., fetch account config or check DB
109+
await new Promise((r) => setTimeout(r, 1));
110+
return webhook.event_type === WebhookEventType.REMOTE_SIGNING;
111+
},
108112
};
109113

110114
const remoteSigningHandler = new RemoteSigningWebhookHandler(

packages/core/src/utils/currency.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const CurrencyUnit = {
2424
EUR: "EUR",
2525
GBP: "GBP",
2626
INR: "INR",
27+
USDT: "USDT",
2728

2829
Bitcoin: "BITCOIN",
2930
Microbitcoin: "MICROBITCOIN",
@@ -36,6 +37,7 @@ export const CurrencyUnit = {
3637
Php: "PHP",
3738
Gbp: "GBP",
3839
Inr: "INR",
40+
Usdt: "USDT",
3941
} as const;
4042

4143
export type CurrencyUnitType = (typeof CurrencyUnit)[keyof typeof CurrencyUnit];
@@ -67,6 +69,7 @@ const standardUnitConversionObj = {
6769
[CurrencyUnit.EUR]: (v: number) => v,
6870
[CurrencyUnit.GBP]: (v: number) => v,
6971
[CurrencyUnit.INR]: (v: number) => v,
72+
[CurrencyUnit.USDT]: (v: number) => v,
7073
};
7174

7275
/* Round without decimals since we're returning cents: */
@@ -97,6 +100,7 @@ const CONVERSION_MAP = {
97100
[CurrencyUnit.EUR]: toBitcoinConversion,
98101
[CurrencyUnit.GBP]: toBitcoinConversion,
99102
[CurrencyUnit.INR]: toBitcoinConversion,
103+
[CurrencyUnit.USDT]: toBitcoinConversion,
100104
},
101105
[CurrencyUnit.MICROBITCOIN]: {
102106
[CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000,
@@ -111,6 +115,7 @@ const CONVERSION_MAP = {
111115
[CurrencyUnit.EUR]: toMicrobitcoinConversion,
112116
[CurrencyUnit.GBP]: toMicrobitcoinConversion,
113117
[CurrencyUnit.INR]: toMicrobitcoinConversion,
118+
[CurrencyUnit.USDT]: toMicrobitcoinConversion,
114119
},
115120
[CurrencyUnit.MILLIBITCOIN]: {
116121
[CurrencyUnit.BITCOIN]: (v: number) => v / 1_000,
@@ -125,6 +130,7 @@ const CONVERSION_MAP = {
125130
[CurrencyUnit.EUR]: toMillibitcoinConversion,
126131
[CurrencyUnit.GBP]: toMillibitcoinConversion,
127132
[CurrencyUnit.INR]: toMillibitcoinConversion,
133+
[CurrencyUnit.USDT]: toMillibitcoinConversion,
128134
},
129135
[CurrencyUnit.MILLISATOSHI]: {
130136
[CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000_000,
@@ -139,6 +145,7 @@ const CONVERSION_MAP = {
139145
[CurrencyUnit.EUR]: toMillisatoshiConversion,
140146
[CurrencyUnit.GBP]: toMillisatoshiConversion,
141147
[CurrencyUnit.INR]: toMillisatoshiConversion,
148+
[CurrencyUnit.USDT]: toMillisatoshiConversion,
142149
},
143150
[CurrencyUnit.NANOBITCOIN]: {
144151
[CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000_000,
@@ -153,6 +160,7 @@ const CONVERSION_MAP = {
153160
[CurrencyUnit.EUR]: toNanobitcoinConversion,
154161
[CurrencyUnit.GBP]: toNanobitcoinConversion,
155162
[CurrencyUnit.INR]: toNanobitcoinConversion,
163+
[CurrencyUnit.USDT]: toNanobitcoinConversion,
156164
},
157165
[CurrencyUnit.SATOSHI]: {
158166
[CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000,
@@ -167,13 +175,15 @@ const CONVERSION_MAP = {
167175
[CurrencyUnit.EUR]: toSatoshiConversion,
168176
[CurrencyUnit.GBP]: toSatoshiConversion,
169177
[CurrencyUnit.INR]: toSatoshiConversion,
178+
[CurrencyUnit.USDT]: toSatoshiConversion,
170179
},
171180
[CurrencyUnit.USD]: standardUnitConversionObj,
172181
[CurrencyUnit.MXN]: standardUnitConversionObj,
173182
[CurrencyUnit.PHP]: standardUnitConversionObj,
174183
[CurrencyUnit.EUR]: standardUnitConversionObj,
175184
[CurrencyUnit.GBP]: standardUnitConversionObj,
176185
[CurrencyUnit.INR]: standardUnitConversionObj,
186+
[CurrencyUnit.USDT]: standardUnitConversionObj,
177187
};
178188

179189
export function convertCurrencyAmountValue(
@@ -241,6 +251,7 @@ export type CurrencyMap = {
241251
[CurrencyUnit.EUR]: number;
242252
[CurrencyUnit.GBP]: number;
243253
[CurrencyUnit.INR]: number;
254+
[CurrencyUnit.USDT]: number;
244255
[CurrencyUnit.FUTURE_VALUE]: number;
245256
formatted: {
246257
sats: string;
@@ -258,6 +269,7 @@ export type CurrencyMap = {
258269
[CurrencyUnit.EUR]: string;
259270
[CurrencyUnit.GBP]: string;
260271
[CurrencyUnit.INR]: string;
272+
[CurrencyUnit.USDT]: string;
261273
[CurrencyUnit.FUTURE_VALUE]: string;
262274
};
263275
isZero: boolean;
@@ -459,6 +471,7 @@ function convertCurrencyAmountValues(
459471
mibtc: CurrencyUnit.MICROBITCOIN,
460472
mlbtc: CurrencyUnit.MILLIBITCOIN,
461473
nbtc: CurrencyUnit.NANOBITCOIN,
474+
usdt: CurrencyUnit.USDT,
462475
};
463476
return Object.entries(namesToUnits).reduce(
464477
(acc, [name, unit]) => {
@@ -505,8 +518,21 @@ export function mapCurrencyAmount(
505518
* preferred_currency_unit on CurrencyAmount types: */
506519
const conversionOverride = getPreferredConversionOverride(currencyAmountArg);
507520

508-
const { sats, msats, btc, usd, mxn, php, mibtc, mlbtc, nbtc, eur, gbp, inr } =
509-
convertCurrencyAmountValues(unit, value, unitsPerBtc, conversionOverride);
521+
const {
522+
sats,
523+
msats,
524+
btc,
525+
usd,
526+
mxn,
527+
php,
528+
mibtc,
529+
mlbtc,
530+
nbtc,
531+
eur,
532+
gbp,
533+
inr,
534+
usdt,
535+
} = convertCurrencyAmountValues(unit, value, unitsPerBtc, conversionOverride);
510536

511537
const mapWithCurrencyUnits = {
512538
[CurrencyUnit.BITCOIN]: btc,
@@ -521,6 +547,7 @@ export function mapCurrencyAmount(
521547
[CurrencyUnit.MICROBITCOIN]: mibtc,
522548
[CurrencyUnit.MILLIBITCOIN]: mlbtc,
523549
[CurrencyUnit.NANOBITCOIN]: nbtc,
550+
[CurrencyUnit.USDT]: usdt,
524551
[CurrencyUnit.FUTURE_VALUE]: NaN,
525552
formatted: {
526553
[CurrencyUnit.BITCOIN]: formatCurrencyStr({
@@ -571,6 +598,10 @@ export function mapCurrencyAmount(
571598
value: inr,
572599
unit: CurrencyUnit.INR,
573600
}),
601+
[CurrencyUnit.USDT]: formatCurrencyStr({
602+
value: usdt,
603+
unit: CurrencyUnit.USDT,
604+
}),
574605
[CurrencyUnit.FUTURE_VALUE]: "-",
575606
},
576607
};
@@ -651,6 +682,8 @@ export const abbrCurrencyUnit = (unit: CurrencyUnitType) => {
651682
return "GBP";
652683
case CurrencyUnit.INR:
653684
return "INR";
685+
case CurrencyUnit.USDT:
686+
return "USDT";
654687
}
655688
return "Unsupported CurrencyUnit";
656689
};
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { hexToBytes } from "@lightsparkdev/core";
2+
import { createHmac } from "crypto";
3+
import type LightsparkClient from "../client.js";
4+
import { RemoteSigningWebhookHandler } from "../webhooks.js";
5+
6+
describe("RemoteSigningWebhookHandler (integration with wasm)", () => {
7+
const seedHex =
8+
"1a6deac8f74fb2e332677e3f4833b5e962f80d153fb368b8ee322a9caca4113d56cccd88f1c6a74e152669d8cd373fee2f27e3645d80de27640177a8c71395f8";
9+
const seedBytes = hexToBytes(seedHex);
10+
11+
test("revoke/ack path returns undefined and does not call client", async () => {
12+
const dataHex =
13+
"7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223031386665366130346663323036613830303030393538343032643337663465222c20226170695f76657273696f6e223a2022323032332d30392d3133222c202274696d657374616d70223a2022323032342d30362d30355430343a32303a31362e3936323134312b30303a3030222c2022656e746974795f6964223a20224368616e6e656c3a30313866653661302d346661372d363965332d303030302d653139633836386564366564222c202264617461223a207b227375625f6576656e745f74797065223a202252455645414c5f434f554e54455250415254595f5045525f434f4d4d49544d454e545f534543524554222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c20227065725f636f6d6d69746d656e745f7365637265745f696478223a2032373336333139312c20227065725f636f6d6d69746d656e745f736563726574223a202236363837613837386538393733353535353131343039653363633762643934663535336336643265616230656231353831343761383337386266386335333261222c20226e6f64655f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139227d7d";
14+
const dataBytes = hexToBytes(dataHex);
15+
const sig =
16+
"864f7f575ec2425eb6ecfe457cbf17b41da0e4cbb70cda126c00ea507fbf699c";
17+
const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws";
18+
19+
const calls: Array<[Parameters<LightsparkClient["executeRawQuery"]>[0]]> =
20+
[];
21+
const executeRawQuery: LightsparkClient["executeRawQuery"] = async (
22+
query,
23+
) => {
24+
calls.push([query]);
25+
return null;
26+
};
27+
const client = { executeRawQuery } as unknown as LightsparkClient;
28+
29+
const handler = new RemoteSigningWebhookHandler(client, seedBytes, {
30+
should_sign: async () => true,
31+
});
32+
33+
const res = await handler.handleWebhookRequest(
34+
dataBytes,
35+
sig,
36+
webhookSecret,
37+
);
38+
expect(res).toBeUndefined();
39+
expect(calls.length).toBe(0);
40+
});
41+
42+
test("async validator true → executes UpdateChannelPerCommitmentPoint", async () => {
43+
const dataHex =
44+
"7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223031386665366138313066333036613830303030363835353239663539316563222c20226170695f76657273696f6e223a2022323032332d30392d3133222c202274696d657374616d70223a2022323032342d30362d30355430343a32383a34352e3137313839392b30303a3030222c2022656e746974795f6964223a20224368616e6e656c3a30313866653661382d313039362d363965332d303030302d623630373635343330663635222c202264617461223a207b227375625f6576656e745f74797065223a20224745545f5045525f434f4d4d49544d454e545f504f494e54222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c202264657269766174696f6e5f70617468223a20226d2f332f3139333238222c20227065725f636f6d6d69746d656e745f706f696e745f696478223a2032373336333139312c20226e6f64655f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139227d7d";
45+
const dataBytes = hexToBytes(dataHex);
46+
const sig =
47+
"5809279c1fd8088a6c62be82d3e858c11ce187ee97a0fadae1ae498e2f69442c";
48+
const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws";
49+
50+
const expectedVarsJson =
51+
'{"channel_id":"Channel:018fe6a8-1096-69e3-0000-b60765430f65","per_commitment_point":"027ba8a666d57947ba8337d0e211cae9125dbaf7c9883cb34f49393bc1a4907dd8","per_commitment_point_index":27363191}';
52+
53+
const calls: Array<[Parameters<LightsparkClient["executeRawQuery"]>[0]]> =
54+
[];
55+
const executeRawQuery: LightsparkClient["executeRawQuery"] = async (
56+
query,
57+
) => {
58+
calls.push([query]);
59+
return null;
60+
};
61+
const client = { executeRawQuery } as unknown as LightsparkClient;
62+
63+
const handler = new RemoteSigningWebhookHandler(client, seedBytes, {
64+
should_sign: async () => true,
65+
});
66+
67+
await handler.handleWebhookRequest(dataBytes, sig, webhookSecret);
68+
expect(calls.length).toBe(1);
69+
const [[firstArg]] = calls;
70+
expect(firstArg.queryPayload).toMatch(
71+
/^mutation UpdateChannelPerCommitmentPoint/,
72+
);
73+
expect(firstArg.variables).toEqual(JSON.parse(expectedVarsJson));
74+
});
75+
76+
test("async validator false → does not execute query and throws", async () => {
77+
const dataHex =
78+
"7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223031386665366138313066333036613830303030363835353239663539316563222c20226170695f76657273696f6e223a2022323032332d30392d3133222c202274696d657374616d70223a2022323032342d30362d30355430343a32383a34352e3137313839392b30303a3030222c2022656e746974795f6964223a20224368616e6e656c3a30313866653661382d313039362d363965332d303030302d623630373635343330663635222c202264617461223a207b227375625f6576656e745f74797065223a20224745545f5045525f434f4d4d49544d454e545f504f494e54222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c202264657269766174696f6e5f70617468223a20226d2f332f3139333238222c20227065725f636f6d6d69746d656e745f706f696e745f696478223a2032373336333139312c20226e6f64655f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139227d7d";
79+
const dataBytes = hexToBytes(dataHex);
80+
const sig =
81+
"5809279c1fd8088a6c62be82d3e858c11ce187ee97a0fadae1ae498e2f69442c";
82+
const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws";
83+
84+
const calls: Array<[Parameters<LightsparkClient["executeRawQuery"]>[0]]> =
85+
[];
86+
const executeRawQuery: LightsparkClient["executeRawQuery"] = async (
87+
query,
88+
) => {
89+
calls.push([query]);
90+
return null;
91+
};
92+
const client = { executeRawQuery } as unknown as LightsparkClient;
93+
94+
const handler = new RemoteSigningWebhookHandler(client, seedBytes, {
95+
should_sign: async () => false,
96+
});
97+
98+
await expect(
99+
handler.handleWebhookRequest(dataBytes, sig, webhookSecret),
100+
).rejects.toBeTruthy();
101+
expect(calls.length).toBe(0);
102+
});
103+
104+
test("async validator false (DERIVE_KEY_AND_SIGN) → decline_to_sign_messages", async () => {
105+
const event = {
106+
event_type: "REMOTE_SIGNING",
107+
event_id: "abc-derive",
108+
timestamp: "2024-06-05T04:28:45.171899+00:00",
109+
entity_id: "Node:018fe6a8-1096-69e3-0000-b60765430f65",
110+
data: {
111+
sub_event_type: "DERIVE_KEY_AND_SIGN",
112+
bitcoin_network: "REGTEST",
113+
signing_jobs: [
114+
{
115+
id: "payload-1",
116+
derivation_path: "m/3/2106220917/0",
117+
message:
118+
"476bdd1db5d91897d00d75300eef50c0da7e0b2dada06dde93cbb5903b7e16b2",
119+
is_raw: true,
120+
},
121+
],
122+
},
123+
};
124+
const json = JSON.stringify(event);
125+
const dataBytes = new TextEncoder().encode(json);
126+
const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws";
127+
const sig = createHmac("sha256", webhookSecret)
128+
.update(dataBytes)
129+
.digest("hex");
130+
131+
const calls: Array<[Parameters<LightsparkClient["executeRawQuery"]>[0]]> =
132+
[];
133+
const executeRawQuery: LightsparkClient["executeRawQuery"] = async (
134+
query,
135+
) => {
136+
calls.push([query]);
137+
return null;
138+
};
139+
const client = { executeRawQuery } as unknown as LightsparkClient;
140+
141+
const handler = new RemoteSigningWebhookHandler(client, seedBytes, {
142+
should_sign: async () => false,
143+
});
144+
145+
await handler.handleWebhookRequest(dataBytes, sig, webhookSecret);
146+
expect(calls.length).toBe(1);
147+
const [[firstArg]] = calls;
148+
expect(firstArg.queryPayload).toMatch(/decline_to_sign_messages\s*\(/);
149+
expect(firstArg.variables).toEqual({ payload_ids: ["payload-1"] });
150+
});
151+
});

packages/lightspark-sdk/src/webhooks.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const parseWebhook = (data: Uint8Array): WebhookEvent => {
4242
};
4343

4444
type Validator = {
45-
should_sign: (event: WebhookEvent) => boolean;
45+
should_sign: (event: WebhookEvent) => boolean | Promise<boolean>;
4646
};
4747

4848
export class RemoteSigningWebhookHandler {
@@ -71,15 +71,25 @@ export class RemoteSigningWebhookHandler {
7171
);
7272
}
7373

74+
// Pre-parse to expose a typed event to the validator and allow async decisions.
75+
const event = await verifyAndParseWebhook(
76+
data,
77+
webhookSignature,
78+
webhookSecret,
79+
);
80+
81+
const decision = await this.validator.should_sign(event);
82+
7483
const { wasm_handle_remote_signing_webhook_event } = await import(
7584
"@lightsparkdev/crypto-wasm"
7685
);
86+
7787
const response = wasm_handle_remote_signing_webhook_event(
7888
data,
7989
webhookSignature,
8090
webhookSecret,
8191
this.#masterSeed,
82-
this.validator,
92+
{ should_sign: () => decision },
8393
);
8494
if (!response) {
8595
return;

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"@wojtekmaj/react-datetimerange-picker": "^5.5.0",
9797
"@zxing/browser": "^0.1.1",
9898
"@zxing/library": "^0.19.2",
99+
"dayjs": "^1.11.7",
99100
"deep-object-diff": "^1.1.9",
100101
"deepmerge": "^4.3.1",
101102
"libphonenumber-js": "^1.11.1",

0 commit comments

Comments
 (0)