diff --git a/.changeset/expose-approved-networks.md b/.changeset/expose-approved-networks.md new file mode 100644 index 0000000000..00df162df3 --- /dev/null +++ b/.changeset/expose-approved-networks.md @@ -0,0 +1,6 @@ +--- +'@reown/appkit': patch +'@reown/appkit-controllers': patch +--- + +Expose `approvedCaipNetworkIds` and `supportsAllNetworks` in `useAppKitNetwork` hook diff --git a/packages/appkit/exports/constants.ts b/packages/appkit/exports/constants.ts index a2aed81db9..52e66c7b0d 100644 --- a/packages/appkit/exports/constants.ts +++ b/packages/appkit/exports/constants.ts @@ -1 +1 @@ -export const PACKAGE_VERSION = '1.8.18' +export const PACKAGE_VERSION = '1.8.19' diff --git a/packages/appkit/exports/react-core.ts b/packages/appkit/exports/react-core.ts index 508477d4b0..49ec7248fd 100644 --- a/packages/appkit/exports/react-core.ts +++ b/packages/appkit/exports/react-core.ts @@ -40,7 +40,8 @@ export type { AppKitOptions } export * from '../src/library/react/index.js' export function useAppKitNetwork(): UseAppKitNetworkReturn { - const { caipNetwork, caipNetworkId, chainId } = useAppKitNetworkCore() + const { caipNetwork, caipNetworkId, chainId, approvedCaipNetworkIds, supportsAllNetworks } = + useAppKitNetworkCore() async function switchNetwork(network: AppKitNetwork) { await modal?.switchNetwork(network) @@ -50,6 +51,8 @@ export function useAppKitNetwork(): UseAppKitNetworkReturn { caipNetwork, caipNetworkId, chainId, + approvedCaipNetworkIds, + supportsAllNetworks, switchNetwork } } diff --git a/packages/appkit/exports/react.ts b/packages/appkit/exports/react.ts index e0b9ce9d3d..7370f423f3 100644 --- a/packages/appkit/exports/react.ts +++ b/packages/appkit/exports/react.ts @@ -47,7 +47,8 @@ export * from '../src/library/react/index.js' export { useAppKitProvider } from '@reown/appkit-controllers/react' export function useAppKitNetwork(): UseAppKitNetworkReturn { - const { caipNetwork, caipNetworkId, chainId } = useAppKitNetworkCore() + const { caipNetwork, caipNetworkId, chainId, approvedCaipNetworkIds, supportsAllNetworks } = + useAppKitNetworkCore() async function switchNetwork(network: AppKitNetwork) { await modal?.switchNetwork(network) @@ -57,6 +58,8 @@ export function useAppKitNetwork(): UseAppKitNetworkReturn { caipNetwork, caipNetworkId, chainId, + approvedCaipNetworkIds, + supportsAllNetworks, switchNetwork } } diff --git a/packages/appkit/exports/vue-core.ts b/packages/appkit/exports/vue-core.ts index 0965c47f8c..d9b6c53dba 100644 --- a/packages/appkit/exports/vue-core.ts +++ b/packages/appkit/exports/vue-core.ts @@ -39,23 +39,46 @@ export type { AppKitOptions } // -- Hooks ------------------------------------------------------------ export function useAppKitNetwork(): Ref { + const networkState = ChainController.state.activeChain + ? ChainController.state.chains.get(ChainController.state.activeChain)?.networkState + : undefined + const state = ref({ caipNetwork: ChainController.state.activeCaipNetwork, chainId: ChainController.state.activeCaipNetwork?.id, caipNetworkId: ChainController.state.activeCaipNetwork?.caipNetworkId, + approvedCaipNetworkIds: networkState?.approvedCaipNetworkIds, + supportsAllNetworks: networkState?.supportsAllNetworks ?? true, switchNetwork: async (network: AppKitNetwork) => { await modal?.switchNetwork(network) } }) - const unsubscribe = ChainController.subscribeKey('activeCaipNetwork', val => { - state.value.caipNetwork = val - state.value.chainId = val?.id - state.value.caipNetworkId = val?.caipNetworkId - }) + const unsubscribes: (() => void)[] = [] + + unsubscribes.push( + ChainController.subscribeKey('activeCaipNetwork', val => { + state.value.caipNetwork = val + state.value.chainId = val?.id + state.value.caipNetworkId = val?.caipNetworkId + + const ns = ChainController.state.activeChain + ? ChainController.state.chains.get(ChainController.state.activeChain)?.networkState + : undefined + state.value.approvedCaipNetworkIds = ns?.approvedCaipNetworkIds + state.value.supportsAllNetworks = ns?.supportsAllNetworks ?? true + }) + ) + + unsubscribes.push( + ChainController.subscribeChainProp('networkState', networkState => { + state.value.approvedCaipNetworkIds = networkState?.approvedCaipNetworkIds + state.value.supportsAllNetworks = networkState?.supportsAllNetworks ?? true + }) + ) onUnmounted(() => { - unsubscribe() + unsubscribes.forEach(u => u()) }) return state diff --git a/packages/appkit/exports/vue.ts b/packages/appkit/exports/vue.ts index 055fab20b2..59a1d57e47 100644 --- a/packages/appkit/exports/vue.ts +++ b/packages/appkit/exports/vue.ts @@ -43,23 +43,46 @@ export type { AppKitOptions } // -- Hooks ------------------------------------------------------------ export function useAppKitNetwork(): Ref { + const networkState = ChainController.state.activeChain + ? ChainController.state.chains.get(ChainController.state.activeChain)?.networkState + : undefined + const state = ref({ caipNetwork: ChainController.state.activeCaipNetwork, chainId: ChainController.state.activeCaipNetwork?.id, caipNetworkId: ChainController.state.activeCaipNetwork?.caipNetworkId, + approvedCaipNetworkIds: networkState?.approvedCaipNetworkIds, + supportsAllNetworks: networkState?.supportsAllNetworks ?? true, switchNetwork: async (network: AppKitNetwork) => { await modal?.switchNetwork(network) } }) - const unsubscribe = ChainController.subscribeKey('activeCaipNetwork', val => { - state.value.caipNetwork = val - state.value.chainId = val?.id - state.value.caipNetworkId = val?.caipNetworkId - }) + const unsubscribes: (() => void)[] = [] + + unsubscribes.push( + ChainController.subscribeKey('activeCaipNetwork', val => { + state.value.caipNetwork = val + state.value.chainId = val?.id + state.value.caipNetworkId = val?.caipNetworkId + + const ns = ChainController.state.activeChain + ? ChainController.state.chains.get(ChainController.state.activeChain)?.networkState + : undefined + state.value.approvedCaipNetworkIds = ns?.approvedCaipNetworkIds + state.value.supportsAllNetworks = ns?.supportsAllNetworks ?? true + }) + ) + + unsubscribes.push( + ChainController.subscribeChainProp('networkState', networkState => { + state.value.approvedCaipNetworkIds = networkState?.approvedCaipNetworkIds + state.value.supportsAllNetworks = networkState?.supportsAllNetworks ?? true + }) + ) onUnmounted(() => { - unsubscribe() + unsubscribes.forEach(u => u()) }) return state diff --git a/packages/appkit/src/client/appkit-base-client.ts b/packages/appkit/src/client/appkit-base-client.ts index 7ff15c8c05..aded361a61 100644 --- a/packages/appkit/src/client/appkit-base-client.ts +++ b/packages/appkit/src/client/appkit-base-client.ts @@ -2488,11 +2488,14 @@ export abstract class AppKitBaseClient { public subscribeNetwork( callback: (newState: Omit) => void ) { - return ChainController.subscribe(({ activeCaipNetwork }) => { + return ChainController.subscribe(({ activeCaipNetwork, activeChain, chains }) => { + const networkState = activeChain ? chains.get(activeChain)?.networkState : undefined callback({ caipNetwork: activeCaipNetwork, chainId: activeCaipNetwork?.id, - caipNetworkId: activeCaipNetwork?.caipNetworkId + caipNetworkId: activeCaipNetwork?.caipNetworkId, + approvedCaipNetworkIds: networkState?.approvedCaipNetworkIds, + supportsAllNetworks: networkState?.supportsAllNetworks ?? true }) }) } diff --git a/packages/appkit/tests/library/vue.test.ts b/packages/appkit/tests/library/vue.test.ts index ccd73656c2..8b8693aa85 100644 --- a/packages/appkit/tests/library/vue.test.ts +++ b/packages/appkit/tests/library/vue.test.ts @@ -3,9 +3,10 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import { defineComponent, h } from 'vue' import type { ChainNamespace } from '@reown/appkit-common' -import { ProviderController } from '@reown/appkit-controllers' +import { ChainController, ProviderController } from '@reown/appkit-controllers' import { type ConnectorType, createAppKit, useAppKitProvider } from '../../exports/vue-core.js' +import { useAppKitNetwork } from '../../exports/vue.js' import { mainnet } from '../mocks/Networks.js' const TestComponent = defineComponent({ @@ -95,3 +96,92 @@ describe('useAppKitProvider', () => { expect(mockUnsubscribe).toHaveBeenCalled() }) }) + +describe('useAppKitNetwork', () => { + const NetworkTestComponent = defineComponent({ + setup() { + const state = useAppKitNetwork() + + return { state } + }, + render() { + return h('div', {}, [ + h( + 'div', + { 'data-testid': 'approvedCaipNetworkIds' }, + JSON.stringify(this.state.approvedCaipNetworkIds) + ), + h( + 'div', + { 'data-testid': 'supportsAllNetworks' }, + JSON.stringify(this.state.supportsAllNetworks) + ) + ]) + } + }) + + let mockUnsubscribeKey: ReturnType + let mockUnsubscribeChainProp: ReturnType + + beforeEach(() => { + vi.clearAllMocks() + + mockUnsubscribeKey = vi.fn() + mockUnsubscribeChainProp = vi.fn() + vi.spyOn(ChainController, 'subscribeKey').mockReturnValue(mockUnsubscribeKey) + vi.spyOn(ChainController, 'subscribeChainProp').mockReturnValue(mockUnsubscribeChainProp) + }) + + it('should return defaults when no chain is active', () => { + ChainController.state.activeChain = undefined + + const wrapper = mount(NetworkTestComponent) + + expect(wrapper.get('[data-testid="supportsAllNetworks"]').text()).toBe('true') + }) + + it('should return approved networks from chain networkState', () => { + ChainController.state.activeChain = 'eip155' + ChainController.state.chains = new Map([ + [ + 'eip155', + { + networkState: { + approvedCaipNetworkIds: ['eip155:1', 'eip155:137'], + supportsAllNetworks: false + } + } + ] + ]) + + const wrapper = mount(NetworkTestComponent) + + expect(wrapper.get('[data-testid="approvedCaipNetworkIds"]').text()).toBe( + '["eip155:1","eip155:137"]' + ) + expect(wrapper.get('[data-testid="supportsAllNetworks"]').text()).toBe('false') + }) + + it('should subscribe to activeCaipNetwork and networkState', () => { + ChainController.state.activeChain = 'eip155' + ChainController.state.chains = new Map([ + ['eip155', { networkState: { supportsAllNetworks: true } }] + ]) + + const wrapper = mount(NetworkTestComponent) + + expect(ChainController.subscribeKey).toHaveBeenCalledWith( + 'activeCaipNetwork', + expect.any(Function) + ) + expect(ChainController.subscribeChainProp).toHaveBeenCalledWith( + 'networkState', + expect.any(Function) + ) + + wrapper.unmount() + + expect(mockUnsubscribeKey).toHaveBeenCalled() + expect(mockUnsubscribeChainProp).toHaveBeenCalled() + }) +}) diff --git a/packages/controllers/exports/react.ts b/packages/controllers/exports/react.ts index 426063389b..1e60f64841 100644 --- a/packages/controllers/exports/react.ts +++ b/packages/controllers/exports/react.ts @@ -80,14 +80,18 @@ export function useAppKitProvider(chainNamespace: ChainNamespace) { export function useAppKitNetworkCore(): Pick< UseAppKitNetworkReturn, - 'caipNetwork' | 'chainId' | 'caipNetworkId' + 'caipNetwork' | 'chainId' | 'caipNetworkId' | 'approvedCaipNetworkIds' | 'supportsAllNetworks' > { - const { activeCaipNetwork } = useSnapshot(ChainController.state) + const { activeCaipNetwork, activeChain, chains } = useSnapshot(ChainController.state) + + const networkState = activeChain ? chains.get(activeChain)?.networkState : undefined return { caipNetwork: activeCaipNetwork as CaipNetwork, chainId: activeCaipNetwork?.id, - caipNetworkId: activeCaipNetwork?.caipNetworkId + caipNetworkId: activeCaipNetwork?.caipNetworkId, + approvedCaipNetworkIds: networkState?.approvedCaipNetworkIds, + supportsAllNetworks: networkState?.supportsAllNetworks ?? true } } @@ -457,8 +461,9 @@ export function useAppKitWallets(): UseAppKitWalletsReturn { * Pre-fetches the WalletConnect URI. Call this when user selects a wallet on mobile. * Uses 'auto' cache to reuse existing valid URI or fetch new one if expired. */ - async function getWcUri() { + async function getWcUri(options?: { wcPayUrl?: string }) { resetWcUri() + setCurrentWcPayUrl(options?.wcPayUrl) await ConnectionController.connectWalletConnect({ cache: 'auto' }) } diff --git a/packages/controllers/src/utils/TypeUtil.ts b/packages/controllers/src/utils/TypeUtil.ts index 9b93ff3fe5..c23fd8f1c1 100644 --- a/packages/controllers/src/utils/TypeUtil.ts +++ b/packages/controllers/src/utils/TypeUtil.ts @@ -1425,6 +1425,8 @@ export type UseAppKitNetworkReturn = { caipNetwork: CaipNetwork | undefined chainId: number | string | undefined caipNetworkId: CaipNetworkId | undefined + approvedCaipNetworkIds: CaipNetworkId[] | undefined + supportsAllNetworks: boolean switchNetwork: (network: AppKitNetwork) => Promise } diff --git a/packages/controllers/tests/hooks/react.test.ts b/packages/controllers/tests/hooks/react.test.ts index febbf42ecd..549ddc1d1f 100644 --- a/packages/controllers/tests/hooks/react.test.ts +++ b/packages/controllers/tests/hooks/react.test.ts @@ -66,14 +66,45 @@ describe('useAppKitNetwork', () => { }) it('should return the correct network state', () => { - useSnapshot.mockReturnValue({ activeCaipNetwork: extendedMainnet }) + useSnapshot.mockReturnValue({ + activeCaipNetwork: extendedMainnet, + activeChain: 'eip155', + chains: new Map([ + [ + 'eip155', + { + networkState: { + approvedCaipNetworkIds: ['eip155:1', 'eip155:137'], + supportsAllNetworks: false + } + } + ] + ]) + }) - const { caipNetwork, chainId } = useAppKitNetworkCore() + const { caipNetwork, chainId, approvedCaipNetworkIds, supportsAllNetworks } = + useAppKitNetworkCore() expect(caipNetwork).toBe(extendedMainnet) expect(chainId).toBe(1) + expect(approvedCaipNetworkIds).toEqual(['eip155:1', 'eip155:137']) + expect(supportsAllNetworks).toBe(false) expect(useSnapshot).toHaveBeenCalledWith(ChainController.state) }) + + it('should return defaults when no chain is active', () => { + useSnapshot.mockReturnValue({ + activeCaipNetwork: undefined, + activeChain: undefined, + chains: new Map() + }) + + const { caipNetwork, approvedCaipNetworkIds, supportsAllNetworks } = useAppKitNetworkCore() + + expect(caipNetwork).toBeUndefined() + expect(approvedCaipNetworkIds).toBeUndefined() + expect(supportsAllNetworks).toBe(true) + }) }) describe('useAppKitAccount', () => {