diff --git a/package-lock.json b/package-lock.json index 816061be..99734bb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@babylonlabs-io/babylon-proto-ts": "0.0.3-canary.3", "@babylonlabs-io/bbn-core-ui": "^0.2.0", - "@babylonlabs-io/bbn-wallet-connect": "^0.0.23", + "@babylonlabs-io/bbn-wallet-connect": "^0.1.6", "@babylonlabs-io/btc-staking-ts": "0.4.0-canary.3", "@bitcoin-js/tiny-secp256k1-asmjs": "2.2.3", "@bitcoinerlab/secp256k1": "^1.1.1", @@ -23,7 +23,7 @@ "@sentry/nextjs": "^8.30.0", "@tanstack/react-query": "^5.28.14", "@tanstack/react-query-next-experimental": "^5.28.14", - "@tomo-inc/wallet-connect-sdk": "0.2.16", + "@tomo-inc/wallet-connect-sdk": "^0.3.3", "@uidotdev/usehooks": "^2.4.1", "axios": "^1.7.4", "bitcoinjs-lib": "6.1.5", @@ -2085,15 +2085,14 @@ } }, "node_modules/@babylonlabs-io/bbn-wallet-connect": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/@babylonlabs-io/bbn-wallet-connect/-/bbn-wallet-connect-0.0.23.tgz", - "integrity": "sha512-x6pFnKmo/2tSofQrtJnYYa05//+d8dLhp2zrxFNtjogVkZMgSeP5X7lq12gNXUE5Wlvzd5C9WfNDC61x3TyQEQ==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@babylonlabs-io/bbn-wallet-connect/-/bbn-wallet-connect-0.1.6.tgz", + "integrity": "sha512-yUO+9WP3mfMX7vlvc9cN4Mm05aU+9Qnqa3MT2SRTlZTQPM0yc2LipbF0oxRaNuvmrI5potdeijrZRjAgIsXrIA==", "dependencies": { "@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" }, "peerDependencies": { "@babylonlabs-io/bbn-core-ui": "^0.2.0", @@ -6630,9 +6629,9 @@ } }, "node_modules/@tomo-inc/tomo-wallet-provider": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@tomo-inc/tomo-wallet-provider/-/tomo-wallet-provider-1.0.21.tgz", - "integrity": "sha512-HyBHHKVa2L4/deWwHw7gASvmR9sTZJnF2hiG0iA+x9pAgPNAjbc60PkCiilYtP32ja3JT1G1VfhLXIPTdkrVLA==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/@tomo-inc/tomo-wallet-provider/-/tomo-wallet-provider-1.0.25.tgz", + "integrity": "sha512-P84NzQ6xiRuiQgVJqzfTWjpnlF65NbqYY/L5OaJZbpPSXaFAeNYClIUn40Okxkg/xKwryoOWEr4KzZxiOOVpxg==", "dependencies": { "events": "^3.3.0", "long": "^5.2.3" @@ -6651,11 +6650,11 @@ } }, "node_modules/@tomo-inc/wallet-connect-sdk": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/@tomo-inc/wallet-connect-sdk/-/wallet-connect-sdk-0.2.16.tgz", - "integrity": "sha512-ywYJZ8XC6daiqQgVLtPGeMXKSBd0q2Bm4saD9EhjT+yXVG4vDHvBbp0CWPx+MoEzWj/0GBgW33JkbPRS5fvBaA==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@tomo-inc/wallet-connect-sdk/-/wallet-connect-sdk-0.3.3.tgz", + "integrity": "sha512-dtfmwL3Rko73WqwbY8cyP793m4Lq/zssPEy0OOSuVl2y6ABvoQRw2Zh+9DhET6U9wt7NP/OYuYYm7F3rwST1fg==", "dependencies": { - "@tomo-inc/tomo-wallet-provider": "^1.0.21", + "@tomo-inc/tomo-wallet-provider": "^1.0.25", "animate.css": "^4.1.1", "buffer": "^6.0.3", "classnames": "^2.5.1", @@ -6663,8 +6662,6 @@ "immutability-helper": "^3.1.1", "jotai": "^2.9.0", "long": "^5.2.3", - "react": "^18", - "react-dom": "^18", "tailwind-merge": "2.2.2" }, "peerDependencies": { @@ -14679,9 +14676,9 @@ } }, "node_modules/jotai": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.1.tgz", - "integrity": "sha512-4FycO+BOTl2auLyF2Chvi6KTDqdsdDDtpaL/WHQMs8f3KS1E3loiUShQzAzFA/sMU5cJ0hz/RT1xum9YbG/zaA==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.3.tgz", + "integrity": "sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/package.json b/package.json index 292aa783..97363676 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "dependencies": { "@babylonlabs-io/babylon-proto-ts": "0.0.3-canary.3", "@babylonlabs-io/bbn-core-ui": "^0.2.0", - "@babylonlabs-io/bbn-wallet-connect": "^0.0.23", + "@babylonlabs-io/bbn-wallet-connect": "^0.1.6", "@babylonlabs-io/btc-staking-ts": "0.4.0-canary.3", "@bitcoin-js/tiny-secp256k1-asmjs": "2.2.3", "@bitcoinerlab/secp256k1": "^1.1.1", @@ -40,7 +40,7 @@ "@sentry/nextjs": "^8.30.0", "@tanstack/react-query": "^5.28.14", "@tanstack/react-query-next-experimental": "^5.28.14", - "@tomo-inc/wallet-connect-sdk": "0.2.16", + "@tomo-inc/wallet-connect-sdk": "^0.3.3", "@uidotdev/usehooks": "^2.4.1", "axios": "^1.7.4", "bitcoinjs-lib": "6.1.5", diff --git a/src/app/context/tomo/BBNConnector.tsx b/src/app/context/tomo/BBNConnector.tsx new file mode 100644 index 00000000..528b8b55 --- /dev/null +++ b/src/app/context/tomo/BBNConnector.tsx @@ -0,0 +1,81 @@ +import { + createExternalWallet, + IBBNProvider, + useChainConnector, + useWidgetState, +} from "@babylonlabs-io/bbn-wallet-connect"; +import { + CosmosProvider, + useTomoProviders, + useTomoWalletConnect, + useTomoWalletState, + useWalletList, +} from "@tomo-inc/wallet-connect-sdk"; +import { memo, useCallback, useEffect, useMemo } from "react"; + +const createProvider = (provider: CosmosProvider): IBBNProvider => { + return { + connectWallet: async () => void provider.connectWallet(), + getAddress: () => provider.getAddress(), + getPublicKeyHex: async () => "", + getSigningStargateClient: (options) => + provider.getSigningStargateClient(options), + getBalance: (searchDenom) => provider.getBalance(searchDenom), + }; +}; + +export const TomoBBNConnector = memo(() => { + const tomoWalletState = useTomoWalletState(); + const walletList = useWalletList(); + const { cosmosProvider: connectedProvider } = useTomoProviders(); + const tomoWalletConnect = useTomoWalletConnect(); + + const { visible } = useWidgetState(); + const connector = useChainConnector("BBN"); + + const connectedWallet = useMemo(() => { + const { connected, walletId } = tomoWalletState.cosmos ?? {}; + + return connected && walletId + ? (walletList.find((wallet: any) => wallet.id === walletId) ?? null) + : null; + }, [tomoWalletState.cosmos, walletList]); + + const connect = useCallback( + async (bbnWallet: any, bbnProvider: CosmosProvider) => { + if (!connector) return; + + const wallet = createExternalWallet({ + id: "tomo-bbn-connector", + name: bbnWallet.name, + icon: bbnWallet.img, + provider: createProvider(bbnProvider), + }); + + await connector.connect(wallet); + }, + [connector], + ); + + useEffect(() => { + if (visible && connectedWallet && connectedProvider) { + connect(connectedWallet, connectedProvider); + } + }, [visible, connectedWallet, connectedProvider, connect]); + + useEffect(() => { + if (!connector) return; + + const unsubscribe = connector.on("disconnect", (wallet) => { + if (wallet.id === "tomo-bbn-connector") { + tomoWalletConnect.disconnect(); + } + }); + + return unsubscribe; + }, [connector, tomoWalletConnect]); + + return null; +}); + +TomoBBNConnector.displayName = "TomoBBNConnector"; diff --git a/src/app/context/tomo/BTCConnector.tsx b/src/app/context/tomo/BTCConnector.tsx new file mode 100644 index 00000000..9a7b2c44 --- /dev/null +++ b/src/app/context/tomo/BTCConnector.tsx @@ -0,0 +1,104 @@ +import { + createExternalWallet, + IBTCProvider, + useChainConnector, + useWidgetState, +} from "@babylonlabs-io/bbn-wallet-connect"; +import { + BTCProvider, + useTomoProviders, + useTomoWalletConnect, + useTomoWalletState, + useWalletList, +} from "@tomo-inc/wallet-connect-sdk"; +import { memo, useCallback, useEffect, useMemo } from "react"; + +const createProvider = (provider: BTCProvider): IBTCProvider => { + return { + connectWallet: async () => void provider.connectWallet(), + getAddress: () => provider.getAddress(), + getPublicKeyHex: () => provider.getPublicKeyHex(), + signPsbt: (psbtHex: string) => provider.signPsbt(psbtHex), + signPsbts: (psbtsHexes: string[]) => provider.signPsbts(psbtsHexes), + getNetwork: () => provider.getNetwork(), + signMessageBIP322: (message: string) => provider.signMessageBIP322(message), + signMessage: (message: string, type: "ecdsa" | "bip322-simple") => + provider.signMessage(message, type), + on: (eventName: string, callBack: () => void) => + provider.on(eventName, callBack), + off: (eventName: string, callBack: () => void) => + provider.off(eventName, callBack), + getBalance: () => provider.getBalance(), + getNetworkFees: () => provider.getNetworkFees(), + pushTx: (txHex: string) => provider.pushTx(txHex), + getUtxos: (address: string, amount?: number) => + provider.getUtxos(address, amount), + getBTCTipHeight: () => provider.getBTCTipHeight(), + getInscriptions: () => + provider + .getInscriptions() + .then((result) => + result.list.map((ordinal) => ({ + txid: ordinal.inscriptionId, + vout: ordinal.outputValue, + })), + ) + .catch(() => []), + }; +}; + +export const TomoBTCConnector = memo(() => { + const tomoWalletState = useTomoWalletState(); + const walletList = useWalletList(); + const { bitcoinProvider: connectedProvider } = useTomoProviders(); + const tomoWalletConnect = useTomoWalletConnect(); + + const { visible } = useWidgetState(); + const connector = useChainConnector("BTC"); + + const connectedWallet = useMemo(() => { + const { connected, walletId } = tomoWalletState.bitcoin ?? {}; + + return connected && walletId + ? (walletList.find((wallet: any) => wallet.id === walletId) ?? null) + : null; + }, [tomoWalletState.bitcoin, walletList]); + + const connect = useCallback( + async (btcWallet: any, btcProvider: BTCProvider) => { + if (!connector) return; + + const wallet = createExternalWallet({ + id: "tomo-btc-connector", + name: btcWallet.name, + icon: btcWallet.img, + provider: createProvider(btcProvider), + }); + + await connector.connect(wallet); + }, + [connector], + ); + + useEffect(() => { + if (visible && connectedWallet && connectedProvider) { + connect(connectedWallet, connectedProvider); + } + }, [visible, connectedWallet, connectedProvider, connect]); + + useEffect(() => { + if (!connector) return; + + const unsubscribe = connector.on("disconnect", (wallet) => { + if (wallet.id === "tomo-btc-connector") { + tomoWalletConnect.disconnect(); + } + }); + + return unsubscribe; + }, [connector, tomoWalletConnect]); + + return null; +}); + +TomoBTCConnector.displayName = "TomoBTCConnector"; diff --git a/src/app/context/tomo/ConnectButton.tsx b/src/app/context/tomo/ConnectButton.tsx new file mode 100644 index 00000000..8660a726 --- /dev/null +++ b/src/app/context/tomo/ConnectButton.tsx @@ -0,0 +1,38 @@ +import { Text } from "@babylonlabs-io/bbn-core-ui"; +import { + useWidgetState, + WalletButton, +} from "@babylonlabs-io/bbn-wallet-connect"; +import { useTomoModalControl } from "@tomo-inc/wallet-connect-sdk"; +import { useCallback } from "react"; + +import logo from "./tomo.png"; + +const CHAINS = { + bitcoin: "BTC", + cosmos: "BBN", +}; + +export const ConnectButton = ({ + chainName, +}: { + chainName: "bitcoin" | "cosmos"; +}) => { + const tomoModal = useTomoModalControl(); + const { displayWallets } = useWidgetState(); + + const open = useCallback(async () => { + const result = await tomoModal.open(chainName); + + if (!result) { + displayWallets?.(CHAINS[chainName]); + } + }, [tomoModal, chainName, displayWallets]); + + return ( +
+ More wallets with Tomo Connect + +
+ ); +}; diff --git a/src/app/context/tomo/TomoProvider.tsx b/src/app/context/tomo/TomoProvider.tsx new file mode 100644 index 00000000..b931f2c5 --- /dev/null +++ b/src/app/context/tomo/TomoProvider.tsx @@ -0,0 +1,53 @@ +import { TomoContextProvider } from "@tomo-inc/wallet-connect-sdk"; +import "@tomo-inc/wallet-connect-sdk/style.css"; +import { useTheme } from "next-themes"; +import { type PropsWithChildren } from "react"; + +import { getNetworkConfig } from "@/config/network.config"; +import { bbnDevnet } from "@/config/wallet/babylon"; + +type ChainType = "bitcoin" | "cosmos"; +type ThemeType = "dark" | "light"; + +export const TomoConnectionProvider = ({ children }: PropsWithChildren) => { + const { resolvedTheme } = useTheme(); + + const { mempoolApiUrl, network, networkName } = getNetworkConfig(); + + const bitcoinChain = { + id: 1, + name: networkName, + type: "bitcoin" as ChainType, + network: network, + backendUrls: { + mempoolUrl: mempoolApiUrl + "/api/", + }, + }; + + const cosmosChain = { + id: 2, + name: bbnDevnet.chainName, + type: "cosmos" as ChainType, + network: bbnDevnet.chainId, + modularData: bbnDevnet, + backendUrls: { + rpcUrl: bbnDevnet.rpc, + }, + logo: bbnDevnet.chainSymbolImageUrl, + }; + + return ( + + {children} + + ); +}; diff --git a/src/app/context/tomo/tomo.png b/src/app/context/tomo/tomo.png new file mode 100644 index 00000000..54d2203e Binary files /dev/null and b/src/app/context/tomo/tomo.png differ diff --git a/src/app/context/wallet/BTCWalletProvider.tsx b/src/app/context/wallet/BTCWalletProvider.tsx index 5d1e4196..87022199 100644 --- a/src/app/context/wallet/BTCWalletProvider.tsx +++ b/src/app/context/wallet/BTCWalletProvider.tsx @@ -1,6 +1,6 @@ "use client"; import { - BTCProvider, + IBTCProvider, useChainConnector, useWalletConnect, UTXO, @@ -76,7 +76,7 @@ const BTCWalletContext = createContext({ }); export const BTCWalletProvider = ({ children }: PropsWithChildren) => { - const [btcWalletProvider, setBTCWalletProvider] = useState(); + const [btcWalletProvider, setBTCWalletProvider] = useState(); const [network, setNetwork] = useState(); const [publicKeyNoCoord, setPublicKeyNoCoord] = useState(""); const [address, setAddress] = useState(""); @@ -93,7 +93,7 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => { }, []); const connectBTC = useCallback( - async (walletProvider: BTCProvider | null) => { + async (walletProvider: IBTCProvider | null) => { if (!walletProvider) return; const supportedNetworkMessage = @@ -147,7 +147,9 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => { useEffect(() => { const unsubscribe = btcConnector?.on("connect", (wallet) => { - connectBTC(wallet.provider); + if (wallet.provider) { + connectBTC(wallet.provider); + } }); return unsubscribe; @@ -185,7 +187,7 @@ export const BTCWalletProvider = ({ children }: PropsWithChildren) => { btcWalletProvider?.getUtxos(address, amount) ?? [], getBTCTipHeight: async () => btcWalletProvider?.getBTCTipHeight() ?? 0, getInscriptions: async (): Promise => - btcWalletProvider?.getInscriptions() ?? [], + btcWalletProvider?.getInscriptions().catch(() => []) ?? [], }), [btcWalletProvider], ); diff --git a/src/app/context/wallet/CosmosWalletProvider.tsx b/src/app/context/wallet/CosmosWalletProvider.tsx index dc016a85..90b5d99b 100644 --- a/src/app/context/wallet/CosmosWalletProvider.tsx +++ b/src/app/context/wallet/CosmosWalletProvider.tsx @@ -1,4 +1,5 @@ "use client"; + import { BBNProvider, useChainConnector, @@ -17,6 +18,7 @@ import { import { useError } from "@/app/context/Error/ErrorContext"; import { ErrorState } from "@/app/types/errors"; +import { createBbnRegistry } from "@/utils/wallet/bbnRegistry"; interface CosmosWalletContextProps { bech32Address: string; @@ -59,7 +61,9 @@ export const CosmosWalletProvider = ({ children }: PropsWithChildren) => { try { const address = await provider.getAddress(); - const client = await provider.getSigningStargateClient(); + const client = await provider.getSigningStargateClient({ + registry: createBbnRegistry(), + }); setSigningStargateClient(client); setBBNWalletProvider(provider); setCosmosBech32Address(address); diff --git a/src/app/context/wallet/WalletConnectionProvider.tsx b/src/app/context/wallet/WalletConnectionProvider.tsx index d6cb4e3d..cd0009ef 100644 --- a/src/app/context/wallet/WalletConnectionProvider.tsx +++ b/src/app/context/wallet/WalletConnectionProvider.tsx @@ -1,17 +1,31 @@ "use client"; -import { Network, WalletProvider } from "@babylonlabs-io/bbn-wallet-connect"; +import { + ChainConfigArr, + Network, + WalletProvider, +} from "@babylonlabs-io/bbn-wallet-connect"; import { type PropsWithChildren } from "react"; +import { ConnectButton } from "@/app/context/tomo/ConnectButton"; +import { TomoConnectionProvider } from "@/app/context/tomo/TomoProvider"; import { ErrorState } from "@/app/types/errors"; import { bbnDevnet } from "@/config/wallet/babylon"; import { useError } from "../Error/ErrorContext"; +import { TomoBBNConnector } from "../tomo/BBNConnector"; +import { TomoBTCConnector } from "../tomo/BTCConnector"; const context = typeof window !== "undefined" ? window : {}; -const config = [ +const config: ChainConfigArr = [ { chain: "BTC", + connectors: [ + { + id: "tomo-btc-connector", + widget: () => , + }, + ], config: { coinName: "Signet BTC", coinSymbol: "sBTC", @@ -22,6 +36,12 @@ const config = [ }, { chain: "BBN", + connectors: [ + { + id: "tomo-bbn-connector", + widget: () => , + }, + ], config: { chainId: bbnDevnet.chainId, rpc: bbnDevnet.rpc, @@ -44,8 +64,12 @@ export const WalletConnectionProvider = ({ children }: PropsWithChildren) => { }; return ( - - {children} - + + + + + {children} + + ); }; diff --git a/src/app/globals.css b/src/app/globals.css index 3f145f2a..c0131192 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -108,6 +108,10 @@ body.modal-open { word-wrap: break-word; } +body > .tomo-social { + z-index: 100 !important; +} + @font-face { font-family: "Px Grotesk"; src: url("/fonts/Px-Grotesk/Px-Grotesk-Black.eot"); diff --git a/src/utils/wallet/bbnRegistry.ts b/src/utils/wallet/bbnRegistry.ts index 207a5a0a..46794b25 100644 --- a/src/utils/wallet/bbnRegistry.ts +++ b/src/utils/wallet/bbnRegistry.ts @@ -29,7 +29,7 @@ const createGeneratedType = (messageType: any): GeneratedType => { }; // Create the registry with the protos to register -export const getBbnRegistry = (): Registry => { +export const createBbnRegistry = (): Registry => { const registry = new Registry(); protosToRegister.forEach((proto) => {