accounts
Version:
Tempo Accounts SDK
100 lines • 3.92 kB
JavaScript
import * as z from 'zod/mini';
import { persist } from 'zustand/middleware';
import { subscribeWithSelector } from 'zustand/middleware';
import { createStore } from 'zustand/vanilla';
import * as Storage from './Storage.js';
/**
* Creates a Zustand vanilla store with `subscribeWithSelector` and `persist` middleware.
*/
export function create(options) {
const { chainId, maxAccounts, persistCredentials = true, schema, storage = typeof window !== 'undefined'
? Storage.idb({ key: 'tempo' })
: Storage.memory({ key: 'tempo' }), } = options;
return createStore(subscribeWithSelector(persist(() => ({
accessKeys: [],
accounts: [],
activeAccount: 0,
chainId,
requestQueue: [],
}), {
merge: (persisted, current) => hydrate(persisted, current, { schema }),
name: 'store',
partialize: (state) => serialize(state, { maxAccounts, persistCredentials }),
storage,
version: 0,
})));
}
/** Converts runtime provider state into the persisted refresh snapshot. */
export function serialize(state, options = {}) {
const { maxAccounts, persistCredentials = true } = options;
const accounts = maxAccounts && state.accounts.length > maxAccounts
? state.accounts.slice(0, maxAccounts)
: state.accounts;
return {
accounts,
activeAccount: state.activeAccount,
...(persistCredentials ? { accessKeys: state.accessKeys } : {}),
...(state.auth ? { auth: state.auth } : {}),
chainId: state.chainId,
};
}
/** Restores runtime provider state from a persisted refresh snapshot. */
export function hydrate(persisted, current, options = {}) {
const state = persisted && typeof persisted === 'object' ? persisted : {};
const accounts_persisted = Array.isArray(state.accounts)
? state.accounts.filter(isStoredAccount)
: undefined;
const accounts = accounts_persisted?.map((persisted) => {
const account = current.accounts.find((a) => a.address.toLowerCase() === persisted.address.toLowerCase());
return account ?? persisted;
}) ?? current.accounts;
const accounts_valid = options.schema
? accounts.filter((account) => z.safeParse(options.schema, account).success)
: accounts;
return {
...state,
...current,
accounts: accounts_valid,
activeAccount: accounts_valid.length === 0
? 0
: Math.min(state.activeAccount ?? current.activeAccount, accounts_valid.length - 1),
accessKeys: normalizeAccessKeys(state.accessKeys) ?? current.accessKeys,
chainId: state.chainId ?? current.chainId,
};
}
function normalizeAccessKeys(accessKeys) {
if (!accessKeys)
return undefined;
return accessKeys.filter((key) => {
if (!key || typeof key !== 'object')
return false;
const value = key;
return (typeof value.access === 'string' &&
typeof value.address === 'string' &&
typeof value.chainId === 'number' &&
(value.keyType === 'secp256k1' ||
value.keyType === 'p256' ||
value.keyType === 'webAuthn' ||
value.keyType === 'webCrypto'));
});
}
function isStoredAccount(account) {
if (!account || typeof account !== 'object')
return false;
return typeof account.address === 'string';
}
/**
* Waits for the store to finish hydrating from storage.
*
* Returns immediately if the store has already hydrated. Otherwise, waits
* for the `onFinishHydration` callback with a 100ms safety timeout fallback.
*/
export async function waitForHydration(store) {
if (store.persist.hasHydrated())
return;
await new Promise((resolve) => {
store.persist.onFinishHydration(() => resolve());
setTimeout(() => resolve(), 100);
});
}
//# sourceMappingURL=Store.js.map