From 2f87649cf991446a34a3a5c861542be4980919c4 Mon Sep 17 00:00:00 2001 From: Govard Barkhatov Date: Tue, 10 Dec 2024 18:36:33 +0200 Subject: [PATCH 1/4] changeset --- .changeset/red-mice-tease.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/red-mice-tease.md diff --git a/.changeset/red-mice-tease.md b/.changeset/red-mice-tease.md new file mode 100644 index 0000000..854d85b --- /dev/null +++ b/.changeset/red-mice-tease.md @@ -0,0 +1,5 @@ +--- +"@babylonlabs-io/bbn-wallet-connect": patch +--- + +OneKey wallet From 4adb6af69e476de82b3d009f86431f7edd48dc26 Mon Sep 17 00:00:00 2001 From: Govard Barkhatov Date: Tue, 10 Dec 2024 18:38:58 +0200 Subject: [PATCH 2/4] add onekey --- src/core/wallets/btc/index.ts | 3 +- src/core/wallets/btc/onekey/index.ts | 18 ++ src/core/wallets/btc/onekey/logo.svg | 12 ++ src/core/wallets/btc/onekey/provider.ts | 215 ++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 src/core/wallets/btc/onekey/index.ts create mode 100644 src/core/wallets/btc/onekey/logo.svg create mode 100644 src/core/wallets/btc/onekey/provider.ts diff --git a/src/core/wallets/btc/index.ts b/src/core/wallets/btc/index.ts index b7e6865..57c67e9 100644 --- a/src/core/wallets/btc/index.ts +++ b/src/core/wallets/btc/index.ts @@ -3,6 +3,7 @@ import icon from "./bitcoin.png"; import injectable from "./injectable"; import okx from "./okx"; +import onekey from "./onekey"; import type { ChainMetadata, BTCConfig } from "@/core/types"; @@ -10,7 +11,7 @@ const metadata: ChainMetadata<"BTC", BTCProvider, BTCConfig> = { chain: "BTC", name: "Bitcoin", icon, - wallets: [injectable, okx], + wallets: [injectable, okx, onekey], }; export default metadata; diff --git a/src/core/wallets/btc/onekey/index.ts b/src/core/wallets/btc/onekey/index.ts new file mode 100644 index 0000000..112b8c7 --- /dev/null +++ b/src/core/wallets/btc/onekey/index.ts @@ -0,0 +1,18 @@ +import { Network, type BTCConfig, type WalletMetadata } from "@/core/types"; + +import type { BTCProvider } from "../BTCProvider"; + +import logo from "./logo.svg"; +import { OneKeyProvider } from "./provider"; + +const metadata: WalletMetadata = { + id: "onekey", + name: "OneKey", + icon: logo, + docs: "https://onekey.so/download", + wallet: "$onekey", + createProvider: (wallet, config) => new OneKeyProvider(wallet, config), + networks: [Network.MAINNET, Network.SIGNET], +}; + +export default metadata; diff --git a/src/core/wallets/btc/onekey/logo.svg b/src/core/wallets/btc/onekey/logo.svg new file mode 100644 index 0000000..948efb1 --- /dev/null +++ b/src/core/wallets/btc/onekey/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/core/wallets/btc/onekey/provider.ts b/src/core/wallets/btc/onekey/provider.ts new file mode 100644 index 0000000..8191ba7 --- /dev/null +++ b/src/core/wallets/btc/onekey/provider.ts @@ -0,0 +1,215 @@ +import type { BTCConfig, Fees, InscriptionIdentifier, UTXO, WalletInfo } from "@/core/types"; +import { Network } from "@/core/types"; +import { validateAddress } from "@/core/utils/wallet"; +import { BTCProvider } from "@/core/wallets/btc/BTCProvider"; + +// Internal network names +const INTERNAL_NETWORK_NAMES = { + [Network.MAINNET]: "livenet", + [Network.TESTNET]: "testnet", + [Network.SIGNET]: "signet", +}; + +export class OneKeyProvider extends BTCProvider { + private provider: any; + private walletInfo: WalletInfo | undefined; + + constructor(wallet: any, config: BTCConfig) { + super(config); + + // check whether there is an OneKey extension + if (!wallet?.btcwallet) { + throw new Error("OneKey Wallet extension not found"); + } + + this.provider = wallet.btcwallet; + } + + connectWallet = async (): Promise => { + try { + await this.provider.connectWallet(); + } catch (error) { + if ((error as Error)?.message?.includes("rejected")) { + throw new Error("Connection to OneKey Wallet was rejected"); + } else { + throw new Error((error as Error)?.message); + } + } + + // TODO `window.$onekey.btcwallet.switchNetwork` does not support "signet" network at the moment + // switch (this.networkEnv) { + // case Network.MAINNET: + // await this.bitcoinNetworkProvider.switchNetwork( + // INTERNAL_NETWORK_NAMES.mainnet, + // ); + // break; + // case Network.TESTNET: + // await this.bitcoinNetworkProvider.switchNetwork( + // INTERNAL_NETWORK_NAMES.testnet, + // ); + // break; + // case Network.SIGNET: + // await this.bitcoinNetworkProvider.switchNetwork( + // INTERNAL_NETWORK_NAMES.signet, + // ); + // break; + // default: + // throw new Error("Unsupported network"); + // } + + const address = await this.provider.getAddress(); + validateAddress(this.config.network, address); + + const publicKeyHex = await this.provider.getPublicKeyHex(); + + if (publicKeyHex && address) { + this.walletInfo = { + publicKeyHex, + address, + }; + } else { + throw new Error("Could not connect to OneKey Wallet"); + } + }; + + getWalletProviderName = async (): Promise => { + return "OneKey"; + }; + + getAddress = async (): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + return this.walletInfo.address; + }; + + getPublicKeyHex = async (): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + return this.walletInfo.publicKeyHex; + }; + + signPsbt = async (psbtHex: string): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + if (!psbtHex) throw new Error("psbt hex is required"); + + return this.provider.signPsbt(psbtHex); + }; + + signPsbts = async (psbtsHexes: string[]): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + if (!psbtsHexes && !Array.isArray(psbtsHexes)) throw new Error("psbts hexes are required"); + + return this.provider.signPsbts(psbtsHexes); + }; + + getNetwork = async (): Promise => { + const internalNetwork = await this.provider.getNetwork(); + + for (const [key, value] of Object.entries(INTERNAL_NETWORK_NAMES)) { + // TODO remove as soon as OneKey implements + if (value === "testnet") { + // in case of testnet return signet + return Network.SIGNET; + } else if (value === internalNetwork) { + return key as Network; + } + } + + throw new Error("Unsupported network"); + }; + + signMessageBIP322 = async (message: string): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + return await this.provider.signMessageBIP322(message); + }; + + signMessage = async (message: string, type: "ecdsa" | "bip322-simple" = "ecdsa"): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + return await this.provider.signMessage(message, type); + }; + + on = (eventName: string, callBack: () => void) => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + // subscribe to account change event: `accountChanged` -> `accountsChanged` + if (eventName === "accountChanged") { + return this.provider.on("accountsChanged", callBack); + } + return this.provider.on(eventName, callBack); + }; + + off = (eventName: string, callBack: () => void) => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + // unsubscribe to account change event + if (eventName === "accountChanged") { + return this.provider.off("accountsChanged", callBack); + } + return this.provider.off(eventName, callBack); + }; + + // Mempool calls + getBalance = async (): Promise => { + return await this.mempool.getAddressBalance(await this.getAddress()); + }; + + getNetworkFees = async (): Promise => { + return await this.mempool.getNetworkFees(); + }; + + pushTx = async (txHex: string): Promise => { + return await this.mempool.pushTx(txHex); + }; + + getUtxos = async (address: string, amount: number): Promise => { + return await this.mempool.getFundingUTXOs(address, amount); + }; + + getBTCTipHeight = async (): Promise => { + return await this.mempool.getTipHeight(); + }; + + // Inscriptions are only available on OneKey Wallet BTC mainnet + getInscriptions = async (): Promise => { + if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); + + if (this.config.network !== Network.MAINNET) { + throw new Error("Inscriptions are only available on OneKey Wallet BTC Mainnet"); + } + + // max num of iterations to prevent infinite loop + const MAX_ITERATIONS = 100; + // Fetch inscriptions in batches of 100 + const limit = 100; + const inscriptionIdentifiers: InscriptionIdentifier[] = []; + let cursor = 0; + let iterations = 0; + try { + while (iterations < MAX_ITERATIONS) { + const { list } = await this.provider.getInscriptions(cursor, limit); + const identifiers = list.map((i: { output: string }) => { + const [txid, vout] = i.output.split(":"); + return { + txid, + vout, + }; + }); + inscriptionIdentifiers.push(...identifiers); + if (list.length < limit) { + break; + } + cursor += limit; + iterations++; + if (iterations >= MAX_ITERATIONS) { + throw new Error("Exceeded maximum iterations when fetching inscriptions"); + } + } + } catch { + throw new Error("Failed to get inscriptions from OneKey Wallet"); + } + + return inscriptionIdentifiers; + }; +} From 2c4f6ed3ed3cbd06cb468da3354148054abd5b34 Mon Sep 17 00:00:00 2001 From: Govard Barkhatov Date: Tue, 10 Dec 2024 18:41:32 +0200 Subject: [PATCH 3/4] comments --- src/core/wallets/btc/onekey/provider.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/wallets/btc/onekey/provider.ts b/src/core/wallets/btc/onekey/provider.ts index 8191ba7..53cb69b 100644 --- a/src/core/wallets/btc/onekey/provider.ts +++ b/src/core/wallets/btc/onekey/provider.ts @@ -3,7 +3,6 @@ import { Network } from "@/core/types"; import { validateAddress } from "@/core/utils/wallet"; import { BTCProvider } from "@/core/wallets/btc/BTCProvider"; -// Internal network names const INTERNAL_NETWORK_NAMES = { [Network.MAINNET]: "livenet", [Network.TESTNET]: "testnet", @@ -174,7 +173,6 @@ export class OneKeyProvider extends BTCProvider { // Inscriptions are only available on OneKey Wallet BTC mainnet getInscriptions = async (): Promise => { if (!this.walletInfo) throw new Error("OneKey Wallet not connected"); - if (this.config.network !== Network.MAINNET) { throw new Error("Inscriptions are only available on OneKey Wallet BTC Mainnet"); } From 6cdd26db9e4a06569a01996c7ae4d6f0222e8c4d Mon Sep 17 00:00:00 2001 From: Govard Barkhatov Date: Wed, 11 Dec 2024 10:36:37 +0200 Subject: [PATCH 4/4] extract issue --- src/core/wallets/btc/onekey/provider.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/core/wallets/btc/onekey/provider.ts b/src/core/wallets/btc/onekey/provider.ts index 53cb69b..c07f1c2 100644 --- a/src/core/wallets/btc/onekey/provider.ts +++ b/src/core/wallets/btc/onekey/provider.ts @@ -35,27 +35,6 @@ export class OneKeyProvider extends BTCProvider { } } - // TODO `window.$onekey.btcwallet.switchNetwork` does not support "signet" network at the moment - // switch (this.networkEnv) { - // case Network.MAINNET: - // await this.bitcoinNetworkProvider.switchNetwork( - // INTERNAL_NETWORK_NAMES.mainnet, - // ); - // break; - // case Network.TESTNET: - // await this.bitcoinNetworkProvider.switchNetwork( - // INTERNAL_NETWORK_NAMES.testnet, - // ); - // break; - // case Network.SIGNET: - // await this.bitcoinNetworkProvider.switchNetwork( - // INTERNAL_NETWORK_NAMES.signet, - // ); - // break; - // default: - // throw new Error("Unsupported network"); - // } - const address = await this.provider.getAddress(); validateAddress(this.config.network, address);