UNPKG

@bigmi/client

Version:

Reactive primitives for Bitcoin apps.

160 lines (159 loc) 12.8 kB
import { ConnectorChainIdDetectionError } from "../errors/connectors.js"; import { createConnector } from "../factories/createConnector.js"; import { debounce } from "../utils/debounce.js"; import { BaseError, ChainId, MethodNotSupportedRpcError, ProviderNotFoundError, UserRejectedRequestError, base64ToHex, getAddressInfo, hexToBase64 } from "@bigmi/core"; //#region src/connectors/unhosted.ts function unhosted(parameters = {}) { const UnhostedBitcoinChainIdMap = { Mainnet: ChainId.BITCOIN_MAINNET, Testnet: ChainId.BITCOIN_TESTNET, Testnet4: ChainId.BITCOIN_TESTNET4, Signet: ChainId.BITCOIN_SIGNET }; const { chainId, shimDisconnect = true } = parameters; let handleAccountsChanged; let handleChainChanged; let handleDisconnect; return createConnector((config) => ({ id: "unhosted.bitcoin", name: "Unhosted Wallet", type: unhosted.type, icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCI+CjxjaXJjbGUgY3g9IjY0IiBjeT0iNjQiIHI9IjU4IiBmaWxsPSIjMDQwNDA1Ii8+CjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ4LjI1LDIyLjc1KSBzY2FsZSgxLjUpIj4KPHBhdGggZD0iTTE1LjQ1ODggNDQuODMyNUMxNS40NTg4IDQ2LjM2MTYgMTUuMDIyOSA0Ny41NzE1IDE0LjE1NSA0OC40NTQyQzEzLjI4MzIgNDkuMzQxIDEyLjAyNjYgNDkuNzgyNCAxMC4zODUgNDkuNzgyNEM4Ljc0MzQ5IDQ5Ljc4MjQgNy40ODI4OSA0OS4zNDEgNi42MTUgNDguNDU0MkM1Ljc0MzE4IDQ3LjU3MTUgNS4zMTEyIDQ2LjM2MTYgNS4zMTEyIDQ0LjgzMjVWMjguNDA2NUgwLjU5MDgyVjQ0LjY4NjdDMC41OTA4MiA0Ni41OTggMC45Nzk2MDQgNDguMjUzMyAxLjc1MzI0IDQ5LjY1MjNDMi41MjY4OCA1MS4wNTE0IDMuNjQ2MTEgNTIuMTI3MiA1LjExMDkyIDUyLjg4QzYuNTc1NzMgNTMuNjMyNyA4LjMzMTE1IDU0LjAxMSAxMC4zODExIDU0LjAxMUMxMi40MzEgNTQuMDExIDE0LjE4NjUgNTMuNjMyNyAxNS42NTEzIDUyLjg4QzE3LjExNjEgNTIuMTI3MiAxOC4yMzUzIDUxLjA1MTQgMTkuMDA5IDQ5LjY1MjNDMTkuNzgyNiA0OC4yNTMzIDIwLjE3MTQgNDYuNTk4IDIwLjE3MTQgNDQuNjg2N1YyOC40MDY1SDE1LjQ1NDlWNDQuODMyNUgxNS40NTg4WiIgZmlsbD0iI0U5MzUzQSIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIwLjY3Mjk4IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTE1LjQ1ODggNDAuODkxNkMxNS40NTg4IDQyLjQyMDcgMTUuMDIyOSA0My42MzA2IDE0LjE1NSA0NC41MTMzQzEzLjI4MzIgNDUuNDAwMSAxMi4wMjY2IDQ1Ljg0MTUgMTAuMzg1IDQ1Ljg0MTVDOC43NDM0OSA0NS44NDE1IDcuNDgyODkgNDUuNDAwMSA2LjYxNSA0NC41MTMzQzUuNzQzMTggNDMuNjMwNiA1LjMxMTIgNDIuNDIwNyA1LjMxMTIgNDAuODkxNlYyNC40NjU2SDAuNTkwODJWNDAuNzQ1OEMwLjU5MDgyIDQyLjY1NzEgMC45Nzk2MDQgNDQuMzEyNCAxLjc1MzI0IDQ1LjcxMTRDMi41MjY4OCA0Ny4xMTA1IDMuNjQ2MTEgNDguMTg2MyA1LjExMDkyIDQ4LjkzOTFDNi41NzU3MyA0OS42OTE4IDguMzMxMTUgNTAuMDcwMSAxMC4zODExIDUwLjA3MDFDMTIuNDMxIDUwLjA3MDEgMTQuMTg2NSA0OS42OTE4IDE1LjY1MTMgNDguOTM5MUMxNy4xMTYxIDQ4LjE4NjMgMTguMjM1MyA0Ny4xMTA1IDE5LjAwOSA0NS43MTE0QzE5Ljc4MjYgNDQuMzEyNCAyMC4xNzE0IDQyLjY1NzEgMjAuMTcxNCA0MC43NDU4VjI0LjQ2NTZIMTUuNDU0OVY0MC44OTE2SDE1LjQ1ODhaIiBmaWxsPSIjRUE1OUY3IiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjAuNjcyOTgiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIvPgo8cGF0aCBkPSJNMTUuNDU4OCAzNi45NTA1QzE1LjQ1ODggMzguNDc5NiAxNS4wMjI5IDM5LjY4OTUgMTQuMTU1IDQwLjU3MjNDMTMuMjgzMiA0MS40NTkgMTIuMDI2NiA0MS45MDA0IDEwLjM4NSA0MS45MDA0QzguNzQzNDkgNDEuOTAwNCA3LjQ4Mjg5IDQxLjQ1OSA2LjYxNSA0MC41NzIzQzUuNzQzMTggMzkuNjg5NSA1LjMxMTIgMzguNDc5NiA1LjMxMTIgMzYuOTUwNVYyMC41MjQ1SDAuNTkwODJWMzYuODA0N0MwLjU5MDgyIDM4LjcxNjEgMC45Nzk2MDQgNDAuMzcxMyAxLjc1MzI0IDQxLjc3MDNDMi41MjY4OCA0My4xNjk0IDMuNjQ2MTEgNDQuMjQ1MyA1LjExMDkyIDQ0Ljk5OEM2LjU3NTczIDQ1Ljc1MDcgOC4zMzExNSA0Ni4xMjkxIDEwLjM4MTEgNDYuMTI5MUMxMi40MzEgNDYuMTI5MSAxNC4xODY1IDQ1Ljc1MDcgMTUuNjUxMyA0NC45OThDMTcuMTE2MSA0NC4yNDUzIDE4LjIzNTMgNDMuMTY5NCAxOS4wMDkgNDEuNzcwM0MxOS43ODI2IDQwLjM3MTMgMjAuMTcxNCAzOC43MTYxIDIwLjE3MTQgMzYuODA0N1YyMC41MjQ1SDE1LjQ1NDlWMzYuOTUwNUgxNS40NTg4WiIgZmlsbD0iIzYzMzNENyIgc3Ryb2tlPSJibGFjayIgc3Ryb2tlLXdpZHRoPSIwLjY3Mjk4IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiLz4KPHBhdGggZD0iTTE1LjQ1ODggMzMuMDA5NkMxNS40NTg4IDM0LjUzODcgMTUuMDIyOSAzNS43NDg2IDE0LjE1NSAzNi42MzE0QzEzLjI4MzIgMzcuNTE4MSAxMi4wMjY2IDM3Ljk1OTUgMTAuMzg1IDM3Ljk1OTVDOC43NDM0OSAzNy45NTk1IDcuNDgyODkgMzcuNTE4MSA2LjYxNSAzNi42MzE0QzUuNzQzMTggMzUuNzQ4NiA1LjMxMTIgMzQuNTM4NyA1LjMxMTIgMzMuMDA5NlYxNi41ODM2SDAuNTkwODJWMzIuODYzOEMwLjU5MDgyIDM0Ljc3NTIgMC45Nzk2MDQgMzYuNDMwNCAxLjc1MzI0IDM3LjgyOTRDMi41MjY4OCAzOS4yMjg1IDMuNjQ2MTEgNDAuMzA0NCA1LjExMDkyIDQxLjA1NzFDNi41NzU3MyA0MS44MDk4IDguMzMxMTUgNDIuMTg4MiAxMC4zODExIDQyLjE4ODJDMTIuNDMxIDQyLjE4ODIgMTQuMTg2NSA0MS44MDk4IDE1LjY1MTMgNDEuMDU3MUMxNy4xMTYxIDQwLjMwNDQgMTguMjM1MyAzOS4yMjg1IDE5LjAwOSAzNy44Mjk0QzE5Ljc4MjYgMzYuNDMwNCAyMC4xNzE0IDM0Ljc3NTIgMjAuMTcxNCAzMi44NjM4VjE2LjU4MzZIMTUuNDU0OVYzMy4wMDk2SDE1LjQ1ODhaIiBmaWxsPSIjMjI4N0VEIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjAuNjcyOTgiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIvPgo8cGF0aCBkPSJNMTUuNDU4OCAyOS4wNjg4QzE1LjQ1ODggMzAuNTk3OSAxNS4wMjI5IDMxLjgwNzcgMTQuMTU1IDMyLjY5MDVDMTMuMjgzMiAzMy41NzcyIDEyLjAyNjYgMzQuMDE4NiAxMC4zODUgMzQuMDE4NkM4Ljc0MzQ5IDM0LjAxODYgNy40ODI4OSAzMy41NzcyIDYuNjE1IDMyLjY5MDVDNS43NDMxOCAzMS44MDc3IDUuMzExMiAzMC41OTc5IDUuMzExMiAyOS4wNjg4VjEyLjY0MjdIMC41OTA4MlYyOC45MjI5QzAuNTkwODIgMzAuODM0MyAwLjk3OTYwNCAzMi40ODk1IDEuNzUzMjQgMzMuODg4NkMyLjUyNjg4IDM1LjI4NzYgMy42NDYxMSAzNi4zNjM1IDUuMTEwOTIgMzcuMTE2MkM2LjU3NTczIDM3Ljg2OSA4LjMzMTE1IDM4LjI0NzMgMTAuMzgxMSAzOC4yNDczQzEyLjQzMSAzOC4yNDczIDE0LjE4NjUgMzcuODY5IDE1LjY1MTMgMzcuMTE2MkMxNy4xMTYxIDM2LjM2MzUgMTguMjM1MyAzNS4yODc2IDE5LjAwOSAzMy44ODg2QzE5Ljc4MjYgMzIuNDg5NSAyMC4xNzE0IDMwLjgzNDMgMjAuMTcxNCAyOC45MjI5VjEyLjY0MjdIMTUuNDU0OVYyOS4wNjg4SDE1LjQ1ODhaIiBmaWxsPSIjRkNGMDVFIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjAuNjcyOTgiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIvPgo8cGF0aCBkPSJNMTUuNDU4OCAyNS4xMjc3QzE1LjQ1ODggMjYuNjU2OCAxNS4wMjI5IDI3Ljg2NjYgMTQuMTU1IDI4Ljc0OTRDMTMuMjgzMiAyOS42MzYxIDEyLjAyNjYgMzAuMDc3NSAxMC4zODUgMzAuMDc3NUM4Ljc0MzQ5IDMwLjA3NzUgNy40ODI4OSAyOS42MzYxIDYuNjE1IDI4Ljc0OTRDNS43NDMxOCAyNy44NjY2IDUuMzExMiAyNi42NTY4IDUuMzExMiAyNS4xMjc3VjguNzAxNjRIMC41OTA4MlYyNC45ODE4QzAuNTkwODIgMjYuODkzMiAwLjk3OTYwNCAyOC41NDg0IDEuNzUzMjQgMjkuOTQ3NUMyLjUyNjg4IDMxLjM0NjUgMy42NDYxMSAzMi40MjI0IDUuMTEwOTIgMzMuMTc1MUM2LjU3NTczIDMzLjkyNzkgOC4zMzExNSAzNC4zMDYyIDEwLjM4MTEgMzQuMzA2MkMxMi40MzEgMzQuMzA2MiAxNC4xODY1IDMzLjkyNzkgMTUuNjUxMyAzMy4xNzUxQzE3LjExNjEgMzIuNDIyNCAxOC4yMzUzIDMxLjM0NjUgMTkuMDA5IDI5Ljk0NzVDMTkuNzgyNiAyOC41NDg0IDIwLjE3MTQgMjYuODkzMiAyMC4xNzE0IDI0Ljk4MThWOC43MDE2NEgxNS40NTQ5VjI1LjEyNzdIMTUuNDU4OFoiIGZpbGw9IiNCRUZBNjEiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMC42NzI5OCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjxwYXRoIGQ9Ik0xNS40NTg4IDIxLjE4NjhDMTUuNDU4OCAyMi43MTU5IDE1LjAyMjkgMjMuOTI1OCAxNC4xNTUgMjQuODA4NkMxMy4yODMyIDI1LjY5NTMgMTIuMDI2NiAyNi4xMzY3IDEwLjM4NSAyNi4xMzY3QzguNzQzNDkgMjYuMTM2NyA3LjQ4Mjg5IDI1LjY5NTMgNi42MTUgMjQuODA4NkM1Ljc0MzE4IDIzLjkyNTggNS4zMTEyIDIyLjcxNTkgNS4zMTEyIDIxLjE4NjhWNC43NjA3N0gwLjU5MDgyVjIxLjA0MUMwLjU5MDgyIDIyLjk1MjQgMC45Nzk2MDQgMjQuNjA3NiAxLjc1MzI0IDI2LjAwNjZDMi41MjY4OCAyNy40MDU3IDMuNjQ2MTEgMjguNDgxNSA1LjExMDkyIDI5LjIzNDNDNi41NzU3MyAyOS45ODcgOC4zMzExNSAzMC4zNjUzIDEwLjM4MTEgMzAuMzY1M0MxMi40MzEgMzAuMzY1MyAxNC4xODY1IDI5Ljk4NyAxNS42NTEzIDI5LjIzNDNDMTcuMTE2MSAyOC40ODE1IDE4LjIzNTMgMjcuNDA1NyAxOS4wMDkgMjYuMDA2NkMxOS43ODI2IDI0LjYwNzYgMjAuMTcxNCAyMi45NTI0IDIwLjE3MTQgMjEuMDQxVjQuNzYwNzdIMTUuNDU0OVYyMS4xODY4SDE1LjQ1ODhaIiBmaWxsPSIjNUJFQzdCIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjAuNjcyOTgiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIvPgo8cGF0aCBkPSJNMTUuNDU4OCAxNy4yNDU3QzE1LjQ1ODggMTguNzc0OCAxNS4wMjI5IDE5Ljk4NDcgMTQuMTU1IDIwLjg2NzVDMTMuMjgzMiAyMS43NTQyIDEyLjAyNjYgMjIuMTk1NiAxMC4zODUgMjIuMTk1NkM4Ljc0MzQ5IDIyLjE5NTYgNy40ODI4OSAyMS43NTQyIDYuNjE1IDIwLjg2NzVDNS43NDMxOCAxOS45ODQ3IDUuMzExMiAxOC43NzQ4IDUuMzExMiAxNy4yNDU3VjAuODE5NjcySDAuNTkwODJWMTcuMDk5OUMwLjU5MDgyIDE5LjAxMTIgMC45Nzk2MDQgMjAuNjY2NSAxLjc1MzI0IDIyLjA2NTVDMi41MjY4OCAyMy40NjQ2IDMuNjQ2MTEgMjQuNTQwNCA1LjExMDkyIDI1LjI5MzJDNi41NzU3MyAyNi4wNDU5IDguMzMxMTUgMjYuNDI0MiAxMC4zODExIDI2LjQyNDJDMTIuNDMxIDI2LjQyNDIgMTQuMTg2NSAyNi4wNDU5IDE1LjY1MTMgMjUuMjkzMkMxNy4xMTYxIDI0LjU0MDQgMTguMjM1MyAyMy40NjQ2IDE5LjAwOSAyMi4wNjU1QzE5Ljc4MjYgMjAuNjY2NSAyMC4xNzE0IDE5LjAxMTIgMjAuMTcxNCAxNy4wOTk5VjAuODE5NjcySDE1LjQ1NDlWMTcuMjQ1N0gxNS40NTg4WiIgZmlsbD0id2hpdGUiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMC42NzI5OCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIi8+CjwvZz4KPC9zdmc+Cg==", async setup() {}, async getInternalProvider() { if (typeof window === "undefined") return; const anyWindow = window; if ("unhosted" in window && anyWindow.unhosted?.bitcoin) { const provider = anyWindow.unhosted.bitcoin; if (provider.isUnhosted) return provider; } if ("btc" in window && anyWindow.btc) { const provider = anyWindow.btc; if (provider.isUnhosted) return provider; } if ("BitcoinProvider" in window && anyWindow.BitcoinProvider) { const provider = anyWindow.BitcoinProvider; if (provider.isUnhosted) return provider; } }, async getProvider() { const internalProvider = await this.getInternalProvider(); if (!internalProvider) return; return { request: this.request.bind(internalProvider) }; }, async request({ method, params }) { switch (method) { case "signPsbt": { const { psbt, inputsToSign, finalize } = params; const psbtBase64 = hexToBase64(psbt); const signInputs = {}; for (const input of inputsToSign) signInputs[input.address] = input.signingIndexes; try { const { result } = await this.signPsbt(psbtBase64, { signInputs, broadcast: Boolean(finalize) }); if (!result.psbt) throw new BaseError("Failed to sign PSBT"); return base64ToHex(result.psbt); } catch (err_) { const err = err_; if (err?.code === 4001) throw new UserRejectedRequestError("User rejected the signing request"); if (err_ instanceof BaseError) throw err_; throw new BaseError(err?.message || "Unknown error", { cause: err_ }); } } default: throw new MethodNotSupportedRpcError(method); } }, async connect({ isReconnecting } = {}) { const provider = await this.getInternalProvider(); if (!provider) throw new ProviderNotFoundError(); if (!isReconnecting) await provider.wallet_connect(); const accounts = await this.getAccounts(); const chainId = await this.getChainId(); if (!handleAccountsChanged) { handleAccountsChanged = debounce(this.onAccountsChanged.bind(this), 100); provider.on("bitcoin:accountsChanged", handleAccountsChanged); } if (!handleChainChanged) { handleChainChanged = (network) => { this.onChainChanged(UnhostedBitcoinChainIdMap[network.name]); }; provider.on("bitcoin:networkChanged", handleChainChanged); } if (!handleDisconnect) { handleDisconnect = this.onDisconnect.bind(this); provider.on("bitcoin:disconnect", handleDisconnect); } if (shimDisconnect) await Promise.all([config.storage?.setItem(`${this.id}.connected`, true), config.storage?.removeItem(`${this.id}.disconnected`)]); return { accounts, chainId }; }, async disconnect() { const provider = await this.getInternalProvider(); if (handleAccountsChanged) { provider?.off?.("bitcoin:accountsChanged", handleAccountsChanged); handleAccountsChanged = void 0; } if (handleChainChanged) { provider?.off?.("bitcoin:networkChanged", handleChainChanged); handleChainChanged = void 0; } if (handleDisconnect) { provider?.off?.("bitcoin:disconnect", handleDisconnect); handleDisconnect = void 0; } if (shimDisconnect) await Promise.all([config.storage?.setItem(`${this.id}.disconnected`, true), config.storage?.removeItem(`${this.id}.connected`)]); }, async getAccounts() { const provider = await this.getInternalProvider(); if (!provider) throw new ProviderNotFoundError(); const { result } = await provider.getAccounts(); if (!result) return []; return result.map((wallet) => { const { type, purpose } = getAddressInfo(wallet.address); return { address: wallet.address, addressType: type, publicKey: wallet.publicKey, purpose }; }); }, async getChainId() { if (chainId) return chainId; const provider = await this.getInternalProvider(); if (!provider) throw new ProviderNotFoundError(); const { result } = await provider.wallet_getNetwork(); const detectedChainId = UnhostedBitcoinChainIdMap[result.bitcoin.name]; if (detectedChainId === void 0) throw new ConnectorChainIdDetectionError({ connector: this.name }); return detectedChainId; }, async isAuthorized() { try { return shimDisconnect && Boolean(await config.storage?.getItem(`${this.id}.connected`)); } catch { return false; } }, async onAccountsChanged(accounts) { if (accounts.length === 0) this.onDisconnect(); else { const newAccounts = await this.getAccounts(); config.emitter.emit("change", { accounts: newAccounts }); } }, onChainChanged(chainId) { config.emitter.emit("change", { chainId }); }, async onDisconnect(_error) { config.emitter.emit("disconnect"); } })); } unhosted.type = "UTXO"; //#endregion export { unhosted }; //# sourceMappingURL=unhosted.js.map