├── .gitignore ├── demos ├── html │ ├── .yarnrc.yml │ ├── .gitignore │ ├── package.json │ ├── bc-button.html │ ├── README.md │ └── bc-pay-button.html ├── react │ ├── src │ │ ├── vite-env.d.ts │ │ ├── webln-types.d.ts │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── ConnectDemo.tsx │ │ │ ├── Home.tsx │ │ │ ├── BasicButtonDemo.tsx │ │ │ ├── PaymentButtonDemo.tsx │ │ │ └── PaymentDemo.tsx │ │ └── App.tsx │ ├── README.md │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── index.html │ ├── .gitignore │ ├── .eslintrc.cjs │ ├── tsconfig.json │ └── package.json ├── nextjs │ ├── .eslintrc.json │ ├── app │ │ ├── favicon.ico │ │ ├── layout.tsx │ │ ├── globals.css │ │ └── components │ │ │ └── BitcoinConnectClientWrapper.tsx │ ├── next.config.js │ ├── postcss.config.js │ ├── .gitignore │ ├── tailwind.config.ts │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── nextjs-legacy │ ├── .eslintrc.json │ ├── public │ │ ├── favicon.ico │ │ ├── vercel.svg │ │ └── next.svg │ ├── postcss.config.js │ ├── next.config.js │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── api │ │ │ └── hello.ts │ ├── .gitignore │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── styles │ │ └── globals.css │ ├── components │ │ └── BitcoinConnectClientWrapper.tsx │ ├── package.json │ └── README.md └── README.md ├── .eslintignore ├── dev ├── README.md └── vite │ ├── src │ └── vite-env.d.ts │ ├── README.md │ ├── .gitignore │ ├── package.json │ ├── tsconfig.json │ ├── button.html │ ├── connector-list.html │ ├── pay-button.html │ ├── button-deeplink.html │ └── button-nwa.html ├── src ├── webln-types.d.ts ├── types │ ├── ConnectorFilter.ts │ ├── PaymentMethods.ts │ ├── ConnectorType.ts │ ├── WebLNProviderConfig.ts │ ├── ConnectorConfig.ts │ └── BitcoinConnectConfig.ts ├── components │ ├── icons │ │ ├── checkIcon.ts │ │ ├── connectors │ │ │ ├── lnbitsIcon.ts │ │ │ ├── albyCloudIcon.ts │ │ │ ├── lncIcon.ts │ │ │ ├── lnfiIcon.ts │ │ │ ├── flashWalleticon.ts │ │ │ ├── albyGoIcon.ts │ │ │ ├── coinosIcon.ts │ │ │ ├── nwcThickIcon.ts │ │ │ ├── primalIcon.ts │ │ │ ├── nwcIcon.ts │ │ │ ├── albyHubIcon.ts │ │ │ └── cashuMeIcon.ts │ │ ├── bcConnectedIcon.ts │ │ ├── crossIcon.ts │ │ ├── backIcon.ts │ │ ├── disconnectIcon.ts │ │ ├── satIcon.ts │ │ ├── walletIcon.ts │ │ ├── linkIcon.ts │ │ ├── copyIcon.ts │ │ ├── copiedIcon.ts │ │ ├── helpIcon.ts │ │ ├── qrIcon.ts │ │ ├── bcIcon.ts │ │ ├── bcCircleIcon.ts │ │ └── waitingIcon.ts │ ├── connectors │ │ ├── index.ts │ │ ├── bc-extension-connector.ts │ │ ├── bc-generic-nwc-connector.ts │ │ ├── bc-lnbits-connector.ts │ │ ├── bc-lnfi-nwc-connector.ts │ │ ├── bc-rizful-connector.ts │ │ ├── bc-primal-connector.ts │ │ ├── bc-flash-connector.ts │ │ ├── bc-alby-hub-connector.ts │ │ ├── bc-cashu-me-connector.ts │ │ ├── bc-lnbits-nwc-connector.ts │ │ ├── bc-lnc-connector.ts │ │ ├── bc-coinos-connector.ts │ │ └── ConnectorElement.ts │ ├── templates │ │ ├── hr.ts │ │ ├── innerBorder.ts │ │ └── disconnectSection.ts │ ├── bc-router-outlet.ts │ ├── internal │ │ ├── bci-connecting.ts │ │ ├── bci-button.ts │ │ └── InternalElement.ts │ ├── bc-modal.ts │ ├── bc-navbar.ts │ ├── pages │ │ ├── bc-connected.ts │ │ ├── bc-nwc.ts │ │ ├── bc-lnfi.ts │ │ ├── bc-new-wallet.ts │ │ ├── bc-help.ts │ │ ├── bc-lnbits-nwc.ts │ │ ├── bc-alby-hub.ts │ │ ├── bc-rizful.ts │ │ ├── bc-cashu-me.ts │ │ ├── bc-primal.ts │ │ └── bc-lnbits.ts │ ├── css │ │ └── classes.ts │ ├── images │ │ └── success.ts │ ├── routes.ts │ ├── flows │ │ ├── bc-connect.ts │ │ └── bc-payment.ts │ ├── bc-modal-header.ts │ ├── BitcoinConnectElement.ts │ ├── bc-button.ts │ ├── bc-pay-button.ts │ ├── bc-start.ts │ ├── twind │ │ └── withTwind.ts │ └── bc-balance.ts ├── connectors │ ├── Connector.ts │ ├── ExtensionConnector.ts │ ├── NWCConnector.ts │ └── index.ts ├── utils │ └── base64ToHex.ts ├── index.ts ├── state │ └── boot.ts └── test │ └── my-element_test.ts ├── .prettierrc.json ├── tailwind.config.js ├── react ├── src │ ├── types │ │ └── ComponentProps.ts │ ├── components │ │ ├── Button.tsx │ │ ├── Connect.tsx │ │ ├── Payment.tsx │ │ └── PayButton.tsx │ ├── index.ts │ └── hooks │ │ ├── useOnPaid.ts │ │ └── useCoreEvents.ts ├── README.md ├── tsconfig.json └── package.json ├── .vscode ├── settings.json └── extensions.json ├── CONTRIBUTING.md ├── tsconfig.json ├── .github └── workflows │ └── publish.yml ├── LICENSE ├── .eslintrc.json ├── doc └── MIGRATION_v3.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /demos/html/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | web-dev-server.config.js 3 | -------------------------------------------------------------------------------- /demos/html/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .yarn/install-state.gz -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | # Dev Projects 2 | 3 | - [vite](vite/README.md) 4 | -------------------------------------------------------------------------------- /dev/vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/webln-types.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /demos/react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/types/ConnectorFilter.ts: -------------------------------------------------------------------------------- 1 | export type ConnectorFilter = 'nwc'; 2 | -------------------------------------------------------------------------------- /demos/nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /demos/react/src/webln-types.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/types/PaymentMethods.ts: -------------------------------------------------------------------------------- 1 | export type PaymentMethods = 'all' | 'internal' | 'external'; 2 | -------------------------------------------------------------------------------- /demos/nextjs/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getAlby/bitcoin-connect/HEAD/demos/nextjs/app/favicon.ico -------------------------------------------------------------------------------- /demos/nextjs-legacy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getAlby/bitcoin-connect/HEAD/demos/nextjs-legacy/public/favicon.ico -------------------------------------------------------------------------------- /demos/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /demos/nextjs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/types/ConnectorType.ts: -------------------------------------------------------------------------------- 1 | import {connectors} from '../connectors'; 2 | 3 | export type ConnectorType = keyof typeof connectors; 4 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "bracketSpacing": false, 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /* empty file required by tailwind css intellisense plugin */ 2 | /* we actually use twind but the intellisense does not seem to work there */ 3 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # Demos 2 | 3 | Please note: these demos point at the production version of Bitcoin Connect. 4 | 5 | - [React](react/README.md) 6 | - [Pure HTML](html/README.md) 7 | -------------------------------------------------------------------------------- /demos/react/README.md: -------------------------------------------------------------------------------- 1 | # demos/react 2 | 3 | A simple example of how to add Bitcoin Connect to your react app 4 | 5 | ## Install 6 | 7 | Run `yarn install` 8 | 9 | ## Development 10 | 11 | Run `yarn dev` 12 | -------------------------------------------------------------------------------- /src/types/WebLNProviderConfig.ts: -------------------------------------------------------------------------------- 1 | import {NWCAuthorizationUrlOptions} from '@getalby/sdk'; 2 | 3 | export type WebLNProviderConfig = { 4 | nwc?: { 5 | authorizationUrlOptions: NWCAuthorizationUrlOptions; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /dev/vite/README.md: -------------------------------------------------------------------------------- 1 | # dev/vite 2 | 3 | Develop Bitcoin Connect components using [Vite](https://vitejs.dev/) 4 | 5 | ## Install 6 | 7 | Run `yarn install` here and in the root directory. 8 | 9 | ## Development 10 | 11 | Run `yarn dev` (either here or in the root directory) 12 | -------------------------------------------------------------------------------- /src/types/ConnectorConfig.ts: -------------------------------------------------------------------------------- 1 | import {ConnectorType} from './ConnectorType'; 2 | 3 | export type ConnectorConfig = { 4 | connectorName: string; 5 | connectorType: ConnectorType; 6 | nwcUrl?: string; 7 | lnbitsInstanceUrl?: string; 8 | lnbitsAdminKey?: string; 9 | }; 10 | -------------------------------------------------------------------------------- /demos/react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | 5 | ReactDOM.createRoot(document.getElementById('root') as Element).render( 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /demos/react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /demos/react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | port: 3000, 9 | }, 10 | base: '/', 11 | }) 12 | -------------------------------------------------------------------------------- /react/src/types/ComponentProps.ts: -------------------------------------------------------------------------------- 1 | import {WebLNProvider} from '@webbtc/webln-types'; 2 | 3 | export type ComponentProps = { 4 | onConnected?(provider: WebLNProvider): void; 5 | onConnecting?(): void; 6 | onDisconnected?(): void; 7 | onModalOpened?(): void; 8 | onModalClosed?(): void; 9 | }; 10 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /react/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Connect React 2 | 3 | A React wrapper for Bitcoin Connect 4 | 5 | ## Development 6 | 7 | `yarn install` 8 | `yarn dev` 9 | 10 | ### Viewing changes 11 | 12 | Run `yarn link` then in `../demos/react` run `yarn link @getalby/bitcoin-connect-react`. The component changes can be seen in the demo app 13 | -------------------------------------------------------------------------------- /src/components/icons/checkIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const checkIcon = svg` 4 | 5 | 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/components/icons/connectors/lnbitsIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const lnbitsIcon = svg` 4 | 5 | 6 | `; 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "dist": true, 4 | "react/dist": true 5 | }, 6 | "editor.formatOnSave": true, 7 | "tailwindCSS.experimental.configFile": { 8 | "tailwind.config.js": "src/**", 9 | "website/tailwind.config.js": "website/**" 10 | }, 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | } 13 | -------------------------------------------------------------------------------- /demos/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bitcoin Connect React 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /dev/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /demos/react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /demos/html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "bc-button": "wds -r ../.. -o ./demos/html/bc-button.html --watch", 5 | "bc-api-usage": "wds -r ../.. -o ./demos/html/bc-api-usage.html --watch", 6 | "bc-pay-button": "wds -r ../.. -o ./demos/html/bc-pay-button.html --watch" 7 | }, 8 | "devDependencies": { 9 | "@web/dev-server": "^0.1.31" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /src/connectors/Connector.ts: -------------------------------------------------------------------------------- 1 | import {WebLNProvider} from '@webbtc/webln-types'; 2 | import {ConnectorConfig} from '../types/ConnectorConfig'; 3 | 4 | export abstract class Connector { 5 | protected _config: ConnectorConfig; 6 | 7 | constructor(config: ConnectorConfig) { 8 | this._config = config; 9 | } 10 | 11 | abstract init(): Promise; 12 | 13 | async unload() {} 14 | } 15 | -------------------------------------------------------------------------------- /dev/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite serve ../.. --open dev/vite/index.html", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "lit": "^3.0.0" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^5.0.2", 16 | "vite": "^4.4.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /react/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@getalby/bitcoin-connect'; 3 | import {ComponentProps} from '../types/ComponentProps'; 4 | import {useCoreEvents} from '../hooks/useCoreEvents'; 5 | 6 | type ButtonProps = ComponentProps & {}; 7 | 8 | export const Button: React.FC = (props) => { 9 | useCoreEvents(props); 10 | 11 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 12 | // @ts-ignore 13 | return ; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/icons/bcConnectedIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const bcConnectedIcon = svg` 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /src/utils/base64ToHex.ts: -------------------------------------------------------------------------------- 1 | // from https://github.com/getAlby/lightning-browser-extension/blob/master/src/common/lib/utils.ts#L12 2 | export const base64ToHex = (str: string) => { 3 | const hex = []; 4 | for ( 5 | let i = 0, bin = atob(str.replace(/[ \r\n]+$/, '')); 6 | i < bin.length; 7 | ++i 8 | ) { 9 | let tmp = bin.charCodeAt(i).toString(16); 10 | if (tmp.length === 1) tmp = '0' + tmp; 11 | hex[hex.length] = tmp; 12 | } 13 | return hex.join(''); 14 | }; 15 | -------------------------------------------------------------------------------- /react/src/components/Connect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@getalby/bitcoin-connect'; 3 | import {ComponentProps} from '../types/ComponentProps'; 4 | import {useCoreEvents} from '../hooks/useCoreEvents'; 5 | 6 | type ConnectProps = ComponentProps & {}; 7 | 8 | export const Connect: React.FC = (props) => { 9 | useCoreEvents(props); 10 | 11 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 12 | // @ts-ignore 13 | return ; 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/icons/crossIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | export const crossIcon = svg` 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/components/connectors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bc-extension-connector'; 2 | export * from './bc-alby-hub-connector'; 3 | export * from './bc-generic-nwc-connector'; 4 | export * from './bc-lnc-connector'; 5 | export * from './bc-lnbits-connector'; 6 | export * from './bc-lnbits-nwc-connector'; 7 | export * from './bc-lnfi-nwc-connector'; 8 | export * from './bc-coinos-connector'; 9 | export * from './bc-flash-connector'; 10 | export * from './bc-primal-connector'; 11 | export * from './bc-rizful-connector'; 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": ["runem.lit-plugin", "esbenp.prettier-vscode"], 7 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 8 | "unwantedRecommendations": [] 9 | } 10 | -------------------------------------------------------------------------------- /src/components/icons/connectors/albyCloudIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const albyCloudIcon = svg` 4 | 5 | `; 6 | -------------------------------------------------------------------------------- /demos/react/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/connectors/ExtensionConnector.ts: -------------------------------------------------------------------------------- 1 | import {Connector} from './Connector'; 2 | import {ConnectorConfig} from '../types/ConnectorConfig'; 3 | import {WebLNProvider} from '@webbtc/webln-types'; 4 | 5 | export class ExtensionConnector extends Connector { 6 | constructor(config: ConnectorConfig) { 7 | super(config); 8 | } 9 | 10 | init(): Promise { 11 | if (!window.webln) { 12 | throw new Error('No WebLN provider available'); 13 | } 14 | return Promise.resolve(window.webln); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/icons/backIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | // - re-add class="..." 6 | 7 | export const backIcon = svg` 8 | 9 | 10 | `; 11 | -------------------------------------------------------------------------------- /demos/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /demos/nextjs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | 5 | const inter = Inter({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/Button'; 2 | export * from './components/PayButton'; 3 | export * from './components/Connect'; 4 | export * from './components/Payment'; 5 | export { 6 | init, 7 | closeModal, 8 | connect, 9 | connectNWC, 10 | disconnect, 11 | isConnected, 12 | launchModal, 13 | launchPaymentModal, 14 | requestProvider, 15 | onConnected, 16 | onConnecting, 17 | onDisconnected, 18 | onModalOpened, 19 | onModalClosed, 20 | getConnectorConfig, 21 | WebLNProviders, 22 | PaymentMethods, 23 | } from '@getalby/bitcoin-connect'; 24 | -------------------------------------------------------------------------------- /demos/nextjs/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./*"] 18 | } 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /src/components/templates/hr.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {classes} from '../css/classes'; 3 | 4 | export function hr(text?: string) { 5 | const hrClasses = `border-t ${classes['border-neutral-tertiary']} ${ 6 | text ? 'w-24' : 'w-full' 7 | }`; 8 | 9 | return html`
12 |
13 | ${text 14 | ? html` 15 | ${text} 16 |
17 | ` 18 | : null} 19 |
`; 20 | } 21 | -------------------------------------------------------------------------------- /demos/nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/connectors/NWCConnector.ts: -------------------------------------------------------------------------------- 1 | import {Connector} from './Connector'; 2 | import {ConnectorConfig} from '../types/ConnectorConfig'; 3 | import {WebLNProvider} from '@webbtc/webln-types'; 4 | import {NostrWebLNProvider} from '@getalby/sdk'; 5 | 6 | export class NWCConnector extends Connector { 7 | constructor(config: ConnectorConfig) { 8 | super(config); 9 | } 10 | 11 | async init(): Promise { 12 | if (!this._config.nwcUrl) { 13 | throw new Error('no nwc URL provided'); 14 | } 15 | return new NostrWebLNProvider({ 16 | nostrWalletConnectUrl: this._config.nwcUrl, 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/connectors/bc-extension-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {extensionIcon} from '../icons/connectors/extensionIcon'; 3 | import {ConnectorElement} from './ConnectorElement'; 4 | 5 | @customElement('bc-extension-connector') 6 | export class ExtensionConnector extends ConnectorElement { 7 | constructor() { 8 | super('extension.generic', 'Browser Extensions', '#F4F4F4', extensionIcon); 9 | } 10 | 11 | protected _onClick() { 12 | this._connect({}); 13 | } 14 | } 15 | 16 | declare global { 17 | interface HTMLElementTagNameMap { 18 | 'bc-extension-connector': ExtensionConnector; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/icons/connectors/lncIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const lncIcon = svg``; 4 | -------------------------------------------------------------------------------- /demos/nextjs/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /dev/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "experimentalDecorators": true, 5 | "useDefineForClassFields": false, 6 | "module": "ESNext", 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"] 24 | } 25 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/bc-router-outlet.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {customElement} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement'; 4 | import {withTwind} from './twind/withTwind'; 5 | import {routes} from './routes'; 6 | 7 | @customElement('bc-router-outlet') 8 | export class RouterOutlet extends withTwind()(BitcoinConnectElement) { 9 | override render() { 10 | //TODO: r = routes[this._route](this._routeParams); 11 | return html`
${routes[this._route]}
`; 12 | } 13 | } 14 | 15 | declare global { 16 | interface HTMLElementTagNameMap { 17 | 'bc-router-outlet': RouterOutlet; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demos/html/bc-button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bc-button Demo 7 | 13 | 14 | 15 | 16 |
17 | <bc-button /> Demo 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /demos/react/src/pages/ConnectDemo.tsx: -------------------------------------------------------------------------------- 1 | import {Connect} from '@getalby/bitcoin-connect-react'; 2 | import toast, {Toaster} from 'react-hot-toast'; 3 | 4 | export default function ConnectDemo() { 5 | return ( 6 |
7 |

Connect Demo

8 | 9 | { 11 | console.log('WebLN connected', provider); 12 | toast('Connected!'); 13 | }} 14 | onConnecting={() => toast('Connecting!')} 15 | onDisconnected={() => toast('Disconnected!')} 16 | onModalOpened={() => toast('Modal opened!')} 17 | onModalClosed={() => toast('Modal closed!')} 18 | /> 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /demos/react/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import {Link} from 'react-router-dom'; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |

Bitcoin Connect React Demo

7 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/connectors/index.ts: -------------------------------------------------------------------------------- 1 | import {ExtensionConnector} from './ExtensionConnector'; 2 | import {LnbitsConnector} from './LnbitsConnector'; 3 | import {LNCConnector} from './LNCConnector'; 4 | import {NWCConnector} from './NWCConnector'; 5 | 6 | export const connectors = { 7 | 'extension.generic': ExtensionConnector, 8 | 'nwc.alby': NWCConnector, 9 | 'nwc.albyhub': NWCConnector, 10 | 'nwc.generic': NWCConnector, 11 | 'nwc.lnfi': NWCConnector, 12 | 'nwc.coinos': NWCConnector, 13 | 'nwc.flash': NWCConnector, 14 | 'nwc.primal': NWCConnector, 15 | 'nwc.cashume': NWCConnector, 16 | 'nwc.lnbits': NWCConnector, 17 | 'nwc.rizful': NWCConnector, 18 | lnbits: LnbitsConnector, 19 | lnc: LNCConnector, 20 | }; 21 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/components/BitcoinConnectClientWrapper.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | const Button = dynamic( 3 | () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Button), 4 | { 5 | ssr: false, 6 | } 7 | ); 8 | 9 | import React from 'react'; 10 | 11 | export function BitcoinConnectClientWrapper() { 12 | return ( 13 | <> 14 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /demos/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /demos/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /demos/nextjs/app/components/BitcoinConnectClientWrapper.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import dynamic from 'next/dynamic'; 4 | const Button = dynamic( 5 | () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Button), 6 | { 7 | ssr: false, 8 | } 9 | ); 10 | import React from 'react'; 11 | 12 | export function BitcoinConnectClientWrapper() { 13 | return ( 14 | <> 15 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /demos/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@getalby/bitcoin-connect-react": "^3.8.1", 13 | "next": "14.0.4", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "autoprefixer": "^10.0.1", 22 | "eslint": "^8", 23 | "eslint-config-next": "14.0.4", 24 | "postcss": "^8", 25 | "tailwindcss": "^3.3.0", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-legacy", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@getalby/bitcoin-connect-react": "^3.2.2", 13 | "next": "14.0.4", 14 | "react": "^18", 15 | "react-dom": "^18" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "autoprefixer": "^10.0.1", 22 | "eslint": "^8", 23 | "eslint-config-next": "14.0.4", 24 | "postcss": "^8", 25 | "tailwindcss": "^3.3.0", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/connectors/bc-generic-nwc-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {nwcIcon} from '../icons/connectors/nwcIcon'; 3 | import {ConnectorElement} from './ConnectorElement'; 4 | import store from '../../state/store'; 5 | 6 | export const genericConnectorTitle = 'NWC'; 7 | 8 | @customElement('bc-nwc-connector') 9 | export class GenericNWCConnector extends ConnectorElement { 10 | constructor() { 11 | super('nwc.generic', genericConnectorTitle, '#ffffff', nwcIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/nwc'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-nwc-connector': GenericNWCConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/react/src/pages/BasicButtonDemo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Button} from '@getalby/bitcoin-connect-react'; 3 | import toast, {Toaster} from 'react-hot-toast'; 4 | 5 | export default function BasicButtonDemo() { 6 | return ( 7 |
8 |

Basic Button Demo

9 | 10 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/connectors/bc-lnbits-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {lnbitsIcon} from '../icons/connectors/lnbitsIcon'; 5 | 6 | export const lnbitsConnectorTitle = 'LNbits'; 7 | 8 | @customElement('bc-lnbits-connector') 9 | export class LNBitsConnector extends ConnectorElement { 10 | constructor() { 11 | super('lnbits', lnbitsConnectorTitle, '#673ab7', lnbitsIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/lnbits'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-lnbits-connector': LNBitsConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/connectors/bc-lnfi-nwc-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {lnfiIcon} from '../icons/connectors/lnfiIcon'; 3 | import {ConnectorElement} from './ConnectorElement'; 4 | import store from '../../state/store'; 5 | 6 | export const lnfiConnectorTitle = 'LN Link'; 7 | 8 | @customElement('bc-lnfi-nwc-connector') 9 | export class LnfiNWCConnector extends ConnectorElement { 10 | constructor() { 11 | super('nwc.generic', lnfiConnectorTitle, '#ffffff', lnfiIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/lnfi'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-lnfi-nwc-connector': LnfiNWCConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/connectors/bc-rizful-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {rizfulIcon} from '../icons/connectors/rizfulIcon'; 5 | 6 | export const rizfulConnectorTitle = 'Rizful'; 7 | 8 | @customElement('bc-rizful-connector') 9 | export class RizfulConnector extends ConnectorElement { 10 | constructor() { 11 | super('nwc.rizful', rizfulConnectorTitle, '#000000', rizfulIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/rizful'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-rizful-connector': RizfulConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/connectors/bc-primal-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {primalIcon} from '../icons/connectors/primalIcon'; 5 | 6 | export const primalConnectorTitle = 'Primal Mobile'; 7 | 8 | @customElement('bc-primal-connector') 9 | export class PrimalConnector extends ConnectorElement { 10 | constructor() { 11 | super('nwc.primal', primalConnectorTitle, '#000000', primalIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/primal'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-primal-connector': PrimalConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/connectors/bc-flash-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {flashWalletIcon} from '../icons/connectors/flashWalleticon'; 5 | export const flashConnectorTitle = 'Flash Wallet'; 6 | 7 | @customElement('bc-flash-connector') 8 | export class FlashConnector extends ConnectorElement { 9 | constructor() { 10 | super('nwc.flash', flashConnectorTitle, '#000000', flashWalletIcon); 11 | } 12 | 13 | protected async _onClick() { 14 | store.getState().pushRoute('/flash-wallet'); 15 | } 16 | } 17 | 18 | declare global { 19 | interface HTMLElementTagNameMap { 20 | 'bc-flash-connector': FlashConnector; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/connectors/bc-alby-hub-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {albyhubIcon} from '../icons/connectors/albyHubIcon'; 5 | 6 | export const albyHubConnectorTitle = 'Alby Hub'; 7 | 8 | @customElement('bc-alby-hub-connector') 9 | export class AlbyHubConnector extends ConnectorElement { 10 | constructor() { 11 | super('nwc.albyhub', albyHubConnectorTitle, '#000000', albyhubIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/alby-hub'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-alby-hub-connector': AlbyHubConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/connectors/bc-cashu-me-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {cashuMeIcon} from '../icons/connectors/cashuMeIcon'; 5 | 6 | export const cashuMeConnectorTitle = 'Cashu.me'; 7 | 8 | @customElement('bc-cashu-me-connector') 9 | export class CashuMeConnector extends ConnectorElement { 10 | constructor() { 11 | super('nwc.cashume', cashuMeConnectorTitle, '#7f38ca', cashuMeIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/cashu-me'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-cashu-me-connector': CashuMeConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/connectors/bc-lnbits-nwc-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {lnbitsIcon} from '../icons/connectors/lnbitsIcon'; 5 | 6 | export const lnbitsNWCConnectorTitle = 'LNbits NWC Plugin'; 7 | 8 | @customElement('bc-lnbits-nwc-connector') 9 | export class LNbitsNWCConnector extends ConnectorElement { 10 | constructor() { 11 | super('lnbits', lnbitsNWCConnectorTitle, '#673ab7', lnbitsIcon); 12 | } 13 | 14 | protected async _onClick() { 15 | store.getState().pushRoute('/lnbits-nwc'); 16 | } 17 | } 18 | 19 | declare global { 20 | interface HTMLElementTagNameMap { 21 | 'bc-lnbits-nwc-connector': LNbitsNWCConnector; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/icons/disconnectIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | // - add class="hover-right" to the second path for the arrow animation 6 | export const disconnectIcon = svg` 7 | 8 | 9 | 10 | `; 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Adding a new Connector component 4 | 5 | A connector component is a web component that will be displayed in the modal. You can customize the name, color, icon and functionality that will happen when the component is clicked. 6 | 7 | See [existing connector components](src/components/connectors/index.ts) 8 | 9 | Make sure to add your new connector component to the [connector list](src/components/bc-connector-list.ts) 10 | 11 | ## Adding a new connector 12 | 13 | A connector exposes a WebLN object for the web application. 14 | 15 | See [existing connectors](src/connectors/index.ts) 16 | 17 | ## Adding a new wrapper package 18 | 19 | A wrapper package simplifies integration of Bitcoin Connect with different web frameworks. 20 | 21 | See [React](/react) 22 | -------------------------------------------------------------------------------- /react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "outDir": "dist", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitThis": true, 18 | "allowSyntheticDefaultImports": true, 19 | "experimentalDecorators": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "noImplicitOverride": true, 22 | "useDefineForClassFields": false 23 | }, 24 | "include": ["src"], 25 | "exclude": ["node_modules", "dist"] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/templates/innerBorder.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {classes} from '../css/classes'; 3 | 4 | export function innerBorder() { 5 | return html`
`; 10 | } 11 | 12 | export function innerBorderBranded() { 13 | return html`
`; 18 | } 19 | 20 | export function innerBorderTertiary() { 21 | return html`
`; 26 | } 27 | -------------------------------------------------------------------------------- /src/components/icons/connectors/lnfiIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - set class="w-10 h-10" 5 | 6 | export const lnfiIcon = svg` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | `; 17 | -------------------------------------------------------------------------------- /dev/vite/button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bitcoin Connect Demo 7 | 22 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /react/src/hooks/useOnPaid.ts: -------------------------------------------------------------------------------- 1 | import {SendPaymentResponse} from '@webbtc/webln-types'; 2 | import React from 'react'; 3 | 4 | export function useOnPaid( 5 | onPaid?: (response: SendPaymentResponse) => void, 6 | payment?: SendPaymentResponse 7 | ) { 8 | React.useEffect(() => { 9 | const onPaidEventHandler = (event: Event) => { 10 | onPaid?.((event as CustomEvent).detail); 11 | }; 12 | window.addEventListener('bc:onpaid', onPaidEventHandler); 13 | 14 | if (payment) { 15 | // TODO: remove once bc-send-payment accepts preimage 16 | window.dispatchEvent( 17 | new CustomEvent('bc:onpaid', { 18 | detail: payment, 19 | }) 20 | ); 21 | } 22 | 23 | return () => { 24 | window.removeEventListener('bc:onpaid', onPaidEventHandler); 25 | }; 26 | }, [onPaid, payment]); 27 | } 28 | -------------------------------------------------------------------------------- /dev/vite/connector-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bitcoin Connect Demo 7 | 22 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/icons/satIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const satIcon = svg` 7 | 8 | `; 9 | -------------------------------------------------------------------------------- /src/components/icons/walletIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const walletIcon = svg` 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /demos/react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import {BrowserRouter, Routes, Route} from 'react-router-dom'; 2 | import {init} from '@getalby/bitcoin-connect-react'; 3 | import Home from './pages/Home'; 4 | import BasicButtonDemo from './pages/BasicButtonDemo'; 5 | import PaymentButtonDemo from './pages/PaymentButtonDemo'; 6 | import ConnectDemo from './pages/ConnectDemo'; 7 | import PaymentDemo from './pages/PaymentDemo'; 8 | 9 | init({ 10 | appName: 'Bitcoin Connect (React Demo)', 11 | }); 12 | 13 | function App() { 14 | return ( 15 | 16 | 17 | } /> 18 | } /> 19 | } /> 20 | } /> 21 | } /> 22 | 23 | 24 | ); 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {NostrWebLNProvider} from '@getalby/sdk'; 2 | import {LNCWebLNProvider} from './connectors/LNCConnector'; 3 | import {LnbitsWebLNProvider} from './connectors/LnbitsConnector'; 4 | import './state/boot'; 5 | 6 | export * from './components/bc-button'; 7 | export * from './components/bc-pay-button'; 8 | export * from './components/bc-modal'; 9 | export * from './components/bc-connector-list'; 10 | export * from './components/bc-balance'; 11 | export * from './components/bc-currency-switcher'; 12 | export * from './components/pages/bc-send-payment'; 13 | export * from './components/connectors/index'; 14 | export * from './components/flows/bc-connect'; 15 | export * from './components/flows/bc-payment'; 16 | export * from './state/store'; 17 | export * from './api'; 18 | 19 | export const WebLNProviders = { 20 | NostrWebLNProvider: NostrWebLNProvider, 21 | LNCWebLNProvider, 22 | LnbitsWebLNProvider, 23 | }; 24 | 25 | export * from './types/PaymentMethods'; 26 | -------------------------------------------------------------------------------- /src/components/internal/bci-connecting.ts: -------------------------------------------------------------------------------- 1 | import {LitElement, html} from 'lit'; 2 | import {withTwind} from '../twind/withTwind'; 3 | import {customElement} from 'lit/decorators.js'; 4 | import {classes} from '../css/classes'; 5 | import {waitingIcon} from '../icons/waitingIcon'; 6 | import {disconnectSection} from '../templates/disconnectSection'; 7 | 8 | @customElement('bci-connecting') 9 | export class Connecting extends withTwind()(LitElement) { 10 | override render() { 11 | return html` 12 |
13 | ${waitingIcon(`w-20 h-20 ${classes['text-neutral-tertiary']} mb-4`)} 14 |

15 | Connecting to wallet... 16 |

17 | ${disconnectSection(undefined)} 18 |
19 | `; 20 | } 21 | } 22 | 23 | declare global { 24 | interface HTMLElementTagNameMap { 25 | 'bci-connecting': Connecting; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "lib": ["es2020", "DOM", "DOM.Iterable"], 6 | "declaration": true, 7 | "declarationMap": false, 8 | "sourceMap": false, 9 | "outDir": "./", 10 | "rootDir": "./src", 11 | "strict": true, 12 | "skipLibCheck": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": true, 18 | "noImplicitThis": true, 19 | "moduleResolution": "node", 20 | "allowSyntheticDefaultImports": true, 21 | "experimentalDecorators": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "noImplicitOverride": true, 24 | "useDefineForClassFields": false, 25 | "plugins": [ 26 | { 27 | "name": "ts-lit-plugin", 28 | "strict": true 29 | } 30 | ] 31 | }, 32 | "include": ["src/**/*.ts"], 33 | "exclude": [] 34 | } 35 | -------------------------------------------------------------------------------- /src/components/icons/linkIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | // - add class="hover-right" to the second path for the arrow animation 6 | export const linkIcon = svg` 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /demos/html/README.md: -------------------------------------------------------------------------------- 1 | ## Previewing the Demos 2 | 3 | ### Option 1: Open HTML Files Directly 4 | 5 | You can open any of the demo files in your browser by double-clicking or using “Open with Browser”: 6 | 7 | * [`bc-button.html`](./bc-button.html) 8 | * [`bc-api-usage.html`](./bc-api-usage.html) 9 | * [`bc-pay-button.html`](./bc-pay-button.html) 10 | 11 | --- 12 | 13 | ### Option 2: Run a Local Development Server 14 | 15 | We recommend using a local server for hot reloading and better cross-origin behavior (especially for web components and modules). 16 | 17 | #### 1. Install dependencies 18 | 19 | ```bash 20 | yarn install 21 | ```` 22 | 23 | #### 2. Start the dev server 24 | 25 | Use any of the following scripts to preview a specific demo: 26 | 27 | ```bash 28 | yarn bc-button # Runs bc-button.html 29 | yarn bc-api-usage # Runs bc-api-usage.html 30 | yarn bc-pay-button # Runs bc-pay-button.html 31 | ``` 32 | 33 | 34 | You can also manually visit any file at `http://localhost:8000/demos/html/.html` 35 | 36 | -------------------------------------------------------------------------------- /src/components/icons/connectors/flashWalleticon.ts: -------------------------------------------------------------------------------- 1 | import { svg } from "lit"; 2 | 3 | export const flashWalletIcon = svg`` 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/connectors/bc-lnc-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import {lncIcon} from '../icons/connectors/lncIcon'; 4 | import {getLNC} from '../../connectors/LNCConnector'; 5 | 6 | @customElement('bc-lnc-connector') 7 | export class LNCConnector extends ConnectorElement { 8 | constructor() { 9 | super('lnc', 'Lightning Node Connect', '#101727', lncIcon); 10 | } 11 | 12 | protected async _onClick() { 13 | // TODO: improve UX for entering pairing phrase, allow scanning QR code? 14 | const pairingPhrase = window.prompt('Enter pairing phrase'); 15 | if (!pairingPhrase) { 16 | return; 17 | } 18 | 19 | const lnc = await getLNC(); 20 | if (!lnc) { 21 | throw new Error('LNC not supported'); 22 | } 23 | lnc.credentials.pairingPhrase = pairingPhrase; 24 | 25 | this._connect({}); 26 | } 27 | } 28 | 29 | declare global { 30 | interface HTMLElementTagNameMap { 31 | 'bc-lnc-connector': LNCConnector; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | # NOTE: this is currently disabled and published manually 5 | name: Publish package 6 | 7 | on: 8 | #release: 9 | # types: [created] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: 18 19 | - run: yarn install --frozen-lockfile 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v3 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version: 18 29 | registry-url: https://registry.npmjs.org/ 30 | - run: yarn install --frozen-lockfile 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | -------------------------------------------------------------------------------- /demos/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@getalby/bitcoin-connect-react": "^3.8.1", 14 | "@getalby/lightning-tools": "^5.1.2", 15 | "react": "^19.1.1", 16 | "react-dom": "^19.1.1", 17 | "react-hot-toast": "^2.4.1", 18 | "react-router-dom": "^7.1.5" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^19.1.12", 22 | "@types/react-dom": "^19.1.9", 23 | "@typescript-eslint/eslint-plugin": "^8.42.0", 24 | "@typescript-eslint/parser": "^8.42.0", 25 | "@vitejs/plugin-react-swc": "^4.0.1", 26 | "@webbtc/webln-types": "^3.0.0", 27 | "eslint": "^9.34.0", 28 | "eslint-plugin-react-hooks": "^5.2.0", 29 | "eslint-plugin-react-refresh": "^0.4.20", 30 | "typescript": "^5.9.2", 31 | "vite": "^7.1.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/state/boot.ts: -------------------------------------------------------------------------------- 1 | import {ConnectorConfig} from '../types/ConnectorConfig'; 2 | import store from './store'; 3 | 4 | function loadConfig() { 5 | const configJson = window.localStorage.getItem('bc:config'); 6 | if (configJson) { 7 | const config = JSON.parse(configJson) as ConnectorConfig; 8 | store.getState().connect(config, {redirectTo: '/start'}); 9 | } 10 | 11 | const currency = window.localStorage.getItem('bc:currency'); 12 | if (currency) { 13 | store.getState().setCurrency(currency); 14 | } 15 | } 16 | 17 | function addEventListeners() { 18 | window.addEventListener('webln:enabled', () => { 19 | if (!store.getState().connecting) { 20 | // webln was enabled from outside 21 | // TODO: use the same name and logic for figuring out what extension as the extension connector 22 | store.getState().connect( 23 | { 24 | connectorName: 'Extension', 25 | connectorType: 'extension.generic', 26 | }, 27 | {redirectTo: '/start'} 28 | ); 29 | } 30 | }); 31 | } 32 | 33 | if (globalThis.window) { 34 | loadConfig(); 35 | addEventListeners(); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/templates/disconnectSection.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {classes} from '../css/classes'; 3 | import {disconnectIcon} from '../icons/disconnectIcon'; 4 | import store from '../../state/store'; 5 | 6 | export function disconnectSection(connectorName: string | undefined) { 7 | return html`
8 | ${connectorName 9 | ? html`Connected through 11 | ${connectorName}` 13 | : null} 14 | 15 | 21 | ${disconnectIcon} 22 | Disconnect 25 | 26 |
`; 27 | } 28 | 29 | function handleDisconnect() { 30 | // disconnect after closing modal 31 | // to avoid flash on modal screen 32 | store.getState().setModalOpen(false); 33 | setTimeout(() => { 34 | store.getState().disconnect(); 35 | }, 200); 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Alby contributors  (https://getalby.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@getalby/bitcoin-connect-react", 3 | "version": "3.11.5", 4 | "type": "module", 5 | "source": "src/index.ts", 6 | "main": "./dist/index.cjs", 7 | "module": "./dist/index.module.js", 8 | "unpkg": "./dist/index.umd.js", 9 | "types": "./dist/index.d.ts", 10 | "exports": { 11 | "require": "./dist/index.cjs", 12 | "types": "./dist/index.d.ts", 13 | "default": "./dist/index.modern.js" 14 | }, 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/getAlby/bitcoin-connect.git", 19 | "directory": "react" 20 | }, 21 | "files": [ 22 | "dist/**/*" 23 | ], 24 | "scripts": { 25 | "dev": "microbundle --globals react=React --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react watch", 26 | "prepack": "yarn build", 27 | "build": "microbundle --globals react=React --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react" 28 | }, 29 | "dependencies": { 30 | "@getalby/bitcoin-connect": "^3.11.5" 31 | }, 32 | "devDependencies": { 33 | "@types/react": "^18.2.21", 34 | "microbundle": "^0.15.1" 35 | }, 36 | "peerDependencies": { 37 | "react": ">=18.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/bc-modal.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {customElement} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement'; 4 | import './bc-router-outlet.js'; 5 | import {withTwind} from './twind/withTwind'; 6 | import './bc-modal-header'; 7 | import {closeModal} from '../api'; 8 | 9 | @customElement('bc-modal') 10 | export class Modal extends withTwind()(BitcoinConnectElement) { 11 | override render() { 12 | return html`
15 |
19 |
23 | 24 |
25 |
`; 26 | } 27 | 28 | private _handleClose = () => { 29 | closeModal(); 30 | }; 31 | } 32 | 33 | declare global { 34 | interface HTMLElementTagNameMap { 35 | 'bc-modal': Modal; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/icons/connectors/albyGoIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const albyGoIcon = svg``; 4 | -------------------------------------------------------------------------------- /demos/nextjs/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react/src/components/Payment.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@getalby/bitcoin-connect'; 3 | import {ComponentProps} from '../types/ComponentProps'; 4 | import {useCoreEvents} from '../hooks/useCoreEvents'; 5 | import {SendPaymentResponse} from '@webbtc/webln-types'; 6 | import {useOnPaid} from '../hooks/useOnPaid'; 7 | import {PaymentMethods} from '@getalby/bitcoin-connect'; 8 | 9 | type PaymentProps = ComponentProps & { 10 | /** 11 | * Bolt 11 invoice to pay 12 | */ 13 | invoice: string; 14 | /** 15 | * Supported payment methods in payment flow 16 | */ 17 | paymentMethods?: PaymentMethods; 18 | /** 19 | * @param response response of the WebLN send payment call 20 | */ 21 | onPaid?: (response: SendPaymentResponse) => void; 22 | /** 23 | * Mark that an external payment was made 24 | */ 25 | payment?: SendPaymentResponse; 26 | }; 27 | 28 | export const Payment: React.FC = (props) => { 29 | useCoreEvents(props); 30 | 31 | const {onPaid, payment} = props; 32 | useOnPaid(onPaid, payment); 33 | 34 | return ( 35 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 36 | // @ts-ignore 37 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/bc-navbar.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement'; 4 | import {withTwind} from './twind/withTwind'; 5 | import store from '../state/store'; 6 | import {backIcon} from './icons/backIcon'; 7 | import {classes} from './css/classes'; 8 | 9 | @customElement('bc-navbar') 10 | export class Navbar extends withTwind()(BitcoinConnectElement) { 11 | @property() 12 | heading?: string; 13 | 14 | override render() { 15 | return html`
18 |
19 | 27 |
28 |
29 | ${this.heading} 30 |
31 |
`; 32 | } 33 | 34 | private _goBack = () => { 35 | store.getState().popRoute(); 36 | store.getState().setError(undefined); 37 | }; 38 | } 39 | 40 | declare global { 41 | interface HTMLElementTagNameMap { 42 | 'bc-navbar': Navbar; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/icons/connectors/coinosIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const coinosIcon = svg``; 4 | -------------------------------------------------------------------------------- /src/components/icons/copyIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const copyIcon = svg` 7 | 8 | `; 9 | -------------------------------------------------------------------------------- /src/components/pages/bc-connected.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {successAnimation} from '../images/success.js'; 7 | import store from '../../state/store'; 8 | import {closeModal} from '../../api'; 9 | import {classes} from '../css/classes'; 10 | 11 | @customElement('bc-connected') 12 | export class ConnectedPage extends withTwind()(BitcoinConnectElement) { 13 | private _timeout: NodeJS.Timeout | undefined; 14 | override connectedCallback(): void { 15 | super.connectedCallback(); 16 | this._timeout = setTimeout(() => { 17 | closeModal(); 18 | store.setState({ 19 | route: '/start', 20 | }); 21 | }, 3000); 22 | } 23 | 24 | override disconnectedCallback(): void { 25 | super.disconnectedCallback(); 26 | if (this._timeout) { 27 | clearTimeout(this._timeout); 28 | } 29 | } 30 | 31 | override render() { 32 | return html`
37 |

Connected!

38 | ${successAnimation} 39 |
`; 40 | } 41 | } 42 | 43 | declare global { 44 | interface HTMLElementTagNameMap { 45 | 'bc-connected': ConnectedPage; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dev/vite/pay-button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bitcoin Connect Demo 7 | 22 | 25 | 40 | 41 | 42 |
43 | 44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /src/components/connectors/bc-coinos-connector.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {ConnectorElement} from './ConnectorElement'; 3 | import store from '../../state/store'; 4 | import {coinosIcon} from '../icons/connectors/coinosIcon'; 5 | import {NWCClient} from '@getalby/sdk'; 6 | 7 | export const coinosConnectorTitle = 'Coinos'; 8 | 9 | @customElement('bc-coinos-connector') 10 | export class CoinosConnector extends ConnectorElement { 11 | constructor() { 12 | super('nwc.coinos', coinosConnectorTitle, '#ffffff', coinosIcon); 13 | } 14 | 15 | protected async _onClick() { 16 | try { 17 | const providerConfig = 18 | store.getState().bitcoinConnectConfig.providerConfig; 19 | const nwcClient = await NWCClient.fromAuthorizationUrl( 20 | 'https://coinos.io/apps/new', 21 | { 22 | ...(providerConfig?.nwc?.authorizationUrlOptions || {}), 23 | name: this._appName, 24 | } 25 | ); 26 | nwcClient.close(); 27 | // TODO: it makes no sense to connect again 28 | await store.getState().connect({ 29 | nwcUrl: nwcClient.nostrWalletConnectUrl, 30 | connectorName: 'Coinos', 31 | connectorType: 'nwc.coinos', 32 | }); 33 | } catch (error) { 34 | console.error(error); 35 | alert('' + error); 36 | } 37 | } 38 | } 39 | 40 | declare global { 41 | interface HTMLElementTagNameMap { 42 | 'bc-coinos-connector': CoinosConnector; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/icons/copiedIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const copiedIcon = svg` 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "sourceType": "module" 12 | }, 13 | "plugins": ["@typescript-eslint"], 14 | "env": { 15 | "browser": true 16 | }, 17 | "rules": { 18 | "no-prototype-builtins": "off", 19 | "@typescript-eslint/ban-types": "off", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/explicit-module-boundary-types": "off", 22 | "@typescript-eslint/no-explicit-any": "error", 23 | "@typescript-eslint/no-empty-function": "off", 24 | "@typescript-eslint/no-non-null-assertion": "off", 25 | "@typescript-eslint/no-unused-vars": [ 26 | "warn", 27 | { 28 | "argsIgnorePattern": "^_" 29 | } 30 | ] 31 | }, 32 | "overrides": [ 33 | { 34 | "files": ["**/*.ts"] 35 | }, 36 | { 37 | "files": ["web-test-runner.config.js"], 38 | "env": { 39 | "node": true 40 | } 41 | }, 42 | { 43 | "files": [ 44 | "*_test.ts", 45 | "**/custom_typings/*.ts", 46 | "packages/labs/ssr/src/test/integration/tests/**", 47 | "packages/labs/ssr/src/lib/util/parse5-utils.ts" 48 | ], 49 | "rules": { 50 | "@typescript-eslint/no-explicit-any": "off" 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /react/src/hooks/useCoreEvents.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ComponentProps} from '../types/ComponentProps'; 3 | import { 4 | onConnected, 5 | onConnecting, 6 | onDisconnected, 7 | onModalClosed, 8 | onModalOpened, 9 | } from '@getalby/bitcoin-connect'; 10 | 11 | export function useCoreEvents(props: ComponentProps) { 12 | React.useEffect(() => { 13 | if (props.onConnected) { 14 | const unsub = onConnected(props.onConnected); 15 | 16 | return () => { 17 | unsub(); 18 | }; 19 | } 20 | return () => {}; 21 | }, []); 22 | React.useEffect(() => { 23 | if (props.onConnecting) { 24 | const unsub = onConnecting(props.onConnecting); 25 | 26 | return () => { 27 | unsub(); 28 | }; 29 | } 30 | return () => {}; 31 | }, []); 32 | React.useEffect(() => { 33 | if (props.onDisconnected) { 34 | const unsub = onDisconnected(props.onDisconnected); 35 | 36 | return () => { 37 | unsub(); 38 | }; 39 | } 40 | return () => {}; 41 | }, []); 42 | React.useEffect(() => { 43 | if (props.onModalOpened) { 44 | const unsub = onModalOpened(props.onModalOpened); 45 | 46 | return () => { 47 | unsub(); 48 | }; 49 | } 50 | return () => {}; 51 | }, []); 52 | React.useEffect(() => { 53 | if (props.onModalClosed) { 54 | const unsub = onModalClosed(props.onModalClosed); 55 | 56 | return () => { 57 | unsub(); 58 | }; 59 | } 60 | return () => {}; 61 | }, []); 62 | } 63 | -------------------------------------------------------------------------------- /src/components/css/classes.ts: -------------------------------------------------------------------------------- 1 | export const classes = { 2 | interactive: 3 | 'transition-all hover:brightness-90 dark:hover:brightness-110 active:scale-95 cursor-pointer', 4 | 'hover-animation': 'hover-animation', 5 | 6 | // text colors 7 | 'text-brand': 'text-brand-light dark:text-brand-dark', 8 | 'text-brand-mixed': 'text-brand-mixed-light dark:text-brand-mixed-dark', 9 | 'text-brand-button-text': 10 | 'text-brand-button-text-light dark:text-brand-button-text-dark', 11 | 'text-foreground': 'text-foreground-light dark:text-foreground-dark', 12 | 'text-neutral-primary': 13 | 'text-neutral-primary-light dark:text-neutral-primary-dark', 14 | 'text-neutral-secondary': 15 | 'text-neutral-secondary-light dark:text-neutral-secondary-dark', 16 | 'text-neutral-tertiary': 17 | 'text-neutral-tertiary-light dark:text-neutral-tertiary-dark', 18 | 19 | // background colors 20 | 'bg-brand': 'bg-brand-light dark:bg-brand-dark', 21 | 'bg-background': 'bg-background-light dark:bg-background-dark', 22 | 'bg-foreground': 'bg-foreground-light dark:bg-foreground-dark', 23 | 'bg-glass': 'bg-glass-light dark:bg-glass-dark', 24 | 25 | // border colors 26 | 'border-brand': 'border-brand-light dark:border-brand-dark', 27 | 'border-brand-mixed': 'border-brand-mixed-light dark:border-brand-mixed-dark', 28 | 'border-neutral-secondary': 29 | 'border-neutral-secondary-light dark:border-neutral-secondary-dark', 30 | 'border-neutral-tertiary': 31 | 'border-neutral-tertiary-light dark:border-neutral-tertiary-dark', 32 | }; 33 | -------------------------------------------------------------------------------- /src/components/images/success.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const successAnimation = svg` 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | `; 23 | -------------------------------------------------------------------------------- /react/src/components/PayButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@getalby/bitcoin-connect'; 3 | import {PaymentMethods} from '@getalby/bitcoin-connect'; 4 | import {ComponentProps} from '../types/ComponentProps'; 5 | import {useCoreEvents} from '../hooks/useCoreEvents'; 6 | import {SendPaymentResponse} from '@webbtc/webln-types'; 7 | import {useOnPaid} from '../hooks/useOnPaid'; 8 | 9 | type PayButtonProps = ComponentProps & { 10 | /** 11 | * Bolt 11 invoice to pay 12 | */ 13 | invoice?: string; 14 | /** 15 | * Supported payment methods in payment flow 16 | */ 17 | paymentMethods?: PaymentMethods; 18 | /** 19 | * @param response response of the WebLN send payment call 20 | */ 21 | onPaid?: (response: SendPaymentResponse) => void; 22 | /** 23 | * Mark that an external payment was made 24 | */ 25 | payment?: SendPaymentResponse; 26 | 27 | /** 28 | * Listen to when the pay button is clicked. 29 | * This is a good time to fetch an invoice to pay. 30 | */ 31 | onClick?: () => void; 32 | }; 33 | 34 | export const PayButton: React.FC = (props) => { 35 | useCoreEvents(props); 36 | 37 | const {onPaid, payment} = props; 38 | useOnPaid(onPaid, payment); 39 | 40 | return ( 41 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 42 | // @ts-ignore 43 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /dev/vite/button-deeplink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bitcoin Connect Demo 7 | 22 | 46 | 47 | 48 |
49 | 50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /doc/MIGRATION_v3.md: -------------------------------------------------------------------------------- 1 | # V2 to V3 migration guide 2 | 3 | ## WebLN global object 4 | 5 | window.webln is no longer set by default. If you rely on WebLN being available in the global window object please add the following lines to a place in your application which will be called on every page: 6 | 7 | ```ts 8 | import {onConnected} from '@getalby/bitcoin-connect'; 9 | 10 | onConnected((provider) => { 11 | window.webln = provider; 12 | }); 13 | ``` 14 | 15 | ## Init 16 | 17 | Attributes (such as `filters` and `appName` that were originally passed to individual components) have been moved to the new `init` function exposed by the Bitcoin Connect api. 18 | 19 | ```ts 20 | import {init} from '@getalby/bitcoin-connect-react'; 21 | 22 | init({ 23 | appName: 'My Lightning App', // your app name 24 | // filters: ["nwc"], 25 | // ...etc 26 | }); 27 | ``` 28 | 29 | ## `launchModal` for payments 30 | 31 | This has been moved to `launchPaymentModal`. 32 | 33 | ## Modal 34 | 35 | `` (or ``) no longer needs to be rendered manually. Make sure to remove it so that the modal does not unexpectedly render on the page. 36 | 37 | > Make sure to set Bitcoin Connect css variables at the root (e.g. html/body selector) to ensure the modal uses the correct colors. 38 | 39 | ## Button 40 | 41 | Attributes have been moved from the button to the `init` function exposed by the API. 42 | 43 | ## Events 44 | 45 | New subscription methods are exposed on the Bitcoin Connect API (`onConnected` etc) replacing window events such as `bc:connected`. 46 | 47 | ## Browser / Pure HTML build 48 | 49 | Use ` 51 | 52 | 53 |
54 | 55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /src/components/flows/bc-connect.ts: -------------------------------------------------------------------------------- 1 | import {customElement, property} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {css, html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import '../internal/bci-connecting'; 7 | import '../bc-modal-header'; 8 | import '../bc-router-outlet'; 9 | import {bcLogo} from '../icons/bcLogo'; 10 | import {bcCircleIcon} from '../icons/bcCircleIcon'; 11 | import {classes} from '../css/classes'; 12 | 13 | @customElement('bc-connect') 14 | export class ConnectFlow extends withTwind()(BitcoinConnectElement) { 15 | static override styles = [ 16 | ...super.styles, 17 | css` 18 | :host { 19 | display: flex; 20 | justify-content: center; 21 | width: 100%; 22 | } 23 | `, 24 | ]; 25 | 26 | @property({ 27 | type: Boolean, 28 | }) 29 | closable?: boolean; 30 | 31 | override render() { 32 | return html`
33 | 34 |
35 | ${bcCircleIcon} 36 |
37 |
${bcLogo}
38 |
39 |
40 | ${this._connecting 41 | ? html`` 42 | : html` `} 43 |
44 | ${this._error 45 | ? html`

46 | ${this._error} 47 |

` 48 | : null} 49 |
`; 50 | } 51 | } 52 | 53 | declare global { 54 | interface HTMLElementTagNameMap { 55 | 'bc-connect': ConnectFlow; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /demos/react/src/pages/PaymentButtonDemo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {PayButton} from '@getalby/bitcoin-connect-react'; 3 | import {Invoice, LightningAddress} from '@getalby/lightning-tools'; 4 | import toast, {Toaster} from 'react-hot-toast'; 5 | 6 | export default function PaymentButtonDemo() { 7 | const [invoice, setInvoice] = React.useState(undefined); 8 | const [preimage, setPreimage] = React.useState(undefined); 9 | 10 | const fetchInvoice = React.useCallback(() => { 11 | (async () => { 12 | try { 13 | toast('Fetching invoice...'); 14 | const ln = new LightningAddress('hello@getalby.com'); 15 | await ln.fetch(); 16 | const invoice = await ln.requestInvoice({ 17 | satoshi: 1, 18 | comment: 'Paid with Bitcoin Connect (React Demo)', 19 | }); 20 | setInvoice(invoice); 21 | 22 | const checkPaymentInterval = setInterval(async () => { 23 | try { 24 | await invoice.verifyPayment(); 25 | if (invoice.preimage) { 26 | setPreimage(invoice.preimage); 27 | clearInterval(checkPaymentInterval); 28 | } 29 | } catch (error) { 30 | console.error(error); 31 | } 32 | }, 1000); 33 | } catch (error) { 34 | console.error(error); 35 | } 36 | })(); 37 | }, []); 38 | 39 | const paymentResponse = React.useMemo( 40 | () => (preimage ? {preimage} : undefined), 41 | [preimage] 42 | ); 43 | 44 | return ( 45 |
46 |

Payment Button Demo

47 | 48 | { 51 | toast('Paid! ' + response.preimage); 52 | setPreimage(response.preimage); 53 | }} 54 | onClick={fetchInvoice} 55 | payment={paymentResponse} 56 | /> 57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /demos/react/src/pages/PaymentDemo.tsx: -------------------------------------------------------------------------------- 1 | import {Payment} from '@getalby/bitcoin-connect-react'; 2 | import {Invoice, LightningAddress} from '@getalby/lightning-tools'; 3 | import React from 'react'; 4 | import toast, {Toaster} from 'react-hot-toast'; 5 | 6 | export default function PaymentDemo() { 7 | const [invoice, setInvoice] = React.useState(undefined); 8 | const [preimage, setPreimage] = React.useState(undefined); 9 | 10 | React.useEffect(() => { 11 | (async () => { 12 | try { 13 | toast('Fetching invoice...'); 14 | const ln = new LightningAddress('hello@getalby.com'); 15 | await ln.fetch(); 16 | const invoice = await ln.requestInvoice({ 17 | satoshi: 1, 18 | comment: 'Paid with Bitcoin Connect (React Demo)', 19 | }); 20 | setInvoice(invoice); 21 | 22 | const checkPaymentInterval = setInterval(async () => { 23 | try { 24 | await invoice.verifyPayment(); 25 | if (invoice.preimage) { 26 | setPreimage(invoice.preimage); 27 | clearInterval(checkPaymentInterval); 28 | } 29 | } catch (error) { 30 | console.error(error); 31 | } 32 | }, 1000); 33 | return () => { 34 | clearInterval(checkPaymentInterval); 35 | }; 36 | } catch (error) { 37 | console.error(error); 38 | } 39 | })(); 40 | }, []); 41 | 42 | const paymentResponse = React.useMemo( 43 | () => (preimage ? {preimage} : undefined), 44 | [preimage] 45 | ); 46 | 47 | return ( 48 |
49 |

Payment Demo

50 | 51 | {invoice && ( 52 | { 55 | toast('Paid! ' + response.preimage); 56 | setPreimage(response.preimage); 57 | }} 58 | payment={paymentResponse} 59 | /> 60 | )} 61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/components/icons/helpIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | export const helpIcon = svg` 6 | 7 | 8 | 9 | 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/icons/qrIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const qrIcon = svg` 7 | 8 | 9 | 10 | 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/bc-modal-header.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {customElement, property} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement'; 4 | import {withTwind} from './twind/withTwind'; 5 | import store from '../state/store'; 6 | import {crossIcon} from './icons/crossIcon'; 7 | import {helpIcon} from './icons/helpIcon'; 8 | import {classes} from './css/classes'; 9 | 10 | // TODO: rename bc-header 11 | @customElement('bc-modal-header') 12 | export class ModalHeader extends withTwind()(BitcoinConnectElement) { 13 | @property({ 14 | type: Boolean, 15 | }) 16 | closable?: boolean; 17 | 18 | @property({ 19 | type: Boolean, 20 | attribute: 'show-help', 21 | }) 22 | showHelp?: boolean; 23 | 24 | override render() { 25 | return html`
28 |
31 | ${this.showHelp 32 | ? html`` 39 | : null} 40 | ${this.closable 41 | ? html`` 48 | : null} 49 |
50 |
51 | 52 |
53 |
`; 54 | } 55 | 56 | private _handleClose() { 57 | this.dispatchEvent(new Event('onclose', {bubbles: true, composed: true})); 58 | } 59 | } 60 | 61 | declare global { 62 | interface HTMLElementTagNameMap { 63 | 'bc-modal-header': ModalHeader; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /demos/nextjs-legacy/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Connect NextJS example (Pages directory) 2 | 3 | NextJS example of including Bitcoin Connect clientside (Rendering components and interacting with the Bitcoin Connect API). 4 | 5 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 6 | 7 | ## Getting Started 8 | 9 | Installation 10 | 11 | ```bash 12 | yarn install 13 | ``` 14 | 15 | First, run the development server: 16 | 17 | ```bash 18 | yarn dev 19 | ``` 20 | 21 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 22 | 23 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 24 | 25 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 26 | 27 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 28 | 29 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 30 | 31 | ## Learn More 32 | 33 | To learn more about Next.js, take a look at the following resources: 34 | 35 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 36 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 37 | 38 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 39 | 40 | ## Deploy on Vercel 41 | 42 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 43 | 44 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 45 | -------------------------------------------------------------------------------- /src/components/connectors/ConnectorElement.ts: -------------------------------------------------------------------------------- 1 | import {TemplateResult, html} from 'lit'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {ConnectorType} from '../../types/ConnectorType'; 5 | import {ConnectorConfig} from '../../types/ConnectorConfig'; 6 | import store from '../../state/store'; 7 | import {classes} from '../css/classes'; 8 | 9 | export abstract class ConnectorElement extends withTwind()( 10 | BitcoinConnectElement 11 | ) { 12 | private _background: string; 13 | private _icon: TemplateResult<2>; 14 | protected _title: string; 15 | protected _connectorType: ConnectorType; 16 | protected abstract _onClick(): void; 17 | constructor( 18 | connectorType: ConnectorType, 19 | title: string, 20 | background: string, 21 | icon: TemplateResult<2> 22 | ) { 23 | super(); 24 | this._connectorType = connectorType; 25 | this._title = title; 26 | this._background = background; 27 | this._icon = icon; 28 | } 29 | 30 | override render() { 31 | return html``; 50 | } 51 | 52 | protected _connect( 53 | config: Omit 54 | ) { 55 | store.getState().connect({ 56 | ...config, 57 | connectorName: this._title, 58 | connectorType: this._connectorType, 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/icons/connectors/nwcThickIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const nwcThickIcon = svg``; 4 | -------------------------------------------------------------------------------- /src/components/BitcoinConnectElement.ts: -------------------------------------------------------------------------------- 1 | import {state} from 'lit/decorators.js'; 2 | import store from '../state/store'; 3 | import {InternalElement} from './internal/InternalElement'; 4 | import {ConnectorFilter} from '../types/ConnectorFilter'; 5 | import {Route} from './routes'; 6 | 7 | export class BitcoinConnectElement extends InternalElement { 8 | // TODO: move state to individual components 9 | // individual components should not have access to all of this! 10 | @state() 11 | protected _modalOpen = false; 12 | @state() 13 | protected _connected = false; 14 | @state() 15 | protected _connecting = false; 16 | 17 | @state() 18 | protected _connectorName: string | undefined = undefined; 19 | 20 | @state() 21 | protected _appName: string | undefined = undefined; 22 | 23 | @state() 24 | protected _appIcon: string | undefined = undefined; 25 | 26 | @state() 27 | protected _filters: ConnectorFilter[] | undefined = undefined; 28 | 29 | @state() 30 | protected _error: string | undefined = undefined; 31 | 32 | @state() 33 | protected _route: Route; 34 | 35 | constructor() { 36 | super(); 37 | this._connected = store.getState().connected; 38 | this._connecting = store.getState().connecting; 39 | this._connectorName = store.getState().connectorName; 40 | this._appName = store.getState().bitcoinConnectConfig.appName; 41 | this._appIcon = store.getState().bitcoinConnectConfig.appIcon; 42 | this._filters = store.getState().bitcoinConnectConfig.filters; 43 | this._error = store.getState().error; 44 | this._route = store.getState().route; 45 | this._modalOpen = store.getState().modalOpen; 46 | 47 | // TODO: handle unsubscribe 48 | store.subscribe((currentState) => { 49 | this._connected = currentState.connected; 50 | this._connecting = currentState.connecting; 51 | this._connectorName = currentState.connectorName; 52 | this._appName = currentState.bitcoinConnectConfig.appName; 53 | this._appIcon = currentState.bitcoinConnectConfig.appIcon; 54 | this._filters = currentState.bitcoinConnectConfig.filters; 55 | this._error = currentState.error; 56 | this._route = currentState.route; 57 | this._modalOpen = currentState.modalOpen; 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/pages/bc-nwc.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {genericConnectorTitle} from '../connectors/bc-generic-nwc-connector'; 9 | 10 | @customElement('bc-nwc') 11 | export class NWCPage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _nwcUrl = ''; 14 | 15 | override render() { 16 | return html`
17 | 18 |
19 |
20 |
21 | Enter your 22 | Connection Secret 27 | 28 | below 29 |
30 | 31 | 40 | 41 | Connect 42 | 43 |
44 |
45 |
`; 46 | } 47 | 48 | private nwcUrlChanged(event: {target: HTMLInputElement}) { 49 | this._nwcUrl = event.target.value; 50 | } 51 | private async onConnect() { 52 | if (!this._nwcUrl) { 53 | store.getState().setError('Please enter a URL'); 54 | return; 55 | } 56 | 57 | await store.getState().connect({ 58 | nwcUrl: this._nwcUrl, 59 | connectorName: genericConnectorTitle, 60 | connectorType: 'nwc.generic', 61 | }); 62 | } 63 | } 64 | 65 | declare global { 66 | interface HTMLElementTagNameMap { 67 | 'bc-nwc': NWCPage; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@getalby/bitcoin-connect", 3 | "version": "3.11.5", 4 | "description": "Web components to connect to a lightning wallet and power a website with WebLN", 5 | "type": "module", 6 | "source": "src/index.ts", 7 | "main": "./dist/index.cjs", 8 | "module": "./dist/index.module.js", 9 | "unpkg": "./dist/index.umd.js", 10 | "types": "./dist/index.d.ts", 11 | "exports": { 12 | "require": "./dist/index.cjs", 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/index.modern.js" 15 | }, 16 | "files": [ 17 | "dist/**/*" 18 | ], 19 | "scripts": { 20 | "prebuild": "yarn run clean", 21 | "prepack": "yarn run build", 22 | "build": "microbundle --no-sourcemap", 23 | "clean": "rm -rf dist", 24 | "dev": "yarn --cwd dev/vite dev --force", 25 | "dev:build": "microbundle watch", 26 | "lint": "npm run lint:lit-analyzer && npm run lint:eslint", 27 | "lint:eslint": "eslint 'src/**/*.ts'", 28 | "lint:lit-analyzer": "lit-analyzer", 29 | "format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --ignore-path ./.eslintignore --write", 30 | "test": "wtr" 31 | }, 32 | "keywords": [ 33 | "lightning", 34 | "bitcoin", 35 | "alby", 36 | "wallet", 37 | "connect", 38 | "web-components", 39 | "lit-element", 40 | "typescript", 41 | "lit" 42 | ], 43 | "author": "Alby", 44 | "license": "MIT", 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/getAlby/bitcoin-connect.git" 48 | }, 49 | "dependencies": { 50 | "@getalby/lightning-tools": "^6.0.0", 51 | "@getalby/sdk": "^6.0.2", 52 | "@lightninglabs/lnc-web": "^0.3.4-alpha", 53 | "qrcode-generator": "1.4.4", 54 | "zustand": "^4.5.7" 55 | }, 56 | "devDependencies": { 57 | "@open-wc/testing": "^3.2.2", 58 | "@twind/core": "^1.1.3", 59 | "@twind/preset-tailwind": "^1.1.4", 60 | "@twind/with-web-components": "^1.1.3", 61 | "@typescript-eslint/eslint-plugin": "^5.25.0", 62 | "@typescript-eslint/parser": "^5.25.0", 63 | "@web/dev-server-esbuild": "^0.4.4", 64 | "@web/test-runner": "^0.15.0", 65 | "@web/test-runner-playwright": "^0.9.0", 66 | "@webbtc/webln-types": "^2.1.0", 67 | "@webcomponents/webcomponentsjs": "^2.6.0", 68 | "concurrently": "^8.2.2", 69 | "esbuild": "^0.25.12", 70 | "eslint": "^8.57.1", 71 | "lit": "^3.3.1", 72 | "lit-analyzer": "^1.2.1", 73 | "microbundle": "^0.15.1", 74 | "prettier": "^2.6.2", 75 | "typescript": "~4.7.4", 76 | "vite": "^7.2.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/pages/bc-lnfi.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {genericConnectorTitle} from '../connectors/bc-generic-nwc-connector'; 9 | 10 | @customElement('bc-lnfi') 11 | export class LnfiNWCPage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _nwcUrl = ''; 14 | 15 | override render() { 16 | return html`
17 | 18 |
19 |
20 |
21 | 1. Add a new 22 | Wallet Connection 27 | 28 | from 29 | LN Node => Generate NWC 32 | and copy the Connection Secret. 33 |
34 |
35 | 2. Paste the Connection Secret below: 36 |
37 | 38 | 47 | 48 | Connect 49 | 50 |
51 |
52 |
`; 53 | } 54 | 55 | private nwcUrlChanged(event: {target: HTMLInputElement}) { 56 | this._nwcUrl = event.target.value; 57 | } 58 | private async onConnect() { 59 | if (!this._nwcUrl) { 60 | store.getState().setError('Please enter a URL'); 61 | return; 62 | } 63 | 64 | await store.getState().connect({ 65 | nwcUrl: this._nwcUrl, 66 | connectorName: genericConnectorTitle, 67 | connectorType: 'nwc.generic', 68 | }); 69 | } 70 | } 71 | 72 | declare global { 73 | interface HTMLElementTagNameMap { 74 | 'bc-lnfi': LnfiNWCPage; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/internal/bci-button.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {withTwind} from '../twind/withTwind'; 3 | import {customElement, property} from 'lit/decorators.js'; 4 | import {InternalElement} from './InternalElement'; 5 | import {classes} from '../css/classes'; 6 | import { 7 | innerBorder, 8 | innerBorderBranded, 9 | innerBorderTertiary, 10 | } from '../templates/innerBorder'; 11 | 12 | @customElement('bci-button') 13 | export class Button extends withTwind()(InternalElement) { 14 | @property() 15 | variant: 'primary' | 'secondary' | 'neutral' = 'secondary'; 16 | 17 | @property({ 18 | type: Boolean, 19 | }) 20 | ghost = false; 21 | 22 | @property({ 23 | type: Boolean, 24 | }) 25 | block = false; 26 | 27 | override render() { 28 | const isDarkMode = 29 | window.matchMedia && 30 | window.matchMedia('(prefers-color-scheme: dark)').matches; 31 | 32 | const hasBrandButtonTextColor = 33 | window 34 | .getComputedStyle(this as HTMLElement) 35 | .getPropertyValue( 36 | isDarkMode 37 | ? '--bc-color-brand-button-text-dark' 38 | : '--bc-color-brand-button-text' 39 | ) || 40 | window 41 | .getComputedStyle(this as HTMLElement) 42 | .getPropertyValue('--bc-color-brand-button-text'); 43 | 44 | return html``; 78 | } 79 | } 80 | 81 | declare global { 82 | interface HTMLElementTagNameMap { 83 | 'bci-button': Button; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /demos/html/bc-pay-button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bc-pay-button Demo 7 | 8 | 59 | 60 | 61 | 62 | 63 |
64 | <bc-pay-button /> Demo 65 | 66 | 67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /src/components/pages/bc-new-wallet.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import {classes} from '../css/classes'; 6 | import '../internal/bci-button'; 7 | import {albyhubIcon} from '../icons/connectors/albyHubIcon'; 8 | import {custodialWalletIcons} from '../icons/new-wallet/custodialWalletIcons'; 9 | 10 | @customElement('bc-new-wallet') 11 | export class NewWalletPage extends withTwind()(BitcoinConnectElement) { 12 | override render() { 13 | return html`
14 | 18 | 19 |
20 |
21 |
22 |
25 | ${albyhubIcon} 26 |
27 |
28 |

29 | To get the best self-custodial bitcoin lightning wallet that can 30 | connect to apps, try 31 | Alby Hub. 39 |

40 |
41 |
42 |
${custodialWalletIcons}
43 |

44 | For a quick setup of a custodial wallet, you can choose between 45 | Primal 53 | or 54 | Coinos. 62 |

63 |
64 |
65 |
`; 66 | } 67 | } 68 | 69 | declare global { 70 | interface HTMLElementTagNameMap { 71 | 'bc-new-wallet': NewWalletPage; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/bc-button.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {customElement, property, state} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement.js'; 4 | import {bcIcon} from './icons/bcIcon.js'; 5 | import {withTwind} from './twind/withTwind.js'; 6 | import {innerBorder} from './templates/innerBorder.js'; 7 | import {classes} from './css/classes.js'; 8 | import {launchModal} from '../api.js'; 9 | import './bc-balance'; 10 | import store from '../state/store.js'; 11 | import {waitingIcon} from './icons/waitingIcon.js'; 12 | 13 | /** 14 | * A button that when clicked launches the modal. 15 | */ 16 | @customElement('bc-button') 17 | export class Button extends withTwind()(BitcoinConnectElement) { 18 | @property() 19 | override title = 'Connect Wallet'; 20 | 21 | @state() 22 | protected _showBalance: boolean | undefined = undefined; 23 | 24 | constructor() { 25 | super(); 26 | 27 | this._showBalance = 28 | store.getState().bitcoinConnectConfig.showBalance && 29 | store.getState().supports('getBalance'); 30 | 31 | // TODO: handle unsubscribe 32 | store.subscribe((store) => { 33 | this._showBalance = 34 | store.bitcoinConnectConfig.showBalance && store.supports('getBalance'); 35 | }); 36 | } 37 | 38 | override render() { 39 | const isLoading = this._connecting || (!this._connected && this._modalOpen); 40 | 41 | return html`
42 |
47 |
53 | ${this._connected ? innerBorder() : ''} 54 | 55 | ${isLoading 56 | ? html` ${waitingIcon(`w-11 h-11 -mr-2 mr-1 -ml-2.5`)} ` 57 | : this._connected 58 | ? null 59 | : html`${bcIcon}`} 60 | 61 | ${isLoading 62 | ? html`Connecting...` 63 | : this._connected 64 | ? html`Connected` 65 | : html`${this.title}`} 66 | 67 | 68 | ${this._connected && this._showBalance 69 | ? html` ` 70 | : null} 71 |
72 |
`; 73 | } 74 | 75 | private _onClick() { 76 | launchModal(); 77 | } 78 | } 79 | 80 | declare global { 81 | interface HTMLElementTagNameMap { 82 | 'bc-button': Button; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/components/icons/connectors/primalIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const primalIcon = svg` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | `; 28 | -------------------------------------------------------------------------------- /src/components/pages/bc-help.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import {linkIcon} from '../icons/linkIcon'; 6 | import {hr} from '../templates/hr'; 7 | import {albyLogo} from '../icons/albyLogo'; 8 | import '../internal/bci-button'; 9 | import {classes} from '../css/classes'; 10 | 11 | @customElement('bc-help') 12 | export class HelpPage extends withTwind()(BitcoinConnectElement) { 13 | override render() { 14 | return html`
15 | 16 |
17 |
18 |
19 | How does it work? 20 |
21 |

22 | Bitcoin Connect is a way to connect to your lightning wallet from 23 | any browser. 24 |

25 |
26 |

27 | 💾 Your connection is saved in local storage, so next time you 28 | visit the site will connect automatically. 29 |

30 |

31 | 💸 Make sure to set budgets and permissions for sites you do not 32 | trust. 33 |

34 |
35 |
36 | 37 | 61 | ${hr()} 62 |
65 | Made with love by 68 | ${albyLogo} 69 |
70 |
71 |
`; 72 | } 73 | } 74 | 75 | declare global { 76 | interface HTMLElementTagNameMap { 77 | 'bc-help': HelpPage; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/pages/bc-lnbits-nwc.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {lnbitsNWCConnectorTitle} from '../connectors/bc-lnbits-nwc-connector'; 9 | 10 | @customElement('bc-lnbits-nwc') 11 | export class PrimalPage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _nwcUrl = ''; 14 | 15 | override render() { 16 | return html`
17 | 21 |
22 |
23 |
24 | 1. In LNBits, go to Plugins and enable the NWC plugin. 25 |
26 |
27 | 2. Create a new connection and update the relay from "nostrclient" 28 | to wss://relay.getalby.com/v1, 29 | then copy the connection secret. 30 |
31 |
32 | 3. Paste the Connection Secret below. 33 |
34 | 35 |
36 |
37 | Enter your 38 | Connection Secret 43 | 44 | below 45 |
46 | 47 | 56 | 57 | Connect 58 | 59 |
60 |
61 |
62 |
`; 63 | } 64 | 65 | private nwcUrlChanged(event: {target: HTMLInputElement}) { 66 | this._nwcUrl = event.target.value; 67 | } 68 | private async onConnect() { 69 | if (!this._nwcUrl) { 70 | store.getState().setError('Please enter a URL'); 71 | return; 72 | } 73 | 74 | await store.getState().connect({ 75 | nwcUrl: this._nwcUrl, 76 | connectorName: lnbitsNWCConnectorTitle, 77 | connectorType: 'nwc.lnbits', 78 | }); 79 | } 80 | } 81 | 82 | declare global { 83 | interface HTMLElementTagNameMap { 84 | 'bc-lnbits-nwc': PrimalPage; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/components/pages/bc-alby-hub.ts: -------------------------------------------------------------------------------- 1 | import {customElement} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {albyHubConnectorTitle} from '../connectors'; 9 | import {albyCloudIcon} from '../icons/connectors/albyCloudIcon'; 10 | import {nwcThickIcon} from '../icons/connectors/nwcThickIcon'; 11 | import {albyGoIcon} from '../icons/connectors/albyGoIcon'; 12 | import {NWCClient} from '@getalby/sdk'; 13 | 14 | @customElement('bc-alby-hub') 15 | export class AlbyHubPage extends withTwind()(BitcoinConnectElement) { 16 | override render() { 17 | return html`
18 | 22 |
23 |
26 |
27 | Choose how to connect 28 |
29 | 30 | 31 | ${albyCloudIcon} 32 | Alby Cloud 33 | 34 | 35 | 36 | ${albyGoIcon} 37 | Alby Go 38 | 39 | 40 | 41 | ${nwcThickIcon} 42 | Connection Secret 45 | 46 |
47 |
48 |
`; 49 | } 50 | 51 | private async onClickAlbyCloud() { 52 | try { 53 | const providerConfig = 54 | store.getState().bitcoinConnectConfig.providerConfig; 55 | const nwcClient = await NWCClient.fromAuthorizationUrl( 56 | 'https://my.albyhub.com/apps/new', 57 | { 58 | ...(providerConfig?.nwc?.authorizationUrlOptions || {}), 59 | name: this._appName, 60 | } 61 | ); 62 | 63 | nwcClient.close(); 64 | // TODO: it makes no sense to connect again 65 | await store.getState().connect({ 66 | nwcUrl: nwcClient.nostrWalletConnectUrl, 67 | connectorName: 'Alby Hub', 68 | connectorType: 'nwc.albyhub', 69 | }); 70 | } catch (error) { 71 | console.error(error); 72 | alert('' + error); 73 | } 74 | } 75 | private async onClickConnectionSecret() { 76 | store.getState().pushRoute('/nwc'); 77 | } 78 | private async onClickAlbyGo() { 79 | store.getState().pushRoute('/alby-go'); 80 | } 81 | } 82 | 83 | declare global { 84 | interface HTMLElementTagNameMap { 85 | 'bc-alby-hub': AlbyHubPage; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/components/icons/connectors/nwcIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const nwcIcon = svg` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | `; 17 | -------------------------------------------------------------------------------- /src/components/flows/bc-payment.ts: -------------------------------------------------------------------------------- 1 | import {customElement, property, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {css, html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import '../internal/bci-connecting'; 7 | import '../bc-modal-header'; 8 | import '../bc-router-outlet'; 9 | import {classes} from '../css/classes'; 10 | import store from '../../state/store'; 11 | import {PaymentMethods} from '../../types/PaymentMethods'; 12 | 13 | @customElement('bc-payment') 14 | export class SendPaymentFlow extends withTwind()(BitcoinConnectElement) { 15 | static override styles = [ 16 | ...super.styles, 17 | css` 18 | :host { 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | width: 100%; 23 | } 24 | `, 25 | ]; 26 | 27 | @property({ 28 | type: Boolean, 29 | }) 30 | closable?: boolean; 31 | 32 | @property({ 33 | type: String, 34 | }) 35 | invoice?: string; 36 | 37 | @property({ 38 | type: String, 39 | attribute: 'payment-methods', 40 | }) 41 | paymentMethods: PaymentMethods = 'all'; 42 | 43 | // TODO: change to preimage and then bc:onpaid event only needs to be fired 44 | // only from bc-send-payment 45 | @property({ 46 | type: Boolean, 47 | }) 48 | paid?: boolean; 49 | 50 | @state() 51 | _showConnect = false; 52 | 53 | constructor() { 54 | super(); 55 | 56 | // TODO: handle unsubscribe 57 | store.subscribe((currentStore, prevStore) => { 58 | if ( 59 | currentStore.connected !== prevStore.connected && 60 | currentStore.connected 61 | ) { 62 | this._showConnect = false; 63 | } 64 | }); 65 | } 66 | 67 | override render() { 68 | return this._showConnect && !this.paid 69 | ? html` ` 70 | : html`
71 | 72 |

75 | Payment Request 76 |

77 |
78 |
79 | 85 |
86 | ${this._error 87 | ? html`

88 | ${this._error} 89 |

` 90 | : null} 91 |
`; 92 | } 93 | 94 | private _onClickConnectWallet() { 95 | this._showConnect = true; 96 | } 97 | } 98 | 99 | declare global { 100 | interface HTMLElementTagNameMap { 101 | 'bc-payment': SendPaymentFlow; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/components/pages/bc-rizful.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {rizfulConnectorTitle} from '../connectors/bc-rizful-connector'; 9 | 10 | @customElement('bc-rizful') 11 | export class RizfulPage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _nwcUrl = ''; 14 | 15 | override render() { 16 | return html`
17 | 21 |
22 |
23 |
24 | 1. Open 25 | Rizful Wallet 27 | 28 |
29 |
30 | 2. Click on 31 | Hamburger Menu -> NWC -> Get NWC codes -> New Connection. Then click Get Connection Code and 34 | click 35 | Copy Connection Code 36 |
37 | 38 |
39 | 3. Paste the Connection Secret below: 40 |
41 | 42 |
43 |
44 | Enter your 45 | Connection Secret 50 | 51 | below 52 |
53 | 54 | 63 | 64 | Connect 65 | 66 |
67 |
68 |
69 |
`; 70 | } 71 | 72 | private nwcUrlChanged(event: {target: HTMLInputElement}) { 73 | this._nwcUrl = event.target.value; 74 | } 75 | private async onConnect() { 76 | if (!this._nwcUrl) { 77 | store.getState().setError('Please enter a URL'); 78 | return; 79 | } 80 | 81 | await store.getState().connect({ 82 | nwcUrl: this._nwcUrl, 83 | connectorName: rizfulConnectorTitle, 84 | connectorType: 'nwc.rizful', 85 | }); 86 | } 87 | } 88 | 89 | declare global { 90 | interface HTMLElementTagNameMap { 91 | 'bc-rizful': RizfulPage; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/components/icons/bcIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | // WARNING: if replacing this icon make sure to: 4 | // - change all colors to "currentColor" 5 | 6 | export const bcIcon = svg` 7 | 8 | 9 | `; 10 | -------------------------------------------------------------------------------- /src/components/pages/bc-cashu-me.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {cashuMeConnectorTitle} from '../connectors/bc-cashu-me-connector'; 9 | 10 | @customElement('bc-cashu-me') 11 | export class CashuMePage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _nwcUrl = ''; 14 | 15 | override render() { 16 | return html`
17 | 21 |
22 |
23 |
24 | 1. Open 25 | Cashu.me 27 | 28 | , click on the hamburger menu on 29 | the top left, then click 30 | Settings. Scroll down to 31 | Nostr Wallet Connect 32 | and enable NWC, and then press the copy button. 33 |
34 |
35 | 2. Keep the Cashu.me browser tab open while using Bitcoin Connect. 36 |
37 |
38 | 3. Paste the Connection Secret below: 39 |
40 | 41 |
42 |
43 | Enter your 44 | Connection Secret 49 | 50 | below 51 |
52 | 53 | 62 | 63 | Connect 64 | 65 |
66 |
67 |
68 |
`; 69 | } 70 | 71 | private nwcUrlChanged(event: {target: HTMLInputElement}) { 72 | this._nwcUrl = event.target.value; 73 | } 74 | private async onConnect() { 75 | if (!this._nwcUrl) { 76 | store.getState().setError('Please enter a URL'); 77 | return; 78 | } 79 | 80 | await store.getState().connect({ 81 | nwcUrl: this._nwcUrl, 82 | connectorName: cashuMeConnectorTitle, 83 | connectorType: 'nwc.cashume', 84 | }); 85 | } 86 | } 87 | 88 | declare global { 89 | interface HTMLElementTagNameMap { 90 | 'bc-cashu-me': CashuMePage; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/components/icons/bcCircleIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const bcCircleIcon = svg` 4 | 5 | 6 | `; 7 | -------------------------------------------------------------------------------- /src/components/pages/bc-primal.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {primalConnectorTitle} from '../connectors/bc-primal-connector'; 9 | 10 | @customElement('bc-primal') 11 | export class PrimalPage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _nwcUrl = ''; 14 | 15 | override render() { 16 | return html`
17 | 21 |
22 |
23 |
24 | 1. In Primal Mobile, click on 25 | your profile image on the top 26 | left, then go to 27 | Settings -> Wallet. Ensure 28 | Use Primal wallet 29 | is toggled on. Scroll to 30 | Connected Apps 31 | at the bottom of the page, and click 32 | create a new wallet connection. 33 | Enter a name, then press next, and press 34 | create wallet connection. Then 35 | press the copy button 36 |
37 |
38 | 2. Paste the Connection Secret below. 39 |
40 | 41 |
42 |
43 | Enter your 44 | Connection Secret 49 | 50 | below 51 |
52 | 53 | 62 | 63 | Connect 64 | 65 |
66 |
67 |
68 |
`; 69 | } 70 | 71 | private nwcUrlChanged(event: {target: HTMLInputElement}) { 72 | this._nwcUrl = event.target.value; 73 | } 74 | private async onConnect() { 75 | if (!this._nwcUrl) { 76 | store.getState().setError('Please enter a URL'); 77 | return; 78 | } 79 | 80 | await store.getState().connect({ 81 | nwcUrl: this._nwcUrl, 82 | connectorName: primalConnectorTitle, 83 | connectorType: 'nwc.primal', 84 | }); 85 | } 86 | } 87 | 88 | declare global { 89 | interface HTMLElementTagNameMap { 90 | 'bc-primal': PrimalPage; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/components/bc-pay-button.ts: -------------------------------------------------------------------------------- 1 | import {PropertyValues, html} from 'lit'; 2 | import {customElement, property, state} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement.js'; 4 | import {bcIcon} from './icons/bcIcon.js'; 5 | import {withTwind} from './twind/withTwind.js'; 6 | import {launchPaymentModal} from '../api.js'; 7 | import './bc-balance.js'; 8 | import {SendPaymentResponse} from '@webbtc/webln-types'; 9 | import {checkIcon} from './icons/checkIcon.js'; 10 | import {waitingIcon} from './icons/waitingIcon.js'; 11 | import {PaymentMethods} from '../types/PaymentMethods.js'; 12 | 13 | /** 14 | * A button that when clicked launches a modal to pay an invoice. 15 | */ 16 | @customElement('bc-pay-button') 17 | export class PayButton extends withTwind()(BitcoinConnectElement) { 18 | @property() 19 | override title = 'Pay Now'; 20 | 21 | @property() 22 | invoice?: string; 23 | 24 | @property({ 25 | type: String, 26 | attribute: 'payment-methods', 27 | }) 28 | paymentMethods: PaymentMethods = 'all'; 29 | 30 | @property({}) 31 | preimage?: string; 32 | 33 | /** 34 | * This will be set to true if the button was clicked 35 | * when no invoice is set on the button. The loading 36 | * state will show until an invoice is set. 37 | */ 38 | @state() 39 | _waitingForInvoice = false; 40 | 41 | @state() 42 | _paid = false; 43 | 44 | private _setPaid?: (response: SendPaymentResponse) => void; 45 | 46 | protected override updated(changedProperties: PropertyValues): void { 47 | super.updated(changedProperties); 48 | 49 | if ( 50 | changedProperties.has('invoice') && 51 | this.invoice && 52 | this._waitingForInvoice 53 | ) { 54 | this._launchModal(); 55 | } 56 | 57 | if (changedProperties.has('preimage') && this.preimage) { 58 | this._setPaid?.({ 59 | preimage: this.preimage, 60 | }); 61 | } 62 | } 63 | 64 | override render() { 65 | const isLoading = this._waitingForInvoice || this._modalOpen; 66 | 67 | return html`
68 | 69 | ${isLoading 70 | ? html`${waitingIcon(`w-11 h-11 -mr-2 -ml-2.5 `)}` 71 | : this._paid 72 | ? html`${checkIcon}` 73 | : html`${bcIcon}`} 74 | 75 | ${isLoading 76 | ? html`Loading...` 77 | : html`${this._paid ? 'Paid' : this.title}`} 78 | 79 | 80 |
`; 81 | } 82 | 83 | private _onClick() { 84 | if (this._paid) { 85 | return; 86 | } 87 | this._waitingForInvoice = true; 88 | if (this.invoice) { 89 | this._launchModal(); 90 | } 91 | } 92 | 93 | private _launchModal() { 94 | this._waitingForInvoice = false; 95 | if (!this.invoice) { 96 | throw new Error('No invoice available'); 97 | } 98 | const {setPaid} = launchPaymentModal({ 99 | onPaid: () => { 100 | this._paid = true; 101 | }, 102 | invoice: this.invoice, 103 | paymentMethods: this.paymentMethods, 104 | }); 105 | this._setPaid = setPaid; 106 | } 107 | } 108 | 109 | declare global { 110 | interface HTMLElementTagNameMap { 111 | 'bc-pay-button': PayButton; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/components/icons/connectors/albyHubIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const albyhubIcon = svg` 4 | 5 | 6 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/components/internal/InternalElement.ts: -------------------------------------------------------------------------------- 1 | import {LitElement, PropertyValues, css} from 'lit'; 2 | 3 | export class InternalElement extends LitElement { 4 | static override styles = [ 5 | css` 6 | :host { 7 | // global css reset in shadow DOM 8 | all: initial; 9 | font-variant-numeric: slashed-zero; 10 | } 11 | // TODO: move to individual components - only needed by a couple of icons 12 | .hover-animation:hover .hover-right-up { 13 | transform: translateX(2px) translateY(-2px); 14 | transition: all 0.3s; 15 | } 16 | .hover-animation:hover .hover-right { 17 | transform: translateX(3px); 18 | transition: all 0.3s; 19 | } 20 | `, 21 | ]; 22 | 23 | protected override updated(changedProperties: PropertyValues): void { 24 | super.updated(changedProperties); 25 | // hack to enable manual dark mode: 26 | // if a dark class is set on the document, pass it to the direct children of this shadow root 27 | // also requires `darkMode: "class"` to be set in twind config 28 | if ( 29 | globalThis.document && 30 | globalThis.document.documentElement.classList.contains('dark') && 31 | this.shadowRoot?.children?.length 32 | ) { 33 | for (const child of this.shadowRoot.children) { 34 | if (!child.classList.contains('dark')) { 35 | child.classList.add('dark'); 36 | } 37 | } 38 | } 39 | } 40 | 41 | // TODO: move, only needed by the button? 42 | protected _getBrandColorLuminance() { 43 | if (!globalThis.window) { 44 | return 0; 45 | } 46 | const isDarkMode = 47 | window.matchMedia && 48 | window.matchMedia('(prefers-color-scheme: dark)').matches; 49 | let brandColor = 50 | window 51 | .getComputedStyle(this as HTMLElement) 52 | .getPropertyValue( 53 | isDarkMode ? '--bc-color-brand-dark' : '--bc-color-brand' 54 | ) || 55 | window 56 | .getComputedStyle(this as HTMLElement) 57 | .getPropertyValue('--bc-color-brand') || 58 | '#196CE7'; 59 | function calculateLuminance(color: string) { 60 | if (color.startsWith('#')) { 61 | color = color.slice(1); // Remove the '#' character 62 | const r = parseInt(color.slice(0, 2), 16); 63 | const g = parseInt(color.slice(2, 4), 16); 64 | const b = parseInt(color.slice(4, 6), 16); 65 | return (0.299 * r + 0.587 * g + 0.114 * b) / 255; 66 | } else if (color.startsWith('rgb') || color.startsWith('rgba')) { 67 | const rgba = color.match(/\d+(\.\d+)?/g)!; 68 | const r = parseFloat(rgba[0]); 69 | const g = parseFloat(rgba[1]); 70 | const b = parseFloat(rgba[2]); 71 | return (0.299 * r + 0.587 * g + 0.114 * b) / 255; 72 | } else { 73 | throw new Error('Unsupported luminance: ' + color); 74 | } 75 | } 76 | 77 | // append a temporary element to the body to get the computed rgb value from any valid css color eg. 'red', 'rgb(0, 0, 0)', '#000' 78 | //if colour is not a hex value with 6 digits, get the computed color 79 | if (!brandColor.match(/^#[0-9A-F]{6}$/i)) { 80 | const tempElement = document.createElement('div'); 81 | tempElement.style.color = brandColor; 82 | tempElement.style.display = 'none'; 83 | document.body.appendChild(tempElement); 84 | brandColor = window.getComputedStyle(tempElement).color; 85 | tempElement.remove(); 86 | } 87 | 88 | return calculateLuminance(brandColor); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/components/bc-start.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from './BitcoinConnectElement'; 3 | import {withTwind} from './twind/withTwind'; 4 | import {html} from 'lit'; 5 | import './internal/bci-button'; 6 | import './bc-connector-list'; 7 | import {classes} from './css/classes'; 8 | import {disconnectSection} from './templates/disconnectSection'; 9 | import './bc-balance'; 10 | import store from '../state/store'; 11 | import './bc-currency-switcher'; 12 | import {DEFAULT_BITCOIN_CONNECT_CONFIG} from '../types/BitcoinConnectConfig'; 13 | 14 | // TODO: split up this component into disconnected and connected 15 | @customElement('bc-start') 16 | export class Start extends withTwind()(BitcoinConnectElement) { 17 | @state() 18 | protected _showBalance: boolean | undefined = undefined; 19 | 20 | constructor() { 21 | super(); 22 | 23 | this._showBalance = 24 | store.getState().bitcoinConnectConfig.showBalance && 25 | store.getState().supports('getBalance'); 26 | 27 | // TODO: handle unsubscribe 28 | store.subscribe((store) => { 29 | this._showBalance = 30 | store.bitcoinConnectConfig.showBalance && store.supports('getBalance'); 31 | }); 32 | } 33 | 34 | override render() { 35 | return html`
38 | ${this._connected 39 | ? html` 40 | ${this._showBalance 41 | ? html`Balance 47 | 48 | 49 | ` 50 | : html` Wallet Connected`} 56 | ${disconnectSection(this._connectorName)} 57 | ` 58 | : html` 59 |

66 | How would you like to 67 | connect${this._appName && 68 | this._appName !== DEFAULT_BITCOIN_CONNECT_CONFIG.appName 69 | ? `\nto ${this._appName}` 70 | : ''}? 71 |

72 | 73 | 74 | 75 |
76 |

77 | Don't have a bitcoin lightning wallet? 78 | 87 |

88 |
89 | `} 90 |
`; 91 | } 92 | } 93 | 94 | declare global { 95 | interface HTMLElementTagNameMap { 96 | 'bc-start': Start; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/components/pages/bc-lnbits.ts: -------------------------------------------------------------------------------- 1 | import {customElement, state} from 'lit/decorators.js'; 2 | import {BitcoinConnectElement} from '../BitcoinConnectElement'; 3 | import {withTwind} from '../twind/withTwind'; 4 | import {html} from 'lit'; 5 | import '../internal/bci-button'; 6 | import {classes} from '../css/classes'; 7 | import store from '../../state/store'; 8 | import {lnbitsConnectorTitle} from '../connectors/bc-lnbits-connector'; 9 | 10 | @customElement('bc-lnbits') 11 | export class lnbitsPage extends withTwind()(BitcoinConnectElement) { 12 | @state() 13 | private _lnbitsAdminKey = ''; 14 | @state() 15 | private _lnbitsUrl = ''; 16 | 17 | override render() { 18 | return html`
19 | 23 |
24 |
25 |
26 | In LNbits, choose the wallet you want to connect, open it, click on 27 | API docs and copy the Admin Key. Paste it below: 28 |
29 | 30 |
31 | LNbits Admin Key 32 |
33 | 42 |
43 | LNbits URL 44 |
45 | 46 | 54 | 55 | Connect 56 | 57 |
58 |
59 |
`; 60 | } 61 | 62 | private _lnbitsAdminKeyChanged(event: {target: HTMLInputElement}) { 63 | this._lnbitsAdminKey = event.target.value; 64 | } 65 | private _lnbitsUrlChanged(event: {target: HTMLInputElement}) { 66 | this._lnbitsUrl = event.target.value; 67 | } 68 | private async onConnect() { 69 | if (!this._lnbitsAdminKey) { 70 | store.getState().setError('Please enter your admin key'); 71 | return; 72 | } 73 | if (!this._lnbitsUrl) { 74 | store.getState().setError('Please enter your LNbits instance URL'); 75 | return; 76 | } 77 | 78 | let lnbitsInstanceUrl = this._lnbitsUrl; 79 | if (lnbitsInstanceUrl.endsWith('/')) { 80 | lnbitsInstanceUrl = lnbitsInstanceUrl.substring( 81 | 0, 82 | lnbitsInstanceUrl.length - 1 83 | ); 84 | } 85 | 86 | await store.getState().connect({ 87 | lnbitsAdminKey: this._lnbitsAdminKey, 88 | lnbitsInstanceUrl, 89 | connectorName: lnbitsConnectorTitle, 90 | connectorType: 'lnbits', 91 | }); 92 | } 93 | } 94 | 95 | declare global { 96 | interface HTMLElementTagNameMap { 97 | 'bc-lnbits': lnbitsPage; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/components/icons/waitingIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const waitingIcon = (className?: string) => svg` 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | `; 57 | -------------------------------------------------------------------------------- /src/components/icons/connectors/cashuMeIcon.ts: -------------------------------------------------------------------------------- 1 | import {svg} from 'lit'; 2 | 3 | export const cashuMeIcon = svg` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /src/components/twind/withTwind.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@twind/core'; 2 | import presetTailwind from '@twind/preset-tailwind'; 3 | import install from '@twind/with-web-components'; 4 | import {LitElement} from 'lit'; 5 | 6 | const colors = { 7 | 'brand-light': 'var(--bc-color-brand, #196CE7)', 8 | 'brand-dark': 'var(--bc-color-brand-dark, var(--bc-color-brand, #3994FF))', 9 | 'brand-button-text-light': 'var(--bc-color-brand-button-text)', 10 | 'brand-button-text-dark': 11 | 'var(--bc-color-brand-button-text-dark, var(--bc-color-brand-button-text))', 12 | 'brand-mixed-light': 13 | 'color-mix(in srgb, var(--bc-color-brand, #196CE7) var(--bc-brand-mix, 100%), black)', 14 | 'brand-mixed-dark': 15 | 'color-mix(in srgb, var(--bc-color-brand-dark, var(--bc-color-brand, #3994FF)) var(--bc-brand-mix, 100%), white)', 16 | 17 | 'foreground-light': '#000', 18 | 'foreground-dark': '#fff', 19 | 'background-light': '#fff', 20 | 'background-dark': '#000', 21 | 'neutral-primary-light': '#262626', 22 | 'neutral-primary-dark': '#E4E4E4', 23 | 'neutral-secondary-light': '#525252', 24 | 'neutral-secondary-dark': '#A2A2A2', 25 | 'neutral-tertiary-light': '#A2A2A2', 26 | 'neutral-tertiary-dark': '#525252', 27 | }; 28 | 29 | const imageColors = { 30 | 'glass-light': 31 | 'linear-gradient(180deg, rgba(211, 211, 211, 0.20) 0%, rgba(255, 255, 255, 0.20) 50%);', 32 | 'glass-dark': 33 | 'linear-gradient(180deg, rgba(211, 211, 211, 0.10) 0%, rgba(0, 0, 0, 0.20) 50%)', 34 | }; 35 | 36 | export const withTwindExtended = () => 37 | install( 38 | defineConfig({ 39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 40 | darkMode: (globalThis as any).bcDarkMode, 41 | theme: { 42 | fontFamily: { 43 | sans: ['Inter', 'sans-serif'], 44 | mono: ['Roboto Mono', 'monospace'], 45 | }, 46 | extend: { 47 | borderColor: { 48 | ...colors, 49 | }, 50 | backgroundColor: { 51 | ...colors, 52 | }, 53 | textColor: { 54 | ...colors, 55 | }, 56 | backgroundImage: { 57 | ...imageColors, 58 | }, 59 | animation: { 60 | darken: 'darken 0.2s ease-out forwards', 61 | 'fade-in': 'fade-in 0.2s ease-out forwards', 62 | 'slide-up': 'slideUp 0.3s ease-out forwards', 63 | }, 64 | keyframes: { 65 | darken: { 66 | '0%': {opacity: 0}, 67 | '100%': {opacity: 0.5}, 68 | }, 69 | lighten: { 70 | '0%': {opacity: 0.5}, 71 | '100%': {opacity: 0}, 72 | }, 73 | 'fade-in': { 74 | '0%': {opacity: 0}, 75 | '100%': {opacity: 1}, 76 | }, 77 | 'fade-out': { 78 | '0%': {opacity: 1}, 79 | '100%': {opacity: 0}, 80 | }, 81 | slideUp: { 82 | from: {transform: 'translateY(100%)'}, 83 | to: {transform: 'translateY(0)'}, 84 | }, 85 | }, 86 | }, 87 | }, 88 | presets: [presetTailwind({})], 89 | hash: false, 90 | }) 91 | ); 92 | 93 | export const withTwind = () => { 94 | if (globalThis.window) { 95 | return withTwindExtended(); 96 | } else { 97 | // prevent SSR issues 98 | return withNothing; 99 | } 100 | }; 101 | 102 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 103 | function withNothing LitElement>(Base: T) { 104 | return Base; 105 | } 106 | -------------------------------------------------------------------------------- /src/components/bc-balance.ts: -------------------------------------------------------------------------------- 1 | import {html} from 'lit'; 2 | import {customElement, state} from 'lit/decorators.js'; 3 | import {BitcoinConnectElement} from './BitcoinConnectElement.js'; 4 | import {withTwind} from './twind/withTwind.js'; 5 | import {classes} from './css/classes.js'; 6 | import store from '../state/store.js'; 7 | import {getFiatValue} from '@getalby/lightning-tools'; 8 | 9 | /** 10 | * Displays the balance of the connected wallet (could be sats or fiat) 11 | */ 12 | @customElement('bc-balance') 13 | export class Balance extends withTwind()(BitcoinConnectElement) { 14 | @state() 15 | _balance: string | undefined; 16 | 17 | @state() 18 | _balanceSats: number | undefined; 19 | 20 | @state() 21 | _loading = false; 22 | 23 | @state() _selectedCurrency: string | undefined; 24 | 25 | constructor() { 26 | super(); 27 | 28 | this._loadBalance(); 29 | 30 | this._selectedCurrency = store.getState().currency; 31 | store.subscribe((currentState, prevState) => { 32 | this._selectedCurrency = currentState.currency; 33 | if (currentState.currency !== prevState.currency) { 34 | this._convertBalance(); 35 | } 36 | 37 | if ( 38 | currentState.connected !== prevState.connected && 39 | currentState.connected 40 | ) { 41 | this._loadBalance(); 42 | } 43 | }); 44 | } 45 | 46 | override render() { 47 | // TODO: if balance is still loading, show skeleton loader 48 | return html` 53 | ${this._balance || 'Loading...'} `; 55 | } 56 | 57 | private async _convertBalance() { 58 | if (this._loading || this._balanceSats === undefined) { 59 | return; 60 | } 61 | 62 | const currency = this._selectedCurrency || 'sats'; 63 | 64 | if (currency === 'BTC') { 65 | const balanceSats = this._balanceSats / 1e8; 66 | this._balance = 67 | balanceSats.toLocaleString(undefined, { 68 | minimumFractionDigits: 8, 69 | useGrouping: true, 70 | }) + ' BTC'; 71 | } else if (currency !== 'sats') { 72 | try { 73 | this._loading = true; 74 | const fiatValue = await getFiatValue({ 75 | satoshi: this._balanceSats, 76 | currency, 77 | }); 78 | const convertedValue = parseFloat(fiatValue.toFixed(2)); 79 | this._balance = new Intl.NumberFormat(undefined, { 80 | style: 'currency', 81 | currency, 82 | }).format(convertedValue); 83 | } catch (error) { 84 | console.error(error); 85 | } 86 | this._loading = false; 87 | } else { 88 | this._balance = 89 | this._balanceSats.toLocaleString(undefined, { 90 | useGrouping: true, 91 | }) + ' sats'; 92 | } 93 | } 94 | 95 | private _loadBalance() { 96 | (async () => { 97 | try { 98 | const provider = store.getState().provider; 99 | if (!provider?.getBalance) { 100 | return; 101 | } 102 | 103 | const balanceResponse = await provider.getBalance(); 104 | if (balanceResponse) { 105 | this._balanceSats = balanceResponse.balance; 106 | this._convertBalance(); 107 | } 108 | } catch (error) { 109 | this._balance = '⚠️'; 110 | // FIXME: better error handling 111 | console.error(error); 112 | } 113 | })(); 114 | } 115 | } 116 | 117 | declare global { 118 | interface HTMLElementTagNameMap { 119 | 'bc-balance': Balance; 120 | } 121 | } 122 | --------------------------------------------------------------------------------