diff --git a/package-lock.json b/package-lock.json index d78a023..a6a020b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@babylonlabs-io/bbn-wallet-connect", - "version": "0.0.18", + "version": "0.0.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@babylonlabs-io/bbn-wallet-connect", - "version": "0.0.18", + "version": "0.0.23", "dependencies": { "@cosmjs/stargate": "^0.32.4", "@keplr-wallet/types": "^0.12.156", @@ -4247,11 +4247,10 @@ "license": "MIT" }, "node_modules/base-x": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.0.tgz", - "integrity": "sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==", - "dev": true, - "license": "MIT" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", + "dev": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -4319,36 +4318,29 @@ } }, "node_modules/bip174": { - "version": "3.0.0-rc.1", - "resolved": "https://registry.npmjs.org/bip174/-/bip174-3.0.0-rc.1.tgz", - "integrity": "sha512-+8P3BpSairVNF2Nee6Ksdc1etIjWjBOi/MH0MwKtq9YaYp+S2Hk2uvup0e8hCT4IKlS58nXJyyQVmW92zPoD4Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", "dev": true, - "license": "MIT", - "dependencies": { - "uint8array-tools": "^0.0.9", - "varuint-bitcoin": "^2.0.0" - }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, "node_modules/bitcoinjs-lib": { - "version": "7.0.0-rc.0", - "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-7.0.0-rc.0.tgz", - "integrity": "sha512-7CQgOIbREemKR/NT2uc3uO/fkEy+6CM0sLxboVVY6bv6DbZmPt3gg5Y/hhWgQFeZu5lfTbtVAv32MIxf7lMh4g==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz", + "integrity": "sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA==", "dev": true, - "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", "bech32": "^2.0.0", - "bip174": "^3.0.0-rc.0", - "bs58check": "^4.0.0", - "uint8array-tools": "^0.0.9", - "valibot": "^0.38.0", - "varuint-bitcoin": "^2.0.0" + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=8.0.0" } }, "node_modules/bitcoinjs-lib/node_modules/bech32": { @@ -4434,24 +4426,22 @@ } }, "node_modules/bs58": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", - "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", "dev": true, - "license": "MIT", "dependencies": { - "base-x": "^5.0.0" + "base-x": "^4.0.0" } }, "node_modules/bs58check": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz", - "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", "dev": true, - "license": "MIT", "dependencies": { "@noble/hashes": "^1.2.0", - "bs58": "^6.0.0" + "bs58": "^5.0.0" } }, "node_modules/buffer": { @@ -4472,7 +4462,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -8524,6 +8513,26 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9323,6 +9332,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==", + "dev": true + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -9371,16 +9386,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uint8array-tools": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz", - "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -9505,39 +9510,13 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/valibot": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.38.0.tgz", - "integrity": "sha512-RCJa0fetnzp+h+KN9BdgYOgtsMAG9bfoJ9JSjIhFHobKWVWyzM3jjaeNTdpFK9tQtf3q1sguXeERJ/LcmdFE7w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/varuint-bitcoin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz", - "integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", "dev": true, - "license": "MIT", "dependencies": { - "uint8array-tools": "^0.0.8" - } - }, - "node_modules/varuint-bitcoin/node_modules/uint8array-tools": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz", - "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" + "safe-buffer": "^5.1.1" } }, "node_modules/vite": { diff --git a/package.json b/package.json index 55eaf9b..3add47b 100644 --- a/package.json +++ b/package.json @@ -33,17 +33,16 @@ "@cosmjs/stargate": "^0.32.4", "@keplr-wallet/types": "^0.12.156", "buffer": "^6.0.3", - "nanoevents": "^9.1.0", - "react-icons": "^5.3.0" + "nanoevents": "^9.1.0" }, "files": [ "dist" ], "peerDependencies": { + "@babylonlabs-io/bbn-core-ui": "^0.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "tailwind-merge": "^2.5.4", - "@babylonlabs-io/bbn-core-ui": "^0.2.0" + "tailwind-merge": "^2.5.4" }, "devDependencies": { "@changesets/cli": "^2.27.9", diff --git a/src/components/Chains/container.tsx b/src/components/Chains/container.tsx index 860739d..ac09d21 100644 --- a/src/components/Chains/container.tsx +++ b/src/components/Chains/container.tsx @@ -10,6 +10,7 @@ interface ContainerProps { className?: string; onClose?: () => void; onConfirm?: () => void; + onDisconnectWallet?: (chainId: string) => void; } export default function ChainsContainer(props: ContainerProps) { diff --git a/src/components/Chains/index.tsx b/src/components/Chains/index.tsx index 10a929d..6cef698 100644 --- a/src/components/Chains/index.tsx +++ b/src/components/Chains/index.tsx @@ -2,7 +2,7 @@ import { memo } from "react"; import { twMerge } from "tailwind-merge"; import { Button, DialogBody, DialogFooter, DialogHeader, Text } from "@babylonlabs-io/bbn-core-ui"; -import ConnectedWallet from "@/components/ConnectedWallet/container"; +import { ConnectedWallet } from "@/components/ConnectedWallet"; import { ChainButton } from "@/components/ChainButton"; import type { IChain, IWallet } from "@/core/types"; @@ -13,11 +13,21 @@ interface ChainsProps { selectedWallets?: Record; onClose?: () => void; onConfirm?: () => void; + onDisconnectWallet?: (chainId: string) => void; onSelectChain?: (chain: IChain) => void; } export const Chains = memo( - ({ disabled = false, chains, selectedWallets = {}, className, onClose, onConfirm, onSelectChain }: ChainsProps) => ( + ({ + disabled = false, + chains, + selectedWallets = {}, + className, + onClose, + onConfirm, + onSelectChain, + onDisconnectWallet, + }: ChainsProps) => (
Connect to both Bitcoin and Babylon Chain Wallets @@ -42,6 +52,7 @@ export const Chains = memo( logo={selectedWallet.icon} name={selectedWallet.name} address={selectedWallet.account?.address ?? ""} + onDisconnect={onDisconnectWallet} /> )} diff --git a/src/components/ConnectedWallet/container.tsx b/src/components/ConnectedWallet/container.tsx deleted file mode 100644 index a06e9f1..0000000 --- a/src/components/ConnectedWallet/container.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useWidgetState } from "@/hooks/useWidgetState"; -import { ConnectedWallet } from "./index"; - -interface ConnectedWalletProps { - className?: string; - chainId: string; - logo: string; - name: string; - address: string; -} - -export default function ConnectedWalletContainer(props: ConnectedWalletProps) { - const { removeWallet } = useWidgetState(); - - return ; -} diff --git a/src/components/WalletProvider/components/Screen.tsx b/src/components/WalletProvider/components/Screen.tsx index 9a1e325..1866302 100644 --- a/src/components/WalletProvider/components/Screen.tsx +++ b/src/components/WalletProvider/components/Screen.tsx @@ -6,7 +6,7 @@ import { TermsOfService } from "@/components/TermsOfService"; import { LoaderScreen } from "@/components/Loader"; import type { IChain, IWallet } from "@/core/types"; -import type { Screen } from "@/state/types"; +import type { Screen } from "@/context/State.context"; interface ScreenProps { className?: string; @@ -14,6 +14,7 @@ interface ScreenProps { lockInscriptions?: boolean; widgets?: Record; onSelectWallet?: (chain: IChain, wallet: IWallet) => void; + onDisconnectWallet?: (chainId: string) => void; onAccepTermsOfService?: () => void; onToggleInscriptions?: (value: boolean, showAgain: boolean) => void; onClose?: () => void; @@ -24,8 +25,8 @@ const SCREENS = { TERMS_OF_SERVICE: ({ className, onClose, onAccepTermsOfService }: ScreenProps) => ( ), - CHAINS: ({ className, onClose, onConfirm }: ScreenProps) => ( - + CHAINS: ({ className, onClose, onConfirm, onDisconnectWallet }: ScreenProps) => ( + ), WALLETS: ({ className, widgets, onClose, onSelectWallet }: ScreenProps) => ( diff --git a/src/components/WalletProvider/components/WalletDialog.tsx b/src/components/WalletProvider/components/WalletDialog.tsx index ad1b9e3..361ae6a 100644 --- a/src/components/WalletProvider/components/WalletDialog.tsx +++ b/src/components/WalletProvider/components/WalletDialog.tsx @@ -1,59 +1,27 @@ -import { type JSX, useCallback } from "react"; +import { useCallback } from "react"; import { Dialog } from "@babylonlabs-io/bbn-core-ui"; import { useWidgetState } from "@/hooks/useWidgetState"; +import { useWalletWidgets } from "@/hooks/useWalletWidgets"; import { useChainProviders } from "@/context/Chain.context"; import { useInscriptionProvider } from "@/context/Inscriptions.context"; -import type { IChain, IWallet } from "@/core/types"; import { Screen } from "./Screen"; +import { useWalletConnectors } from "@/hooks/useWalletConnectors"; interface WalletDialogProps { onError?: (e: Error) => void; - widgets?: Record; + config: any; } const ANIMATION_DELAY = 1000; -export function WalletDialog({ widgets, onError }: WalletDialogProps) { - const { - visible, - screen, - close, - reset = () => {}, - confirm, - selectWallet, - displayLoader, - displayChains, - displayInscriptions, - } = useWidgetState(); - const { showAgain, toggleShowAgain, toggleLockInscriptions } = useInscriptionProvider(); +export function WalletDialog({ config, onError }: WalletDialogProps) { + const { visible, screen, close, reset = () => {}, confirm, displayChains } = useWidgetState(); + const { toggleShowAgain, toggleLockInscriptions } = useInscriptionProvider(); const connectors = useChainProviders(); - - const handleSelectWallet = useCallback( - async (chain: IChain, wallet: IWallet) => { - try { - displayLoader?.(`Connecting ${wallet.name}`); - - const connector = connectors[chain.id as keyof typeof connectors]; - const connectedWallet = await connector?.connect(wallet.id); - - if (connectedWallet) { - selectWallet?.(chain.id, connectedWallet); - } - - if (showAgain && chain.id === "BTC") { - displayInscriptions?.(); - } else { - displayChains?.(); - } - } catch (e: any) { - onError?.(e); - displayChains?.(); - } - }, - [displayLoader, selectWallet, displayInscriptions, connectors, showAgain], - ); + const walletWidgets = useWalletWidgets(connectors, config); + const { connect, disconnect } = useWalletConnectors(onError); const handleToggleInscriptions = useCallback( (lockInscriptions: boolean, showAgain: boolean) => { @@ -78,13 +46,14 @@ export function WalletDialog({ widgets, onError }: WalletDialogProps) { ); diff --git a/src/components/WalletProvider/constants.tsx b/src/components/WalletProvider/constants.tsx new file mode 100644 index 0000000..dbcd4b8 --- /dev/null +++ b/src/components/WalletProvider/constants.tsx @@ -0,0 +1,103 @@ +import { ChainInfo } from "@keplr-wallet/types"; +import { Text } from "@babylonlabs-io/bbn-core-ui"; + +import { Network } from "@/core/types"; +import { WalletButton } from "@/components/WalletButton"; +import { ChainConfigArr } from "@/context/Chain.context"; + +export const config: ChainConfigArr = [ + { + chain: "BTC", + connectors: [ + { + id: "tomo-connect", + widget: () => ( +
+ More wallets with Tomo Connect + alert("Hello Tomo!")} /> +
+ ), + }, + ], + config: { + coinName: "Signet BTC", + coinSymbol: "sBTC", + networkName: "BTC signet", + mempoolApiUrl: "https://mempool.space/signet", + network: Network.SIGNET, + }, + }, + { + chain: "BBN", + connectors: [ + { + id: "tomo-connect", + widget: () => ( +
+ More wallets with Tomo Connect + alert("Hello Tomo!")} /> +
+ ), + }, + ], + config: { + chainId: "devnet-7", + rpc: "https://rpc.devnet.babylonlabs.io", + chainData: { + chainId: "devnet-7", + chainName: "Babylon Devnet 7", + chainSymbolImageUrl: + "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", + rpc: "https://rpc.devnet.babylonlabs.io", + rest: "https://lcd.devnet.babylonlabs.io", + nodeProvider: { + name: "Babylonlabs", + email: "contact@babylonlabs.io", + website: "https://babylonlabs.io/", + }, + bip44: { + coinType: 118, + }, + bech32Config: { + bech32PrefixAccAddr: "bbn", + bech32PrefixAccPub: "bbnpub", + bech32PrefixValAddr: "bbnvaloper", + bech32PrefixValPub: "bbnvaloperpub", + bech32PrefixConsAddr: "bbnvalcons", + bech32PrefixConsPub: "bbnvalconspub", + }, + currencies: [ + { + coinDenom: "BBN", + coinMinimalDenom: "ubbn", + coinDecimals: 6, + coinImageUrl: + "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", + }, + ], + feeCurrencies: [ + { + coinDenom: "BBN", + coinMinimalDenom: "ubbn", + coinDecimals: 6, + coinImageUrl: + "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", + gasPriceStep: { + low: 0.007, + average: 0.007, + high: 0.01, + }, + }, + ], + stakeCurrency: { + coinDenom: "BBN", + coinMinimalDenom: "ubbn", + coinDecimals: 6, + coinImageUrl: + "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", + }, + features: ["cosmwasm"], + } as ChainInfo, + }, + }, +]; diff --git a/src/components/WalletProvider/index.stories.tsx b/src/components/WalletProvider/index.stories.tsx index 241ca2d..faeab8c 100644 --- a/src/components/WalletProvider/index.stories.tsx +++ b/src/components/WalletProvider/index.stories.tsx @@ -2,10 +2,9 @@ import type { Meta, StoryObj } from "@storybook/react"; import { Button, ScrollLocker } from "@babylonlabs-io/bbn-core-ui"; import { useWidgetState } from "@/hooks/useWidgetState"; -import { Network } from "@/core/types"; -import { bbnDevnet } from "@/core/chains/bbnDevnet"; import { WalletProvider } from "./index"; +import { config } from "./constants"; const meta: Meta = { component: WalletProvider, @@ -16,27 +15,6 @@ export default meta; type Story = StoryObj; -const config = [ - { - chain: "BTC", - config: { - coinName: "Signet BTC", - coinSymbol: "sBTC", - networkName: "BTC signet", - mempoolApiUrl: "https://mempool.space/signet", - network: Network.SIGNET, - }, - }, - { - chain: "BBN", - config: { - chainId: bbnDevnet.chainId, - rpc: bbnDevnet.rpc, - chainData: bbnDevnet, - }, - }, -] as const; - export const Default: Story = { args: { onError: console.log, @@ -44,7 +22,7 @@ export const Default: Story = { decorators: [ (Story) => ( - + diff --git a/src/components/WalletProvider/index.tsx b/src/components/WalletProvider/index.tsx index b312099..f4e1ca0 100644 --- a/src/components/WalletProvider/index.tsx +++ b/src/components/WalletProvider/index.tsx @@ -1,15 +1,11 @@ -import { type PropsWithChildren, JSX } from "react"; +import { type PropsWithChildren } from "react"; -import { StateProvider } from "@/state/state"; import { ChainConfigArr, ChainProvider } from "@/context/Chain.context"; -import { InscriptionProvider } from "@/context/Inscriptions.context"; - import { WalletDialog } from "./components/WalletDialog"; interface WalletProviderProps { context?: any; config: Readonly; - walletWidgets?: Record; onError?: (e: Error) => void; } @@ -17,17 +13,12 @@ export function WalletProvider({ children, config, context = window, - walletWidgets, onError, }: PropsWithChildren) { return ( - - - - {children} - - - - + + {children} + + ); } diff --git a/src/context/Chain.context.tsx b/src/context/Chain.context.tsx index c4b35f0..8d1eb84 100644 --- a/src/context/Chain.context.tsx +++ b/src/context/Chain.context.tsx @@ -1,22 +1,27 @@ -import { createContext, PropsWithChildren, useEffect, useState, useCallback, useContext } from "react"; - -import { useWidgetState } from "@/hooks/useWidgetState"; +import { createContext, PropsWithChildren, useEffect, useState, useCallback, useContext, useMemo } from "react"; import { createWalletConnector } from "@/core"; import metadata from "@/core/wallets"; import { WalletConnector } from "@/core/WalletConnector"; import { BTCProvider } from "@/core/wallets/btc/BTCProvider"; import { BBNProvider } from "@/core/wallets/bbn/BBNProvider"; -import type { BBNConfig, IProvider, BTCConfig } from "@/core/types"; +import type { BBNConfig, IProvider, BTCConfig, ExternalConnector, IBTCProvider, IBBNProvider } from "@/core/types"; + +import { StateProvider } from "./State.context"; +import { InscriptionProvider } from "./Inscriptions.context"; -interface ChainConfig { +interface ChainConfig { chain: K; name?: string; icon?: string; config: C; + connectors?: ExternalConnector

[]; } -export type ChainConfigArr = (ChainConfig<"BTC", BTCConfig> | ChainConfig<"BBN", BBNConfig>)[]; +export type ChainConfigArr = ( + | ChainConfig<"BTC", IBTCProvider, BTCConfig> + | ChainConfig<"BBN", IBBNProvider, BBNConfig> +)[]; interface ProviderProps { context: any; @@ -38,7 +43,6 @@ export const Context = createContext(defaultState); export function ChainProvider({ children, context, config, onError }: PropsWithChildren) { const [connectors, setConnectors] = useState(defaultState); - const { addChain, displayLoader, displayTermsOfService } = useWidgetState(); const init = useCallback(async () => { const connectorPromises = config @@ -50,23 +54,22 @@ export function ChainProvider({ children, context, config, onError }: PropsWithC }, []); useEffect(() => { - if (!displayLoader || !addChain || !setConnectors || !displayTermsOfService) return; - - displayLoader(); - init() .then((connectors) => { setConnectors(connectors); - - Object.values(connectors).forEach((connector) => { - addChain(connector); - }); }) - .catch(onError) - .finally(displayTermsOfService); - }, [displayLoader, addChain, setConnectors, init, displayTermsOfService, onError]); - - return {children}; + .catch(onError); + }, [setConnectors, init, onError]); + + const supportedChains = useMemo(() => Object.values(connectors).filter(Boolean), [connectors]); + + return ( + + + {children} + + + ); } export const useChainProviders = () => { diff --git a/src/state/state.tsx b/src/context/State.context.tsx similarity index 60% rename from src/state/state.tsx rename to src/context/State.context.tsx index b850bb3..819369a 100644 --- a/src/state/state.tsx +++ b/src/context/State.context.tsx @@ -1,7 +1,40 @@ -import { type PropsWithChildren, createContext, useMemo, useState } from "react"; +import { type PropsWithChildren, createContext, useEffect, useMemo, useState } from "react"; -import { IChain, IWallet } from "@/core/types"; -import { Actions, type State } from "./types"; +import type { IChain, IWallet } from "@/core/types"; + +export type Screen = { + type: T; + params?: Record; +}; + +export type Screens = + | Screen<"LOADER"> + | Screen<"TERMS_OF_SERVICE"> + | Screen<"CHAINS"> + | Screen<"WALLETS"> + | Screen<"INSCRIPTIONS">; + +export interface State { + confirmed: boolean; + visible: boolean; + screen: Screens; + selectedWallets: Record; + chains: Record; +} + +export interface Actions { + open?: () => void; + close?: () => void; + displayLoader?: (message?: string) => void; + displayChains?: () => void; + displayWallets?: (chain: string) => void; + displayInscriptions?: () => void; + displayTermsOfService?: () => void; + selectWallet?: (chain: string, wallet: IWallet) => void; + removeWallet?: (chain: string) => void; + confirm?: () => void; + reset?: () => void; +} const defaultState: State = { confirmed: false, @@ -13,9 +46,17 @@ const defaultState: State = { export const StateContext = createContext(defaultState); -export function StateProvider({ children }: PropsWithChildren) { +interface StateProviderProps { + chains: IChain[]; +} + +export function StateProvider({ children, chains }: PropsWithChildren) { const [state, setState] = useState(defaultState); + useEffect(() => { + setState((state) => ({ ...state, chains: chains.reduce((acc, chain) => ({ ...acc, [chain.id]: chain }), {}) })); + }, [chains]); + const actions: Actions = useMemo( () => ({ open: () => { @@ -64,10 +105,6 @@ export function StateProvider({ children }: PropsWithChildren) { })); }, - addChain: (chain: IChain) => { - setState((state) => ({ ...state, chains: { ...state.chains, [chain.id]: chain } })); - }, - confirm: () => { setState((state) => ({ ...state, confirmed: true })); }, diff --git a/src/core/Wallet.ts b/src/core/Wallet.ts index dd483e3..55b4d12 100644 --- a/src/core/Wallet.ts +++ b/src/core/Wallet.ts @@ -16,7 +16,7 @@ export class Wallet

implements IWallet { readonly name: string; readonly icon: string; readonly docs: string; - readonly networkds: Network[]; + readonly networks: Network[]; readonly provider: P | null = null; account: Account | null = null; @@ -26,7 +26,7 @@ export class Wallet

implements IWallet { this.name = name; this.icon = icon; this.docs = docs; - this.networkds = networks; + this.networks = networks; this.provider = provider; } @@ -54,7 +54,7 @@ export class Wallet

implements IWallet { name: this.name, icon: this.icon, docs: this.docs, - networks: this.networkds, + networks: this.networks, provider: this.provider, }); } diff --git a/src/core/WalletConnector.ts b/src/core/WalletConnector.ts index e987f02..c22482c 100644 --- a/src/core/WalletConnector.ts +++ b/src/core/WalletConnector.ts @@ -1,12 +1,15 @@ import { createNanoEvents } from "nanoevents"; import { Wallet } from "@/core/Wallet"; -import type { IProvider, IChain } from "@/core/types"; +import type { IProvider, IConnector } from "@/core/types"; export interface ConnectorEvents

{ + connecting: (message?: string) => void; connect: (wallet: Wallet

) => void; + disconnect: (wallet: Wallet

) => void; + error: (error: Error) => void; } -export class WalletConnector implements IChain { +export class WalletConnector implements IConnector { private _connectedWallet: Wallet

| null = null; private _ee = createNanoEvents>(); @@ -21,21 +24,31 @@ export class WalletConnector implements I return this._connectedWallet; } - async connect(walletId: string) { - const wallet = this.wallets.find((wallet) => wallet.id === walletId); + async connect(wallet: string | Wallet

) { + try { + const selectedWallet = typeof wallet === "string" ? this.wallets.find((w) => w.id === wallet) : wallet; - if (!wallet) { - throw new Error("Wallet not found"); - } + if (!selectedWallet) { + throw new Error("Wallet not found"); + } + this._ee.emit("connecting", `Connecting ${selectedWallet.name}`); - this._connectedWallet = await wallet.connect(); - this._ee.emit("connect", this._connectedWallet); + await selectedWallet.connect(); + this._connectedWallet = selectedWallet; + this._ee.emit("connect", this._connectedWallet); - return this.connectedWallet; + return this.connectedWallet; + } catch (e: any) { + this._ee.emit("error", e); + return null; + } } - disconnect() { - this._connectedWallet = null; + async disconnect() { + if (this._connectedWallet) { + this._ee.emit("disconnect", this._connectedWallet); + this._connectedWallet = null; + } } clone() { diff --git a/src/core/chains/bbnDevnet.ts b/src/core/chains/bbnDevnet.ts deleted file mode 100644 index dbfd815..0000000 --- a/src/core/chains/bbnDevnet.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ChainInfo } from "@keplr-wallet/types"; - -export const bbnDevnet: ChainInfo = { - chainId: "devnet-7", - chainName: "Babylon Devnet 7", - chainSymbolImageUrl: - "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", - rpc: "https://rpc.devnet.babylonlabs.io", - rest: "https://lcd.devnet.babylonlabs.io", - nodeProvider: { - name: "Babylonlabs", - email: "contact@babylonlabs.io", - website: "https://babylonlabs.io/", - }, - bip44: { - coinType: 118, - }, - bech32Config: { - bech32PrefixAccAddr: "bbn", - bech32PrefixAccPub: "bbnpub", - bech32PrefixValAddr: "bbnvaloper", - bech32PrefixValPub: "bbnvaloperpub", - bech32PrefixConsAddr: "bbnvalcons", - bech32PrefixConsPub: "bbnvalconspub", - }, - currencies: [ - { - coinDenom: "BBN", - coinMinimalDenom: "ubbn", - coinDecimals: 6, - coinImageUrl: "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", - }, - ], - feeCurrencies: [ - { - coinDenom: "BBN", - coinMinimalDenom: "ubbn", - coinDecimals: 6, - coinImageUrl: "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", - gasPriceStep: { - low: 0.007, - average: 0.007, - high: 0.01, - }, - }, - ], - stakeCurrency: { - coinDenom: "BBN", - coinMinimalDenom: "ubbn", - coinDecimals: 6, - coinImageUrl: "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bbn-dev/chain.png", - }, - features: ["cosmwasm"], -} as const; diff --git a/src/core/index.ts b/src/core/index.ts index a72e41a..38c220b 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,6 +1,6 @@ import { type WalletOptions, Wallet } from "./Wallet"; import { WalletConnector } from "./WalletConnector"; -import { ChainMetadata, IProvider, WalletMetadata } from "./types"; +import { ChainMetadata, ExternalWalletProps, IProvider, Network, WalletMetadata } from "./types"; const defaultWalletGetter = (key: string) => (context: any) => context[key]; @@ -50,6 +50,17 @@ export const createWallet = async

(metadata: WalletMetad return new Wallet(options); }; +export const createExternalWallet =

({ id, name, icon, provider }: ExternalWalletProps

) => + new Wallet({ + id, + origin: null, + name, + icon, + docs: "", + networks: [Network.MAINNET, Network.SIGNET], + provider, + }); + export const createWalletConnector = async ( metadata: ChainMetadata, context: any, diff --git a/src/core/types.ts b/src/core/types.ts index 5d0672f..97fbd2d 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,4 +1,6 @@ +import { ComponentType } from "react"; import { ChainInfo } from "@keplr-wallet/types"; +import { SigningStargateClient } from "@cosmjs/stargate"; export type Fees = { // fee for inclusion in the next block @@ -59,32 +61,39 @@ export type BBNConfig = { }; export interface IProvider { - connectWallet: () => Promise; + connectWallet: () => Promise; getAddress: () => Promise; getPublicKeyHex: () => Promise; } -export interface IWallet { +export interface IWallet

{ id: string; name: string; icon: string; docs: string; installed: boolean; - provider: IProvider | null; + provider: P | null; account: Account | null; } -export interface IChain { - id: string; +export interface IChain { + id: K; name: string; icon: string; - wallets: IWallet[]; + wallets: IWallet

[]; +} + +export interface IConnector extends IChain { + connect(wallet: string | IWallet

): Promise | null>; + disconnect(): Promise; + on(event: string, cb: (wallet: IWallet

) => void): () => void; } export interface Account { address: string; publicKeyHex: string; } + export interface WalletMetadata

{ id: string; wallet?: string | ((context: any, config: C) => any); @@ -101,3 +110,162 @@ export interface ChainMetadata { icon: string; wallets: WalletMetadata[]; } + +export interface ExternalWalletProps

{ + id: string; + name: string; + icon: string; + provider: P; +} + +export interface WidgetProps

{ + id: string; + connector: IConnector; + createWallet: (props: ExternalWalletProps

) => IWallet

; +} + +export type WidgetComponent

= ComponentType>; + +export interface ExternalConnector

{ + id: string; + widget: WidgetComponent

; +} + +export interface IBTCProvider extends IProvider { + /** + * Connects to the wallet and returns the instance of the wallet provider. + * Currently only supports "native segwit" and "taproot" address types. + * @returns A promise that resolves to an instance of the wrapper wallet provider in babylon friendly format. + * @throws An error if the wallet is not installed or if connection fails. + */ + connectWallet(): Promise; + + /** + * Gets the address of the connected wallet. + * @returns A promise that resolves to the address of the connected wallet. + */ + getAddress(): Promise; + + /** + * Gets the public key of the connected wallet. + * @returns A promise that resolves to the public key of the connected wallet. + */ + getPublicKeyHex(): Promise; + + /** + * Signs the given PSBT in hex format. + * @param psbtHex - The hex string of the unsigned PSBT to sign. + * @returns A promise that resolves to the hex string of the signed PSBT. + */ + signPsbt(psbtHex: string): Promise; + + /** + * Signs multiple PSBTs in hex format. + * @param psbtsHexes - The hex strings of the unsigned PSBTs to sign. + * @returns A promise that resolves to an array of hex strings, each representing a signed PSBT. + */ + signPsbts(psbtsHexes: string[]): Promise; + + /** + * Gets the network of the current account. + * @returns A promise that resolves to the network of the current account. + */ + getNetwork(): Promise; + + /** + * Signs a message using BIP-322 simple. + * @param message - The message to sign. + * @returns A promise that resolves to the signed message. + */ + signMessageBIP322(message: string): Promise; + + signMessage(message: string, type: "ecdsa" | "bip322-simple"): Promise; + + /** + * Registers an event listener for the specified event. + * At the moment, only the "accountChanged" event is supported. + * @param eventName - The name of the event to listen for. + * @param callBack - The callback function to be executed when the event occurs. + */ + on(eventName: string, callBack: () => void): void; + + off(eventName: string, callBack: () => void): void; + + /** + * Gets the balance for the connected wallet address. + * By default, this method will return the mempool balance if not implemented by the child class. + * @returns A promise that resolves to the balance of the wallet. + */ + getBalance(): Promise; + + /** + * Retrieves the network fees. + * @returns A promise that resolves to the network fees. + */ + getNetworkFees(): Promise; + + /** + * Pushes a transaction to the network. + * @param txHex - The hexadecimal representation of the transaction. + * @returns A promise that resolves to a string representing the transaction ID. + */ + pushTx(txHex: string): Promise; + + /** + * Retrieves the unspent transaction outputs (UTXOs) for a given address and amount. + * + * If the amount is provided, it will return UTXOs that cover the specified amount. + * If the amount is not provided, it will return all available UTXOs for the address. + * + * @param address - The address to retrieve UTXOs for. + * @param amount - Optional amount of funds required. + * @returns A promise that resolves to an array of UTXOs. + */ + getUtxos(address: string, amount?: number): Promise; + + /** + * Retrieves the tip height of the BTC chain. + * @returns A promise that resolves to the block height. + */ + getBTCTipHeight(): Promise; + + /** + * Retrieves the inscriptions for the connected wallet. + * @returns A promise that resolves to an array of inscriptions. + */ + getInscriptions(): Promise; +} + +export interface IBBNProvider extends IProvider { + /** + * Connects to the wallet and returns the instance of the wallet provider. + * @returns A promise that resolves to an instance of the wrapper wallet provider. + * @throws An error if the wallet is not installed or if connection fails. + */ + connectWallet(): Promise; + + /** + * Gets the address of the connected wallet. + * @returns A promise that resolves to the address of the connected wallet. + */ + getAddress(): Promise; + + /** + * Gets the public key of the connected wallet. + * @returns A promise that resolves to the public key of the connected wallet. + */ + getPublicKeyHex(): Promise; + + /** + * Gets the signing stargate client. + * @returns A promise that resolves to the signing stargate client. + */ + getSigningStargateClient(): Promise; + /** + * Gets the balance of the connected wallet. + * @param searchDenom - The denomination to search for in the wallet's balance. + * @returns A promise that resolves to the balance of the connected wallet. + */ + + getBalance(searchDenom: string): Promise; +} diff --git a/src/core/wallets/bbn/BBNProvider.ts b/src/core/wallets/bbn/BBNProvider.ts index 562afc1..66e465a 100644 --- a/src/core/wallets/bbn/BBNProvider.ts +++ b/src/core/wallets/bbn/BBNProvider.ts @@ -1,13 +1,13 @@ -import { IProvider } from "@/core/types"; +import { IBBNProvider } from "@/core/types"; import { SigningStargateClient } from "@cosmjs/stargate"; -export abstract class BBNProvider implements IProvider { +export abstract class BBNProvider implements IBBNProvider { /** * Connects to the wallet and returns the instance of the wallet provider. * @returns A promise that resolves to an instance of the wrapper wallet provider. * @throws An error if the wallet is not installed or if connection fails. */ - abstract connectWallet(): Promise; + abstract connectWallet(): Promise; /** * Gets the address of the connected wallet. diff --git a/src/core/wallets/btc/BTCProvider.ts b/src/core/wallets/btc/BTCProvider.ts index 3fcc283..fa67161 100644 --- a/src/core/wallets/btc/BTCProvider.ts +++ b/src/core/wallets/btc/BTCProvider.ts @@ -1,11 +1,11 @@ -import type { Fees, InscriptionIdentifier, Network, BTCConfig, UTXO, IProvider } from "../../types"; +import type { Fees, InscriptionIdentifier, Network, BTCConfig, UTXO, IBTCProvider } from "../../types"; import { createMempoolAPI, MempoolApi } from "../../utils/mempool"; /** * Abstract class representing a wallet provider. * Provides methods for connecting to a wallet, retrieving wallet information, signing transactions, and more. */ -export abstract class BTCProvider implements IProvider { +export abstract class BTCProvider implements IBTCProvider { protected mempool: MempoolApi; constructor(protected config: BTCConfig) { @@ -17,7 +17,7 @@ export abstract class BTCProvider implements IProvider { * @returns A promise that resolves to an instance of the wrapper wallet provider in babylon friendly format. * @throws An error if the wallet is not installed or if connection fails. */ - abstract connectWallet(): Promise; + abstract connectWallet(): Promise; /** * Gets the address of the connected wallet. diff --git a/src/hooks/useWalletConnectors.tsx b/src/hooks/useWalletConnectors.tsx new file mode 100644 index 0000000..e138823 --- /dev/null +++ b/src/hooks/useWalletConnectors.tsx @@ -0,0 +1,94 @@ +import { useCallback, useEffect } from "react"; + +import { useChainProviders } from "@/context/Chain.context"; +import { useInscriptionProvider } from "@/context/Inscriptions.context"; +import { useWidgetState } from "./useWidgetState"; +import { IChain, IWallet } from "@/core/types"; + +export function useWalletConnectors(onError?: (e: Error) => void) { + const connectors = useChainProviders(); + const { selectWallet, removeWallet, displayLoader, displayChains, displayInscriptions } = useWidgetState(); + const { showAgain } = useInscriptionProvider(); + + // Connecting event + useEffect(() => { + const connectorArr = Object.values(connectors); + + const unsubscribeArr = connectorArr.filter(Boolean).map((connector) => + connector.on("connecting", (message: string) => { + displayLoader?.(message); + }), + ); + + return () => unsubscribeArr.forEach((unsubscribe) => unsubscribe()); + }, [displayLoader, connectors]); + + // Connect Event + useEffect(() => { + const connectorArr = Object.values(connectors); + + const unsubscribeArr = connectorArr.filter(Boolean).map((connector) => + connector.on("connect", (connectedWallet: IWallet) => { + if (connectedWallet) { + selectWallet?.(connector.id, connectedWallet); + } + + if (showAgain && connector.id === "BTC") { + displayInscriptions?.(); + } else { + displayChains?.(); + } + }), + ); + + return () => unsubscribeArr.forEach((unsubscribe) => unsubscribe()); + }, [selectWallet, displayInscriptions, displayChains, connectors, showAgain]); + + // Disconnect Event + useEffect(() => { + const connectorArr = Object.values(connectors); + + const unsubscribeArr = connectorArr.filter(Boolean).map((connector) => + connector.on("disconnect", (connectedWallet: IWallet) => { + if (connectedWallet) { + removeWallet?.(connector.id); + displayChains?.(); + } + }), + ); + + return () => unsubscribeArr.forEach((unsubscribe) => unsubscribe()); + }, [removeWallet, displayChains, connectors]); + + // Error Event + useEffect(() => { + const connectorArr = Object.values(connectors); + + const unsubscribeArr = connectorArr.filter(Boolean).map((connector) => + connector.on("error", (error: Error) => { + onError?.(error); + displayChains?.(); + }), + ); + + return () => unsubscribeArr.forEach((unsubscribe) => unsubscribe()); + }, [onError, displayChains, connectors]); + + const connect = useCallback( + async (chain: IChain, wallet: IWallet) => { + const connector = connectors[chain.id as keyof typeof connectors]; + await connector?.connect(wallet.id); + }, + [displayLoader, displayInscriptions, connectors, showAgain], + ); + + const disconnect = useCallback( + async (chainId: string) => { + const connector = connectors[chainId as keyof typeof connectors]; + await connector?.disconnect(); + }, + [displayLoader, displayChains, connectors], + ); + + return { connect, disconnect }; +} diff --git a/src/hooks/useWalletWidgets.tsx b/src/hooks/useWalletWidgets.tsx new file mode 100644 index 0000000..89f71ab --- /dev/null +++ b/src/hooks/useWalletWidgets.tsx @@ -0,0 +1,29 @@ +import { useCallback, useMemo } from "react"; + +import { createExternalWallet } from "@/core"; +import { ExternalConnector } from "@/core/types"; + +export function useWalletWidgets(connectors: any, config: any) { + const createChainWidget = useCallback( + (chainId: string, externalConnectors: ExternalConnector[]) => ( + <> + {externalConnectors.map(({ id, widget: Component }) => ( + + ))} + + ), + [connectors], + ); + + return useMemo( + () => + config.reduce( + (acc: Record, config: any) => ({ + ...acc, + [config.chain]: createChainWidget(config.chain, config.connectors ?? []), + }), + {}, + ) as Record, + [createChainWidget, config], + ); +} diff --git a/src/hooks/useWidgetState.ts b/src/hooks/useWidgetState.ts index 0897da1..83d1c4b 100644 --- a/src/hooks/useWidgetState.ts +++ b/src/hooks/useWidgetState.ts @@ -1,4 +1,4 @@ import { useContext } from "react"; -import { StateContext } from "@/state/state"; +import { StateContext } from "@/context/State.context"; export const useWidgetState = () => useContext(StateContext); diff --git a/src/index.tsx b/src/index.tsx index 18ac3d5..5b78d73 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,15 @@ import "./index.css"; export { WalletProvider } from "@/components/WalletProvider"; +export { WalletButton } from "@/components/WalletButton"; + export { useChainConnector } from "@/hooks/useChainConnector"; export { useWidgetState } from "@/hooks/useWidgetState"; export { useWalletConnect } from "@/hooks/useWalletConnect"; -export { WalletButton } from "@/components/WalletButton"; + export { useInscriptionProvider } from "@/context/Inscriptions.context"; -export * from "@/state/types"; +export * from "@/context/State.context"; +export { type ChainConfigArr } from "@/context/Chain.context"; export * from "@/core/wallets/btc/BTCProvider"; export * from "@/core/wallets/bbn/BBNProvider"; diff --git a/src/state/types.ts b/src/state/types.ts deleted file mode 100644 index 77036b5..0000000 --- a/src/state/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { IChain, IWallet } from "@/core/types"; - -export type Screen = { - type: T; - params?: Record; -}; - -export type Screens = - | Screen<"LOADER"> - | Screen<"TERMS_OF_SERVICE"> - | Screen<"CHAINS"> - | Screen<"WALLETS"> - | Screen<"INSCRIPTIONS">; - -export interface State { - confirmed: boolean; - visible: boolean; - screen: Screens; - selectedWallets: Record; - chains: Record; -} - -export interface Actions { - open?: () => void; - close?: () => void; - displayLoader?: (message?: string) => void; - displayChains?: () => void; - displayWallets?: (chain: string) => void; - displayInscriptions?: () => void; - displayTermsOfService?: () => void; - selectWallet?: (chain: string, wallet: IWallet) => void; - removeWallet?: (chain: string) => void; - addChain?: (chain: IChain) => void; - confirm?: () => void; - reset?: () => void; -}