UNPKG

accounts

Version:

Tempo Accounts SDK

189 lines (181 loc) 7.9 kB
import { Hex, Provider as core_Provider } from 'ox' import { custom } from 'viem' import { z } from 'zod/mini' import * as Adapter from '../../Adapter.js' import type * as Keystore from '../../Keystore.js' import * as Rpc from '../../zod/rpc.js' import * as u from '../../zod/utils.js' /** * Builds an adapter that forwards wallet RPC to a remote wallet through a * single request function. * * Shared core for adapters whose wallet lives behind a request/response * session (e.g. Wata mobile web auth or postMessage). Access keys are * generated locally; every other wallet action round-trips through * `request`. */ export function fromRequest(options: fromRequest.Options): Adapter.Adapter { const { actions, bind, cleanup, close, icon, keystores, name, rdns, request: requestRemote, } = options return Adapter.define( { ...(icon ? { icon } : {}), name, rdns, schema: z.object({ address: u.address() }) }, ({ getAccount, getClient, store }) => { // Late-bind the store so the request channel can reconcile local // connection state (e.g. a wallet `accountsChanged` notification). bind?.({ store }) const provider = core_Provider.from({ async request(request) { const { accounts, activeAccount, chainId } = store.getState() if (request.method === 'eth_chainId') return Hex.fromNumber(chainId) // Forward the active account + chain as request context so the wallet // acts against the session the app believes it is using rather than // whatever the wallet last selected for itself. const account = accounts[activeAccount]?.address return await requestRemote({ method: request.method, params: request.params as readonly unknown[] | undefined, context: { chainId, ...(account ? { account } : {}) }, }) }, }) // Connect capabilities are read from the first account only: the remote // wallet connects a single account per exchange. function toConnectReturn(result: Rpc.wallet_connect.Encoded['returns']) { const capabilities = result.accounts[0]?.capabilities return { accounts: result.accounts.map((account) => ({ address: account.address })), ...(capabilities?.auth ? { auth: capabilities.auth } : {}), ...(capabilities?.identity ? { identity: capabilities.identity } : {}), ...(capabilities?.keyAuthorization ? { keyAuthorization: capabilities.keyAuthorization } : {}), ...(capabilities?.personalSign ? { personalSign: capabilities.personalSign } : {}), ...(capabilities?.signature ? { signature: capabilities.signature } : {}), ...(capabilities?.username !== undefined ? { username: capabilities.username } : {}), } } return { forwardsAuth: true, ...(keystores ? { accessKey: { keystores } } : {}), actions: { async createAccount(_parameters, request) { return toConnectReturn( (await provider.request(request)) as Rpc.wallet_connect.Encoded['returns'], ) }, async loadAccounts(_parameters, request) { return toConnectReturn( (await provider.request(request)) as Rpc.wallet_connect.Encoded['returns'], ) }, async deposit(_parameters, request) { return (await provider.request(request)) as Rpc.wallet_deposit.Encoded['returns'] }, async depositZone(parameters, request) { return (await provider.request({ ...request, params: [z.encode(Rpc.wallet_depositZone.parameters, parameters)] as const, })) as Rpc.wallet_depositZone.Encoded['returns'] }, // `disconnect` (when `close` is set) only tears down the local // wallet session — it is never forwarded. No `switchChain` action: // chain selection is local state the provider handles itself, and // forwarding it would open a wallet session for nothing. ...(close ? { disconnect: close } : {}), async swap(_parameters, request) { return (await provider.request(request)) as Rpc.wallet_swap.Encoded['returns'] }, async transfer(parameters, request) { return (await provider.request({ ...request, params: [z.encode(Rpc.wallet_transfer.parameters, parameters)] as const, })) as Rpc.wallet_transfer.Encoded['returns'] }, async withdrawZone(parameters, request) { return (await provider.request({ ...request, params: [z.encode(Rpc.wallet_withdrawZone.parameters, parameters)] as const, })) as Rpc.wallet_withdrawZone.Encoded['returns'] }, ...actions?.({ getClient, request: requestRemote, store }), }, ...(cleanup ? { cleanup } : close ? { cleanup: () => void close() } : {}), getAccount(parameters = {}) { return { account: { address: parameters.address ?? getAccount({ signable: false }).address, type: 'json-rpc' as const, }, transport: custom(provider), } }, } }, ) } export declare namespace fromRequest { /** Options for {@link fromRequest}. */ export type Options = { /** * Adapter-specific action overrides layered over the request-forwarding * defaults. Receives the request channel plus the adapter's client and * store for actions that need local bookkeeping around an exchange. */ actions?: | ((context: { getClient: Adapter.SetupFn.Parameters['getClient'] request: Options['request'] store: Adapter.SetupFn.Parameters['store'] }) => Partial<Adapter.Instance['actions']>) | undefined /** * Called once during setup with the adapter's store, for late wiring * that needs to mutate local state (e.g. reconciling connection state * from a wallet `accountsChanged` notification on the request channel). */ bind?: ((context: { store: Adapter.SetupFn.Parameters['store'] }) => void) | undefined /** * Tears down adapter-owned resources (sessions, mounted UI) when the * provider discards the adapter instance. @default `close`, when set. */ cleanup?: (() => void) | undefined /** Data URI of the provider icon, announced via EIP-6963. */ icon?: Adapter.Meta['icon'] | undefined /** * Default keystores backing locally generated access keys. Omit to use * the SDK default (non-extractable `webCryptoP256`). Pass an explicit * keystore where the transport's environment needs one — e.g. pure-JS * P-256 for React Native, which may lack WebCrypto and persists access * keys through string-based storage. */ keystores?: Keystore.Keystores | undefined /** * Tears down the adapter's wallet session. Wired to the `disconnect` * action and instance cleanup; never forwarded to the remote wallet. * Omit for single-exchange transports with no session to clean up. */ close?: (() => Promise<void>) | undefined /** Provider display name. */ name: string /** Reverse-DNS provider identifier. */ rdns: string /** Forwards a single wallet JSON-RPC request to the remote wallet. */ request: (request: { /** JSON-RPC method name. */ method: string /** JSON-RPC params. */ params?: readonly unknown[] | undefined /** Session selectors (active account + chain) for the wallet to act on. */ context?: { account?: string | undefined; chainId?: number | undefined } | undefined }) => Promise<unknown> } }