diff --git a/.changeset/swift-melons-cover.md b/.changeset/swift-melons-cover.md new file mode 100644 index 0000000000..202e1506df --- /dev/null +++ b/.changeset/swift-melons-cover.md @@ -0,0 +1,22 @@ +--- +'@reown/appkit': patch +'@reown/appkit-core': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Adds parsing of Universal Provider session_event to get accountsChanged event diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index bbd31ce715..b959183710 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -81,6 +81,7 @@ import UniversalProvider from '@walletconnect/universal-provider' import type { SessionTypes } from '@walletconnect/types' import type { UniversalProviderOpts } from '@walletconnect/universal-provider' import { W3mFrameProviderSingleton } from './auth-provider/W3MFrameProviderSingleton.js' +import { WcHelpersUtil } from './utils/HelpersUtil.js' declare global { interface Window { @@ -1286,6 +1287,20 @@ export class AppKit { this.setCaipNetwork(caipNetwork) } }) + + this.universalProvider.on('session_event', (callbackData: unknown) => { + if (WcHelpersUtil.isSessionEventData(callbackData)) { + const { name, data } = callbackData.params.event + + if (name === 'accountsChanged' && Array.isArray(data)) { + const caipAddress = CoreHelperUtil.parseCaipAddress(data[0]) + + if (caipAddress) { + this.syncAccount(caipAddress) + } + } + } + }) } } diff --git a/packages/appkit/src/tests/utils/HelpersUtil.test.ts b/packages/appkit/src/tests/utils/HelpersUtil.test.ts index b490a443fa..d6d31507cc 100644 --- a/packages/appkit/src/tests/utils/HelpersUtil.test.ts +++ b/packages/appkit/src/tests/utils/HelpersUtil.test.ts @@ -205,4 +205,27 @@ describe('WcHelpersUtil', () => { ]) }) }) + + describe('isSessionEventData', () => { + test.each([ + [undefined, false], + [{}, false], + [ + { + id: 1734112958243866, + topic: 'b2cb2748499532d9c307846c444b364dd881c959d9a080e30d63b6a76270a0f8', + params: { + event: { + name: 'accountsChanged', + data: ['eip155:1:0x53F31e8972Ebddac1553E37887C25C1b748485A6'] + }, + chainId: 'eip155:1' + } + }, + true + ] + ])('should validate session event data', (data, expected) => { + expect(WcHelpersUtil.isSessionEventData(data)).toBe(expected) + }) + }) }) diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts index 601aea1a06..932f2336bb 100644 --- a/packages/appkit/src/utils/HelpersUtil.ts +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -101,5 +101,29 @@ export const WcHelpersUtil = { return Array.from(new Set([...chains, ...accountsChains])) }) + }, + + isSessionEventData(data: unknown): data is WcHelpersUtil.SessionEventData { + return ( + typeof data === 'object' && + data !== null && + 'id' in data && + 'topic' in data && + 'params' in data && + typeof data.params === 'object' && + data.params !== null && + 'chainId' in data.params && + 'event' in data.params && + typeof data.params.event === 'object' && + data.params.event !== null + ) + } +} + +export namespace WcHelpersUtil { + export type SessionEventData = { + id: string + topic: string + params: { chainId: string; event: { data: unknown; name: string } } } } diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 1034800e25..97293a8061 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -357,5 +357,36 @@ export const CoreHelperUtil = { address, type } as AccountTypeMap[N] + }, + + isCaipAddress(address?: unknown): address is CaipAddress { + if (typeof address !== 'string') { + return false + } + + const sections = address.split(':') + const namespace = sections[0] + + return sections.length === 3 && (namespace as string) in CommonConstants.CHAIN_NAME_MAP + }, + + parseCaipAddress( + caipAddress?: unknown + ): { chainNamespace: ChainNamespace; chainId: string; address: string } | undefined { + if (CoreHelperUtil.isCaipAddress(caipAddress)) { + const [chainNamespace, chainId, address] = caipAddress.split(':') as [ + ChainNamespace, + string, + string + ] + + return { + chainNamespace, + chainId, + address + } + } + + return undefined } } diff --git a/packages/core/tests/utils/CoreHelperUtil.test.ts b/packages/core/tests/utils/CoreHelperUtil.test.ts index b3f4c69112..1c88058805 100644 --- a/packages/core/tests/utils/CoreHelperUtil.test.ts +++ b/packages/core/tests/utils/CoreHelperUtil.test.ts @@ -57,4 +57,37 @@ describe('CoreHelperUtil', () => { global.window.self = originalSelf } }) + + it.each([ + [undefined, false], + [{}, false], + ['0x0', false], + ['invalid_namespace:mock_chain_id:address', false], + ['eip155:mock_chain_id:mock_address', true], + ['solana:mock_chain_id:mock_address', true], + ['bip122:mock_chain_id:mock_address', true] + ])('should validate the value $s is valid caip address $b', (caipAddress, expected) => { + expect(CoreHelperUtil.isCaipAddress(caipAddress)).toEqual(expected) + }) + + it.each([ + [undefined, undefined], + [{}, undefined], + ['0x0', undefined], + ['invalid_namespace:mock_chain_id:address', undefined], + [ + 'eip155:mock_chain_id:mock_address', + { chainNamespace: 'eip155', chainId: 'mock_chain_id', address: 'mock_address' } + ], + [ + 'solana:mock_chain_id:mock_address', + { chainNamespace: 'solana', chainId: 'mock_chain_id', address: 'mock_address' } + ], + [ + 'bip122:mock_chain_id:mock_address', + { chainNamespace: 'bip122', chainId: 'mock_chain_id', address: 'mock_address' } + ] + ])('should validate the value $s is valid caip address $b', (caipAddress, expected) => { + expect(CoreHelperUtil.parseCaipAddress(caipAddress)).toEqual(expected) + }) })