UNPKG

accounts

Version:

Tempo Accounts SDK

1,861 lines 95.3 kB
import { announceProvider } from 'mipd';
import { Mppx, tempo as mppx_tempo } from 'mppx/client';
import { Address, Hash, Hex, Json, Provider as ox_Provider, RpcResponse } from 'ox';
import { KeyAuthorization } from 'ox/tempo';
import { createWalletClient, custom, hashMessage, hashTypedData, http, parseUnits, } from 'viem';
import { prepareTransactionRequest, sendTransaction, sendTransactionSync as viem_sendTransactionSync, signTransaction as viem_signTransaction, } from 'viem/actions';
import { parseSiweMessage } from 'viem/siwe';
import { Account as TempoAccount, Actions } from 'viem/tempo';
import { tempo, tempoDevnet, tempoModerato } from 'viem/tempo/chains';
import * as z from 'zod/mini';
import * as AccessKey from './AccessKey.js';
import * as Account from './Account.js';
import { dialog } from './adapters/dialog.js';
import * as Client from './Client.js';
import * as AccessKeyTransaction from './internal/AccessKeyTransaction.js';
import * as AddressUtil from './internal/address.js';
import { withDedupe } from './internal/withDedupe.js';
import * as Keystore from './Keystore.js';
import * as Schema from './Schema.js';
import * as Storage from './Storage.js';
import * as Store from './Store.js';
import * as Tokenlist from './Tokenlist.js';
import * as Request from './zod/request.js';
import * as Rpc from './zod/rpc.js';
const announced = new Set();
function isBrowserWebCrypto() {
    return typeof document !== 'undefined' && !!globalThis.crypto?.subtle;
}
/**
 * Creates an EIP-1193 provider with a pluggable adapter.
 *
 * @example
 * ```ts
 * import { Provider } from 'accounts'
 *
 * const provider = Provider.create()
 * ```
 */
export function create(options = {}) {
    const { adapter = dialog(), chains = [tempo, tempoModerato, tempoDevnet], maxAccounts, persistCredentials, relay, testnet, storage = typeof window !== 'undefined' ? Storage.idb() : Storage.memory(), } = options;
    const authorizeAccessKey_default = options.accessKey?.authorize ?? options.authorizeAccessKey;
    // Filled in below once the adapter instance exists (adapters may supply
    // environment defaults). The object identity is shared with the store's
    // access-key manager and serializer; nothing reads it before the first
    // request.
    const keystores = {};
    // Build per-chain transports from `relay` (if set), then layer caller-provided
    // `transports` on top so explicit per-chain overrides win.
    const transports = (() => {
        if (!relay && !options.transports)
            return undefined;
        const base = relay
            ? Object.fromEntries(chains.map((c) => [c.id, http(`${relay.replace(/\/$/, '')}/${c.id}`)]))
            : {};
        return { ...base, ...options.transports };
    })();
    const feePayerConfig = (() => {
        if (!options.feePayer)
            return undefined;
        if (typeof options.feePayer === 'string')
            return { precedence: 'fee-payer-first', url: options.feePayer };
        return {
            precedence: options.feePayer.precedence ?? 'fee-payer-first',
            url: options.feePayer.url,
        };
    })();
    const defaultChain = testnet
        ? (chains.find((c) => c.testnet) ?? chains[chains.length - 1])
        : chains[0];
    const store = Store.create({
        chainId: defaultChain.id,
        keystores,
        maxAccounts,
        persistCredentials,
        schema: adapter.schema,
        storage,
    });
    const getAccount = (options = {}) => Account.find({ ...options, store });
    // Lazy reference — assigned after the provider is created so the client
    // transport can route provider methods (wallet_connect, etc.) through it.
    let providerRef;
    function getClient(options = {}) {
        const { chainId, feePayer } = options;
        return Client.fromChainId(chainId, {
            chains,
            feePayer: (() => {
                if (feePayer === false)
                    return false;
                if (feePayer)
                    return { url: feePayer, precedence: feePayerConfig?.precedence };
                return undefined;
            })(),
            store,
            transports,
        });
    }
    const instance = adapter({ getAccount, getClient, storage, store });
    const { actions } = instance;
    // App-level keystores override adapter-supplied defaults.
    const keystores_configured = options.accessKey?.keystores ?? instance.accessKey?.keystores;
    Object.assign(keystores, keystores_configured ?? Keystore.defaults);
    const emitter = ox_Provider.createEmitter();
    const requestCache = new Map();
    // Emit EIP-1193 events on state changes.
    store.subscribe((state) => state.accounts.map((a) => a.address).join(), () => emitter.emit('accountsChanged', store.getState().accounts.map((a) => a.address)));
    store.subscribe((state) => state.chainId, (chainId) => emitter.emit('chainChanged', Hex.fromNumber(chainId)));
    store.subscribe((state) => state.accounts.length > 0, (connected) => {
        if (connected)
            emitter.emit('connect', { chainId: Hex.fromNumber(store.getState().chainId) });
        else
            emitter.emit('disconnect', new ox_Provider.DisconnectedError());
    });
    /** Throws `DisconnectedError` if no accounts are connected. */
    function assertConnected() {
        if (store.getState().accounts.length === 0)
            throw new ox_Provider.DisconnectedError({ message: 'No accounts connected.' });
    }
    /** Returns connected account addresses with the active account first. */
    function getAccountAddresses() {
        const { accounts, activeAccount } = store.getState();
        if (accounts.length === 0)
            return [];
        const active = accounts[activeAccount]?.address;
        const activeIdx = accounts.findIndex((a) => a.address === active);
        const sorted = [...accounts];
        if (activeIdx >= 0) {
            const [account] = sorted.splice(activeIdx, 1);
            return [account.address, ...sorted.map((a) => a.address)];
        }
        return sorted.map((a) => a.address);
    }
    function unsupported(action) {
        throw new ox_Provider.UnsupportedMethodError({
            message: `\`${action}\` not supported by adapter.`,
        });
    }
    async function getAdapterAccount(options = {}) {
        if (!instance.getAccount)
            unsupported('getAccount');
        return await instance.getAccount(options);
    }
    function getWalletClient(options) {
        const client = getClient({ chainId: options.chainId, feePayer: options.feePayer });
        return createWalletClient({
            account: options.account,
            chain: client.chain,
            transport: options.transport ?? custom({ request: client.request }),
        });
    }
    function forwardFeePayer(feePayer, account) {
        if (account.type === 'json-rpc' && feePayer !== undefined)
            return { feePayer };
        if (feePayer)
            return { feePayer: true };
        return {};
    }
    async function prepareRootTransaction(parameters) {
        const { feePayer, ...rest } = parameters;
        const selected = await getAdapterAccount({ address: parameters.from });
        const client = getWalletClient({
            account: selected.account,
            chainId: parameters.chainId,
            feePayer: feePayer === true ? undefined : feePayer,
            transport: selected.transport,
        });
        const request = {
            ...rest,
            ...forwardFeePayer(feePayer, selected.account),
        };
        if (selected.account.type === 'json-rpc')
            return { client, request, selected };
        const prepared = await prepareTransactionRequest(client, {
            account: selected.account,
            ...request,
            type: 'tempo',
        });
        return { client, request: prepared, selected };
    }
    async function signTransaction_(parameters) {
        {
            const state = store.getState();
            const chainId = parameters.chainId ?? state.chainId;
            const client = getClient({
                chainId,
                feePayer: (() => {
                    if (parameters.feePayer === false)
                        return false;
                    if (typeof parameters.feePayer === 'string')
                        return parameters.feePayer;
                    return undefined;
                })(),
            });
            const address = parameters.from ?? state.accounts[state.activeAccount]?.address;
            const transaction = address
                ? await AccessKeyTransaction.create({
                    address,
                    calls: parameters.calls,
                    chainId,
                    client,
                    store,
                })
                : undefined;
            if (transaction)
                try {
                    const { feePayer, ...rest } = parameters;
                    const prepared = await transaction.prepare({
                        ...rest,
                        ...(feePayer ? { feePayer: true } : {}),
                    });
                    return await prepared.sign();
                }
                catch { }
        }
        const { client, request, selected } = await prepareRootTransaction(parameters);
        if (selected.account.type === 'json-rpc')
            return await viem_signTransaction(client, request);
        return await selected.account.signTransaction(request);
    }
    async function sendTransaction_(parameters) {
        {
            const state = store.getState();
            const chainId = parameters.chainId ?? state.chainId;
            const client = getClient({
                chainId,
                feePayer: (() => {
                    if (parameters.feePayer === false)
                        return false;
                    if (typeof parameters.feePayer === 'string')
                        return parameters.feePayer;
                    return undefined;
                })(),
            });
            const address = parameters.from ?? state.accounts[state.activeAccount]?.address;
            const transaction = address
                ? await AccessKeyTransaction.create({
                    address,
                    calls: parameters.calls,
                    chainId,
                    client,
                    store,
                })
                : undefined;
            if (transaction)
                try {
                    const { feePayer, ...rest } = parameters;
                    const prepared = await transaction.prepare({
                        ...rest,
                        ...(feePayer ? { feePayer: true } : {}),
                    });
                    return await prepared.send();
                }
                catch { }
        }
        const { feePayer, ...rest } = parameters;
        const selected = await getAdapterAccount({ address: parameters.from });
        const client = getWalletClient({
            account: selected.account,
            chainId: parameters.chainId,
            feePayer: feePayer === true ? undefined : feePayer,
            transport: selected.transport,
        });
        return await sendTransaction(client, {
            ...rest,
            ...forwardFeePayer(feePayer, selected.account),
        });
    }
    async function sendTransactionSync_(parameters) {
        {
            const state = store.getState();
            const chainId = parameters.chainId ?? state.chainId;
            const client = getClient({
                chainId,
                feePayer: (() => {
                    if (parameters.feePayer === false)
                        return false;
                    if (typeof parameters.feePayer === 'string')
                        return parameters.feePayer;
                    return undefined;
                })(),
            });
            const address = parameters.from ?? state.accounts[state.activeAccount]?.address;
            const transaction = address
                ? await AccessKeyTransaction.create({
                    address,
                    calls: parameters.calls,
                    chainId,
                    client,
                    store,
                })
                : undefined;
            if (transaction)
                try {
                    const { feePayer, ...rest } = parameters;
                    const prepared = await transaction.prepare({
                        ...rest,
                        ...(feePayer ? { feePayer: true } : {}),
                    });
                    return await prepared.sendSync();
                }
                catch { }
        }
        const { feePayer, ...rest } = parameters;
        const selected = await getAdapterAccount({ address: parameters.from });
        const client = getWalletClient({
            account: selected.account,
            chainId: parameters.chainId,
            feePayer: feePayer === true ? undefined : feePayer,
            transport: selected.transport,
        });
        const request = {
            ...rest,
            ...forwardFeePayer(feePayer, selected.account),
        };
        if (selected.account.type === 'json-rpc')
            return (await client.request({
                method: 'eth_sendTransactionSync',
                params: [z.encode(Rpc.transactionRequest, request)],
            }));
        const receipt = await viem_sendTransactionSync(client, {
            account: selected.account,
            ...request,
        });
        return z.encode(Rpc.receipt, receipt);
    }
    async function signPersonalMessage(parameters) {
        const selected = await getAdapterAccount({ address: parameters.address });
        const client = getWalletClient({ account: selected.account, transport: selected.transport });
        if (selected.account.type === 'json-rpc')
            return await client.signMessage({
                account: selected.account,
                message: { raw: parameters.data },
            });
        return await selected.account.sign({ hash: hashMessage({ raw: parameters.data }) });
    }
    async function signTypedData(parameters) {
        const typedData = JSON.parse(parameters.data);
        const selected = await getAdapterAccount({ address: parameters.address });
        const client = getWalletClient({ account: selected.account, transport: selected.transport });
        if (selected.account.type === 'json-rpc')
            return await client.signTypedData({
                account: selected.account,
                ...typedData,
            });
        return await selected.account.sign({ hash: hashTypedData(typedData) });
    }
    async function authorizeAccessKey(parameters) {
        const selected = await getAdapterAccount();
        const chainId = parameters.chainId ?? getClient().chain.id;
        if (selected.account.type !== 'json-rpc') {
            const keyAuthorization = await store.accessKeys.authorize({
                account: selected.account,
                chainId,
                parameters,
            });
            return { keyAuthorization, rootAddress: selected.account.address };
        }
        const prepared = await prepareAuthorizeAccessKey(parameters, Number(chainId));
        const client = getWalletClient({
            account: selected.account,
            chainId: Number(chainId),
            transport: selected.transport,
        });
        const result = (await client.request({
            method: 'wallet_authorizeAccessKey',
            params: [z.encode(Rpc.wallet_authorizeAccessKey.parameters, prepared.parameters)],
        }));
        await savePreparedAccessKey({
            account: result.rootAddress,
            accessKey: prepared,
            keyAuthorization: result.keyAuthorization,
        });
        return result;
    }
    async function prepareAuthorizeAccessKey(parameters, chainId) {
        const chainId_ = parameters.chainId ?? chainId ?? getClient().chain.id;
        if (parameters.privateKey || parameters.address || parameters.publicKey) {
            const prepared = await AccessKey.prepareAuthorization({
                ...parameters,
                chainId: chainId_,
            });
            return {
                parameters: toAuthorizeAccessKeyParameters(parameters, prepared.keyAuthorization),
                ...(prepared.privateKey ? { privateKey: prepared.privateKey } : {}),
            };
        }
        // Resolve the key source: configured keystores (app-level, else the
        // adapter's defaults) win; otherwise the built-in keystore — in browsers
        // only, since elsewhere the wallet generates the key.
        if (!keystores_configured && !isBrowserWebCrypto())
            return { parameters };
        const { key, keyAuthorization } = await AccessKey.prepareAuthorization({
            ...parameters,
            chainId: chainId_,
            keystores,
        });
        if (!key)
            throw new RpcResponse.InternalError({
                message: 'Keystore did not produce access-key material.',
            });
        return {
            key,
            parameters: {
                ...toAuthorizeAccessKeyParameters(parameters, keyAuthorization),
                publicKey: key.publicKey,
            },
        };
    }
    function toAuthorizeAccessKeyParameters(parameters, keyAuthorization) {
        const parameters_rpc = { ...parameters };
        delete parameters_rpc.privateKey;
        return {
            ...parameters_rpc,
            address: keyAuthorization.address,
            chainId: keyAuthorization.chainId,
            keyType: keyAuthorization.type,
        };
    }
    async function savePreparedAccessKey(options) {
        const { accessKey, account, keyAuthorization } = options;
        if (!account || !keyAuthorization || !accessKey)
            return;
        const { key, privateKey } = accessKey;
        const material = key
            ? { handle: key.handle, publicKey: key.publicKey }
            : privateKey
                ? { privateKey }
                : undefined;
        if (!material)
            return;
        store.accessKeys.add({
            account,
            authorization: KeyAuthorization.fromRpc(keyAuthorization),
            ...material,
        });
    }
    async function revokeAccessKey(parameters) {
        const selected = await getAdapterAccount({ address: parameters.address });
        const client = getWalletClient({
            account: selected.account,
            transport: selected.transport,
        });
        if (selected.account.type === 'json-rpc') {
            await client.request({
                method: 'wallet_revokeAccessKey',
                params: [parameters],
            });
        }
        else {
            try {
                await Actions.accessKey.revokeSync(getClient(), {
                    account: selected.account,
                    accessKey: parameters.accessKeyAddress,
                });
            }
            catch (error) {
                if (!AccessKey.isUnavailableError(error))
                    throw error;
            }
        }
        store.accessKeys.remove({
            accessKey: parameters.accessKeyAddress,
            account: parameters.address,
            chainId: store.getState().chainId,
        });
    }
    async function updateAccessKey(parameters) {
        const selected = await getAdapterAccount({ address: parameters.address });
        const chainId = Number(parameters.chainId ?? getClient().chain.id);
        if (selected.account.type === 'json-rpc') {
            const client = getWalletClient({
                account: selected.account,
                chainId,
                transport: selected.transport,
            });
            await client.request({
                method: 'wallet_updateAccessKey',
                params: [z.encode(Rpc.wallet_updateAccessKey.parameters, parameters)],
            });
            return;
        }
        // One transaction; `updateSpendingLimit` writes each token's remaining
        // allowance directly, preserving period configuration.
        const calls = parameters.limits.map((limit) => Actions.accessKey.updateLimit.call({
            accessKey: parameters.accessKeyAddress,
            limit: limit.limit,
            token: limit.token,
        }));
        await viem_sendTransactionSync(getClient({ chainId }), {
            account: selected.account,
            calls,
        });
    }
    async function signTransactionAction(parameters, request) {
        if (actions.signTransaction)
            return await actions.signTransaction(parameters, request);
        if (instance.getAccount)
            return await signTransaction_(parameters);
        unsupported('signTransaction');
    }
    async function sendTransactionAction(parameters, request) {
        if (actions.sendTransaction)
            return await actions.sendTransaction(parameters, request);
        if (instance.getAccount)
            return await sendTransaction_(parameters);
        unsupported('sendTransaction');
    }
    async function sendTransactionSyncAction(parameters, request) {
        if (actions.sendTransactionSync)
            return await actions.sendTransactionSync(parameters, request);
        if (instance.getAccount)
            return await sendTransactionSync_(parameters);
        unsupported('sendTransactionSync');
    }
    async function signPersonalMessageAction(parameters, request) {
        if (actions.signPersonalMessage)
            return await actions.signPersonalMessage(parameters, request);
        if (instance.getAccount)
            return await signPersonalMessage(parameters);
        unsupported('signPersonalMessage');
    }
    async function signTypedDataAction(parameters, request) {
        if (actions.signTypedData)
            return await actions.signTypedData(parameters, request);
        if (instance.getAccount)
            return await signTypedData(parameters);
        unsupported('signTypedData');
    }
    async function authorizeAccessKeyAction(parameters, request) {
        if (actions.authorizeAccessKey)
            return await actions.authorizeAccessKey(parameters, request);
        if (instance.getAccount)
            return await authorizeAccessKey(parameters);
        unsupported('authorizeAccessKey');
    }
    async function revokeAccessKeyAction(parameters, request) {
        if (actions.revokeAccessKey)
            return await actions.revokeAccessKey(parameters, request);
        if (instance.getAccount)
            return await revokeAccessKey(parameters);
        unsupported('revokeAccessKey');
    }
    async function updateAccessKeyAction(parameters, request) {
        if (actions.updateAccessKey)
            return await actions.updateAccessKey(parameters, request);
        if (instance.getAccount)
            return await updateAccessKey(parameters);
        unsupported('updateAccessKey');
    }
    /** Returns accounts to persist. When `persistAccounts` is set, merges new accounts with existing ones. */
    function resolveAccounts(accounts) {
        if (!instance.persistAccounts)
            return accounts;
        const merged = [...accounts];
        for (const a of store.getState().accounts)
            if (!merged.some((m) => AddressUtil.isEqual(m.address, a.address)))
                merged.push(a);
        return merged;
    }
    /** Resolves the `feePayer` field from a transaction request into an absolute URL string or `undefined`. */
    function resolveFeePayer(feePayer) {
        if (feePayer === false)
            return false;
        const url = (() => {
            if (typeof feePayer === 'string')
                return feePayer;
            return feePayerConfig?.url;
        })();
        if (!url)
            return undefined;
        if (url.startsWith('http://') || url.startsWith('https://'))
            return url;
        if (typeof window !== 'undefined')
            return new URL(url, window.location.origin).href;
        return url;
    }
    function stripAuthorizeAccessKey(parameters) {
        const { reuse: _reuse, ...rest } = parameters;
        return rest;
    }
    function resolveDefaultAuthorizeAccessKey() {
        if (typeof authorizeAccessKey_default === 'function')
            return authorizeAccessKey_default();
        return authorizeAccessKey_default;
    }
    async function defaultAuthorizeAccessKeyForConnect(options_) {
        const parameters = resolveDefaultAuthorizeAccessKey();
        if (!parameters)
            return undefined;
        const address = reusableConnectAccount(options_.capabilities);
        if (address &&
            (await AccessKey.hasReusableAuthorization({
                account: address,
                chainId: Number(parameters.chainId ?? options_.chainId),
                parameters,
                store: { keystores, state: store },
            })))
            return undefined;
        return stripAuthorizeAccessKey(parameters);
    }
    function reusableConnectAccount(capabilities) {
        const state = store.getState();
        if (state.accounts.length === 0)
            return undefined;
        if (capabilities && 'selectAccount' in capabilities && capabilities.selectAccount)
            return undefined;
        if (capabilities && 'credentialId' in capabilities && capabilities.credentialId) {
            if (Array.isArray(capabilities.credentialId) && capabilities.credentialId.length !== 1)
                return undefined;
            const credentialId = Array.isArray(capabilities.credentialId)
                ? capabilities.credentialId[0]
                : capabilities.credentialId;
            const account = state.accounts.find((a) => 'credential' in a && a.credential?.id === credentialId);
            return account?.address;
        }
        if (capabilities?.method === 'register' && capabilities.name) {
            const account = state.accounts.find((a) => 'credential' in a && a.label?.toLowerCase() === capabilities.name.toLowerCase());
            return account?.address;
        }
        return state.accounts[state.activeAccount]?.address ?? state.accounts[0]?.address;
    }
    async function authorizeDefaultAccessKeyForTransaction(options_) {
        const parameters = resolveDefaultAuthorizeAccessKey();
        if (!parameters)
            return;
        const existing = await store.accessKeys.select({
            account: options_.address,
            ...(options_.calls ? { calls: options_.calls } : {}),
            chainId: Number(parameters.chainId ?? options_.chainId),
        });
        if (existing)
            return;
        if (!AccessKey.canAuthorizeCalls({ parameters, calls: options_.calls }))
            return;
        const decoded = stripAuthorizeAccessKey(parameters);
        await authorizeAccessKeyAction(decoded, {
            method: 'wallet_authorizeAccessKey',
            params: [z.encode(Rpc.wallet_authorizeAccessKey.parameters, decoded)],
        });
    }
    function assertMppAccountConnected(address) {
        const { accounts } = store.getState();
        if (accounts.length === 0)
            throw new ox_Provider.DisconnectedError({ message: 'No accounts connected.' });
        if (!accounts.some((account) => AddressUtil.isEqual(account.address, address)))
            throw new ox_Provider.UnauthorizedError({ message: `Account "${address}" not found.` });
    }
    async function resolveMppAccount(info, options_ = {}) {
        const account = AddressUtil.from(info.account.address);
        if (!account)
            return undefined;
        assertMppAccountConnected(account);
        if (options_.accessKey) {
            const authority = info.operation.kind === 'authorizePaymentChannel'
                ? AddressUtil.from(info.operation.authority)
                : undefined;
            if (authority && !AddressUtil.isEqual(authority, options_.accessKey))
                throw new ox_Provider.UnauthorizedError({
                    message: `Access key "${options_.accessKey}" cannot satisfy channel authority "${authority}".`,
                });
            const query = {
                account,
                accessKey: options_.accessKey,
                chainId: info.chainId,
            };
            if (info.operation.kind === 'executeCalls' && !info.operation.calls) {
                const accessKey = await store.accessKeys.get(query);
                const record = store.accessKeys.list(query)[0];
                if (accessKey && record?.scopes)
                    throw new ox_Provider.UnauthorizedError({
                        message: `Access key "${options_.accessKey}" cannot be selected for executeCalls without transaction call details.`,
                    });
                if (accessKey)
                    return accessKey;
            }
            const accessKey = await store.accessKeys.get({
                ...query,
                ...(info.operation.kind === 'executeCalls' ? { calls: info.operation.calls } : {}),
            });
            if (!accessKey) {
                throw new ox_Provider.UnauthorizedError({
                    message: `Access key "${options_.accessKey}" cannot sign for account "${account}".`,
                });
            }
            return accessKey;
        }
        if (info.operation.kind === 'executeCalls')
            return await store.accessKeys.select({
                account,
                ...(info.operation.calls ? { calls: info.operation.calls } : {}),
                chainId: info.chainId,
            });
        const accessKey = AddressUtil.from(info.operation.authority);
        if (!accessKey)
            return await store.accessKeys.select({ account, chainId: info.chainId });
        if (AddressUtil.isZero(accessKey) || AddressUtil.isEqual(accessKey, account))
            return undefined;
        return await store.accessKeys.get({
            account,
            accessKey,
            chainId: info.chainId,
        });
    }
    function getMppxParameters(options_ = {}) {
        const accessKey = (() => {
            if (!options_.accessKey)
                return undefined;
            const accessKey = AddressUtil.from(options_.accessKey);
            if (!accessKey)
                throw new RpcResponse.InvalidParamsError({
                    message: `Invalid access key address "${options_.accessKey}".`,
                });
            return accessKey;
        })();
        return {
            getClient({ chainId }) {
                if (chainId !== undefined && !chains.some((chain) => chain.id === chainId))
                    throw new ox_Provider.UnsupportedChainIdError({
                        message: `Chain ${chainId} not configured.`,
                    });
                const client = provider.getClient({ chainId });
                const account = store.getState().accounts[store.getState().activeAccount];
                if (!account)
                    throw new ox_Provider.DisconnectedError({ message: 'No active account.' });
                return Object.assign(Object.create(Object.getPrototypeOf(client)), client, {
                    account: {
                        address: account.address,
                        type: 'json-rpc',
                    },
                });
            },
            resolveAccount: (info) => resolveMppAccount(info, { accessKey }),
        };
    }
    const provider = Object.assign(ox_Provider.from({
        ...emitter,
        async request({ method, params }) {
            await Store.waitForHydration(store);
            const shouldDedupe = [
                'eth_accounts',
                'eth_chainId',
                'eth_requestAccounts',
                'wallet_connect',
                'wallet_getBalances',
                'wallet_getCapabilities',
            ].includes(method);
            return withDedupe(async () => {
                // Validate known methods. Unknown methods fall through to the RPC proxy.
                let request;
                try {
                    request = Request.validate(Schema.Request, { method, params });
                }
                catch (e) {
                    if (!(e instanceof ox_Provider.UnsupportedMethodError))
                        throw e;
                    // Proxy unknown methods to the RPC node.
                    return await Client.fromChainId(undefined, { chains, store, transports }).request({
                        method: method,
                        params: params,
                    });
                }
                const result = await (async () => {
                    switch (request.method) {
                        case 'eth_accounts':
                            return getAccountAddresses();
                        case 'eth_chainId':
                            return Hex.fromNumber(store.getState().chainId);
                        case 'eth_requestAccounts': {
                            const existing = getAccountAddresses();
                            if (existing.length > 0)
                                return existing;
                            const { accounts } = await actions.loadAccounts(undefined, {
                                method: 'wallet_connect',
                                params: undefined,
                            });
                            store.setState({ accounts: resolveAccounts(accounts), activeAccount: 0 });
                            return accounts.map((a) => a.address);
                        }
                        case 'eth_sendTransaction': {
                            assertConnected();
                            const [decoded] = request._decoded.params;
                            const { to, data, ...rest } = decoded;
                            const calls = decoded.calls ?? (to ? [{ to, data, value: decoded.value }] : undefined);
                            const state = store.getState();
                            const chainId = decoded.chainId ?? state.chainId;
                            const from = decoded.from ?? state.accounts[state.activeAccount]?.address;
                            if (from)
                                await authorizeDefaultAccessKeyForTransaction({
                                    address: from,
                                    ...(calls ? { calls } : {}),
                                    chainId,
                                });
                            return (await sendTransactionAction({
                                ...rest,
                                chainId,
                                from,
                                ...(calls ? { calls } : {}),
                                feePayer: resolveFeePayer(decoded.feePayer),
                            }, request));
                        }
                        case 'eth_fillTransaction': {
                            const [decoded] = request._decoded.params;
                            const parameters = { ...decoded };
                            const feePayer = resolveFeePayer(parameters.feePayer);
                            const client = getClient({ chainId: parameters.chainId, feePayer });
                            const state = store.getState();
                            const chainId = parameters.chainId ?? state.chainId;
                            const address = parameters.from ?? state.accounts[state.activeAccount]?.address;
                            // The node sizes intrinsic gas from the signing key's signature
                            // type; absent it, it assumes secp256k1 (the smallest signature)
                            // and underestimates gas for p256/webAuthn signers. The key type
                            // is the *signer's*, not the embedded `keyAuthorization`'s (which
                            // is just a registration payload). The `fill` path below always
                            // signs with the root account, so resolve its key type (a caller's
                            // explicit `keyType` wins) and hand viem's tempo formatter an
                            // account carrying it — viem forwards `keyType` and derives the
                            // webAuthn `keyData` hint. secp256k1 (the node default) flows
                            // through the legacy formatter unchanged. The managed access-key
                            // path carries the access key's type via its own viem account.
                            const signer = (() => {
                                if (!address)
                                    return undefined;
                                const keyType = parameters.keyType ??
                                    Account.signatureKeyType(state.accounts.find((a) => AddressUtil.isEqual(a.address, address)));
                                if (!keyType)
                                    return undefined;
                                return { address, keyType, type: 'json-rpc' };
                            })();
                            const fill = (account = signer) => {
                                const fillRequest = {
                                    ...parameters,
                                    ...(account ? { account, from: account.address } : {}),
                                    chainId,
                                    ...(feePayer ? { feePayer: true } : {}),
                                };
                                const formatter = client.chain?.formatters?.transactionRequest;
                                const formatted = formatter
                                    ? formatter.format({ ...fillRequest }, 'fillTransaction')
                                    : fillRequest;
                                return client.request({
                                    method: 'eth_fillTransaction',
                                    params: [formatted],
                                });
                            };
                            // Dapp-provided `keyAuthorization`: the root account signs and
                            // the authorization rides along in `parameters` (so the node
                            // prices its on-chain registration). Skip managed access-key
                            // routing, which would otherwise attach a second authorization.
                            if (parameters.keyAuthorization)
                                return await fill();
                            // Locally-managed access key: the access key signs, and viem
                            // attaches a stored `keyAuthorization` if one is still pending
                            // (i.e. not yet authorized on-chain). When it does, the estimate
                            // must include the gas to authorize the key on-chain — so this
                            // path can't be collapsed into the plain root fill. Falls back to
                            // the root account on failure.
                            if (address) {
                                const calls = parameters.calls ??
                                    (parameters.to ? [{ data: parameters.data, to: parameters.to }] : undefined);
                                const transaction = await AccessKeyTransaction.create({
                                    address,
                                    calls,
                                    chainId,
                                    client,
                                    store,
                                });
                                if (transaction)
                                    try {
                                        return await transaction.fill({
                                            ...parameters,
                                            chainId,
                                            from: address,
                                            ...(feePayer ? { feePayer: true } : {}),
                                        });
                                    }
                                    catch {
                                        // Fall through to the root account fill.
                                    }
                            }
                            return await fill();
                        }
                        case 'eth_signTransaction': {
                            assertConnected();
                            const [decoded] = request._decoded.params;
                            const { to, data, ...rest } = decoded;
                            const calls = decoded.calls ?? (to ? [{ to, data, value: decoded.value }] : undefined);
                            const state = store.getState();
                            return (await signTransactionAction({
                                ...rest,
                                chainId: decoded.chainId ?? state.chainId,
                                from: decoded.from ?? state.accounts[state.activeAccount]?.address,
                                ...(calls ? { calls } : {}),
                                feePayer: decoded.feePayer === true && !feePayerConfig
                                    ? true
                                    : resolveFeePayer(decoded.feePayer),
                            }, request));
                        }
                        case 'eth_sendTransactionSync': {
                            assertConnected();
                            const [decoded] = request._decoded.params;
                            const { to, data, ...rest } = decoded;
                            const calls = decoded.calls ?? (to ? [{ to, data, value: decoded.value }] : undefined);
                            const state = store.getState();
                            const chainId = decoded.chainId ?? state.chainId;
                            const from = decoded.from ?? state.accounts[state.activeAccount]?.address;
                            if (from)
                                await authorizeDefaultAccessKeyForTransaction({
                                    address: from,
                                    ...(calls ? { calls } : {}),
                                    chainId,
                                });
                            return (await sendTransactionSyncAction({
                                ...rest,
                                chainId,
                                from,
                                ...(calls ? { calls } : {}),
                                feePayer: resolveFeePayer(decoded.feePayer),
                            }, request));
                        }
                        case 'eth_signTypedData_v4': {
                            assertConnected();
                            const [address, data] = request._decoded.params;
                            return (await signTypedDataAction({
                                address,
                                data,
                            }, request));
                        }
                        case 'personal_sign': {
                            assertConnected();
                            const [data, address] = request._decoded.params;
                            return (await signPersonalMessageAction({
                                address,
                                data,
                            }, request));
                        }
                        case 'wallet_sendCalls': {
                            try {
                                assertConnected();
                                const decoded = request._decoded.params?.[0];
                                const { calls = [], capabilities, chainId, from } = decoded ?? {};
                                const sync = capabilities?.sync;
                                const feePayer = resolveFeePayer(capabilities?.feePayer ?? (feePayerConfig ? true : undefined));
                                const state = store.getState();
                                const from_ = from ?? state.accounts[state.activeAccount]?.address;
                                if (from_)
                                    await authorizeDefaultAccessKeyForTransaction({
                                        address: from_,
                                        calls,
                                        chainId: chainId ?? state.chainId,
                                    });
                                const txRequest = {
                                    calls,
                                    chainId,
                                    from: from_,
                                    ...(feePayer !== undefined ? { feePayer } : {}),
                                };
                                if (!sync) {
                                    const hash = await sendTransactionAction(txRequest, {
                                        method: 'eth_sendTransaction',
                                        params: [z.encode(Rpc.transactionRequest, txRequest)],
                                    });
                                    const chainId = Hex.fromNumber(store.getState().chainId);
                                    const id = Hex.concat(hash, Hex.padLeft(chainId, 32), sendCallsMagic);
                                    return { capabilities: { sync }, id };
                                }
                                const receipt = await sendTransactionSyncAction(txRequest, {
                                    method: 'eth_sendTransactionSync',
                                    params: [z.encode(Rpc.transactionRequest, txRequest)],
                                });
                                const hash = receipt.transactionHash;
                                const chainIdHex = Hex.fromNumber(store.getState().chainId);
                                const id = Hex.concat(hash, Hex.padLeft(chainIdHex, 32), sendCallsMagic);
                                return {
                                    atomic: true,
                                    capabilities: { sync },
                                    chainId: chainIdHex,
                                    id,
                                    receipts: [receipt],
                                    status: receipt.status === '0x1' ? 200 : 500,
                                    version: '2.0.0',
                                };
                            }
                            catch (error) {
                                throw withDetails(error);
                            }
                        }
                        case 'wallet_getBalances': {
                            const decoded = request._decoded.params?.[0];
                            const { accounts, activeAccount } = store.getState();
                            const account = decoded?.account ?? accounts[activeAccount]?.address;
                            if (!account)
                                throw new ox_Provider.DisconnectedError({
                                    message: 'No accounts connected.',
                                });
                            const tokens = decoded?.tokens;
                            // TODO: hook up to indexer
                            if (!tokens || tokens.length === 0)
                                throw new RpcResponse.InvalidParamsError({
                                    message: '`tokens` is required.',
                                });
                            const client = Client.fromChainId(decoded?.chainId, {
                                chains,
                                store,
                                transports,
                            });
                            return (await Promise.all(tokens.map(async (token) => {
                                const [balance, metadata] = await Promise.all([
                                    Actions.token.getBalance(client, { account, token }),
                                    Actions.token.getMetadata(client, { token }),
                                ]);
                                const value = Number(balance.amount) / 10 ** metadata.decimals;
                                const display = new Intl.NumberFormat('en-US', {
                                    style: 'currency',
                                    currency: 'USD',
                                }).format(value);
                                return {
                                    address: token,
                                    balance: Hex.fromNumber(balance.amount),
                                    decimals: metadata.decimals,
                                    display,
                                    name: metadata.name,
                                    symbol: metadata.symbol,
                                };
                            })));
                        }
                        case 'wallet_getCallsStatus': {
                            const [id] = request._decoded.params ?? [];
                            if (!id)
                                throw new Error('`id` not found');
                            if (!id.endsWith(sendCallsMagic.slice(2)))
                                throw new Error('`id` not supported');
                            Hex.assert(id);
                            const hash = Hex.slice(id, 0, 32);
                            const chainId = Hex.fromNumber(Number(Hex.slice(id, 32, 64)));
                            const client = Client.fromChainId(Number(chainId), {
                                chains,
                                store,
                                transports,
                            });
                            const receipt = await client.request({
                                method: 'eth_getTransactionReceipt',
                                params: [hash],
                            });
                            return {
                                atomic: true,
                                chainId,
                                id,
                                receipts: receipt ? [receipt] : [],
                                status: (() => {
                                    if (!receipt)
                                        return 100; // pending
                                    if (receipt.status === '0x1')
                                        return 200; // success
                                    return 500; // failed
                                })(),
                                version: '2.0.0',
                            };
                        }
                        case 'wallet_getCapabilities': {
                            const decoded = request._decoded.params;
                            const address = decoded?.[0];
                            const chainIds = decoded?.[1];
                            if (address) {
                                const { accounts } = store.getState();
                                if (!accounts.some((a) => AddressUtil.isEqual(a.address, address)))
                                    throw new ox_Provider.UnauthorizedError({
                                        message: `Address ${address} is not connected.`,
                                    });
                            }
                            const filtered = chainIds
                                ? chains.filter((c) => chainIds.includes(Hex.fromNumber(c.id)))
                                : chains;
                            const result = {};
                            for (const chain of filtered)
                                result[Hex.fromNumber(chain.id)] = {
                                    accessKeys: { status: 'supported' },
                                    atomic: { status: 'supported' },
                                    ...(feePayerConfig ? { feePayer: { status: 'supported' } } : {}),
                                };
                            return result;
                        }
                        case 'wallet_connect': {
                            const chainId = request._decoded.params?.[0]?.chainId;
                            if (chainId)
                                store.setState((x) => ({ ...x, chainId }));
                            const capabilities = request._decoded.params?.[0]?.capabilities;
                            const authorizeAccessKey = capabilities?.authorizeAccessKey ??
                                (await defaultAuthorizeAccessKeyForConnect({
                                    capabilities,
                                    chainId: chainId ?? store.getState().chainId,
                                }));
                            // Server Authentication: pre-resolve `auth` URLs against
                            // this dapp-side Provider's `window.location.origin`. The
                            // wallet host (different origin in dialog mode) cannot
                            // reconstruct the dapp's origin, so forwarding the raw
                            // relative URLs would resolve to the wrong host. We then
                            // fetch the challenge BEFORE the ceremony so we can fold
                            // its message into the existing `personalSign` capability.
                            // Forwarding adapters (dialog) skip orchestration — the
                            // wallet host's Provider runs it instead.
                            const auth_input = capabilities?.auth ?? options.auth;
                            const auth_request = auth_input
                                ? absolutizeAuth(auth_input)
                                : undefined;
                            if (auth_request && typeof auth_request === 'object' && !auth_request.challenge)
                                throw new RpcResponse.InvalidParamsError({
                                    message: '`auth` capability must include either `url` or an explicit `challenge` endpoint.',
                                });
                            if (auth_request && capabilities?.personalSign)
                                throw new RpcResponse.InvalidParamsError({
                                    message: '`auth` and `personalSign` cannot both be set on `wallet_connect`.',
                                });
                            const authChainId = chainId ?? store.getState().chainId ?? 0;
                            const accessKey = authorizeAccessKey
                                ? await prepareAuthorizeAccessKey(authorizeAccessKey, chainId)
                                : undefined;
                            // Patch the raw request so forwarding adapters carry the
                            // absolutized auth URLs and prepared access-key material
                            // downstream.
                            if (auth_request || accessKey)
                                request = {
                                    ...request,
                                    params: [
                                        {
                                            ...request.params?.[0],
                                            capabilities: {
                                                ...request.params?.[0]?.capabilities,
                                                ...(auth_request ? { auth: auth_request } : {}),
                                                ...(accessKey
                                                    ? {
                                                        authorizeAccessKey: z.encode(Rpc.wallet_connect.authorizeAccessKey, accessKey.parameters),
                                                    }
                                                    : {}),
                                            },
                                        },
                                    ],
                                };
                            const auth = auth_request && !instance.forwardsAuth
                                ? await fetchAuthChallenge(auth_request, authChainId)
                                : undefined;
                            const personalSign_request = auth
                                ? { message: auth.message }
                                : capabilities?.personalSign;
                            const { accounts, auth: auth_capability, identity, keyAuthorization, personalSign, signature, username, } = await (async () => {
                                if (capabilities?.method === 'register') {
                                    // If a stored account already has this label, sign in
                                    // with its credential instead of creating a new one.
                                    const existing = capabilities.name
                                        ? store
                                            .getState()
                                            .accounts.find((a) => 'credential' in a &&
                                            a.label?.toLowerCase() === capabilities.name.toLowerCase())
                                        : undefined;
                                    if (existing && 'credential' in existing)
                                        return await actions.loadAccounts({
                                            credentialId: existing.credential?.id,
                                            digest: capabilities.digest,
                                            authorizeAccessKey: accessKey?.parameters,
                                            ...(personalSign_request
                                                ? { personalSign: personalSign_request }
                                                : {}),
                                            ...(capabilities.showDeposit !== undefined
                                                ? { showDeposit: capabilities.showDeposit }
                                                : {}),
                                        }, request);
                                    return await actions.createAccount({
                                        digest: capabilities.digest,
                                        authorizeAccessKey: accessKey?.parameters,
                                        name: capabilities.name ?? 'default',
                                        ...(capabilities.showDeposit !== undefined
                                            ? { showDeposit: capabilities.showDeposit }
                                            : {}),
                                        userId: capabilities.userId ?? Hex.random(16),
                                        ...(personalSign_request ? { personalSign: personalSign_request } : {}),
                                    }, request);
                                }
                                return await actions.loadAccounts({
                                    credentialId: capabilities?.credentialId,
                                    digest: capabilities?.digest,
                                    authorizeAccessKey: accessKey?.parameters,
                                    selectAccount: capabilities?.selectAccount,
                                    ...(personalSign_request ? { personalSign: personalSign_request } : {}),
                                    ...(capabilities?.showDeposit !== undefined
                                        ? { showDeposit: capabilities.showDeposit }
                                        : {}),
                                }, request);
                            })();
                            store.setState({
                                accounts: resolveAccounts(accounts),
                                activeAccount: 0,
                                // Persist absolutized auth URLs so a later
                                // `wallet_disconnect` can hit logout even when the
                                // URL was passed per-call. Always overwrite (never
                                // merge) so a connect WITHOUT auth clears stale
                                // state from a prior connect — otherwise a later
                                // disconnect could POST to a logout URL the
                                // current page never opted into.
                                auth: auth_request && typeof auth_request === 'object' ? auth_request : undefined,
                            });
                            const accountAddress = accounts[0]?.address;
                            await savePreparedAccessKey({
                                accessKey,
                                account: accountAddress,
                                keyAuthorization,
                            });
                            // Local adapters return no identity claims — there is no
                            // wallet host to mint them. When the request asks for the
                            // email and an issuer is configured, mint the id token
                            // provider-side. Best-effort, mirroring the wallet host: a
                            // failed mint omits the claim, never the connect.
                            const identity_result = identity ??
                                (await (async () => {
                                    const email = capabilities?.identity?.email;
                                    if (!email || !options.identity || instance.forwardsAuth)
                                        return undefined;
                                    if (!accountAddress)
                                        return undefined;
                                    const audience = options.identity.audience ?? globalThis.location?.origin;
                                    if (!audience)
                                        return undefined;
                                    const nonce = (typeof email === 'object' ? email.nonce : undefined) ??
                                        (auth ? parseAuthNonce(auth.message) : undefined);
                                    return await mintIdentity({
                                        audience,
                                        credentials: options.identity.credentials,
                                        issuer: options.identity.issuer,
                                        nonce,
                                        subject: accountAddress,
                                    }).catch(() => undefined);
                                })());
                            // Server Authentication verify: POST the signed SIWE message
                            // to the verify endpoint. Skipped when the auth capability
                            // omits `verify` — typical when the wallet host strips it
                            // so the dapp-origin Provider does the verify call (and
                            // receives the session cookie on the dapp's origin).
                            //
                            // The signed message comes from one of two places:
                            // - terminal Provider (wallet host): `auth.message` we just fetched.
                            // - forwarding Provider (dapp): `personalSign.message` echoed back
                            //   by the wallet host's Provider.
                            const verifyUrl = auth_request && typeof auth_request === 'object'
                                ? auth_request.verify
                                : undefined;
                            const verifyMessage = auth?.message ?? personalSign?.message;
                            if (auth_request && verifyUrl && verifyMessage && signature && accountAddress)
                                validateAuthMessage(auth_request, {
                                    chainId: authChainId,
                                    message: verifyMessage,
                                });
                            const auth_result = auth_request && verifyUrl && verifyMessage && signature && accountAddress
                                ? await verifyAuthMessage(auth_request, {
                                    address: accountAddress,
                                    message: verifyMessage,
                                    signature,
                                    // Forward the verified-email id token so a
                                    // `Handler.auth({ identity })` can verify it and fold
                                    // the email onto the session in the same round-trip.
                                    // The minter reused this SIWE nonce, so the
                                    // handler's nonce check passes.
                                    ...(identity_result?.idToken
                                        ? { idToken: identity_result.idToken }
                                        : {}),
                                    ...(personalSign?.keyAuthorization
                                        ? { keyAuthorization: personalSign.keyAuthorization }
                                        : {}),
                                })
                                : undefined;
                            return {
                                accounts: accounts.map((a) => ({
                                    address: a.address,
                                    capabilities: a.address === accountAddress
                                        ? {
                                            ...(keyAuthorization
                                                ? {
                                                    keyAuthorization: {
                                                        ...keyAuthorization,
                                                        address: keyAuthorization.keyId,
                                                    },
                                                }
                                                : {}),
                                            ...(signature && (!auth_request || auth_result || !verifyUrl)
                                                ? { signature }
                                                : {}),
                                            ...(username !== undefined ? { username } : {}),
                                            ...((auth_result ?? auth_capability)
                                                ? { auth: auth_result ?? auth_capability }
                                                : {}),
                                            ...(identity_result ? { identity: identity_result } : {}),
                                            ...(personalSign
                                                ? {
                                                    personalSign: {
                                                        message: personalSign.message,
                                                        ...(personalSign.keyAuthorization
                                                            ? { keyAuthorization: personalSign.keyAuthorization }
                                                            : {}),
                                                    },
                                                }
                                                : {}),
                                        }
                                        : {},
                                })),
                            };
                        }
                        case 'wallet_disconnect': {
                            // Best-effort logout. Source of the URL, in order:
                            // 1. Last-connected `auth` URLs persisted in the store
                            //    (handles per-call `auth` passed via wallet_connect).
                            // 2. Provider.create({ auth }) option fallback.
                            // Swallows all errors — disconnect must succeed even
                            // when the session is already gone or the server is
                            // unreachable.
                            const logoutUrl = (() => {
                                const stored = store.getState().auth;
                                if (stored?.logout)
                                    return stored.logout;
                                if (!options.auth)
                                    return undefined;
                                try {
                                    const absolute = absolutizeAuth(options.auth);
                                    return typeof absolute === 'object' ? absolute.logout : undefined;
                                }
                                catch {
                                    return undefined;
                                }
                            })();
                            if (logoutUrl)
                                await fetch(logoutUrl, {
                                    method: 'POST',
                                    credentials: 'include',
                                }).catch(() => { });
                            await actions.disconnect?.();
                            store.disconnect();
                            return;
                        }
                        case 'wallet_authorizeAccessKey': {
                            const decoded = request._decoded.params[0];
                            const result = await authorizeAccessKeyAction(decoded, request);
                            return {
                                keyAuthorization: {
                                    ...result.keyAuthorization,
                                    address: result.keyAuthorization.keyId,
                                },
                                rootAddress: result.rootAddress,
                            };
                        }
                        case 'wallet_revokeAccessKey': {
                            assertConnected();
                            const [decoded] = request._decoded.params;
                            await revokeAccessKeyAction({ ...decoded }, request);
                            return;
                        }
                        case 'wallet_updateAccessKey': {
                            assertConnected();
                            const [decoded] = request._decoded.params;
                            await updateAccessKeyAction({ ...decoded }, request);
                            return;
                        }
                        case 'wallet_deposit': {
                            if (!actions.deposit)
                                throw new ox_Provider.UnsupportedMethodError({
                                    message: '`deposit` not supported by adapter.',
                                });
                            return (await actions.deposit(request._decoded.params?.[0] ?? {}, request));
                        }
                        case 'wallet_transfer': {
                            assertConnected();
                            // Default to the editable variant when params are
                            // omitted — Read-only mode requires `amount`,
                            // `to`, and `token`, so an empty call only makes
                            // sense as "open the wallet send UI".
                            const decoded = request._decoded.params?.[0] ?? { editable: true };
                            // Editable variant: forward to the wallet host UI.
                            if (decoded.editable === true) {
                                if (!actions.transfer)
                                    throw new ox_Provider.UnsupportedMethodError({
                                        message: '`transfer` not supported by adapter.',
                                    });
                                const parameters = {
                                    ...decoded,
                                    ...(typeof decoded.feePayer !== 'undefined'
                                        ? { feePayer: resolveFeePayer(decoded.feePayer) }
                                        : {}),
                                };
                                return (await actions.transfer(parameters, request));
                            }
                            // Programmatic variant (default): skip the wallet UI,
                            // build the TIP-20 `transfer` call inline, and route
                            // through `eth_sendTransactionSync` (which uses an
                            // access key when one matches, falling back to the
                            // dialog otherwise).
                            const { amount, feePayer, from, memo, to, token } = decoded;
                            const state = store.getState();
                            const chainId = decoded.chainId ?? state.chainId;
                            const resolvedFeePayer = resolveFeePayer(feePayer);
                            const client = getClient({
                                chainId,
                                feePayer: typeof resolvedFeePayer === 'string' ? resolvedFeePayer : undefined,
                            });
                            const { address: tokenAddress, decimals } = await (async () => {
                                if (Address.validate(token)) {
                                    const metadata = await Actions.token.getMetadata(client, {
                                        token,
                                    });
                                    return { address: token, decimals: metadata.decimals };
                                }
                                const resolved = await Tokenlist.resolveSymbol({
                                    chainId: client.chain.id,
                                    symbol: token,
                                });
                                if (!resolved)
                                    throw new ox_Provider.ProviderRpcError(-32602, `Unknown token symbol "${token}".`);
                                return { address: resolved.address, decimals: resolved.decimals };
                            })();
                            const amountUnits = parseUnits(amount, decimals);
                            // The signer is the active account (or its access
                            // key). `from` here is the TIP-20 source for
                            // `transferFrom` semantics, so we only forward it
                            // when the caller explicitly set it to a different
                            // address — otherwise `Actions.token.transfer.call`
                            // emits `transferFrom` (different selector) instead
                            // of plain `transfer`, breaking access-key scope
                            // matching.
                            const signerAddress = state.accounts[state.activeAccount]?.address;
                            const sourceFrom = from && signerAddress && from.toLowerCase() !== signerAddress.toLowerCase()
                                ? from
                                : undefined;
                            const call = Actions.token.transfer.call({
                                amount: amountUnits,
                                ...(sourceFrom ? { from: sourceFrom } : {}),
                                memo: memo ? Hex.fromString(memo) : undefined,
                                to,
                                token: tokenAddress,
                            });
                            const txRequest = {
                                calls: [call],
                                chainId,
                                from: signerAddress,
                                ...(resolvedFeePayer !== undefined ? { feePayer: resolvedFeePayer } : {}),
                            };
                            const receipt = await sendTransactionSyncAction(txRequest, {
                                method: 'eth_sendTransactionSync',
                                params: [z.encode(Rpc.transactionRequest, txRequest)],
                            });
                            return {
                                chainId: Hex.fromNumber(chainId),
                                receipt,
                            };
                        }
                        case 'wallet_swap': {
                            assertConnected();
                            if (!actions.swap)
                                throw new ox_Provider.UnsupportedMethodError({
                                    message: '`swap` not supported by adapter.',
                                });
                            return (await actions.swap((request._decoded.params?.[0] ?? {}), request));
                        }
                        case 'wallet_depositZone': {
                            assertConnected();
                            if (!actions.depositZone)
                                throw new ox_Provider.UnsupportedMethodError({
                                    message: '`depositZone` not supported by adapter.',
                                });
                            const decoded = request._decoded.params?.[0] ?? {};
                            const parameters = {
                                ...decoded,
                                ...(typeof decoded.feePayer !== 'undefined'
                                    ? { feePayer: resolveFeePayer(decoded.feePayer) }
                                    : {}),
                            };
                            return (await actions.depositZone(parameters, request));
                        }
                        case 'wallet_withdrawZone': {
                            assertConnected();
                            if (!actions.withdrawZone)
                                throw new ox_Provider.UnsupportedMethodError({
                                    message: '`withdrawZone` not supported by adapter.',
                                });
                            return (await actions.withdrawZone((request._decoded.params?.[0] ?? {}), request));
                        }
                        case 'wallet_switchEthereumChain': {
                            const { chainId } = request._decoded.params[0];
                            if (!chains.some((c) => c.id === chainId))
                                throw new ox_Provider.UnsupportedChainIdError({
                                    message: `Chain ${chainId} not configured.`,
                                });
                            await actions.switchChain?.({ chainId });
                            store.setState({ chainId });
                            return;
                        }
                    }
                })();
                return result;
            }, {
                cache: requestCache,
                enabled: shouldDedupe,
                id: Json.stringify({ method, params }),
            });
        },
    }, { schema: Schema.ox }), {
        chains,
        getAccount: ((options) => {
            const account = getAccount(options);
            if (options?.signable)
                return account;
            return { address: account.address, type: 'json-rpc' };
        }),
        async getAccessKeyStatus(options = {}) {
            const state = store.getState();
            const address = options.address ?? state.accounts[state.activeAccount]?.address;
            if (!address)
                return 'missing';
            const chainId = options.chainId ?? state.chainId;
            const { accessKey, calls } = options;
            return await store.accessKeys.getStatus({
                account: address,
                ...(accessKey ? { accessKey } : {}),
                ...(calls ? { calls } : {}),
                chainId,
                client: provider.getClient({ chainId }),
            });
        },
        getClient(options = {}) {
            const { chainId, feePayer } = options;
            return Client.fromChainId(chainId, {
                chains,
                feePayer,
                provider: providerRef,
                store,
                transports,
            });
        },
        getMppxParameters,
        store,
    });
    if (typeof window !== 'undefined' &&
        typeof CustomEvent !== 'undefined' &&
        typeof crypto.randomUUID === 'function') {
        const rdns = adapter.rdns ?? `com.${(adapter.name ?? 'Injected Wallet').toLowerCase().replace(/\s+/g, '')}`;
        if (!announced.has(rdns)) {
            announced.add(rdns);
            announceProvider({
                info: {
                    icon: adapter.icon ?? defaultIcon,
                    name: adapter.name ?? 'Injected Wallet',
                    rdns,
                    uuid: crypto.randomUUID(),
                },
                provider,
            });
        }
    }
    const mpp = (() => {
        if (options.mpp === false)
            return undefined;
        if (typeof options.mpp === 'object')
            return options.mpp;
        return {};
    })();
    if (mpp) {
        const { mode = 'push', polyfill: polyfill_option, ...methodOptions } = mpp;
        // Skip polyfill on runtimes where `globalThis.fetch` is read-only (e.g.
        // Cloudflare Workers). Caller can also explicitly opt out via `mpp.polyfill`.
        const polyfill = polyfill_option ?? isFetchWritable();
        const parameters = provider.getMppxParameters();
        const tempoOptions = {
            ...methodOptions,
            ...parameters,
            mode,
        };
        Mppx.create({
            methods: [mppx_tempo(tempoOptions), mppx_tempo.subscription(parameters)],
            polyfill,
        });
    }
    providerRef = provider;
    return provider;
}
const defaultIcon = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"><rect width="1" height="1"/></svg>';
const sendCallsMagic = Hash.keccak256(Hex.fromString('TEMPO_5792'));
function withDetails(error) {
    if (error instanceof Error) {
        const details = error.details;
        if (typeof details === 'string')
            return error;
        Object.assign(error, { details: error.message });
        return error;
    }
    const next = new Error(String(error));
    Object.assign(next, { details: next.message });
    return next;
}
/**
 * Returns `true` if `globalThis.fetch` can be reassigned. Some runtimes
 * (notably Cloudflare Workers) expose a non-writable, non-configurable
 * `fetch` that throws when `Mppx.create({ polyfill: true })` tries to
 * replace it.
 *
 * Tries an actual no-op self-reassignment because some runtimes report a
 * writable descriptor but still throw at assignment time (e.g. Workers
 * dev runner via Durable Objects).
 */
function isFetchWritable() {
    try {
        const original = globalThis.fetch;
        globalThis.fetch = original;
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Heuristic for whether the current runtime carries cookies on
 * `fetch(..., { credentials: 'include' })`:
 *
 * - **Browser**: `document.cookie` exists → uses the browser cookie jar.
 * - **React Native / Node / CLI**: neither — `credentials: 'include'` is not
 *   enough for the SDK to surface a bearer token to the caller.
 *
 * False negatives are possible (Node with `tough-cookie` shimmed in); the
 * caller can always force token mode via `auth: { returnToken: true }`.
 */
function hasCookieJar() {
    if (typeof document !== 'undefined' && typeof document.cookie === 'string')
        return true;
    return false;
}
/**
 * Resolves a Server Authentication endpoint from the `auth` capability
 * into an absolute URL.
 *
 * - `auth: '/api/auth'`            → `/api/auth/challenge`, `/api/auth` (verify), `/api/auth/logout`
 * - `auth: { url: '/api/auth' }`   → same as above
 * - `auth: { challenge, verify }`  → explicit per-endpoint
 * - Mix: explicit endpoint wins over derivation from `url`.
 *
 * Relative paths (`/api/auth`, `auth/challenge`) are absolutized against
 * `window.location.origin` when available — same shape as `resolveFeePayer`.
 * Already-absolute `http(s)://` URLs pass through verbatim.
 */
function resolveAuthEndpoint(auth, kind) {
    const path = (() => {
        if (typeof auth === 'string') {
            const base = auth.endsWith('/') ? auth.slice(0, -1) : auth;
            return kind === 'verify' ? base : `${base}/${kind}`;
        }
        const explicit = auth[kind];
        if (explicit)
            return explicit;
        if (auth.url) {
            const base = auth.url.endsWith('/') ? auth.url.slice(0, -1) : auth.url;
            return kind === 'verify' ? base : `${base}/${kind}`;
        }
        throw new RpcResponse.InvalidParamsError({
            message: `\`auth\` capability must include either \`url\` or an explicit \`${kind}\` endpoint.`,
        });
    })();
    if (path.startsWith('http://') || path.startsWith('https://'))
        return path;
    if (typeof window !== 'undefined')
        return new URL(path, window.location.origin).href;
    return path;
}
/**
 * Pre-resolves the `auth` capability into its absolute object form. Run
 * once at the dapp-side Provider so forwarding adapters (dialog) carry
 * absolute URLs to the wallet host — the wallet's `window.location.origin`
 * belongs to the wallet, not the dapp, and cannot resolve relative paths
 * correctly.
 *
 * Individual endpoints are omitted when the input doesn't supply enough
 * info to derive them. `logout` is optional in the protocol; `verify` can
 * also be omitted by wallet-host re-entry so the dapp-origin Provider runs
 * verification and receives the session cookie.
 */
function absolutizeAuth(auth) {
    // Wallet-host re-entry can strip endpoints (e.g. drop `verify` so the
    // dapp-origin Provider runs verify). Only resolve endpoints the input
    // can derive — pass through everything else as-is.
    const hasUrl = typeof auth === 'string' || Boolean(auth.url);
    const hasChallenge = hasUrl || (typeof auth === 'object' && Boolean(auth.challenge));
    const hasVerify = hasUrl || (typeof auth === 'object' && Boolean(auth.verify));
    const hasLogout = hasUrl || (typeof auth === 'object' && Boolean(auth.logout));
    const resolved = {
        ...(hasChallenge ? { challenge: resolveAuthEndpoint(auth, 'challenge') } : {}),
        ...(hasVerify ? { verify: resolveAuthEndpoint(auth, 'verify') } : {}),
        ...(hasLogout ? { logout: resolveAuthEndpoint(auth, 'logout') } : {}),
        ...(typeof auth === 'object' && auth.resources !== undefined
            ? { resources: auth.resources }
            : {}),
        ...(typeof auth === 'object' && auth.returnToken ? { returnToken: true } : {}),
    };
    assertSameAuthOrigin(resolved);
    return resolved;
}
function assertSameAuthOrigin(auth) {
    if (typeof auth !== 'object')
        return;
    const urls = [auth.challenge, auth.verify, auth.logout].filter((u) => typeof u === 'string');
    const origins = urls.map((url) => {
        try {
            return new URL(url).origin;
        }
        catch {
            throw new RpcResponse.InvalidParamsError({
                message: `\`auth\` endpoint is not a valid URL: ${url}`,
            });
        }
    });
    const first = origins[0];
    if (origins.some((origin) => origin !== first))
        throw new RpcResponse.InvalidParamsError({
            message: '`auth` endpoints (`challenge`, `verify`, `logout`) must share the same origin.',
        });
}
/**
 * Hint appended to "domain mismatch" / "uri mismatch" errors raised in
 * {@link fetchAuthChallenge}. Most of the time these come from a server
 * sitting behind a TLS-terminating proxy (Cloudflare Tunnel, ngrok, a
 * CDN) that forwards `x-forwarded-proto` / `x-forwarded-host` headers the
 * auth handler isn't honoring by default.
 */
const authOriginHint = ' Hint: if the server is behind a reverse proxy or tunnel, set `Handler.auth({ trustProxy: true })` to honor `x-forwarded-*` headers, or pin the public origin with `Handler.auth({ origin: "https://app.example.com" })`.';
function stringsEqual(a, b) {
    if (a.length !== b.length)
        return false;
    return a.every((value, index) => value === b[index]);
}
function validateAuthMessage(auth, options) {
    const { chainId, message } = options;
    const url = options.url ??
        (typeof auth === 'object' ? auth.challenge : resolveAuthEndpoint(auth, 'challenge'));
    const resources = typeof auth === 'object' ? auth.resources : undefined;
    const parsed = parseSiweMessage(message);
    const expected = new URL(url);
    if (parsed.version !== '1')
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` returned a non-SIWE-v1 message.`,
        });
    if (!parsed.nonce)
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` response is missing a \`nonce\`.`,
        });
    if (parsed.domain !== expected.host)
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` returned a message bound to \`${parsed.domain}\` (expected \`${expected.host}\`).${authOriginHint}`,
        });
    if (parsed.uri !== expected.origin)
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` returned a message with \`uri\` \`${parsed.uri}\` (expected \`${expected.origin}\`).${authOriginHint}`,
        });
    if (parsed.chainId !== chainId)
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` returned a message bound to chainId \`${parsed.chainId}\` (expected \`${chainId}\`).`,
        });
    if (resources !== undefined && !stringsEqual(parsed.resources ?? [], resources))
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` did not echo the requested SIWE resources.`,
        });
}
/**
 * Fetches an auth challenge from the auth endpoint and validates that the
 * server-supplied message is bound to the auth endpoint's origin and the
 * requested chain.
 *
 * Expects an absolutized auth capability (post-`absolutizeAuth`).
 *
 * The signature produced from this challenge is a portable artifact: once
 * the wallet signs, anyone holding the bytes can replay it against any
 * auth verifier that accepts the embedded domain. We therefore refuse to
 * sign a message whose `domain`/`uri` doesn't match the auth endpoint —
 * otherwise a compromised auth provider could trick the wallet into
 * signing "Sign in to attacker.com" and use it to log in as the user
 * elsewhere.
 */
async function fetchAuthChallenge(auth, chainId) {
    const url = typeof auth === 'object' ? auth.challenge : resolveAuthEndpoint(auth, 'challenge');
    const resources = typeof auth === 'object' ? auth.resources : undefined;
    const res = await fetch(url, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
            chainId,
            ...(resources !== undefined ? { resources } : {}),
        }),
    });
    if (!res.ok)
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` returned ${res.status}.`,
        });
    const body = (await res.json().catch(() => ({})));
    if (!body.message)
        throw new RpcResponse.InvalidParamsError({
            message: `Server Authentication challenge endpoint \`${url}\` response missing \`message\`.`,
        });
    validateAuthMessage(auth, { chainId, message: body.message, url });
    return { message: body.message };
}
/**
 * Extracts the nonce from an EIP-4361 (SIWE) challenge message so a minted
 * identity token binds to the same single-use value the server checks.
 */
function parseAuthNonce(message) {
    return message.match(/^Nonce: (.+)$/m)?.[1];
}
/**
 * Mints a verified-email identity token from an OIDC issuer's `/token` route
 * (`Handler.oidcProvider` shape). Local adapters fulfill the connect ceremony
 * without a wallet host, so the provider mints the token itself when
 * configured (see `create.Options.identity`).
 */
async function mintIdentity(options) {
    const { audience, credentials, issuer, nonce, subject } = options;
    const res = await fetch(`${issuer.replace(/\/+$/, '')}/token`, {
        body: JSON.stringify({
            audience,
            ...(nonce ? { nonce } : {}),
            subject,
        }),
        ...(credentials ? { credentials } : {}),
        headers: { 'content-type': 'application/json' },
        method: 'POST',
    });
    if (!res.ok)
        throw new Error(`Identity issuer \`${issuer}\` returned ${res.status}.`);
    const { idToken } = (await res.json());
    // Display-only claim read; server-side trust comes from JWKS verification.
    const payload = JSON.parse(atob(idToken.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
    return { email: payload.email ?? null, idToken };
}
/**
 * Posts the signed message to the auth `verify` endpoint and returns
 * the SDK-shaped `auth` capability output. `{ token }` remains reserved
 * for token mode; other JSON fields returned by the verify endpoint are
 * preserved for application-specific metadata.
 */
async function verifyAuthMessage(auth, body) {
    const url = typeof auth === 'object' ? auth.verify : resolveAuthEndpoint(auth, 'verify');
    // Auto-request the token in environments without a cookie jar (React
    // Native / Node / CLI). Browser lets the cookie do the work; explicit
    // `returnToken: true` always wins.
    const explicitReturnToken = typeof auth === 'object' && auth.returnToken === true;
    const returnToken = explicitReturnToken || !hasCookieJar();
    const res = await fetch(url, {
        method: 'POST',
        credentials: 'include',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
            ...body,
            ...(returnToken ? { returnToken: true } : {}),
        }),
    });
    if (!res.ok)
        throw new RpcResponse.InternalError({
            message: `Server Authentication verify endpoint \`${url}\` returned ${res.status}.`,
        });
    const json = await res.json().catch(() => ({}));
    if (!json || typeof json !== 'object' || Array.isArray(json))
        return {};
    return json;
}
//# sourceMappingURL=Provider.js.map