From b89579431227789f552d4cc9e67dac048497e691 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:10:22 -0700 Subject: [PATCH] [uma] Remove SDK from private repo (#6780) (#280) * [uma] Remove SDK from private repo (#6780) GitOrigin-RevId: 232ebc6dbdad88222d5525731c921117a795f748 * feat: add initial typography tokens and typography components GitOrigin-RevId: 8a517bb99f1d4d9799c6803dad003e1d58a9f10c * feat: add ArticleBody component to specify spacings in Article-like contexts GitOrigin-RevId: 191a2458f746e387203a691c474da0bc3954de8b * CI update lock file for PR * [remote-signing-server] Handle actual webhook data (#6645) GitOrigin-RevId: 5426782d6fd6f84f1d1df5868ed1e73aa763b868 * feat: add uma light theme (#6798) GitOrigin-RevId: bedb3b59faa3d1e5a640e67ed5cc03b4b290b904 --------- Co-authored-by: Corey Martin Co-authored-by: Brian Siao Tick Chong Co-authored-by: Lightspark Eng --- .../remote-signing-server/server.test.ts | 26 +- .../remote-signing-server/src/index.ts | 80 +-- packages/ui/components/UnstyledButton.tsx | 2 +- packages/ui/styles/colors.tsx | 36 +- packages/ui/styles/fields.tsx | 2 +- .../ui/styles/fonts/typography/Article.tsx | 29 + packages/ui/styles/fonts/typography/Body.tsx | 30 ++ packages/ui/styles/fonts/typography/Code.tsx | 30 ++ .../ui/styles/fonts/typography/Headline.tsx | 62 +++ packages/ui/styles/fonts/typography/index.ts | 4 + packages/ui/styles/fonts/typographyTokens.ts | 446 ++++++++++++++++ packages/ui/styles/global.tsx | 2 +- packages/uma/.eslintrc.cjs | 9 - packages/uma/.fossa.yml | 6 - packages/uma/.prettierrc | 3 - packages/uma/CHANGELOG.md | 83 --- packages/uma/LICENSE | 201 ------- packages/uma/README.md | 1 - packages/uma/jest.config.ts | 18 - packages/uma/package.json | 98 ---- packages/uma/src/Currency.ts | 12 - packages/uma/src/KycStatus.ts | 36 -- packages/uma/src/PayerData.ts | 47 -- packages/uma/src/PublicKeyCache.ts | 33 -- packages/uma/src/client.ts | 54 -- packages/uma/src/index.ts | 10 - packages/uma/src/protocol.ts | 225 -------- packages/uma/src/tests/protocol.test.ts | 60 --- packages/uma/src/tests/uma.test.ts | 404 -------------- packages/uma/src/uma.ts | 495 ------------------ packages/uma/src/version.ts | 109 ---- packages/uma/tsconfig-test.json | 9 - packages/uma/tsconfig.json | 5 - yarn.lock | 75 --- 34 files changed, 690 insertions(+), 2052 deletions(-) create mode 100644 packages/ui/styles/fonts/typography/Article.tsx create mode 100644 packages/ui/styles/fonts/typography/Body.tsx create mode 100644 packages/ui/styles/fonts/typography/Code.tsx create mode 100644 packages/ui/styles/fonts/typography/Headline.tsx create mode 100644 packages/ui/styles/fonts/typography/index.ts create mode 100644 packages/ui/styles/fonts/typographyTokens.ts delete mode 100644 packages/uma/.eslintrc.cjs delete mode 100644 packages/uma/.fossa.yml delete mode 100644 packages/uma/.prettierrc delete mode 100644 packages/uma/CHANGELOG.md delete mode 100644 packages/uma/LICENSE delete mode 100644 packages/uma/README.md delete mode 100644 packages/uma/jest.config.ts delete mode 100644 packages/uma/package.json delete mode 100644 packages/uma/src/Currency.ts delete mode 100644 packages/uma/src/KycStatus.ts delete mode 100644 packages/uma/src/PayerData.ts delete mode 100644 packages/uma/src/PublicKeyCache.ts delete mode 100644 packages/uma/src/client.ts delete mode 100644 packages/uma/src/index.ts delete mode 100644 packages/uma/src/protocol.ts delete mode 100644 packages/uma/src/tests/protocol.test.ts delete mode 100644 packages/uma/src/tests/uma.test.ts delete mode 100644 packages/uma/src/uma.ts delete mode 100644 packages/uma/src/version.ts delete mode 100644 packages/uma/tsconfig-test.json delete mode 100644 packages/uma/tsconfig.json diff --git a/apps/examples/remote-signing-server/server.test.ts b/apps/examples/remote-signing-server/server.test.ts index 796fb26cf..0a14f9379 100644 --- a/apps/examples/remote-signing-server/server.test.ts +++ b/apps/examples/remote-signing-server/server.test.ts @@ -36,7 +36,7 @@ describe("Test server routes", () => { "a64c69f1266bc1dc1322c3f40eba7ba2d536c714774a4fc04f0938609482f5d9", ) .send( - "7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223530353364626438633562303435333439346631633134653031646136396364222c202274696d657374616d70223a2022323032332d30392d31385432333a35303a31352e3335353630332b30303a3030222c2022656e746974795f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139222c202264617461223a207b227375625f6576656e745f74797065223a202245434448222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c2022706565725f7075626c69635f6b6579223a2022303331373364393764303937336435393637313663386364313430363665323065323766363836366162323134666430346431363033303136313564653738663732227d7d", + `{"event_type": "REMOTE_SIGNING", "event_id": "5053dbd8c5b0453494f1c14e01da69cd", "timestamp": "2023-09-18T23:50:15.355603+00:00", "entity_id": "node_with_server_signing:018a9635-3673-88df-0000-827f23051b19", "data": {"sub_event_type": "ECDH", "bitcoin_network": "REGTEST", "peer_public_key": "03173d97d0973d596716c8cd14066e20e27f6866ab214fd04d160301615de78f72"}}`, ); expect(response.status).toBe(200); @@ -44,18 +44,16 @@ describe("Test server routes", () => { test("error reponse from /lightspark-webhook webhook data is invalid", async () => { let response: supertest.Response; - try { - jest - .spyOn(LightsparkClient.prototype, "executeRawQuery") - .mockReturnValue(Promise.resolve("mock-query-response")); - response = await request - .post("/lightspark-webhook") - .set( - "lightspark-signature", - "a64c69f1266bc1dc1322c3f40eba7ba2d536c714774a4fc04f0938609482f5d9", - ) - .send("7b226576656e"); - expect(response.status).toBe(500); - } catch (error) {} + jest + .spyOn(LightsparkClient.prototype, "executeRawQuery") + .mockReturnValue(Promise.resolve("mock-query-response")); + response = await request + .post("/lightspark-webhook") + .set( + "lightspark-signature", + "a64c69f1266bc1dc1322c3f40eba7ba2d536c714774a4fc04f0938609482f5d9", + ) + .send("7b226576656e"); + expect(response.status).toBe(500); }); }); diff --git a/apps/examples/remote-signing-server/src/index.ts b/apps/examples/remote-signing-server/src/index.ts index acbe25816..6399e0ea9 100644 --- a/apps/examples/remote-signing-server/src/index.ts +++ b/apps/examples/remote-signing-server/src/index.ts @@ -3,7 +3,10 @@ import { AccountTokenAuthProvider, LightsparkClient, RemoteSigningWebhookHandler, + verifyAndParseWebhook, WebhookEvent, + WebhookEventType, + WEBHOOKS_SIGNATURE_HEADER, } from "@lightsparkdev/lightspark-sdk"; import { EnvCredentials, @@ -17,61 +20,68 @@ export const app = express(); const WEBHOOK_SECRET = process.env.RK_WEBHOOK_SECRET; const MASTER_SEED_HEX = process.env.RK_MASTER_SEED_HEX; -app.use(bodyParser.text({ type: "*/*" })); // Middleware to parse raw body +app.use(bodyParser.raw({ type: "*/*" })); // Middleware to parse raw body app.get("/ping", (req, res) => { console.log("ping"); res.send("OK"); }); -app.post("/lightspark-webhook", (req, res) => { +app.post("/lightspark-webhook", async (req, res) => { let credentials: EnvCredentials; try { - try { - credentials = getCredentialsFromEnvOrThrow(); - } catch (e) { - res.status(500).send("Unable to get credentials from env"); - return; - } - - const lightsparkClient = new LightsparkClient( - new AccountTokenAuthProvider( - credentials.apiTokenClientId, - credentials.apiTokenClientSecret, - ), - credentials.baseUrl, - ); - - const validator = { - should_sign: (webhook: WebhookEvent) => true, - }; - const remoteSigningHandler = new RemoteSigningWebhookHandler( - lightsparkClient, - hexToBytes(MASTER_SEED_HEX), - validator, - ); - - const signatureHeader = req.headers["lightspark-signature"]; - console.log("signatureHeader", signatureHeader); + const signatureHeader = req.headers[WEBHOOKS_SIGNATURE_HEADER]; if (typeof signatureHeader !== "string") { - console.error("Invalid signature header"); res.status(400).send("Invalid signature header"); return; } - console.log("body", req.body); - - const result = remoteSigningHandler.handleWebhookRequest( - hexToBytes(req.body), + const webhook = await verifyAndParseWebhook( + req.body, signatureHeader, WEBHOOK_SECRET, ); - console.log("result", result); + + switch (webhook.event_type) { + case WebhookEventType.REMOTE_SIGNING: + try { + credentials = getCredentialsFromEnvOrThrow(); + } catch (e) { + res.status(500).send("Unable to get credentials from env"); + return; + } + + const lightsparkClient = new LightsparkClient( + new AccountTokenAuthProvider( + credentials.apiTokenClientId, + credentials.apiTokenClientSecret, + ), + credentials.baseUrl, + ); + + const validator = { + should_sign: (webhook: WebhookEvent) => true, + }; + + const remoteSigningHandler = new RemoteSigningWebhookHandler( + lightsparkClient, + hexToBytes(MASTER_SEED_HEX), + validator, + ); + + const result = remoteSigningHandler.handleWebhookRequest( + req.body, + signatureHeader, + WEBHOOK_SECRET, + ); + break; + default: + res.status(400).send("Unknown webhook type"); + } res.send("OK"); } catch (e) { - console.error(e); res.status(500).send("Internal server error"); } }); diff --git a/packages/ui/components/UnstyledButton.tsx b/packages/ui/components/UnstyledButton.tsx index 3c00e63a4..ef93ce07f 100644 --- a/packages/ui/components/UnstyledButton.tsx +++ b/packages/ui/components/UnstyledButton.tsx @@ -4,7 +4,7 @@ import { standardFocusOutline } from "@lightsparkdev/ui/styles/common"; export const UnstyledButton = styled.button` ${standardFocusOutline} - font-family: ${({ theme }) => theme.fontFamily}; + font-family: ${({ theme }) => theme.typography.fontFamilies.main}; appearance: none; background: transparent; border: none; diff --git a/packages/ui/styles/colors.tsx b/packages/ui/styles/colors.tsx index a5b15b1ad..7720ece7e 100644 --- a/packages/ui/styles/colors.tsx +++ b/packages/ui/styles/colors.tsx @@ -2,6 +2,7 @@ import type { CSSInterpolation } from "@emotion/css"; import type { Theme } from "@emotion/react"; import { css, useTheme } from "@emotion/react"; import { Breakpoints, useBreakpoints } from "./breakpoints"; +import { getTypography } from "./fonts/typographyTokens"; const neutral = { black: "#000000", @@ -23,11 +24,14 @@ const neutral = { }; const uma = { + background: "#F9F9F9", + black: "#16171A", blue: "#0068C9", blue50: "#C0C9D6", blue80: "#DCE2EA", blue90: "#EBEEF2", blue95: "#F2F5F7", + secondary: "#686A72", stroke: "#C0C9D6", }; @@ -80,7 +84,6 @@ interface BaseTheme { c8Neutral: string; c9Neutral: string; danger: string; - fontFamily: string; hcNeutral: string; hcNeutralFromBg: (hex: string) => string; info: string; @@ -92,6 +95,7 @@ interface BaseTheme { primary: string; success: string; text: string; + typography: ReturnType; vlcNeutral: string; warning: string; } @@ -108,11 +112,11 @@ declare module "@emotion/react" { export interface Theme extends LightsparkTheme {} } -function extend(obj: BaseTheme, rest: LightsparkSurfaces) { +function extend(obj: BaseTheme, rest: Partial) { return { ...obj, ...rest, - }; + } as LightsparkTheme; } function extendBase(obj: BaseTheme, rest: Partial) { @@ -149,7 +153,6 @@ const lightBaseTheme: BaseTheme = { c8Neutral: neutral.gray20, c9Neutral: neutral.gray10, danger: colors.danger, - fontFamily: "Montserrat", hcNeutral: colors.black, hcNeutralFromBg: (bgHex) => hcNeutralFromBg(bgHex, colors.black, colors.white), @@ -162,6 +165,7 @@ const lightBaseTheme: BaseTheme = { primary: colors.primary, success: colors.success, text: colors.black, + typography: getTypography(), vlcNeutral: neutral.gray95, warning: colors.warning, }; @@ -182,7 +186,6 @@ const darkBaseTheme: BaseTheme = { c8Neutral: neutral.gray80, c9Neutral: neutral.gray90, danger: colors.danger, - fontFamily: "Montserrat", hcNeutral: colors.white, hcNeutralFromBg: (bgHex) => hcNeutralFromBg(bgHex, colors.white, colors.black), @@ -195,6 +198,7 @@ const darkBaseTheme: BaseTheme = { primary: colors.primary, success: colors.success, text: colors.white, + typography: getTypography(), vlcNeutral: neutral.gray20, warning: colors.warning, }; @@ -228,16 +232,34 @@ const darkTheme = extend(darkBaseTheme, { }), }); -export const themeWithFont = (theme: Theme, fontFamily: string) => { - return extendBase(theme, { fontFamily }); +const umaLightTheme = extend(lightTheme, { + bg: uma.background, + smBg: uma.background, +}); + +/** + * Allows setting typography in cases where a custom font is needed. + * Setting custom fonts should only be necessary for next fonts. + */ +export const themeWithTypography = ( + theme: Theme, + typography: ReturnType, +) => { + return extendBase(theme, { typography }); }; export const themes: { light: LightsparkTheme; dark: LightsparkTheme; + uma: { + light: LightsparkTheme; + }; } = { light: lightTheme, dark: darkTheme, + uma: { + light: umaLightTheme, + }, }; export const isDark = (theme: Theme) => theme.type === Themes.Dark; diff --git a/packages/ui/styles/fields.tsx b/packages/ui/styles/fields.tsx index 71b00b9f1..9d77c1a2a 100644 --- a/packages/ui/styles/fields.tsx +++ b/packages/ui/styles/fields.tsx @@ -53,7 +53,7 @@ export const textInputStyle = ({ position: relative; z-index: ${z.textInput}; - font-family: ${theme.fontFamily}; + font-family: ${theme.typography?.fontFamilies.main}; padding: ${textInputPaddingPx - (hasError ? 1 : 0)}px; ${paddingLeftPx ? `padding-left: ${paddingLeftPx - (hasError ? 1 : 0)}px;` diff --git a/packages/ui/styles/fonts/typography/Article.tsx b/packages/ui/styles/fonts/typography/Article.tsx new file mode 100644 index 000000000..7b8a9f4a7 --- /dev/null +++ b/packages/ui/styles/fonts/typography/Article.tsx @@ -0,0 +1,29 @@ +"use client"; +import styled from "@emotion/styled"; +import { StyledBody } from "./Body"; +import { headlineSelector } from "./Headline"; + +export const Article = styled.article` + ${headlineSelector("h1")} { + margin: 0; + padding-bottom: 8px; + } + + ${headlineSelector("h2")}, ${headlineSelector("h3")}}, ${headlineSelector( + "h4", + )}, ${headlineSelector("h5")}, ${headlineSelector("h6")} { + padding-top: 32px; + padding-bottom: 8px; + margin: 0; + } + + ${StyledBody} { + margin-top: 8px; + margin-bottom: 8px; + } + + img { + margin-top: 16px; + margin-bottom: 16px; + } +`; diff --git a/packages/ui/styles/fonts/typography/Body.tsx b/packages/ui/styles/fonts/typography/Body.tsx new file mode 100644 index 000000000..8ea522de9 --- /dev/null +++ b/packages/ui/styles/fonts/typography/Body.tsx @@ -0,0 +1,30 @@ +"use client"; + +import styled from "@emotion/styled"; +import { App, getTypographyString, TokenSize } from "../typographyTokens"; + +interface Props { + children: React.ReactNode; + app?: App; + size?: TokenSize; +} + +export const Body = ({ + children, + app = App.Lightspark, + size = TokenSize.Medium, +}: Props) => { + return ( + + {children} + + ); +}; + +export const StyledBody = styled.p` + ${({ theme, app, size }) => { + return app && size + ? getTypographyString(theme.typography[app].Body[size]) + : ""; + }} +`; diff --git a/packages/ui/styles/fonts/typography/Code.tsx b/packages/ui/styles/fonts/typography/Code.tsx new file mode 100644 index 000000000..7f0d43943 --- /dev/null +++ b/packages/ui/styles/fonts/typography/Code.tsx @@ -0,0 +1,30 @@ +"use client"; + +import styled from "@emotion/styled"; +import { App, getTypographyString, TokenSize } from "../typographyTokens"; + +interface Props { + children: React.ReactNode; + app?: App; + size?: TokenSize; +} + +export const Code = ({ + children, + app = App.Lightspark, + size = TokenSize.Medium, +}: Props) => { + return ( + + {children} + + ); +}; + +const CodeStyles = styled.div` + ${({ theme, app, size }) => { + return app && size + ? getTypographyString(theme.typography[app].Code[size]) + : ""; + }} +`; diff --git a/packages/ui/styles/fonts/typography/Headline.tsx b/packages/ui/styles/fonts/typography/Headline.tsx new file mode 100644 index 000000000..5f6069825 --- /dev/null +++ b/packages/ui/styles/fonts/typography/Headline.tsx @@ -0,0 +1,62 @@ +"use client"; + +import styled from "@emotion/styled"; +import { colors } from "../../colors"; +import { App, getTypographyString, TokenSize } from "../typographyTokens"; + +type Heading = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + +interface Props { + children: React.ReactNode; + app?: App; + size?: TokenSize; + heading?: Heading; + pt?: number; + mt?: number; + color?: string | undefined; +} + +const toKebabCase = (str: string) => { + return str + .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) + ?.join("-") + .toLowerCase(); +}; + +export const Headline = ({ + children, + color, + app = App.Lightspark, + size = TokenSize.Medium, + heading = "h1", +}: Props) => { + if (!color) { + if (app === App.UmaDocs) { + color = colors.uma.black; + } + } + return ( + + {children} + + ); +}; + +const StyledHeadline = styled.span` + ${(props) => (props.color === undefined ? "" : `color: ${props.color};`)} + ${({ theme, app, size }) => { + return app && size + ? getTypographyString(theme.typography[app].Headline[size]) + : ""; + }} +`; + +export function headlineSelector(heading: Heading) { + return `${heading}${StyledHeadline}`; +} diff --git a/packages/ui/styles/fonts/typography/index.ts b/packages/ui/styles/fonts/typography/index.ts new file mode 100644 index 000000000..079c5b4bb --- /dev/null +++ b/packages/ui/styles/fonts/typography/index.ts @@ -0,0 +1,4 @@ +export { Article } from "./Article"; +export { Body } from "./Body"; +export { Code } from "./Code"; +export { Headline } from "./Headline"; diff --git a/packages/ui/styles/fonts/typographyTokens.ts b/packages/ui/styles/fonts/typographyTokens.ts new file mode 100644 index 000000000..2df4bdd64 --- /dev/null +++ b/packages/ui/styles/fonts/typographyTokens.ts @@ -0,0 +1,446 @@ +const FONT_FAMILIES = { + main: "Montserrat", + code: "Roboto mono", +}; + +const LINE_HEIGHTS = { + "5xl": "72px", + "4xl": "60px", + "3xl": "44px", + "2xl": "40px", + xl: "36px", + lg: "32px", + md: "28px", + sm: "24px", + xs: "20px", + "2xs": "16px", +}; + +const FONT_WEIGHTS = { + main: { + Bold: 700, + SemiBold: 600, + Medium: 500, + }, + code: { + Bold: 700, + Regular: 400, + }, +}; + +const FONT_SIZE = { + "2xs": "11px", + xs: "12px", + sm: "14px", + md: "16px", + lg: "20px", + xl: "24px", + "2xl": "28px", + "3xl": "32px", + "4xl": "36px", + "5xl": "48px", + "6xl": "64px", +}; + +const LETTER_SPACING = { + tight: "-.02em", + normal: "0", + loose: ".1em", +}; + +const TEXT_CASE = { + none: "none", + uppercase: "uppercase", +}; + +const TEXT_DECORATION = { + none: "none", +}; + +const PARAGRAPH_INDENT = { + 0: "0px", +}; + +const PARAGRAPH_SPACING = { + default: "default", + 16: "16px", + 40: "40px", +}; + +export interface FontFamilies { + main: string; + code: string; +} + +interface Token { + "font-family": string; + "font-weight": string; + "line-height": string; + "font-size": string; + "letter-spacing": string; + "paragraph-spacing": string; + "paragraph-indent": string; + "text-case": string; + "text-decoration": string; +} + +export enum TokenSize { + Large, + Medium, + Small, +} + +export enum App { + Lightspark, + UmaDocs, +} + +export const getTypography = (customFontFamilies?: FontFamilies) => { + const fontFamilies = customFontFamilies ?? FONT_FAMILIES; + return { + fontFamilies, + lineHeights: LINE_HEIGHTS, + fontWeights: FONT_WEIGHTS, + fontSize: FONT_SIZE, + letterSpacing: LETTER_SPACING, + textCase: TEXT_CASE, + textDecoration: TEXT_DECORATION, + paragraphIndent: PARAGRAPH_INDENT, + paragraphSpacing: PARAGRAPH_SPACING, + [App.UmaDocs]: { + Display: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["5xl"]}`, + "font-size": `${FONT_SIZE["6xl"]}`, + "letter-spacing": `${LETTER_SPACING.tight}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["4xl"]}`, + "font-size": `${FONT_SIZE["5xl"]}`, + "letter-spacing": `${LETTER_SPACING.tight}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["3xl"]}`, + "font-size": `${FONT_SIZE["4xl"]}`, + "letter-spacing": `${LETTER_SPACING.tight}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + Headline: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["2xl"]}`, + "font-size": `${FONT_SIZE["3xl"]}`, + "letter-spacing": `${LETTER_SPACING.tight}`, + "paragraph-spacing": `${PARAGRAPH_SPACING[16]}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.xl}`, + "font-size": `${FONT_SIZE["2xl"]}`, + "letter-spacing": `${LETTER_SPACING.tight}`, + "paragraph-spacing": `${PARAGRAPH_SPACING[16]}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.lg}`, + "font-size": `${FONT_SIZE.xl}`, + "letter-spacing": `${LETTER_SPACING.tight}`, + "paragraph-spacing": `${PARAGRAPH_SPACING[16]}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + Title: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.md}`, + "font-size": `${FONT_SIZE.lg}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.md}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.xs}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + Body: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.md}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Medium}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Medium}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.xs}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + Label: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.xs}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["2xs"]}`, + "font-size": `${FONT_SIZE.xs}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["2xs"]}`, + "font-size": `${FONT_SIZE["2xs"]}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + "Label Strong": { + [TokenSize.Large]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS.xs}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["2xs"]}`, + "font-size": `${FONT_SIZE.xs}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.main}`, + "font-weight": `${FONT_WEIGHTS.main.Bold}`, + "line-height": `${LINE_HEIGHTS["2xs"]}`, + "font-size": `${FONT_SIZE["2xs"]}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + Overline: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Bold}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.md}`, + "letter-spacing": `${LETTER_SPACING.loose}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.uppercase}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Bold}`, + "line-height": `${LINE_HEIGHTS.xs}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.loose}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.uppercase}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Bold}`, + "line-height": `${LINE_HEIGHTS["2xs"]}`, + "font-size": `${FONT_SIZE.xs}`, + "letter-spacing": `${LETTER_SPACING.loose}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.uppercase}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + Code: { + [TokenSize.Large]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Regular}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.md}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Regular}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Regular}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.xs}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + "Code Strong": { + [TokenSize.Large]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Bold}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.md}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Medium]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Bold}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.sm}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + [TokenSize.Small]: { + "font-family": `${fontFamilies.code}`, + "font-weight": `${FONT_WEIGHTS.code.Bold}`, + "line-height": `${LINE_HEIGHTS.sm}`, + "font-size": `${FONT_SIZE.xs}`, + "letter-spacing": `${LETTER_SPACING.normal}`, + "paragraph-spacing": `${PARAGRAPH_SPACING.default}`, + "paragraph-indent": `${PARAGRAPH_INDENT[0]}`, + "text-case": `${TEXT_CASE.none}`, + "text-decoration": `${TEXT_DECORATION.none}`, + }, + }, + }, + }; +}; + +export const getTypographyString = (token: Token) => { + return Object.entries(token) + .map((entry) => { + // Transform any non-css token properties into valid css. + switch (entry[0]) { + case "paragraph-spacing": + return entry[1] === "default" ? "" : `margin-bottom: ${entry[1]};`; + case "paragraph-indent": + return `text-indent: ${entry[1]};`; + case "text-case": + return `text-transform: ${entry[1]};`; + default: + return `${entry[0]}: ${entry[1]};`; + } + }) + .join(""); +}; diff --git a/packages/ui/styles/global.tsx b/packages/ui/styles/global.tsx index 54063f831..69817fcbe 100644 --- a/packages/ui/styles/global.tsx +++ b/packages/ui/styles/global.tsx @@ -40,7 +40,7 @@ export function GlobalStyles() { } body { - font-family: ${theme.fontFamily}, sans-serif; + font-family: ${theme.typography.fontFamilies.main}, sans-serif; font-weight: 500; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/packages/uma/.eslintrc.cjs b/packages/uma/.eslintrc.cjs deleted file mode 100644 index 44b6e8dd2..000000000 --- a/packages/uma/.eslintrc.cjs +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - extends: ["@lightsparkdev/eslint-config/base"], - ignorePatterns: ["jest.config.ts"], - overrides: [ - { - files: ["**/*.ts?(x)"], - }, - ], -}; diff --git a/packages/uma/.fossa.yml b/packages/uma/.fossa.yml deleted file mode 100644 index 5877824ec..000000000 --- a/packages/uma/.fossa.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 3 - -project: - id: lightspark/js-uma-sdk - name: js-uma-sdk - url: https://github.com/lightsparkdev/js-sdk diff --git a/packages/uma/.prettierrc b/packages/uma/.prettierrc deleted file mode 100644 index 55c1943ae..000000000 --- a/packages/uma/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["prettier-plugin-organize-imports"] -} diff --git a/packages/uma/CHANGELOG.md b/packages/uma/CHANGELOG.md deleted file mode 100644 index d51f239fd..000000000 --- a/packages/uma/CHANGELOG.md +++ /dev/null @@ -1,83 +0,0 @@ -# @lightsparkdev/uma - -## 0.0.10 - -### Patch Changes - -- Updated dependencies [c4926df] - - @lightsparkdev/lightspark-sdk@1.1.0 - -## 0.0.9 - -### Patch Changes - -- Updated dependencies [e156e3e] - - @lightsparkdev/lightspark-sdk@1.0.7 - -## 0.0.8 - -### Patch Changes - -- Updated dependencies [ca58c08] - - @lightsparkdev/lightspark-sdk@1.0.6 - - @lightsparkdev/core@1.0.5 - -## 0.0.7 - -### Patch Changes - -- Updated dependencies [545fe1f] -- Updated dependencies [545fe1f] - - @lightsparkdev/core@1.0.4 - - @lightsparkdev/lightspark-sdk@1.0.5 - -## 0.0.6 - -### Patch Changes - -- Updated dependencies [e451948] - - @lightsparkdev/core@1.0.3 - - @lightsparkdev/lightspark-sdk@1.0.4 - -## 0.0.5 - -### Patch Changes - -- Updated dependencies [0e8767b] - - @lightsparkdev/core@1.0.2 - - @lightsparkdev/lightspark-sdk@1.0.3 - -## 0.0.4 - -### Patch Changes - -- be6cd89: [uma] Complete core SDK functions and tests - -## 0.0.3 - -### Patch Changes - -- e2b1515: - [uma] Add fetchPublicKeyForVasp - - [lightspark-cli] add payUmaInvoice - - [lightspark-cli] allow generate-node-keys to be used without running init-env and always asks user to choose the bitcoin network to generate the node keys - - [lightspark-cli] updates commands to get bitcoin network from the selected node in most scenarios - - [lightspark-sdk] use crypto-wasm for crypto operations - - [lightspark-sdk] dynamically import crypto-wasm for node environments only -- Updated dependencies [e2b1515] - - @lightsparkdev/lightspark-sdk@1.0.2 - -## 0.0.2 - -### Patch Changes - -- 808c77a: Add validation functions and tests -- Updated dependencies [808c77a] - - @lightsparkdev/lightspark-sdk@1.0.1 - - @lightsparkdev/core@1.0.1 - -## 0.0.1 - -### Patch Changes - -- Updated dependencies [1f00a50] - - @lightsparkdev/core@1.0.0 diff --git a/packages/uma/LICENSE b/packages/uma/LICENSE deleted file mode 100644 index cbb91e498..000000000 --- a/packages/uma/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2023 Lightspark Group, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/uma/README.md b/packages/uma/README.md deleted file mode 100644 index ec9269eb2..000000000 --- a/packages/uma/README.md +++ /dev/null @@ -1 +0,0 @@ -# UMA SDK for Node.js diff --git a/packages/uma/jest.config.ts b/packages/uma/jest.config.ts deleted file mode 100644 index b5d8b34e8..000000000 --- a/packages/uma/jest.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: "ts-jest/presets/default-esm", - testEnvironment: "node", - extensionsToTreatAsEsm: [".ts"], - moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", - }, - transform: { - "^.+\\.tsx?$": [ - "ts-jest", - { - tsconfig: "tsconfig-test.json", - useESM: true, - }, - ], - }, -}; diff --git a/packages/uma/package.json b/packages/uma/package.json deleted file mode 100644 index 05aae92af..000000000 --- a/packages/uma/package.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "name": "@lightsparkdev/uma", - "version": "0.0.10", - "description": "UMA SDK for JavaScript", - "author": "Lightspark Inc.", - "keywords": [ - "lightspark", - "bitcoin", - "lightning", - "payments", - "typescript" - ], - "homepage": "https://github.com/lightsparkdev/js-sdk", - "repository": { - "type": "git", - "url": "https://github.com/lightsparkdev/js-sdk.git" - }, - "bugs": { - "url": "https://github.com/lightsparkdev/js-sdk/issues" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.cjs" - } - } - }, - "type": "module", - "types": "./dist/index.d.ts", - "main": "./dist/index.js", - "module": "./dist/index.js", - "engines": { - "node": ">=18.17.0" - }, - "browser": { - "crypto": false - }, - "files": [ - "src/*", - "dist/*", - "CHANGELOG.md" - ], - "scripts": { - "build": "yarn tsc && tsup --entry src/index.ts --entry src/objects/index.ts --format cjs,esm --dts", - "build:watch": "yarn build --watch", - "clean": "rm -rf .turbo && rm -rf dist", - "dev": "yarn build -- --watch", - "docs": "typedoc --media docs-media src", - "format:fix": "prettier src --write", - "format": "prettier src --check", - "lint:fix": "eslint --fix .", - "lint:fix:continue": "eslint --fix . || exit 0", - "lint:watch": "esw ./src -w --ext .ts,.tsx,.js --color", - "lint": "eslint .", - "postversion": "yarn build", - "test": "node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --bail", - "types": "tsc" - }, - "license": "Apache-2.0", - "dependencies": { - "@lightsparkdev/core": "1.0.5", - "@lightsparkdev/lightspark-sdk": "1.1.0", - "auto-bind": "^5.0.1", - "crypto": "^1.0.1", - "crypto-browserify": "^3.12.0", - "dayjs": "^1.11.7", - "eciesjs": "^0.4.4", - "graphql": "^16.6.0", - "graphql-ws": "^5.11.3", - "secp256k1": "^5.0.0", - "ws": "^8.12.1", - "zen-observable-ts": "^1.1.0", - "zod": "^3.22.2" - }, - "devDependencies": { - "@lightsparkdev/eslint-config": "*", - "@types/crypto-js": "^4.1.1", - "@types/ws": "^8.5.4", - "dotenv": "^16.3.1", - "dotenv-cli": "^7.3.0", - "eslint": "^8.3.0", - "eslint-watch": "^8.0.0", - "jest": "^29.6.2", - "jsonwebtoken": "^9.0.1", - "prettier": "3.0.2", - "prettier-plugin-organize-imports": "^3.2.2", - "ts-jest": "^29.1.1", - "tsup": "^6.7.0", - "typedoc": "^0.24.7", - "typescript": "^4.9.5" - } -} diff --git a/packages/uma/src/Currency.ts b/packages/uma/src/Currency.ts deleted file mode 100644 index ac73d2d4f..000000000 --- a/packages/uma/src/Currency.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from "zod"; - -export const CurrencySchema = z.object({ - code: z.string(), - name: z.string(), - symbol: z.string(), - multiplier: z.number(), // millisatoshi per unit - minSendable: z.number(), - maxSendable: z.number(), -}); - -export type Currency = z.infer; diff --git a/packages/uma/src/KycStatus.ts b/packages/uma/src/KycStatus.ts deleted file mode 100644 index 0b054e4c6..000000000 --- a/packages/uma/src/KycStatus.ts +++ /dev/null @@ -1,36 +0,0 @@ -export enum KycStatus { - Unknown = "UNKNOWN", - NotVerified = "NOT_VERIFIED", - Pending = "PENDING", - Verified = "VERIFIED", -} - -export function kycStatusFromString(s: string): KycStatus { - switch (s) { - default: - return KycStatus.Unknown; - case "UNKNOWN": - return KycStatus.Unknown; - case "NOT_VERIFIED": - return KycStatus.NotVerified; - case "PENDING": - return KycStatus.Pending; - case "VERIFIED": - return KycStatus.Verified; - } -} - -export function kycStatusToString(k: KycStatus): string { - switch (k) { - default: - return "undefined"; - case KycStatus.Unknown: - return KycStatus.Unknown; - case KycStatus.NotVerified: - return KycStatus.NotVerified; - case KycStatus.Pending: - return KycStatus.Pending; - case KycStatus.Verified: - return KycStatus.Verified; - } -} diff --git a/packages/uma/src/PayerData.ts b/packages/uma/src/PayerData.ts deleted file mode 100644 index aa1d6475d..000000000 --- a/packages/uma/src/PayerData.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { z } from "zod"; -import { KycStatus } from "./KycStatus.js"; - -export const PayerDataOptionsSchema = z.object({ - nameRequired: z.boolean(), - emailRequired: z.boolean(), - complianceRequired: z.boolean(), -}); - -export type PayerDataOptions = z.infer; - -const CompliancePayerDataSchema = z.object({ - // Utxos is the list of UTXOs of the sender's channels that might be used to fund the payment. - utxos: z.optional(z.array(z.string())), - // NodePubKey is the public key of the sender's node if known. - nodePubKey: z.optional(z.string()), - // KycStatus indicates whether VASP1 has KYC information about the sender. - kycStatus: z.nativeEnum(KycStatus), - // EncryptedTravelRuleInfo is the travel rule information of the sender. This is encrypted with the receiver's public encryption key. - encryptedTravelRuleInfo: z.optional(z.string()), - // Signature is the base64-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). - signature: z.string(), - signatureNonce: z.string(), - signatureTimestamp: z.number(), - // UtxoCallback is the URL that the receiver will call to send UTXOs of the channel that the receiver used to receive the payment once it completes. - utxoCallback: z.string(), -}); - -export type CompliancePayerData = z.infer; - -export const PayerDataSchema = z.object({ - name: z.optional(z.string()), - email: z.optional(z.string()), - identifier: z.string(), - compliance: CompliancePayerDataSchema, -}); - -export type PayerData = z.infer; - -export function payerDataOptionsToJSON(p: PayerDataOptions): string { - return JSON.stringify({ - identifier: { mandatory: true }, - name: { mandatory: p.nameRequired }, - email: { mandatory: p.emailRequired }, - compliance: { mandatory: p.complianceRequired }, - }); -} diff --git a/packages/uma/src/PublicKeyCache.ts b/packages/uma/src/PublicKeyCache.ts deleted file mode 100644 index 20b499860..000000000 --- a/packages/uma/src/PublicKeyCache.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { type PubKeyResponse } from "./protocol.js"; - -export class PublicKeyCache { - cache: Map; - - constructor() { - this.cache = new Map(); - } - - fetchPublicKeyForVasp(vaspDomain: string) { - const entry = this.cache.get(vaspDomain); - if ( - entry === undefined || - (entry.expirationTimestamp !== undefined && - entry.expirationTimestamp < Date.now()) - ) { - return undefined; - } - return entry; - } - - addPublicKeyForVasp(vaspDomain: string, pubKey: PubKeyResponse) { - this.cache.set(vaspDomain, pubKey); - } - - removePublicKeyForVasp(vaspDomain: string) { - this.cache.delete(vaspDomain); - } - - clear() { - this.cache.clear(); - } -} diff --git a/packages/uma/src/client.ts b/packages/uma/src/client.ts deleted file mode 100644 index aaa770fde..000000000 --- a/packages/uma/src/client.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Invoice as InvoiceType } from "@lightsparkdev/lightspark-sdk"; - -type Invoice = InvoiceType; - -type CreateUmaInvoiceArgs = { - amountMsats: number; - metadataHash: string; -}; - -type UmaProvider = { - createUmaInvoice: ( - nodeId: string, - amountMsats: number, - metadata: string, - expirySecs?: number | undefined, - ) => Promise; -}; - -type UmaClientArgs = { - provider: UmaProvider; - receiverNodeId: string; - invoiceExpirySeconds?: number | undefined; -}; - -export class UmaClient { - provider: UmaProvider; - receiverNodeId: string; - invoiceExpirySeconds: number | undefined; - - constructor({ - provider, - receiverNodeId, // the node ID of the receiver. - invoiceExpirySeconds, // the number of seconds until the invoice expires. - }: UmaClientArgs) { - this.provider = provider; - this.receiverNodeId = receiverNodeId; - this.invoiceExpirySeconds = invoiceExpirySeconds; - } - - async createUmaInvoice({ amountMsats, metadataHash }: CreateUmaInvoiceArgs) { - const result = await this.provider.createUmaInvoice( - this.receiverNodeId, - amountMsats, - metadataHash, - this.invoiceExpirySeconds, - ); - - if (!result) { - throw new Error("Failed to create invoice"); - } - - return result; - } -} diff --git a/packages/uma/src/index.ts b/packages/uma/src/index.ts deleted file mode 100644 index a9b46acfc..000000000 --- a/packages/uma/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved - -export * from "./client.js"; -export * from "./Currency.js"; -export * from "./KycStatus.js"; -export * from "./PayerData.js"; -export * from "./protocol.js"; -export * from "./PublicKeyCache.js"; -export * from "./uma.js"; -export * from "./version.js"; diff --git a/packages/uma/src/protocol.ts b/packages/uma/src/protocol.ts deleted file mode 100644 index 3bb61c9e4..000000000 --- a/packages/uma/src/protocol.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { z } from "zod"; -import { CurrencySchema } from "./Currency.js"; -import { KycStatus } from "./KycStatus.js"; -import { PayerDataOptionsSchema, PayerDataSchema } from "./PayerData.js"; - -// LnurlpRequest is the first request in the UMA protocol. It is sent by the VASP that is sending the payment to find out information about the receiver. -export type LnurlpRequest = { - // ReceiverAddress is the address of the user at VASP2 that is receiving the payment. - receiverAddress: string; - // Nonce is a random string that is used to prevent replay attacks. - nonce: string; - // Signature is the base64-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). - signature: string; - // IsSubjectToTravelRule indicates VASP1 is a financial institution that requires travel rule information. - isSubjectToTravelRule: boolean; - // VaspDomain is the domain of the VASP that is sending the payment. It will be used by VASP2 to fetch the public keys of VASP1. - vaspDomain: string; - // Timestamp is the unix timestamp of when the request was sent. Used in the signature. - timestamp: Date; - // UmaVersion is the version of the UMA protocol that VASP1 prefers to use for this transaction. For the version negotiation flow, see https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png - umaVersion: string; -}; - -// LnurlComplianceResponse is the `compliance` field of the LnurlpResponse. -export const LnurlpComplianceResponseSchema = z.object({ - // KycStatus indicates whether VASP2 has KYC information about the receiver. - kycStatus: z.nativeEnum(KycStatus), - // Signature is the base64-encoded signature of sha256(ReceiverAddress|Nonce|Timestamp). - signature: z.string(), - // Nonce is a random string that is used to prevent replay attacks. - signatureNonce: z.string(), - // Timestamp is the unix timestamp of when the request was sent. Used in the signature. - signatureTimestamp: z.number(), - // IsSubjectToTravelRule indicates whether VASP2 is a financial institution that requires travel rule information. - isSubjectToTravelRule: z.boolean(), - // ReceiverIdentifier is the identifier of the receiver at VASP2. - receiverIdentifier: z.string(), -}); - -export type LnurlComplianceResponse = z.infer< - typeof LnurlpComplianceResponseSchema ->; - -// LnurlpResponse is the response to the LnurlpRequest. It is sent by the VASP that is receiving the payment to provide information to the sender about the receiver. -export const LnurlpResponseSchema = z.object({ - tag: z.string(), - callback: z.string(), - minSendable: z.number(), - maxSendable: z.number(), - encodedMetadata: z.string(), - currencies: z.array(CurrencySchema), - requiredPayerData: PayerDataOptionsSchema, - compliance: LnurlpComplianceResponseSchema, - // UmaVersion is the version of the UMA protocol that VASP2 has chosen for this transaction based on its own support and VASP1's specified preference in the LnurlpRequest. For the version negotiation flow, see https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png - umaVersion: z.string(), -}); - -export type LnurlpResponse = z.infer; - -export function parseLnurlpResponse(jsonStr: string): LnurlpResponse { - const parsed = JSON.parse(jsonStr); - let validated: LnurlpResponse; - try { - validated = LnurlpResponseSchema.parse(parsed); - } catch (e) { - throw new Error("invalid lnurlp response"); - } - return validated; -} - -// PayRequest is the request sent by the sender to the receiver to retrieve an invoice. -export const PayRequestSchema = z.object({ - // CurrencyCode is the ISO 3-digit currency code that the receiver will receive for this payment. - currencyCode: z.string(), - // Amount is the amount that the receiver will receive for this payment in the smallest unit of the specified currency (i.e. cents for USD). - amount: z.number(), - // PayerData is the data that the sender will send to the receiver to identify themselves. - payerData: PayerDataSchema, -}); - -export type PayRequest = z.infer; - -export const RouteSchema = z.object({ - pubkey: z.string(), - path: z.array( - z.object({ - pubkey: z.string(), - fee: z.number(), - msatoshi: z.number(), - channel: z.string(), - }), - ), -}); - -export type Route = z.infer; - -export const PayReqResponseComplianceSchema = z.object({ - // nodePubKey is the public key of the receiver's node if known. - nodePubKey: z.string().optional(), - // utxos is a list of UTXOs of channels over which the receiver will likely receive the payment. - utxos: z.array(z.string()), - // utxoCallback is the URL that the sender VASP will call to send UTXOs of the channel that the sender used to send the payment once it completes. - utxoCallback: z.string(), -}); - -export type PayReqResponseCompliance = z.infer< - typeof PayReqResponseComplianceSchema ->; - -export const PayReqResponsePaymentInfoSchema = z.object({ - // currencyCode is the ISO 3-digit currency code that the receiver will receive for this payment. - currencyCode: z.string(), - // multiplier is the conversion rate. It is the number of millisatoshis that the receiver will receive for 1 unit of the specified currency. - multiplier: z.number(), - // exchangeFeesMillisatoshi is the fees charged (in millisats) by the receiving VASP for this transaction. This is separate from the Multiplier. - exchangeFeesMillisatoshi: z.number(), -}); - -export type PayReqResponsePaymentInfo = z.infer< - typeof PayReqResponsePaymentInfoSchema ->; - -// PayReqResponse is the response sent by the receiver to the sender to provide an invoice. -export const PayReqResponseSchema = z.object({ - // encodedInvoice is the BOLT11 invoice that the sender will pay. - encodedInvoice: z.string(), - // routes is usually just an empty list from legacy LNURL, which was replaced by route hints in the BOLT11 invoice. - routes: z.array(RouteSchema), - compliance: PayReqResponseComplianceSchema, - paymentInfo: PayReqResponsePaymentInfoSchema, -}); - -export type PayReqResponse = z.infer; - -// PubKeyResponse is sent from a VASP to another VASP to provide its public keys. It is the response to GET requests at `/.well-known/lnurlpubkey`. -export type PubKeyResponse = { - // SigningPubKey is used to verify signatures from a VASP. - signingPubKey: string; - // EncryptionPubKey is used to encrypt TR info sent to a VASP. - encryptionPubKey: string; - // ExpirationTimestamp [Optional] Seconds since epoch at which these pub keys must be refreshed. They can be safely cached until this expiration (or forever if null). - expirationTimestamp?: number; -}; - -// UtxoWithAmount is a pair of utxo and amount transferred over that corresponding channel. It can be used to register payment for KYT. -export type UtxoWithAmount = { - // Utxo The utxo of the channel over which the payment went through in the format of :. - utxo: string; - // Amount The amount of funds transferred in the payment in mSats. - amount: number; -}; - -export function dateToUnixSeconds(date: Date) { - return Math.floor(date.getTime() / 1000); -} - -export function encodeToUrl(q: LnurlpRequest): URL { - const receiverAddressParts = q.receiverAddress.split("@"); - if (receiverAddressParts.length !== 2) { - throw new Error("invalid receiver address"); - } - const scheme = receiverAddressParts[1].startsWith("localhost:") - ? "http" - : "https"; - const lnurlpUrl = new URL( - `${scheme}://${receiverAddressParts[1]}/.well-known/lnurlp/${receiverAddressParts[0]}`, - ); - const queryParams = lnurlpUrl.searchParams; - queryParams.set("signature", q.signature); - queryParams.set("vaspDomain", q.vaspDomain); - queryParams.set("nonce", q.nonce); - queryParams.set("isSubjectToTravelRule", q.isSubjectToTravelRule.toString()); - queryParams.set("timestamp", String(dateToUnixSeconds(q.timestamp))); - queryParams.set("umaVersion", q.umaVersion); - lnurlpUrl.search = queryParams.toString(); - return lnurlpUrl; -} - -export function encodePayRequest(q: PayRequest) { - return JSON.stringify(q); -} - -export function parsePayRequest(payRequest: string): PayRequest { - const parsed = JSON.parse(payRequest); - let validated: PayRequest; - try { - validated = PayRequestSchema.parse(parsed); - } catch (e) { - throw new Error("invalid pay request"); - } - return validated; -} - -export function parsePayReqResponse(jsonStr: string): PayReqResponse { - const parsed = JSON.parse(jsonStr); - let validated: PayReqResponse; - try { - validated = PayReqResponseSchema.parse(parsed); - } catch (e) { - throw new Error("invalid pay request response"); - } - return validated; -} - -export function getSignableLnurlpRequestPayload(q: LnurlpRequest): string { - return [ - q.receiverAddress, - q.nonce, - String(dateToUnixSeconds(q.timestamp)), - ].join("|"); -} - -export function getSignableLnurlpResponsePayload(r: LnurlpResponse): string { - return [ - r.compliance.receiverIdentifier, - r.compliance.signatureNonce, - r.compliance.signatureTimestamp.toString(), - ].join("|"); -} - -export function getSignablePayRequestPayload(q: PayRequest): string { - return `${q.payerData.identifier}|${ - q.payerData.compliance.signatureNonce - }|${q.payerData.compliance.signatureTimestamp.toString()}`; -} diff --git a/packages/uma/src/tests/protocol.test.ts b/packages/uma/src/tests/protocol.test.ts deleted file mode 100644 index 51d3a7986..000000000 --- a/packages/uma/src/tests/protocol.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { KycStatus } from "../KycStatus.js"; -import { PayRequestSchema } from "../protocol.js"; - -describe("uma protocol", () => { - it("should validate pay requests", async () => { - /* Missing payerData: */ - await expect(() => - PayRequestSchema.parse({ - currencyCode: "USD", - amount: 100, - }), - ).toThrow(/.*payerData.*/g); - - /* Invalid compliance: */ - await expect(() => - PayRequestSchema.parse({ - currencyCode: "USD", - amount: 100, - payerData: { - name: "Bob", - email: "$bob@lightspark.com", - identifier: "1234", - compliance: { - utxos: ["utxo1", "utxo2"], - nodePubKey: "nodePubKey", - kycStatus: KycStatus.Pending, - encryptedTravelRuleInfo: "encryptedTravelRuleInfo", - signature: "signature", - signatureNonce: "signatureNonce", - signatureTimestamp: "this should be a number", - utxoCallback: "utxoCallback", - }, - }, - }), - ).toThrow(/.*signatureTimestamp.*/g); - - /* Valid request: */ - await expect(() => - PayRequestSchema.parse({ - currencyCode: "USD", - amount: 100, - payerData: { - name: "Bob", - email: "$bob@lightspark.com", - identifier: "1234", - compliance: { - utxos: ["utxo1", "utxo2"], - nodePubKey: "nodePubKey", - kycStatus: KycStatus.Pending, - encryptedTravelRuleInfo: "encryptedTravelRuleInfo", - signature: "signature", - signatureNonce: "signatureNonce", - signatureTimestamp: 12345678, - utxoCallback: "utxoCallback", - }, - }, - }), - ).not.toThrow(); - }); -}); diff --git a/packages/uma/src/tests/uma.test.ts b/packages/uma/src/tests/uma.test.ts deleted file mode 100644 index 2968032bf..000000000 --- a/packages/uma/src/tests/uma.test.ts +++ /dev/null @@ -1,404 +0,0 @@ -import { isError } from "@lightsparkdev/core"; -import { - AccountTokenAuthProvider, - LightsparkClient, -} from "@lightsparkdev/lightspark-sdk"; -import { getCredentialsFromEnvOrThrow } from "@lightsparkdev/lightspark-sdk/env"; -import { randomBytes } from "crypto"; -import { decrypt, PrivateKey } from "eciesjs"; -import secp256k1 from "secp256k1"; -import { UmaClient } from "../client.js"; -import { KycStatus } from "../KycStatus.js"; -import { - dateToUnixSeconds, - parseLnurlpResponse, - parsePayReqResponse, - parsePayRequest, - type LnurlpRequest, -} from "../protocol.js"; -import { - getLnurlpResponse, - getPayReqResponse, - getPayRequest, - getSignedLnurlpRequestUrl, - getVaspDomainFromUmaAddress, - isUmaLnurlpQuery, - isValidUmaAddress, - parseLnurlpRequest, - verifyPayReqSignature, - verifyUmaLnurlpQuerySignature, - verifyUmaLnurlpResponseSignature, -} from "../uma.js"; -import { UmaProtocolVersion } from "../version.js"; - -const generateKeypair = async () => { - let privateKey: Uint8Array; - do { - privateKey = new Uint8Array(randomBytes(32)); - } while (!secp256k1.privateKeyVerify(privateKey)); - - const publicKey = secp256k1.publicKeyCreate(privateKey, false); - - return { - privateKey, - publicKey, - }; -}; - -function createMetadataForBob(): string { - const metadata = [ - ["text/plain", "Pay to vasp2.com user $bob"], - ["text/identifier", "$bob@vasp2.com"], - ]; - - return JSON.stringify(metadata); -} - -async function createLnurlpRequest( - senderSigningPrivateKey: Uint8Array, -): Promise { - const queryUrl = await getSignedLnurlpRequestUrl({ - signingPrivateKey: senderSigningPrivateKey, - receiverAddress: "$bob@vasp2.com", - senderVaspDomain: "vasp1.com", - isSubjectToTravelRule: true, - }); - const query = parseLnurlpRequest(queryUrl); - return query; -} - -describe("uma", () => { - it("should construct the UMA client", () => { - const credentials = getCredentialsFromEnvOrThrow(); - const lightsparkClient = new LightsparkClient( - new AccountTokenAuthProvider( - credentials.apiTokenClientId, - credentials.apiTokenClientSecret, - ), - credentials.baseUrl, - ); - const umaClient = new UmaClient({ - provider: lightsparkClient, - receiverNodeId: "1234", - invoiceExpirySeconds: 60, - }); - expect(umaClient).toBeTruthy(); - }); - - it("parses a valid lnurlp request", () => { - const expectedTime = new Date("2023-07-27T22:46:08Z"); - const timeSec = dateToUnixSeconds(expectedTime); - const expectedQuery = { - receiverAddress: "bob@vasp2", - signature: "signature", - isSubjectToTravelRule: true, - nonce: "12345", - timestamp: expectedTime, - vaspDomain: "vasp1", - umaVersion: "0.1", - }; - const urlString = - "https://vasp2/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1&umaVersion=0.1&isSubjectToTravelRule=true×tamp=" + - timeSec; - const urlObj = new URL(urlString); - const query = parseLnurlpRequest(urlObj); - expect(query).toEqual(expectedQuery); - }); - - it("validates uma queries", () => { - const umaQuery = - "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678"; - expect(isUmaLnurlpQuery(new URL(umaQuery))).toBeTruthy(); - }); - - it("returns expected result for missing query params", () => { - // Missing signature - let url = new URL( - "https://vasp2.com/.well-known/lnurlp/bob?nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - // Missing umaVersion - url = new URL( - "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - // Missing nonce - url = new URL( - "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - // Missing vaspDomain - url = new URL( - "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&umaVersion=0.1&nonce=12345&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - url = new URL( - "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&umaVersion=0.1&nonce=12345&vaspDomain=vasp1.com×tamp=12345678", - ); - // IsSubjectToTravelRule is optional - expect(isUmaLnurlpQuery(url)).toBe(true); - - // Missing timestamp - url = new URL( - "https://vasp2.com/.well-known/lnurlp/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - // Missing all required params - url = new URL("https://vasp2.com/.well-known/lnurlp/bob"); - expect(isUmaLnurlpQuery(url)).toBe(false); - }); - - it("should be invalid uma query when url path is invalid", () => { - let url = new URL( - "https://vasp2.com/.well-known/lnurla/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - url = new URL( - "https://vasp2.com/bob?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - - url = new URL( - "https://vasp2.com/?signature=signature&nonce=12345&vaspDomain=vasp1.com&umaVersion=0.1&isSubjectToTravelRule=true×tamp=12345678", - ); - expect(isUmaLnurlpQuery(url)).toBe(false); - }); - - it("should validate valid uma addresses", () => { - expect(isValidUmaAddress("$bob@lighspark.com")).toBe(true); - expect(isValidUmaAddress("$BOb@lighspark.com")).toBe(true); - expect(isValidUmaAddress("$BOb_+.somewhere@lighspark.com")).toBe(true); - expect(isValidUmaAddress("$.@lightspark.com")).toBe(true); - expect(isValidUmaAddress("$232358BOB@lightspark.com")).toBe(true); - expect( - isValidUmaAddress( - "$therearelessthan65charactersinthisusername1234567891234567891234@lightspark.com", - ), - ).toBe(true); - }); - - it("should validate invalid uma addresses", () => { - expect(isValidUmaAddress("$@lighspark.com")).toBe(false); - expect(isValidUmaAddress("bob@lightspark.com")).toBe(false); - expect(isValidUmaAddress("bob@lightspark")).toBe(false); - expect(isValidUmaAddress("$%@lightspark.com")).toBe(false); - expect( - isValidUmaAddress( - "$therearemorethan64charactersinthisusername12345678912345678912345@lightspark.com", - ), - ).toBe(false); - }); - - it("should get the vasp domain from an uma address", () => { - expect(getVaspDomainFromUmaAddress("$bob@lightspark.com")).toEqual( - "lightspark.com", - ); - }); - - it("should sign and verify lnurlp request", async () => { - const { privateKey, publicKey } = await generateKeypair(); - const queryUrl = await getSignedLnurlpRequestUrl({ - signingPrivateKey: privateKey, - receiverAddress: "$bob@vasp2.com", - senderVaspDomain: "vasp1.com", - isSubjectToTravelRule: true, - }); - - const query = parseLnurlpRequest(queryUrl); - expect(query.umaVersion).toBe(UmaProtocolVersion); - const verified = await verifyUmaLnurlpQuerySignature(query, publicKey); - expect(verified).toBe(true); - }); - - it("should throw for incorrect public key", async () => { - const { privateKey } = await generateKeypair(); - const { publicKey: incorrectPublicKey } = await generateKeypair(); - const queryUrl = await getSignedLnurlpRequestUrl({ - signingPrivateKey: privateKey, - receiverAddress: "$bob@vasp2.com", - senderVaspDomain: "vasp1.com", - isSubjectToTravelRule: true, - }); - - const query = parseLnurlpRequest(queryUrl); - expect(query.umaVersion).toBe(UmaProtocolVersion); - const verified = await verifyUmaLnurlpQuerySignature( - query, - incorrectPublicKey, - ); - expect(verified).toBe(false); - }); - - it("should throw for invalid public key", async () => { - const { privateKey, publicKey } = await generateKeypair(); - const queryUrl = await getSignedLnurlpRequestUrl({ - signingPrivateKey: privateKey, - receiverAddress: "$bob@vasp2.com", - senderVaspDomain: "vasp1.com", - isSubjectToTravelRule: true, - }); - - const query = parseLnurlpRequest(queryUrl); - expect(query.umaVersion).toBe(UmaProtocolVersion); - /* see https://bit.ly/3Zov3ZA */ - publicKey[0] = 0x01; - try { - await verifyUmaLnurlpQuerySignature(query, publicKey); - } catch (e) { - if (!isError(e)) { - throw new Error("Invalid error type"); - } - expect(e.message).toMatch(/Public Key could not be parsed/); - } - }); - - it("should sign and verify lnurlp response", async () => { - const { privateKey: senderSigningPrivateKey } = await generateKeypair(); - const { - privateKey: receiverSigningPrivateKey, - publicKey: receiverSigningPublicKey, - } = await generateKeypair(); - const request = await createLnurlpRequest(senderSigningPrivateKey); - const metadata = createMetadataForBob(); - const response = await getLnurlpResponse({ - request, - privateKeyBytes: receiverSigningPrivateKey, - requiresTravelRuleInfo: true, - callback: "https://vasp2.com/api/lnurl/payreq/$bob", - encodedMetadata: metadata, - minSendableSats: 1, - maxSendableSats: 10_000_000, - payerDataOptions: { - nameRequired: false, - emailRequired: false, - complianceRequired: true, - }, - currencyOptions: [ - { - code: "USD", - name: "US Dollar", - symbol: "$", - multiplier: 34_150, - minSendable: 1, - maxSendable: 10_000_000, - }, - ], - receiverKycStatus: KycStatus.Verified, - }); - - const responseJson = JSON.stringify(response); - const parsedResponse = parseLnurlpResponse(responseJson); - const verified = verifyUmaLnurlpResponseSignature( - parsedResponse, - receiverSigningPublicKey, - ); - expect(verified).toBeTruthy(); - }); - - it("should handle a pay request response", async () => { - const { privateKey: senderSigningPrivateKey } = await generateKeypair(); - const { publicKey: receiverEncryptionPublicKey } = await generateKeypair(); - - const trInfo = "some TR info for VASP2"; - const payreq = await getPayRequest({ - amount: 1000, - currencyCode: "USD", - payerIdentifier: "$alice@vasp1.com", - payerKycStatus: KycStatus.Verified, - receiverEncryptionPubKey: receiverEncryptionPublicKey, - sendingVaspPrivateKey: senderSigningPrivateKey, - trInfo: trInfo, - utxoCallback: "/api/lnurl/utxocallback?txid=1234", - }); - - const credentials = getCredentialsFromEnvOrThrow(); - const lightsparkClient = new LightsparkClient( - new AccountTokenAuthProvider( - credentials.apiTokenClientId, - credentials.apiTokenClientSecret, - ), - credentials.baseUrl, - ); - const umaClient = new UmaClient({ - provider: lightsparkClient, - receiverNodeId: - "LightsparkNodeWithOSKLND:018a2384-f78d-f96b-0000-0618465e9389", - invoiceExpirySeconds: 60, - }); - - const metadata = createMetadataForBob(); - - const payreqResponse = await getPayReqResponse({ - query: payreq, - invoiceCreator: umaClient, - metadata, - currencyCode: "USD", - conversionRate: 34_150, - receiverFeesMillisats: 100_000, - receiverChannelUtxos: ["abcdef12345"], - utxoCallback: "/api/lnurl/utxocallback?txid=1234", - }); - - const payreqResponseJson = JSON.stringify(payreqResponse); - const parsedPayreqResponse = parsePayReqResponse(payreqResponseJson); - expect(parsedPayreqResponse).toEqual(payreqResponse); - }); - - it("should create and parse a payreq", async () => { - const { - privateKey: senderSigningPrivateKey, - publicKey: senderSigningPublicKey, - } = await generateKeypair(); - const { - privateKey: receiverEncryptionPrivateKey, - publicKey: receiverEncryptionPublicKey, - } = await generateKeypair(); - - const trInfo = "some TR info for VASP2"; - const payreq = await getPayRequest({ - receiverEncryptionPubKey: receiverEncryptionPublicKey, - sendingVaspPrivateKey: senderSigningPrivateKey, - currencyCode: "USD", - amount: 1000, - payerIdentifier: "$alice@vasp1.com", - trInfo, - payerKycStatus: KycStatus.Verified, - utxoCallback: "/api/lnurl/utxocallback?txid=1234", - }); - - const payreqJson = JSON.stringify(payreq); - - const parsedPayreq = parsePayRequest(payreqJson); - - const verified = await verifyPayReqSignature( - parsedPayreq, - senderSigningPublicKey, - ); - expect(verified).toBe(true); - - const encryptedTrInfo = - parsedPayreq.payerData.compliance.encryptedTravelRuleInfo; - if (!encryptedTrInfo) { - throw new Error("encryptedTrInfo is undefined"); - } - - const encryptedTrInfoBytes = Buffer.from(encryptedTrInfo, "hex"); - const receiverEncryptionPrivKeyBuffer = Buffer.from( - receiverEncryptionPrivateKey, - ); - const eciesReceiverPrivKey = new PrivateKey( - receiverEncryptionPrivKeyBuffer, - ); - const decryptedTrInfo = decrypt( - eciesReceiverPrivKey.toHex(), - encryptedTrInfoBytes, - ).toString(); - expect(decryptedTrInfo).toBe(trInfo); - }); -}); diff --git a/packages/uma/src/uma.ts b/packages/uma/src/uma.ts deleted file mode 100644 index f01486ee2..000000000 --- a/packages/uma/src/uma.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { createSha256Hash } from "@lightsparkdev/core"; -import crypto from "crypto"; -import { encrypt, PublicKey } from "eciesjs"; -import secp256k1 from "secp256k1"; -import { type UmaClient } from "./client.js"; -import { type Currency } from "./Currency.js"; -import { type KycStatus } from "./KycStatus.js"; -import { type PayerDataOptions } from "./PayerData.js"; -import { - encodeToUrl, - getSignableLnurlpRequestPayload, - getSignableLnurlpResponsePayload, - getSignablePayRequestPayload, - type LnurlComplianceResponse, - type LnurlpRequest, - type LnurlpResponse, - type PayReqResponse, - type PayRequest, - type PubKeyResponse, -} from "./protocol.js"; -import { type PublicKeyCache } from "./PublicKeyCache.js"; -import { - isVersionSupported, - selectLowerVersion, - UmaProtocolVersion, -} from "./version.js"; - -export type ParsedLnurlpRequest = { - vaspDomain: string; - umaVersion: string; - signature: string; - receiverAddress: string; - nonce: string; - timestamp: Date; - isSubjectToTravelRule: boolean; -}; - -export function parseLnurlpRequest(url: URL) { - const query = url.searchParams; - const signature = query.get("signature"); - const vaspDomain = query.get("vaspDomain"); - const nonce = query.get("nonce"); - const isSubjectToTravelRule = query.get("isSubjectToTravelRule"); - const umaVersion = query.get("umaVersion"); - const timestamp = query.get("timestamp"); - - if (!vaspDomain || !signature || !nonce || !timestamp || !umaVersion) { - throw new Error( - "missing uma query parameters. vaspDomain, umaVersion, signature, nonce, and timestamp are required", - ); - } - - const timestampUnixSeconds = parseInt(timestamp, 10); - /* Date expects milliseconds: */ - const timestampAsTime = new Date(timestampUnixSeconds * 1000); - - const pathParts = url.pathname.split("/"); - if ( - pathParts.length != 4 || - pathParts[1] != ".well-known" || - pathParts[2] != "lnurlp" - ) { - throw new Error("invalid uma request path"); - } - const receiverAddress = pathParts[3] + "@" + url.host; - - if (!isVersionSupported(umaVersion)) { - throw new Error("unsupported uma version"); - } - - return { - vaspDomain, - umaVersion, - signature, - receiverAddress, - nonce, - timestamp: timestampAsTime, - isSubjectToTravelRule: Boolean( - isSubjectToTravelRule?.toLowerCase() == "true", - ), - }; -} - -/* Checks if the given URL is a valid UMA request. */ -export function isUmaLnurlpQuery(url: URL) { - let query: null | ParsedLnurlpRequest = null; - try { - query = parseLnurlpRequest(url); - } catch { - return false; - } - return query !== null; -} - -export function generateNonce() { - return String(crypto.getRandomValues(new Uint32Array(1))); -} - -/* fetchPublicKeyForVasp fetches the public key for another VASP. - -If the public key is not in the cache, it will be fetched from the VASP's domain. -The public key will be cached for future use. */ - -type FetchPublicKeyForVaspArgs = { - cache: PublicKeyCache; // the domain of the VASP. - vaspDomain: string; // the PublicKeyCache cache to use. You can use the InMemoryPublicKeyCache struct, or implement your own persistent cache with any storage type. -}; - -export async function fetchPublicKeyForVasp({ - cache, - vaspDomain, -}: FetchPublicKeyForVaspArgs): Promise { - const publicKey = cache.fetchPublicKeyForVasp(vaspDomain); - if (publicKey) { - return Promise.resolve(publicKey); - } - - let scheme = "https://"; - if (vaspDomain.startsWith("localhost:")) { - scheme = "http://"; - } - - const response = await fetch( - scheme + vaspDomain + "/.well-known/lnurlpubkey", - ); - if (response.status != 200) { - if (response.status !== 200) { - return Promise.reject(new Error("invalid response from VASP")); - } - } - const pubKeyResponse = await response.json(); - cache.addPublicKeyForVasp(vaspDomain, pubKeyResponse); - return pubKeyResponse; -} - -/* getSignedLnurlpRequestUrl Creates a signed uma request URL. */ -type GetSignedLnurlpRequestUrlArgs = { - isSubjectToTravelRule: boolean; // whether the sending VASP is a financial institution that requires travel rule information. - receiverAddress: string; // the address of the receiver of the payment (i.e. $bob@vasp2). - senderVaspDomain: string; // the domain of the VASP that is sending the payment. It will be used by the receiver to fetch the public keys of the sender. - signingPrivateKey: Uint8Array; // the private key of the VASP that is sending the payment. This will be used to sign the request. - umaVersionOverride?: string | undefined; // the version of the UMA protocol to use. If not specified, the latest version will be used. -}; - -export async function getSignedLnurlpRequestUrl({ - isSubjectToTravelRule, - receiverAddress, - senderVaspDomain, - signingPrivateKey, - umaVersionOverride, -}: GetSignedLnurlpRequestUrlArgs) { - const nonce = generateNonce(); - const umaVersion = umaVersionOverride ?? UmaProtocolVersion; - const unsignedRequest: LnurlpRequest = { - receiverAddress, - isSubjectToTravelRule, - vaspDomain: senderVaspDomain, - timestamp: new Date(), - nonce: String(nonce), - signature: "", - umaVersion, - }; - - const payload = getSignableLnurlpRequestPayload(unsignedRequest); - - const signature = await signPayload(payload, signingPrivateKey); - unsignedRequest.signature = signature; - return encodeToUrl(unsignedRequest); -} - -function uint8ArrayToHexString(uint8Array: Uint8Array) { - return Array.from(uint8Array) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); -} - -async function signPayload(payload: string, privateKeyBytes: Uint8Array) { - const encoder = new TextEncoder(); - const encodedPayload = encoder.encode(payload); - const hashedPayload = await createSha256Hash(encodedPayload); - - const { signature } = secp256k1.ecdsaSign(hashedPayload, privateKeyBytes); - return uint8ArrayToHexString(signature); -} - -export async function verifyUmaLnurlpQuerySignature( - query: LnurlpRequest, - otherVaspSigningPubKey: Uint8Array, -) { - const payload = getSignableLnurlpRequestPayload(query); - const encoder = new TextEncoder(); - const encodedPayload = encoder.encode(payload); - const hashedPayload = await createSha256Hash(encodedPayload); - return verifySignature( - hashedPayload, - query.signature, - otherVaspSigningPubKey, - ); -} - -function verifySignature( - hashedPayload: Uint8Array, - signature: string, - otherVaspPubKey: Uint8Array, -) { - const decodedSignature = Buffer.from(signature, "hex"); - - const verified = secp256k1.ecdsaVerify( - decodedSignature, - hashedPayload, - otherVaspPubKey, - ); - return verified; -} - -export function isValidUmaAddress(umaAddress: string) { - if (!umaAddress.startsWith("$")) { - return false; - } - - const addressParts = umaAddress.split("@"); - if (addressParts.length != 2) { - return false; - } - - const userName = addressParts[0].slice(1); - if (!userName.match(/^[a-z0-9-_\.\+]+$/i)) { - return false; - } - - if (userName.length > 64) { - return false; - } - - return true; -} - -export function getVaspDomainFromUmaAddress(umaAddress: string) { - if (!isValidUmaAddress(umaAddress)) { - throw new Error("invalid uma address"); - } - const addressParts = umaAddress.split("@"); - return addressParts[1]; -} - -/* getPayRequest Creates a signed uma pay request. */ -type GetPayRequestArgs = { - receiverEncryptionPubKey: Uint8Array; // the public key of the receiver that will be used to encrypt the travel rule information. - sendingVaspPrivateKey: Uint8Array; // the private key of the VASP that is sending the payment. This will be used to sign the request. - currencyCode: string; // the code of the currency that the receiver will receive for this payment. - amount: number; // the amount of the payment in the smallest unit of the specified currency (i.e. cents for USD). - payerIdentifier: string; // the identifier of the sender. For example, $alice@vasp1.com - payerName?: string | undefined; // the name of the sender (optional). - payerEmail?: string | undefined; // the email of the sender (optional). - trInfo: string | undefined; // the travel rule information. This will be encrypted before sending to the receiver. - payerKycStatus: KycStatus; // whether the sender is a KYC'd customer of the sending VASP. - payerUtxos?: string[] | undefined; // the list of UTXOs of the sender's channels that might be used to fund the payment. - payerNodePubKey?: string | undefined; // If known, the public key of the sender's node. If supported by the receiving VASP's compliance provider, this will be used to pre-screen the sender's UTXOs for compliance purposes. - utxoCallback: string; // the URL that the receiver will call to send UTXOs of the channel that the receiver used to receive the payment once it completes. -}; - -export async function getPayRequest({ - amount, - currencyCode, - payerEmail, - payerIdentifier, - payerKycStatus, - payerName, - payerNodePubKey, - payerUtxos, - receiverEncryptionPubKey, - sendingVaspPrivateKey, - trInfo, - utxoCallback, -}: GetPayRequestArgs): Promise { - const complianceData = await getSignedCompliancePayerData( - receiverEncryptionPubKey, - sendingVaspPrivateKey, - payerIdentifier, - trInfo, - payerKycStatus, - payerUtxos, - payerNodePubKey, - utxoCallback, - ); - - return { - currencyCode, - amount, - payerData: { - name: payerName, - email: payerEmail, - identifier: payerIdentifier, - compliance: complianceData, - }, - }; -} - -async function getSignedCompliancePayerData( - receiverEncryptionPubKeyBytes: Uint8Array, - sendingVaspPrivateKeyBytes: Uint8Array, - payerIdentifier: string, - trInfo: string | undefined, - payerKycStatus: KycStatus, - payerUtxos: string[] | undefined, - payerNodePubKey: string | undefined, - utxoCallback: string, -) { - const signatureTimestamp = Date.now(); - const signatureNonce = generateNonce(); - - let encryptedTravelRuleInfo: string | undefined; - if (trInfo) { - encryptedTravelRuleInfo = encryptTrInfo( - trInfo, - receiverEncryptionPubKeyBytes, - ); - } - - const payloadString = `${payerIdentifier}|${signatureNonce}|${signatureTimestamp}`; - const signature = await signPayload( - payloadString, - sendingVaspPrivateKeyBytes, - ); - return { - encryptedTravelRuleInfo, - kycStatus: payerKycStatus, - utxos: payerUtxos, - nodePubKey: payerNodePubKey, - utxoCallback, - signatureNonce, - signatureTimestamp, - signature, - }; -} - -function encryptTrInfo( - trInfo: string, - receiverEncryptionPubKey: Uint8Array, -): string { - const pubKeyBuffer = Buffer.from(receiverEncryptionPubKey.buffer); - const pubKey = new PublicKey(pubKeyBuffer); - const trInfoBuffer = Buffer.from(trInfo); - const encryptedTrInfoBytes = encrypt(pubKey.toHex(), trInfoBuffer); - const encryptedTrInfoHex = uint8ArrayToHexString(encryptedTrInfoBytes); - return encryptedTrInfoHex; -} - -type PayRequestResponseArgs = { - query: PayRequest; // the uma pay request. - conversionRate: number; // milli-satoshis per the smallest unit of the specified currency. This rate is committed to by the receiving VASP until the invoice expires. - currencyCode: string; // the code of the currency that the receiver will receive for this payment. - invoiceCreator: UmaClient; // UmaClient that calls createUmaInvoice using your provider. - metadata: string; // the metadata that will be added to the invoice's metadata hash field. Note that this should not include the extra payer data. That will be appended automatically. - receiverChannelUtxos: string[]; // the list of UTXOs of the receiver's channels that might be used to fund the payment. - receiverFeesMillisats: number; // the fees charged (in millisats) by the receiving VASP to convert to the target currency. This is separate from the conversion rate. - receiverNodePubKey?: string | undefined; // If known, the public key of the receiver's node. If supported by the sending VASP's compliance provider, this will be used to pre-screen the receiver's UTXOs for compliance purposes. - utxoCallback: string; // the URL that the receiving VASP will call to send UTXOs of the channel that the receiver used to receive the payment once it completes. -}; - -export async function getPayReqResponse({ - conversionRate, - currencyCode, - invoiceCreator, - metadata, - query, - receiverChannelUtxos, - receiverFeesMillisats, - receiverNodePubKey, - utxoCallback, -}: PayRequestResponseArgs): Promise { - const msatsAmount = query.amount * conversionRate; - const encodedPayerData = JSON.stringify(query.payerData); - const encodedInvoice = await invoiceCreator.createUmaInvoice({ - amountMsats: msatsAmount, - metadataHash: metadata + "{" + encodedPayerData + "}", - }); - - return { - encodedInvoice: encodedInvoice.data.encodedPaymentRequest, - routes: [], - compliance: { - utxos: receiverChannelUtxos, - nodePubKey: receiverNodePubKey, - utxoCallback, - }, - paymentInfo: { - currencyCode, - multiplier: conversionRate, - exchangeFeesMillisatoshi: receiverFeesMillisats, - }, - }; -} - -type GetSignedLnurlpResponseArgs = { - request: LnurlpRequest; - privateKeyBytes: Uint8Array; - requiresTravelRuleInfo: boolean; - callback: string; - encodedMetadata: string; - minSendableSats: number; - maxSendableSats: number; - payerDataOptions: PayerDataOptions; - currencyOptions: Currency[]; - receiverKycStatus: KycStatus; -}; - -export async function getLnurlpResponse({ - request, - privateKeyBytes, - requiresTravelRuleInfo, - callback, - encodedMetadata, - minSendableSats, - maxSendableSats, - payerDataOptions, - currencyOptions, - receiverKycStatus, -}: GetSignedLnurlpResponseArgs): Promise { - const umaVersion = selectLowerVersion(request.umaVersion, UmaProtocolVersion); - const complianceResponse = await getSignedLnurlpComplianceResponse({ - query: request, - privateKeyBytes, - isSubjectToTravelRule: requiresTravelRuleInfo, - receiverKycStatus, - }); - return { - tag: "payRequest", - callback, - minSendable: minSendableSats, - maxSendable: maxSendableSats, - encodedMetadata, - currencies: currencyOptions, - requiredPayerData: payerDataOptions, - compliance: complianceResponse, - umaVersion, - }; -} - -type GetSignedLnurlpComplianceResponseArgs = { - query: LnurlpRequest; - privateKeyBytes: Uint8Array; - isSubjectToTravelRule: boolean; - receiverKycStatus: KycStatus; -}; - -async function getSignedLnurlpComplianceResponse({ - query, - privateKeyBytes, - isSubjectToTravelRule, - receiverKycStatus, -}: GetSignedLnurlpComplianceResponseArgs): Promise { - const timestamp = Math.floor(Date.now() / 1000); - const nonce = generateNonce(); - const payloadString = `${query.receiverAddress}|${nonce}|${timestamp}`; - const signature = await signPayload(payloadString, privateKeyBytes); - return { - kycStatus: receiverKycStatus, - signature, - signatureNonce: nonce, - signatureTimestamp: timestamp, - isSubjectToTravelRule, - receiverIdentifier: query.receiverAddress, - }; -} - -export async function verifyUmaLnurlpResponseSignature( - response: LnurlpResponse, - otherVaspSigningPubKey: Uint8Array, -) { - const encoder = new TextEncoder(); - const encodedResponse = encoder.encode( - getSignableLnurlpResponsePayload(response), - ); - const hashedPayload = await createSha256Hash(encodedResponse); - return verifySignature( - hashedPayload, - response.compliance.signature, - otherVaspSigningPubKey, - ); -} - -export async function verifyPayReqSignature( - query: PayRequest, - otherVaspPubKey: Uint8Array, -) { - const encoder = new TextEncoder(); - const encodedQuery = encoder.encode(getSignablePayRequestPayload(query)); - const hashedPayload = await createSha256Hash(encodedQuery); - return verifySignature( - hashedPayload, - query.payerData.compliance.signature, - otherVaspPubKey, - ); -} diff --git a/packages/uma/src/version.ts b/packages/uma/src/version.ts deleted file mode 100644 index b9f56efb5..000000000 --- a/packages/uma/src/version.ts +++ /dev/null @@ -1,109 +0,0 @@ -export const MAJOR_VERSION = 0; -export const MINOR_VERSION = 1; - -export const UmaProtocolVersion = `${MAJOR_VERSION}.${MINOR_VERSION}`; - -export class UnsupportedVersionError extends Error { - unsupportedVersion: string; - supportedMajorVersions: number[]; - - constructor(unsupportedVersion: string, supportedMajorVersions: number[]) { - super(`unsupported version: ${unsupportedVersion}`); - this.unsupportedVersion = unsupportedVersion; - this.supportedMajorVersions = supportedMajorVersions; - } -} - -export function getHighestSupportedVersionForMajorVersion( - majorVersion: number, -): string { - if (majorVersion !== MAJOR_VERSION) { - throw new Error("unsupported major version"); - } - return UmaProtocolVersion; -} - -export function selectHighestSupportedVersion( - otherVaspSupportedMajorVersions: number[], -): string { - let highestVersion: string | undefined; - const supportedMajorVersions = getSupportedMajorVersions(); - for (const otherVaspMajorVersion of otherVaspSupportedMajorVersions) { - if (!supportedMajorVersions.has(otherVaspMajorVersion)) { - continue; - } - - if (highestVersion === undefined) { - highestVersion = getHighestSupportedVersionForMajorVersion( - otherVaspMajorVersion, - ); - continue; - } - if (otherVaspMajorVersion > getMajorVersion(highestVersion)) { - highestVersion = getHighestSupportedVersionForMajorVersion( - otherVaspMajorVersion, - ); - } - } - if (highestVersion === undefined) { - throw new Error("no supported versions"); - } - return highestVersion; -} - -export function selectLowerVersion( - version1String: string, - version2String: string, -): string { - const version1 = parseVersion(version1String); - const version2 = parseVersion(version2String); - if ( - version1.major > version2.major || - (version1.major === version2.major && version1.minor > version2.minor) - ) { - return version2String; - } else { - return version1String; - } -} - -export function isVersionSupported(version: string): boolean { - const parsedVersion = parseVersion(version); - if (parsedVersion === undefined) { - return false; - } - return getSupportedMajorVersions().has(parsedVersion.major); -} - -export function getMajorVersion(version: string): number { - const parsedVersion = parseVersion(version); - if (parsedVersion === undefined) { - throw new Error("invalid version"); - } - return parsedVersion.major; -} - -export function getMinorVersion(version: string): number { - const parsedVersion = parseVersion(version); - if (parsedVersion === undefined) { - throw new Error("invalid version"); - } - return parsedVersion.minor; -} - -export function parseVersion(version: string): { - major: number; - minor: number; -} { - const [major, minor] = version.split("."); - if (major === undefined || minor === undefined) { - throw new Error("Invalid UMA version"); - } - return { major: parseInt(major), minor: parseInt(minor) }; -} - -export function getSupportedMajorVersions(): Set { - // NOTE: In the future, we may want to support multiple major versions in the same SDK, but for now, this keeps - // things simple. - return new Set([MAJOR_VERSION]); -} diff --git a/packages/uma/tsconfig-test.json b/packages/uma/tsconfig-test.json deleted file mode 100644 index b31191369..000000000 --- a/packages/uma/tsconfig-test.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "resolveJsonModule": true, - "types": ["jest", "node"] - }, - "include": ["src/**/*"], - "exclude": ["node_modules"] -} diff --git a/packages/uma/tsconfig.json b/packages/uma/tsconfig.json deleted file mode 100644 index 541ea73bb..000000000 --- a/packages/uma/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@lightsparkdev/tsconfig/base.json", - "include": ["src"], - "exclude": ["test", "node_modules", "dist"] -} diff --git a/yarn.lock b/yarn.lock index 577ece4b0..88228110e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4021,41 +4021,6 @@ __metadata: languageName: unknown linkType: soft -"@lightsparkdev/uma@workspace:packages/uma": - version: 0.0.0-use.local - resolution: "@lightsparkdev/uma@workspace:packages/uma" - dependencies: - "@lightsparkdev/core": 1.0.5 - "@lightsparkdev/eslint-config": "*" - "@lightsparkdev/lightspark-sdk": 1.1.0 - "@types/crypto-js": ^4.1.1 - "@types/ws": ^8.5.4 - auto-bind: ^5.0.1 - crypto: ^1.0.1 - crypto-browserify: ^3.12.0 - dayjs: ^1.11.7 - dotenv: ^16.3.1 - dotenv-cli: ^7.3.0 - eciesjs: ^0.4.4 - eslint: ^8.3.0 - eslint-watch: ^8.0.0 - graphql: ^16.6.0 - graphql-ws: ^5.11.3 - jest: ^29.6.2 - jsonwebtoken: ^9.0.1 - prettier: 3.0.2 - prettier-plugin-organize-imports: ^3.2.2 - secp256k1: ^5.0.0 - ts-jest: ^29.1.1 - tsup: ^6.7.0 - typedoc: ^0.24.7 - typescript: ^4.9.5 - ws: ^8.12.1 - zen-observable-ts: ^1.1.0 - zod: ^3.22.2 - languageName: unknown - linkType: soft - "@lightsparkdev/vite@*, @lightsparkdev/vite@0.0.0, @lightsparkdev/vite@workspace:packages/vite": version: 0.0.0-use.local resolution: "@lightsparkdev/vite@workspace:packages/vite" @@ -4240,29 +4205,6 @@ __metadata: languageName: node linkType: hard -"@noble/ciphers@npm:^0.1.4": - version: 0.1.4 - resolution: "@noble/ciphers@npm:0.1.4" - checksum: a846f91dc876ea8cf01c20f04df2816926ad4e4d90169e6334de39b477ce13bf5e720f4df9f9898dd2a87643660ccc8a04aa466baf885c43860c270bcc7deced - languageName: node - linkType: hard - -"@noble/curves@npm:^1.1.0": - version: 1.2.0 - resolution: "@noble/curves@npm:1.2.0" - dependencies: - "@noble/hashes": 1.3.2 - checksum: bb798d7a66d8e43789e93bc3c2ddff91a1e19fdb79a99b86cd98f1e5eff0ee2024a2672902c2576ef3577b6f282f3b5c778bebd55761ddbb30e36bf275e83dd0 - languageName: node - linkType: hard - -"@noble/hashes@npm:1.3.2": - version: 1.3.2 - resolution: "@noble/hashes@npm:1.3.2" - checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -9324,16 +9266,6 @@ __metadata: languageName: node linkType: hard -"eciesjs@npm:^0.4.4": - version: 0.4.4 - resolution: "eciesjs@npm:0.4.4" - dependencies: - "@noble/ciphers": ^0.1.4 - "@noble/curves": ^1.1.0 - checksum: 8147d68c4805c2663ffa841cf37e8e6ea88270ac142009d18c6083f2524587a1c479ca58cdf26238f46fdad70ed8df920d82d108df94f6fd0b940ec526656f06 - languageName: node - linkType: hard - "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -21557,10 +21489,3 @@ __metadata: checksum: b7289084bc1fc74a559b7259faa23d3214b14b538a8843d2b001a35e27147833f4107590b1b44bf5bc7f6dfe6f488660d3a3725f268e09b3925b3476153b7821 languageName: node linkType: hard - -"zod@npm:^3.22.2": - version: 3.22.3 - resolution: "zod@npm:3.22.3" - checksum: 65b05139be337078a70700b05942ab7f2ef5f11abe194df14ef257fac4e5c383476a4dc290731842996bd57fc8d5bf38e5a4c907fe8cdf8b15477f8da5bfcc00 - languageName: node - linkType: hard