diff --git a/packages/store/__tests__/local-storage.test.ts b/packages/store/__tests__/local-storage.test.ts index e9e0fc905..911537a1c 100644 --- a/packages/store/__tests__/local-storage.test.ts +++ b/packages/store/__tests__/local-storage.test.ts @@ -126,18 +126,18 @@ describe('LocalStorage', () => { localStorageMock.setItem('interchain-kit-store', JSON.stringify(data)); const loadedData = localStorage.load(); - // Note: Uint8Array gets serialized to plain object in JSON const expectedData = { ...data, chainWalletStates: data.chainWalletStates?.map(state => ({ ...state, account: state.account ? { ...state.account, - pubkey: { 0: 1, 1: 2, 2: 3 } + pubkey: new Uint8Array([1, 2, 3]) } : undefined })) }; expect(loadedData).toEqual(expectedData); + expect(loadedData.chainWalletStates?.[0]?.account?.pubkey).toBeInstanceOf(Uint8Array); }); it('should return empty object when no data exists', () => { @@ -199,18 +199,19 @@ describe('LocalStorage', () => { localStorage.save(complexData); const loadedData = localStorage.load(); - // Note: Uint8Array gets serialized to plain object in JSON const expectedData = { ...complexData, chainWalletStates: complexData.chainWalletStates?.map(state => ({ ...state, account: state.account ? { ...state.account, - pubkey: { 0: 1, 1: 2, 2: 3 } + pubkey: new Uint8Array([1, 2, 3]) } : undefined })) }; expect(loadedData).toEqual(expectedData); + expect(loadedData.chainWalletStates?.[0]?.account?.pubkey).toBeInstanceOf(Uint8Array); + expect(loadedData.chainWalletStates?.[1]?.account?.pubkey).toBeInstanceOf(Uint8Array); }); }); @@ -240,18 +241,18 @@ describe('LocalStorage', () => { localStorage.save(originalData); const loadedData = localStorage.load(); - // Note: Uint8Array gets serialized to plain object in JSON const expectedData = { ...originalData, chainWalletStates: originalData.chainWalletStates?.map(state => ({ ...state, account: state.account ? { ...state.account, - pubkey: { 0: 1, 1: 2, 2: 3 } + pubkey: new Uint8Array([1, 2, 3]) } : undefined })) }; expect(loadedData).toEqual(expectedData); + expect(loadedData.chainWalletStates?.[0]?.account?.pubkey).toBeInstanceOf(Uint8Array); }); it('should handle multiple save/load cycles', () => { @@ -272,5 +273,35 @@ describe('LocalStorage', () => { localStorage.save(data2); expect(localStorage.load()).toEqual(data2); }); + + it('should revive Uint8Array pubkeys after save/load round-trip', () => { + const originalData: Partial = { + currentWalletName: 'keplr', + currentChainName: 'cosmoshub', + isReady: true, + chainWalletStates: [ + { + chainName: 'cosmoshub', + walletName: 'keplr', + walletState: 'Connected' as any, + rpcEndpoint: 'https://rpc.cosmos.network', + errorMessage: '', + account: { + address: 'cosmos1abc123', + pubkey: new Uint8Array([1, 2, 3]), + isNanoLedger: false, + algo: 'secp256k1' as Algo, + }, + }, + ], + }; + + localStorage.save(originalData); + const loadedData = localStorage.load(); + const pubkey = loadedData.chainWalletStates?.[0]?.account?.pubkey; + + expect(pubkey).toBeInstanceOf(Uint8Array); + expect(Array.from(pubkey as Uint8Array)).toEqual([1, 2, 3]); + }); }); }); diff --git a/packages/store/src/utils/local-storage.ts b/packages/store/src/utils/local-storage.ts index 8cd9f6d7b..1c2d3a863 100644 --- a/packages/store/src/utils/local-storage.ts +++ b/packages/store/src/utils/local-storage.ts @@ -1,12 +1,42 @@ -import { InterchainStoreType } from '../types'; +import { ChainWalletState, InterchainStoreType } from '../types'; const INTERCHAIN_KIT_STORAGE_KEY = 'interchain-kit-store'; +const revivePubkey = (pubkey: object): Uint8Array => { + if (pubkey instanceof Uint8Array) { + return pubkey; + } + // JSON.parse turns Uint8Array into a plain {0: byte, 1: byte, ...} object. + const keys = Object.keys(pubkey) + .filter((key) => /^\d+$/.test(key)) + .sort((a, b) => Number(a) - Number(b)); + return Uint8Array.from(keys.map((key) => (pubkey as Record)[key])); +}; + +const reviveAccount = (account: ChainWalletState['account']): ChainWalletState['account'] => { + if (!account || account.pubkey == null || typeof account.pubkey !== 'object') { + return account; + } + return { ...account, pubkey: revivePubkey(account.pubkey) }; +}; + +const reviveChainWalletState = (state: ChainWalletState): ChainWalletState => ({ + ...state, + account: reviveAccount(state.account), +}); + export class LocalStorage { save(value: Partial) { localStorage.setItem(INTERCHAIN_KIT_STORAGE_KEY, JSON.stringify(value)); } load(): Partial { - return JSON.parse(localStorage.getItem(INTERCHAIN_KIT_STORAGE_KEY) || '{}'); + const value = JSON.parse(localStorage.getItem(INTERCHAIN_KIT_STORAGE_KEY) || '{}') as Partial; + if (!value.chainWalletStates) { + return value; + } + return { + ...value, + chainWalletStates: value.chainWalletStates.map(reviveChainWalletState), + }; } -} \ No newline at end of file +}