diff --git a/.changeset/kind-parents-hug.md b/.changeset/kind-parents-hug.md new file mode 100644 index 0000000000..3f554b3b50 --- /dev/null +++ b/.changeset/kind-parents-hug.md @@ -0,0 +1,7 @@ +--- +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-utils': patch +'@reown/appkit-controllers': patch +--- + +Fixes issue where OKX wallet would not target the Signet network correctly. diff --git a/packages/adapters/bitcoin/src/adapter.ts b/packages/adapters/bitcoin/src/adapter.ts index 25d73a0942..418b58700d 100644 --- a/packages/adapters/bitcoin/src/adapter.ts +++ b/packages/adapters/bitcoin/src/adapter.ts @@ -56,33 +56,39 @@ export class BitcoinAdapter extends AdapterBlueprint { throw new Error('The connector does not support any of the requested chains') } - const connection = this.getConnection({ - address: params.address, - connectorId: connector.id, - connections: this.connections, - connectors: this.connectors - }) - - if (connection?.account) { - this.emit('accountChanged', { - address: connection.account.address, - chainId: connection.caipNetwork?.id, - connector + // If we are already connected to the address, return the connection to prevent extra interactions + if (params.address) { + const connection = this.getConnection({ + address: params.address, + connectorId: connector.id, + connections: this.connections, + connectors: this.connectors }) - return { - id: connector.id, - type: connector.type, - address: connection.account.address, - chainId: chain.id, - provider: connector.provider + if (connection?.account) { + this.emit('accountChanged', { + address: connection.account.address, + chainId: connection.caipNetwork?.id, + connector + }) + + return { + id: connector.id, + type: connector.type, + address: connection.account.address, + chainId: chain.id, + provider: connector.provider + } } } - const address = await connector.connect().catch(err => { + const address = await connector.connect({ caipNetworkId: chain.caipNetworkId }).catch(err => { throw new UserRejectedRequestError(err) }) - const accounts = await this.getAccounts({ id: connector.id }) + const accounts = await this.getAccounts({ + id: connector.id, + caipNetworkId: chain.caipNetworkId + }) this.emit('accountChanged', { address, @@ -119,7 +125,7 @@ export class BitcoinAdapter extends AdapterBlueprint { ): Promise { const addresses = await this.connectors .find(connector => connector.id === params.id) - ?.getAccountAddresses() + ?.getAccountAddresses({ caipNetworkId: params.caipNetworkId }) .catch(() => []) let accounts = addresses?.map(a => @@ -180,13 +186,13 @@ export class BitcoinAdapter extends AdapterBlueprint { }) ) - const okxConnector = OKXConnector.getWallet({ + const okxConnector = new OKXConnector({ requestedChains: this.networks, requestedCaipNetworkId: ChainController.getActiveCaipNetwork(ConstantsUtil.CHAIN.BITCOIN) ?.caipNetworkId }) - if (okxConnector) { + if (CoreHelperUtil.isClient()) { this.addConnector(okxConnector) } @@ -220,7 +226,6 @@ export class BitcoinAdapter extends AdapterBlueprint { const { address } = CoreHelperUtil.getAccount(accounts[0]) const allAccounts = await this.getAccounts({ id: connectorId }) - const connection = this.getConnection({ connectorId, connections: this.connections, @@ -570,6 +575,11 @@ export class BitcoinAdapter extends AdapterBlueprint { if (!connector) { throw new Error('BitcoinAdapter:onChainChanged - connector is undefined') } + const chain = connector.chains.find(c => c.id === chainId) || connector.chains[0] + + if (!chain) { + throw new Error('BitcoinAdapter:onChainChanged - chain is undefined') + } const { address } = await this.connect({ id: connector.id, @@ -577,17 +587,10 @@ export class BitcoinAdapter extends AdapterBlueprint { type: '' }) - const accounts = await this.getAccounts({ id: connector.id }) - const chain = connector.chains.find(c => c.id === chainId) || connector.chains[0] - - if ( - HelpersUtil.isLowerCaseMatch( - this.getConnectorId(CommonConstantsUtil.CHAIN.BITCOIN), - connector.id - ) - ) { - this.emit('switchNetwork', { chainId, address }) - } + const accounts = await this.getAccounts({ + id: connector.id, + caipNetworkId: chain?.caipNetworkId + }) this.addConnection({ connectorId: connector.id, @@ -599,6 +602,15 @@ export class BitcoinAdapter extends AdapterBlueprint { })), caipNetwork: chain }) + + if ( + HelpersUtil.isLowerCaseMatch( + this.getConnectorId(CommonConstantsUtil.CHAIN.BITCOIN), + connector.id + ) + ) { + this.emit('switchNetwork', { chainId: chain.id, address }) + } } // -- Private ------------------------------------------ // diff --git a/packages/adapters/bitcoin/src/connectors/OKXConnector.ts b/packages/adapters/bitcoin/src/connectors/OKXConnector.ts index 29a93d08d5..bae826cdff 100644 --- a/packages/adapters/bitcoin/src/connectors/OKXConnector.ts +++ b/packages/adapters/bitcoin/src/connectors/OKXConnector.ts @@ -1,13 +1,14 @@ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/require-await */ import { type CaipNetwork, + type CaipNetworkId, ConstantsUtil as CommonConstantsUtil, ConstantsUtil } from '@reown/appkit-common' import { ChainController, CoreHelperUtil, type RequestArguments } from '@reown/appkit-controllers' import { PresetsUtil } from '@reown/appkit-utils' import type { BitcoinConnector } from '@reown/appkit-utils/bitcoin' -import { bitcoin, bitcoinTestnet } from '@reown/appkit/networks' +import { bitcoin, bitcoinSignet, bitcoinTestnet } from '@reown/appkit/networks' import { MethodNotSupportedError } from '../errors/MethodNotSupportedError.js' import { AddressPurpose } from '../utils/BitcoinConnector.js' @@ -16,9 +17,21 @@ import { UnitsUtil } from '../utils/UnitsUtil.js' const OKX_NETWORK_KEYS = { [bitcoin.caipNetworkId]: 'bitcoin', - [bitcoinTestnet.caipNetworkId]: 'bitcoinTestnet' + [bitcoinTestnet.caipNetworkId]: 'bitcoinTestnet', + [bitcoinSignet.caipNetworkId]: 'bitcoinSignet' } as const +declare global { + interface Window { + okxwallet?: { + bitcoin?: OKXConnector.Wallet + bitcoinTestnet?: OKXConnector.Wallet + bitcoinSignet?: OKXConnector.Wallet + cardano?: { icon: string } + } + } +} + export class OKXConnector extends ProviderEventEmitter implements BitcoinConnector { public readonly id = 'OKX' public readonly name = 'OKX Wallet' @@ -26,53 +39,67 @@ export class OKXConnector extends ProviderEventEmitter implements BitcoinConnect public readonly type = 'ANNOUNCED' public readonly explorerId = PresetsUtil.ConnectorExplorerIds[CommonConstantsUtil.CONNECTOR_ID.OKX] - public readonly imageUrl: string public readonly requestedCaipNetworkId?: CaipNetwork['caipNetworkId'] + public readonly imageUrl: string public readonly provider = this - private wallet: OKXConnector.Wallet private readonly requestedChains: CaipNetwork[] = [] - constructor({ - wallet, - requestedChains, - imageUrl, - requestedCaipNetworkId - }: OKXConnector.ConstructorParams) { + constructor({ requestedChains, requestedCaipNetworkId }: OKXConnector.ConstructorParams) { super() - this.wallet = wallet this.requestedChains = requestedChains - this.imageUrl = imageUrl this.requestedCaipNetworkId = requestedCaipNetworkId + this.imageUrl = typeof window === 'undefined' ? '' : window.okxwallet?.cardano?.icon || '' } public get chains() { - return this.requestedChains.filter( - chain => - chain.caipNetworkId === - ChainController.getActiveCaipNetwork(ConstantsUtil.CHAIN.BITCOIN)?.caipNetworkId - ) + return this.requestedChains } - public async connect(): Promise { - const result = await this.wallet.connect() + public async connect(params?: { caipNetworkId?: CaipNetworkId }): Promise { + const caipNetworkId = + params?.caipNetworkId ?? + ChainController.getActiveCaipNetwork(ConstantsUtil.CHAIN.BITCOIN)?.caipNetworkId - this.bindEvents() + if (!caipNetworkId) { + throw new Error('No active network available') + } - this.emit('accountsChanged', [result.address]) + const currentWallet = this.getWallet() + if (currentWallet) { + this.unbindEvents({ wallet: currentWallet }) + } + const wallet = this.getWallet({ requestedCaipNetworkId: caipNetworkId }) + + this.bindEvents({ wallet }) + + const result = await wallet.connect() return result.address } public async disconnect(): Promise { - this.unbindEvents() - await this.wallet.disconnect() + const wallet = this.getWallet() + await wallet.disconnect() + this.unbindEvents({ wallet }) } - public async getAccountAddresses(): Promise { - const accounts = await this.wallet.getAccounts() - const publicKeyOfActiveAccount = await this.wallet.getPublicKey() + public async getAccountAddresses(params?: { + caipNetworkId?: CaipNetwork['caipNetworkId'] + }): Promise { + const caipNetworkId = + params?.caipNetworkId ?? + ChainController.getActiveCaipNetwork(ConstantsUtil.CHAIN.BITCOIN)?.caipNetworkId + + const wallet = this.getWallet({ requestedCaipNetworkId: caipNetworkId }) + + if (caipNetworkId === bitcoinSignet.caipNetworkId) { + return [wallet.selectedAccount] + } + + const accounts = await wallet.getAccounts() + const publicKeyOfActiveAccount = await wallet.getPublicKey() const accountList = accounts.map(account => ({ address: account, @@ -86,7 +113,9 @@ export class OKXConnector extends ProviderEventEmitter implements BitcoinConnect public async signMessage(params: BitcoinConnector.SignMessageParams): Promise { const protocol = params.protocol === 'bip322' ? 'bip322-simple' : params.protocol - return this.wallet.signMessage(params.message, protocol) + const wallet = this.getWallet() + + return wallet.signMessage(params.message, protocol) } public async sendTransfer(params: BitcoinConnector.SendTransferParams): Promise { @@ -96,13 +125,16 @@ export class OKXConnector extends ProviderEventEmitter implements BitcoinConnect throw new Error('No active network available') } - const from = (await this.wallet.getAccounts())[0] + const requestedCaipNetworkId = network.caipNetworkId ?? this.requestedCaipNetworkId + const wallet = this.getWallet({ requestedCaipNetworkId }) + + const from = (await wallet.getAccounts())[0] if (!from) { throw new Error('No account available') } - const result = await this.wallet.send({ + const result = await wallet.send({ from, to: params.recipient, value: UnitsUtil.parseSatoshis(params.amount, network) @@ -130,14 +162,16 @@ export class OKXConnector extends ProviderEventEmitter implements BitcoinConnect } } - const signedPsbtHex = await this.wallet.signPsbt(psbtHex, options) + const wallet = this.getWallet() + + const signedPsbtHex = await wallet.signPsbt(psbtHex, options) let txid: string | undefined = undefined if (params.broadcast) { if (params.signInputs?.length > 0) { throw new Error('Broadcast not supported for partial signing') } - txid = await this.wallet.pushPsbt(signedPsbtHex) + txid = await wallet.pushPsbt(signedPsbtHex) } return { @@ -147,20 +181,16 @@ export class OKXConnector extends ProviderEventEmitter implements BitcoinConnect } public async switchNetwork(_caipNetworkId: CaipNetwork['caipNetworkId']): Promise { - const connector = OKXConnector.getWallet({ - requestedChains: this.requestedChains, - requestedCaipNetworkId: _caipNetworkId - }) - - if (!connector) { - throw new Error(`${this.name} wallet does not support network switching`) - } - - this.unbindEvents() - this.wallet = connector.wallet - try { - await this.connect() + const currentWallet = this.getWallet() + if (currentWallet) { + this.unbindEvents({ wallet: currentWallet }) + } + const chain = this.chains.find(c => c.caipNetworkId === _caipNetworkId) + if (!chain) { + throw new Error(`Chain not found: ${_caipNetworkId}`) + } + this.emit('chainChanged', chain.id) } catch (error) { throw new Error(`${this.name} wallet does not support network switching`) } @@ -170,66 +200,58 @@ export class OKXConnector extends ProviderEventEmitter implements BitcoinConnect return Promise.reject(new MethodNotSupportedError(this.id, 'request')) } - private bindEvents(): void { - this.unbindEvents() + private bindEvents({ wallet }: { wallet: OKXConnector.Wallet }): void { + this.unbindEvents({ wallet }) - this.wallet.on('accountChanged', account => { + wallet.on('accountChanged', account => { if (typeof account === 'object' && account && 'address' in account) { this.emit('accountsChanged', [account.address]) } }) - this.wallet.on('disconnect', () => { + wallet.on('disconnect', () => { this.emit('disconnect') }) } - private unbindEvents(): void { - this.wallet.removeAllListeners() + private unbindEvents({ wallet }: { wallet: OKXConnector.Wallet }): void { + wallet.removeAllListeners() } - public static getWallet(params: OKXConnector.GetWalletParams): OKXConnector | undefined { - if (!CoreHelperUtil.isClient()) { - return undefined - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let wallet: any = undefined - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const okxwallet = (window as any)?.okxwallet + public getWallet(params?: OKXConnector.GetWalletParams): OKXConnector.Wallet { + const requestedCaipNetworkId = + params?.requestedCaipNetworkId ?? + ChainController.getActiveCaipNetwork(ConstantsUtil.CHAIN.BITCOIN)?.caipNetworkId ?? + this.requestedCaipNetworkId - const networkKey = - OKX_NETWORK_KEYS[params.requestedCaipNetworkId as keyof typeof OKX_NETWORK_KEYS] + if (CoreHelperUtil.isClient()) { + const okxwallet = window.okxwallet + const networkKey = OKX_NETWORK_KEYS[requestedCaipNetworkId as keyof typeof OKX_NETWORK_KEYS] - wallet = okxwallet?.[networkKey] || okxwallet?.bitcoin + const wallet = okxwallet?.[networkKey] || okxwallet?.bitcoin - /** - * OKX doesn't provide a way to get the image URL specifally for bitcoin - * so we use the icon for cardano as a fallback - */ - const imageUrl = okxwallet?.cardano?.icon || '' - - if (wallet) { - return new OKXConnector({ wallet, imageUrl, ...params }) + if (wallet) { + return wallet + } } - return undefined + throw new Error('No wallet available') } public async getPublicKey(): Promise { - return this.wallet.getPublicKey() + const wallet = this.getWallet() + + return wallet.getPublicKey() } } export namespace OKXConnector { export type ConstructorParams = { - wallet: Wallet requestedChains: CaipNetwork[] - imageUrl: string requestedCaipNetworkId?: CaipNetwork['caipNetworkId'] } export type Wallet = { + selectedAccount: BitcoinConnector.AccountAddress /* * This interface doesn't include all available methods * Reference: https://www.okx.com/web3/build/docs/sdks/chains/bitcoin/provider @@ -264,7 +286,7 @@ export namespace OKXConnector { getPublicKey(): Promise } - export type GetWalletParams = Omit + export type GetWalletParams = Omit export type SignPSBTParams = { toSignInputs: Omit[] diff --git a/packages/adapters/bitcoin/src/connectors/UnisatConnector/index.ts b/packages/adapters/bitcoin/src/connectors/UnisatConnector/index.ts index cb95db479d..a24dda1488 100644 --- a/packages/adapters/bitcoin/src/connectors/UnisatConnector/index.ts +++ b/packages/adapters/bitcoin/src/connectors/UnisatConnector/index.ts @@ -143,6 +143,11 @@ export class UnisatConnector extends ProviderEventEmitter implements BitcoinConn const network = this.getNetwork(caipNetworkId) await this.wallet.switchChain(network) + const chain = this.chains.find(c => c.caipNetworkId === caipNetworkId) + if (!chain) { + throw new Error('UnisatConnector:switchNetwork - chain is undefined') + } + this.emit('chainChanged', chain.id) } public request(_args: RequestArguments): Promise { diff --git a/packages/adapters/bitcoin/tests/BitcoinAdapter.test.ts b/packages/adapters/bitcoin/tests/BitcoinAdapter.test.ts index b922c52e65..ed9fc10053 100644 --- a/packages/adapters/bitcoin/tests/BitcoinAdapter.test.ts +++ b/packages/adapters/bitcoin/tests/BitcoinAdapter.test.ts @@ -228,28 +228,27 @@ describe('BitcoinAdapter', () => { it('should get wallets from all the available connectors', async () => { const walletStandardConnectorSpy = vi.spyOn(WalletStandardConnector, 'watchWallets') const satsConnectConnectorSpy = vi.spyOn(SatsConnectConnector, 'getWallets') - const okxConnectorSpy = vi.spyOn(OKXConnector, 'getWallet') await adapter.syncConnectors(undefined) expect(walletStandardConnectorSpy).toHaveBeenCalled() expect(satsConnectConnectorSpy).toHaveBeenCalled() - expect(okxConnectorSpy).toHaveBeenCalled() + expect(adapter.connectors.some(c => c instanceof OKXConnector)).toBe(true) }) it('should add connectors from SatsConnectConnector', async () => { mockSatsConnectProvider() await adapter.syncConnectors(undefined) - expect(adapter.connectors).toHaveLength(1) - expect(adapter.connectors[0]).toBeInstanceOf(SatsConnectConnector) + expect(adapter.connectors.some(c => c instanceof SatsConnectConnector)).toBe(true) + expect(adapter.connectors.some(c => c instanceof OKXConnector)).toBe(true) }) it('should map LeatherConnector', async () => { mockSatsConnectProvider({ id: LeatherConnector.ProviderId, name: 'Leather' }) await adapter.syncConnectors(undefined) - expect(adapter.connectors[1]).toBeInstanceOf(LeatherConnector) + expect(adapter.connectors.some(c => c instanceof LeatherConnector)).toBe(true) }) it('should add OKXConnector', async () => { @@ -257,7 +256,7 @@ describe('BitcoinAdapter', () => { await adapter.syncConnectors(undefined) - expect(adapter.connectors[0]).toBeInstanceOf(OKXConnector) + expect(adapter.connectors.some(c => c instanceof OKXConnector)).toBe(true) }) }) diff --git a/packages/adapters/bitcoin/tests/connectors/OKXConnector.test.ts b/packages/adapters/bitcoin/tests/connectors/OKXConnector.test.ts index 57e9d5bfec..a73d94cd2f 100644 --- a/packages/adapters/bitcoin/tests/connectors/OKXConnector.test.ts +++ b/packages/adapters/bitcoin/tests/connectors/OKXConnector.test.ts @@ -1,39 +1,79 @@ -import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import type { CaipNetwork } from '@reown/appkit-common' import { ChainController, CoreHelperUtil } from '@reown/appkit-controllers' -import { bitcoin, bitcoinTestnet } from '@reown/appkit/networks' +import { bitcoin, bitcoinSignet, bitcoinTestnet } from '@reown/appkit/networks' import { OKXConnector } from '../../src/connectors/OKXConnector' import { MethodNotSupportedError } from '../../src/errors/MethodNotSupportedError' -function mockOKXWallet(): { [K in keyof OKXConnector.Wallet]: Mock } { +function createWalletMock() { + const connect = vi.fn(() => Promise.resolve({ address: 'mock_address', publicKey: 'publicKey' })) + const disconnect = vi.fn(() => Promise.resolve()) + const getAccounts = vi.fn(() => Promise.resolve(['mock_address'])) + const signMessage = vi.fn(() => Promise.resolve('mock_signature')) + const signPsbt = vi.fn(() => Promise.resolve(Buffer.from('mock_psbt').toString('hex'))) + const pushPsbt = vi.fn(() => Promise.resolve('mock_txhash')) + const send = vi.fn(() => Promise.resolve({ txhash: 'mock_txhash' })) + const on = vi.fn() + const removeAllListeners = vi.fn() + const getPublicKey = vi.fn(() => Promise.resolve('publicKey')) + + const wallet = { + selectedAccount: { + address: 'signet_address', + purpose: 'payment', + publicKey: 'signet_public' + } as any, + connect, + disconnect, + getAccounts, + signMessage, + signPsbt, + pushPsbt, + send, + on, + removeAllListeners, + getPublicKey + } as unknown as OKXConnector.Wallet + return { - connect: vi.fn(() => Promise.resolve({ address: 'mock_address', publicKey: 'publicKey' })), - disconnect: vi.fn(), - getAccounts: vi.fn(() => Promise.resolve(['mock_address'])), - signMessage: vi.fn(() => Promise.resolve('mock_signature')), - signPsbt: vi.fn(() => Promise.resolve(Buffer.from('mock_psbt').toString('hex'))), - pushPsbt: vi.fn(() => Promise.resolve('mock_txhash')), - send: vi.fn(() => Promise.resolve({ txhash: 'mock_txhash' })), - on: vi.fn(), - removeAllListeners: vi.fn(), - getPublicKey: vi.fn(() => Promise.resolve('publicKey')) + wallet, + fns: { + connect, + disconnect, + getAccounts, + signMessage, + signPsbt, + pushPsbt, + send, + on, + removeAllListeners, + getPublicKey + } } } describe('OKXConnector', () => { - let wallet: ReturnType let requestedChains: CaipNetwork[] let connector: OKXConnector - let imageUrl: string + let mainnet: ReturnType + let testnet: ReturnType + let signet: ReturnType beforeEach(() => { - imageUrl = 'mock_image_url' - requestedChains = [bitcoin, bitcoinTestnet] + requestedChains = [bitcoin, bitcoinTestnet, bitcoinSignet] vi.spyOn(ChainController, 'getActiveCaipNetwork').mockReturnValue(bitcoin) - wallet = mockOKXWallet() - connector = new OKXConnector({ wallet, requestedChains, imageUrl }) + mainnet = createWalletMock() + testnet = createWalletMock() + signet = createWalletMock() + ;(window as any).okxwallet = { + bitcoin: mainnet.wallet, + bitcoinTestnet: testnet.wallet, + bitcoinSignet: signet.wallet, + cardano: { icon: 'mock_image_url' } + } + connector = new OKXConnector({ requestedChains }) }) it('should validate metadata', () => { @@ -44,8 +84,8 @@ describe('OKXConnector', () => { expect(connector.imageUrl).toBe('mock_image_url') }) - it('should return chain that is active', () => { - expect(connector.chains).toEqual([bitcoin]) + it('should return requested chains', () => { + expect(connector.chains).toEqual([bitcoin, bitcoinTestnet, bitcoinSignet]) }) describe('connect', () => { @@ -53,40 +93,31 @@ describe('OKXConnector', () => { const address = await connector.connect() expect(address).toBe('mock_address') - expect(wallet.connect).toHaveBeenCalled() + expect(mainnet.fns.connect).toHaveBeenCalled() }) - it('should emit accountsChanged event on connect', async () => { + it('should emit accountsChanged when wallet emits accountChanged', async () => { const listener = vi.fn() connector.on('accountsChanged', listener) await connector.connect() + // trigger bound listener + mainnet.fns.on.mock.calls[0]![1]({ address: 'mock_address' }) expect(listener).toHaveBeenCalledWith(['mock_address']) }) it('should bind events', async () => { await connector.connect() - expect(wallet.removeAllListeners).toHaveBeenCalled() - expect(wallet.on).toHaveBeenNthCalledWith(1, 'accountChanged', expect.any(Function)) - expect(wallet.on).toHaveBeenNthCalledWith(2, 'disconnect', expect.any(Function)) + expect(mainnet.fns.removeAllListeners).toHaveBeenCalled() + expect(mainnet.fns.on).toHaveBeenNthCalledWith(1, 'accountChanged', expect.any(Function)) + expect(mainnet.fns.on).toHaveBeenNthCalledWith(2, 'disconnect', expect.any(Function)) }) it('should connect with testnet', async () => { - vi.spyOn(ChainController, 'getActiveCaipNetwork').mockReturnValue(bitcoinTestnet) - - const testnetWallet = mockOKXWallet() - const testnetConnector = new OKXConnector({ - wallet: testnetWallet, - requestedChains: [bitcoin, bitcoinTestnet], - imageUrl: 'mock_image_url', - requestedCaipNetworkId: bitcoinTestnet.caipNetworkId - }) - - const address = await testnetConnector.connect() - + const address = await connector.connect({ caipNetworkId: bitcoinTestnet.caipNetworkId }) expect(address).toBe('mock_address') - expect(testnetWallet.connect).toHaveBeenCalled() - expect(testnetConnector.chains).toEqual([bitcoinTestnet]) + expect(testnet.fns.connect).toHaveBeenCalled() + expect(mainnet.fns.connect).not.toHaveBeenCalled() }) }) @@ -94,13 +125,13 @@ describe('OKXConnector', () => { it('should disconnect the wallet', async () => { await connector.disconnect() - expect(wallet.disconnect).toHaveBeenCalled() + expect(mainnet.fns.disconnect).toHaveBeenCalled() }) it('should unbind events', async () => { await connector.disconnect() - expect(wallet.removeAllListeners).toHaveBeenCalled() + expect(mainnet.fns.removeAllListeners).toHaveBeenCalled() }) }) @@ -111,7 +142,7 @@ describe('OKXConnector', () => { expect(accounts).toEqual([ { address: 'mock_address', purpose: 'payment', publicKey: 'publicKey' } ]) - expect(wallet.getAccounts).toHaveBeenCalled() + expect(mainnet.fns.getAccounts).toHaveBeenCalled() }) }) @@ -120,7 +151,7 @@ describe('OKXConnector', () => { const signature = await connector.signMessage({ address: 'mock_address', message: 'message' }) expect(signature).toBe('mock_signature') - expect(wallet.signMessage).toHaveBeenCalledWith('message', undefined) + expect(mainnet.fns.signMessage).toHaveBeenCalledWith('message', undefined) }) it('should sign a message with ecdsa protocol', async () => { @@ -131,7 +162,7 @@ describe('OKXConnector', () => { }) expect(signature).toBe('mock_signature') - expect(wallet.signMessage).toHaveBeenCalledWith('message', 'ecdsa') + expect(mainnet.fns.signMessage).toHaveBeenCalledWith('message', 'ecdsa') }) it('should sign a message with bip322 protocol', async () => { @@ -142,7 +173,7 @@ describe('OKXConnector', () => { }) expect(signature).toBe('mock_signature') - expect(wallet.signMessage).toHaveBeenCalledWith('message', 'bip322-simple') + expect(mainnet.fns.signMessage).toHaveBeenCalledWith('message', 'bip322-simple') }) }) @@ -151,7 +182,7 @@ describe('OKXConnector', () => { const txid = await connector.sendTransfer({ amount: '1500', recipient: 'mock_to_address' }) expect(txid).toBe('mock_txhash') - expect(wallet.send).toHaveBeenCalledWith({ + expect(mainnet.fns.send).toHaveBeenCalledWith({ from: 'mock_address', to: 'mock_to_address', value: '0.000015' @@ -167,7 +198,7 @@ describe('OKXConnector', () => { }) it('should throw an error if no account is available', async () => { - wallet.getAccounts.mockResolvedValueOnce([]) + mainnet.fns.getAccounts.mockResolvedValueOnce([]) await expect( connector.sendTransfer({ amount: '1500', recipient: 'mock_to_address' }) @@ -215,7 +246,7 @@ describe('OKXConnector', () => { broadcast: false }) expect(result).toEqual({ psbt: 'bW9ja19wc2J0', txid: undefined }) - expect(wallet.signPsbt).toHaveBeenCalledWith( + expect(mainnet.fns.signPsbt).toHaveBeenCalledWith( Buffer.from(psbtBase64, 'base64').toString('hex'), { autoFinalized: false, @@ -230,7 +261,7 @@ describe('OKXConnector', () => { ] } ) - expect(wallet.pushPsbt).not.toHaveBeenCalled() + expect(mainnet.fns.pushPsbt).not.toHaveBeenCalled() }) it('should throw error when trying to broadcast with partial signing', async () => { const signInputs = [ @@ -266,7 +297,7 @@ describe('OKXConnector', () => { connector.on('accountsChanged', listener) await connector.connect() - wallet.on.mock.calls[0]![1]({ address: 'mock_address' }) + mainnet.fns.on.mock.calls[0]![1]({ address: 'mock_address' }) expect(listener).toHaveBeenCalled() }) @@ -276,7 +307,7 @@ describe('OKXConnector', () => { connector.on('disconnect', listener) await connector.connect() - wallet.on.mock.calls[1]![1]() + mainnet.fns.on.mock.calls[1]![1]() expect(listener).toHaveBeenCalled() }) @@ -284,60 +315,52 @@ describe('OKXConnector', () => { describe('getWallet', () => { it('should return undefined if there is no wallet', () => { - expect(OKXConnector.getWallet({ requestedChains: [] })).toBeUndefined() + ;(window as any).okxwallet = undefined + const localConnector = new OKXConnector({ requestedChains }) + expect(localConnector.getWallet()).toBeUndefined() }) it('should return the Connector if there is a wallet', () => { - ;(window as any).okxwallet = { bitcoin: wallet } - const connector = OKXConnector.getWallet({ requestedChains }) - expect(connector).toBeInstanceOf(OKXConnector) + ;(window as any).okxwallet = { bitcoin: mainnet.wallet } + const localConnector = new OKXConnector({ requestedChains: [bitcoin] }) + expect(localConnector).toBeInstanceOf(OKXConnector) }) it('should get image url', () => { - ;(window as any).okxwallet = { bitcoin: wallet, cardano: { icon: 'mock_image' } } - const connector = OKXConnector.getWallet({ requestedChains }) - expect(connector?.imageUrl).toBe('mock_image') + ;(window as any).okxwallet = { bitcoin: mainnet.wallet, cardano: { icon: 'mock_image' } } + const localConnector = new OKXConnector({ requestedChains: [bitcoin] }) + expect(localConnector?.imageUrl).toBe('mock_image') }) it('should return undefined if window is undefined (server-side)', () => { vi.spyOn(CoreHelperUtil, 'isClient').mockReturnValue(false) - expect(OKXConnector.getWallet({ requestedChains })).toBeUndefined() + expect(new OKXConnector({ requestedChains: [bitcoin] }).getWallet()).toBeUndefined() }) }) describe('getPublicKey', () => { it('should return the public key', async () => { + vi.spyOn(connector, 'getWallet').mockReturnValue(mainnet.wallet) const publicKey = await connector.getPublicKey() expect(publicKey).toBe('publicKey') - expect(wallet.getPublicKey).toHaveBeenCalled() + expect(mainnet.fns.getPublicKey).toHaveBeenCalled() }) }) describe('switchNetwork', () => { - it('should switch to testnet network and connect', async () => { - const testnetWallet = mockOKXWallet() - const testnetConnector = new OKXConnector({ - wallet: testnetWallet, - requestedChains, - imageUrl: 'mock_image' - }) - - vi.spyOn(OKXConnector, 'getWallet').mockReturnValue(testnetConnector) - - const accountsChangedListener = vi.fn() - connector.on('accountsChanged', accountsChangedListener) - - // Switch to testnet bitcoin - await connector.switchNetwork('bip122:000000000933ea01ad0ee984209779ba') - - expect(testnetWallet.connect).toHaveBeenCalled() - expect(accountsChangedListener).toHaveBeenCalledWith(['mock_address']) + it('should emit chainChanged with the resolved chain id and unbind listeners', async () => { + const chainChangedListener = vi.fn() + vi.spyOn(connector, 'getWallet').mockReturnValue(mainnet.wallet) + connector.on('chainChanged', chainChangedListener) + await connector.switchNetwork(bitcoinTestnet.caipNetworkId) + + expect(mainnet.fns.removeAllListeners).toHaveBeenCalled() + expect(chainChangedListener).toHaveBeenCalledWith(bitcoinTestnet.id) + expect(testnet.fns.connect).not.toHaveBeenCalled() }) it('should throw error when wallet is not available', async () => { - vi.spyOn(OKXConnector, 'getWallet').mockReturnValue(undefined) - await expect(connector.switchNetwork('bip122:fake-network')).rejects.toThrow( 'OKX Wallet wallet does not support network switching' ) diff --git a/packages/adapters/bitcoin/tests/connectors/UnisatConnector.test.ts b/packages/adapters/bitcoin/tests/connectors/UnisatConnector.test.ts index 9c043d5c73..933482bb7d 100644 --- a/packages/adapters/bitcoin/tests/connectors/UnisatConnector.test.ts +++ b/packages/adapters/bitcoin/tests/connectors/UnisatConnector.test.ts @@ -39,7 +39,7 @@ describe('UnisatConnector', () => { beforeEach(() => { imageUrl = 'mock_image_url' - requestedChains = [bitcoin, bitcoinTestnet] + requestedChains = [bitcoin, bitcoinTestnet, bitcoinSignet] vi.spyOn(ChainController, 'getActiveCaipNetwork').mockReturnValue(bitcoin) wallet = mockUnisatWallet() connector = new UnisatConnector({ @@ -60,7 +60,7 @@ describe('UnisatConnector', () => { }) it('should return bitcoin chains', () => { - expect(connector.chains).toEqual([bitcoin, bitcoinTestnet]) + expect(connector.chains).toEqual([bitcoin, bitcoinTestnet, bitcoinSignet]) }) describe('connect', () => { diff --git a/packages/adapters/solana/src/client.ts b/packages/adapters/solana/src/client.ts index c084f1acf3..1935b91993 100644 --- a/packages/adapters/solana/src/client.ts +++ b/packages/adapters/solana/src/client.ts @@ -304,26 +304,28 @@ export class SolanaAdapter extends AdapterBlueprint { throw new Error(`RPC URL not found for chainId: ${params.chainId}`) } - const connection = this.getConnection({ - address: params.address, - connectorId: connector.id, - connections: this.connections, - connectors: this.connectors - }) - - if (connection?.account) { - this.emit('accountChanged', { - address: connection.account.address, - chainId: connection.caipNetwork?.id, - connector: connectorWithProvider + if (params.address) { + const connection = this.getConnection({ + address: params.address, + connectorId: connector.id, + connections: this.connections, + connectors: this.connectors }) - return { - id: connector.id, - address: connection.account.address, - chainId: params.chainId as string, - provider: connector as CoreProvider, - type: connector.type + if (connection?.account) { + this.emit('accountChanged', { + address: connection.account.address, + chainId: connection.caipNetwork?.id, + connector: connectorWithProvider + }) + + return { + id: connector.id, + address: connection.account.address, + chainId: params.chainId as string, + provider: connector as CoreProvider, + type: connector.type + } } } diff --git a/packages/appkit-utils/src/bitcoin/BitcoinTypesUtil.ts b/packages/appkit-utils/src/bitcoin/BitcoinTypesUtil.ts index b3477080b9..cc6dcea5f0 100644 --- a/packages/appkit-utils/src/bitcoin/BitcoinTypesUtil.ts +++ b/packages/appkit-utils/src/bitcoin/BitcoinTypesUtil.ts @@ -9,7 +9,9 @@ interface ChainAdapterConnector extends Connector { * This is the interface for a Bitcoin connector. */ export interface BitcoinConnector extends ChainAdapterConnector, Provider { - getAccountAddresses(): Promise + getAccountAddresses(params?: { + caipNetworkId?: CaipNetwork['caipNetworkId'] + }): Promise signMessage(params: BitcoinConnector.SignMessageParams): Promise sendTransfer(params: BitcoinConnector.SendTransferParams): Promise signPSBT(params: BitcoinConnector.SignPSBTParams): Promise diff --git a/packages/controllers/src/controllers/AdapterController/ChainAdapterBlueprint.ts b/packages/controllers/src/controllers/AdapterController/ChainAdapterBlueprint.ts index 8038637472..73b5fb4a6c 100644 --- a/packages/controllers/src/controllers/AdapterController/ChainAdapterBlueprint.ts +++ b/packages/controllers/src/controllers/AdapterController/ChainAdapterBlueprint.ts @@ -939,6 +939,7 @@ export namespace AdapterBlueprint { export type GetAccountsParams = { id: AppKitConnector['id'] namespace?: ChainNamespace + caipNetworkId?: CaipNetwork['caipNetworkId'] } export interface GetConnectionParams { diff --git a/packages/controllers/src/utils/TypeUtil.ts b/packages/controllers/src/utils/TypeUtil.ts index 68864ecb51..dc1fe11218 100644 --- a/packages/controllers/src/utils/TypeUtil.ts +++ b/packages/controllers/src/utils/TypeUtil.ts @@ -1232,7 +1232,10 @@ export interface RequestArguments { } export interface Provider { - connect: (params?: { onUri?: (uri: string) => void }) => Promise + connect: (params?: { + caipNetworkId?: CaipNetworkId + onUri?: (uri: string) => void + }) => Promise disconnect: () => Promise request: (args: RequestArguments) => Promise on(event: T, listener: ProviderEventListener[T]): void