import type { Account, Address, SignPsbtParameters } from '@bigmi/core' import { base64ToHex, getAddressChainId, getAddressInfo, hexToBase64, MethodNotSupportedRpcError, ProviderNotFoundError, UserRejectedRequestError, withTimeout, } from '@bigmi/core' import { ConnectorChainIdDetectionError } from '../errors/connectors.js' import { createConnector } from '../factories/createConnector.js' import type { CreateConnectorFn } from '../types/connector.js' import type { ProviderRequestParams, UTXOConnectorParameters, UTXOWalletProvider, } from './types.js' type ReownConnectorProperties = { getAccounts(): Promise onAccountsChanged(accounts: Address[]): void getInternalProvider(): Promise } & UTXOWalletProvider type InternalReownProvider = { id: string name: string imageUrl?: string connector: BitcoinConnector address: Address | undefined } export declare enum AddressPurpose { Ordinal = 'ordinal', Payment = 'payment', Stacks = 'stx', } type BitcoinConnector = { connect(): Promise getAccountAddresses(): Promise< Array<{ address: string publicKey?: string purpose: AddressPurpose }> > signPSBT(params: { psbt: string signInputs: Array<{ address: string index: number sighashTypes: number[] }> broadcast?: boolean }): Promise<{ psbt: string; txid?: string }> } export type ReownWalletInfo = { name?: string icon?: string } export type ReownConnectorParameters = { connector: BitcoinConnector address?: Address walletInfo?: ReownWalletInfo } & UTXOConnectorParameters export function reown( parameters: ReownConnectorParameters ): CreateConnectorFn { const { chainId, shimDisconnect = true, connector, address, walletInfo, } = parameters // Generate connector id and name from wallet info const id = walletInfo?.name?.toLowerCase().replace(/\s+/g, '-') || 'reown' const name = walletInfo?.name || 'Reown Bitcoin Wallet' const imageUrl = walletInfo?.icon return createConnector< UTXOWalletProvider | undefined, ReownConnectorProperties >((config) => ({ id, name, type: reown.type, icon: imageUrl, async setup() { // }, async getInternalProvider() { // Create internal provider that wraps the BitcoinConnector const internalProvider: InternalReownProvider = { id, name, imageUrl, connector, address, } return internalProvider }, async getProvider() { const provider = await this.getInternalProvider() if (!provider) { return } const walletProvider = { request: this.request.bind(provider), } return walletProvider }, async request( this: InternalReownProvider, { method, params }: ProviderRequestParams ): Promise { switch (method) { case 'signPsbt': { const { psbt, ...options } = params as SignPsbtParameters const signInputs = options.inputsToSign.flatMap( ({ address, signingIndexes, sigHash }) => signingIndexes.map((index) => ({ address, index, sighashTypes: sigHash !== undefined ? [sigHash] : [], })) ) const psbtBase64 = hexToBase64(psbt) const result = await this.connector.signPSBT({ psbt: psbtBase64, signInputs, broadcast: false, }) const signedPsbtHex = base64ToHex(result.psbt) return signedPsbtHex } default: throw new MethodNotSupportedRpcError(method) } }, async connect() { const provider = await this.getInternalProvider() if (!provider) { throw new ProviderNotFoundError() } try { await provider.connector.connect() const accounts = await this.getAccounts() const detectedChainId = getAddressChainId(accounts[0].address) // Remove disconnected shim if it exists if (shimDisconnect) { await Promise.all([ config.storage?.setItem(`${this.id}.connected`, true), config.storage?.removeItem(`${this.id}.disconnected`), ]) } return { accounts, chainId: chainId ?? detectedChainId } } catch (error: any) { throw new UserRejectedRequestError(error.message) } }, async disconnect() { // Add shim signalling connector is disconnected 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 createAccount = (addr: string, publicKey = ''): Account => { const { type, purpose } = getAddressInfo(addr) return { address: addr, addressType: type, publicKey, purpose } } try { const accounts = await withTimeout( () => provider.connector.getAccountAddresses(), { timeout: 1000 } ) const paymentAccount = accounts.find((acc) => acc.purpose === 'payment') if (paymentAccount) { return [ createAccount( paymentAccount.address, paymentAccount.publicKey ?? '' ), ] } } catch { // getAccountAddresses not supported or timed out } // fallback to provided address if available if (provider.address) { return [createAccount(provider.address)] } throw new ProviderNotFoundError() }, async getChainId() { if (chainId) { return chainId } const accounts = await this.getAccounts() if (accounts.length === 0) { throw new ConnectorChainIdDetectionError({ connector: this.name }) } return getAddressChainId(accounts[0].address) }, async isAuthorized() { try { const isConnected = shimDisconnect && // Check storage to see if a connection exists already Boolean(await config.storage?.getItem(`${this.id}.connected`)) return isConnected } 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?) { // No need to remove `${this.id}.disconnected` from storage because `onDisconnect` is typically // only called when the wallet is disconnected through the wallet's interface, meaning the wallet // actually disconnected and we don't need to simulate it. config.emitter.emit('disconnect') }, })) } export declare namespace reown { export var type: 'UTXO' } reown.type = 'UTXO' as const