UNPKG

viem

Version:

TypeScript Interface for Ethereum

134 lines (115 loc) 3.71 kB
import type { Address } from 'abitype' import { getTransactionCount } from '../actions/public/getTransactionCount.js' import type { Client } from '../clients/createClient.js' import type { MaybePromise } from '../types/utils.js' import { LruMap } from './lru.js' export type CreateNonceManagerParameters = { source: NonceManagerSource } type FunctionParameters = { address: Address chainId: number } export type NonceManager = { /** Get and increment a nonce. */ consume: ( parameters: FunctionParameters & { client: Client }, ) => Promise<number> /** Increment a nonce. */ increment: (chainId: FunctionParameters) => void /** Get a nonce. */ get: (chainId: FunctionParameters & { client: Client }) => Promise<number> /** Reset a nonce. */ reset: (chainId: FunctionParameters) => void } /** * Creates a nonce manager for auto-incrementing transaction nonces. * * - Docs: https://viem.sh/docs/accounts/createNonceManager * * @example * ```ts * const nonceManager = createNonceManager({ * source: jsonRpc(), * }) * ``` */ export function createNonceManager( parameters: CreateNonceManagerParameters, ): NonceManager { const { source } = parameters const deltaMap = new Map() const nonceMap = new LruMap<number>(8192) const promiseMap = new Map<string, Promise<number>>() const getKey = ({ address, chainId }: FunctionParameters) => `${address}.${chainId}` return { async consume({ address, chainId, client }) { const key = getKey({ address, chainId }) const promise = this.get({ address, chainId, client }) this.increment({ address, chainId }) const nonce = await promise await source.set({ address, chainId }, nonce) nonceMap.set(key, nonce) return nonce }, async increment({ address, chainId }) { const key = getKey({ address, chainId }) const delta = deltaMap.get(key) ?? 0 deltaMap.set(key, delta + 1) }, async get({ address, chainId, client }) { const key = getKey({ address, chainId }) let promise = promiseMap.get(key) if (!promise) { promise = (async () => { try { const nonce = await source.get({ address, chainId, client }) const previousNonce = nonceMap.get(key) ?? 0 if (previousNonce > 0 && nonce <= previousNonce) return previousNonce + 1 nonceMap.delete(key) return nonce } finally { this.reset({ address, chainId }) } })() promiseMap.set(key, promise) } const delta = deltaMap.get(key) ?? 0 return delta + (await promise) }, reset({ address, chainId }) { const key = getKey({ address, chainId }) deltaMap.delete(key) promiseMap.delete(key) }, } } //////////////////////////////////////////////////////////////////////////////////////////// // Sources export type NonceManagerSource = { /** Get a nonce. */ get(parameters: FunctionParameters & { client: Client }): MaybePromise<number> /** Set a nonce. */ set(parameters: FunctionParameters, nonce: number): MaybePromise<void> } /** JSON-RPC source for a nonce manager. */ export function jsonRpc(): NonceManagerSource { return { async get(parameters) { const { address, client } = parameters return getTransactionCount(client, { address, blockTag: 'pending', }) }, set() {}, } } //////////////////////////////////////////////////////////////////////////////////////////// // Default /** Default Nonce Manager with a JSON-RPC source. */ export const nonceManager = /*#__PURE__*/ createNonceManager({ source: jsonRpc(), })