UNPKG

viem

Version:

TypeScript Interface for Ethereum

1,628 lines (1,530 loc) • 46.8 kB
import type { Address } from 'abitype' import type { KeyAuthorization } from 'ox/tempo' import type { Account } from '../../accounts/types.js' import { parseAccount } from '../../accounts/utils/parseAccount.js' import type { ReadContractReturnType } from '../../actions/public/readContract.js' import { readContract } from '../../actions/public/readContract.js' import type { WatchContractEventParameters } from '../../actions/public/watchContractEvent.js' import { watchContractEvent } from '../../actions/public/watchContractEvent.js' import { sendTransaction } from '../../actions/wallet/sendTransaction.js' import { sendTransactionSync } from '../../actions/wallet/sendTransactionSync.js' import type { WriteContractReturnType } from '../../actions/wallet/writeContract.js' import { writeContract } from '../../actions/wallet/writeContract.js' import { writeContractSync } from '../../actions/wallet/writeContractSync.js' import type { Client } from '../../clients/createClient.js' import type { Transport } from '../../clients/transports/createTransport.js' import type { BaseErrorType } from '../../errors/base.js' import type { Chain } from '../../types/chain.js' import type { ExtractAbiItem, GetEventArgs } from '../../types/contract.js' import type { Log, Log as viem_Log } from '../../types/log.js' import type { Hex } from '../../types/misc.js' import type { Compute, UnionOmit } from '../../types/utils.js' import { parseEventLogs } from '../../utils/abi/parseEventLogs.js' import * as Abis from '../Abis.js' import type { AccessKeyAccount, resolveAccessKey } from '../Account.js' import { signKeyAuthorization } from '../Account.js' import * as Addresses from '../Addresses.js' import * as Hardfork from '../Hardfork.js' import type { GetAccountParameter, ReadParameters, WriteParameters, } from '../internal/types.js' import { defineCall } from '../internal/utils.js' import type { TransactionReceipt } from '../Transaction.js' /** @internal */ const signatureTypes = { 0: 'secp256k1', 1: 'p256', 2: 'webAuthn', } as const satisfies Record<number, string> /** @internal */ const spendPolicies = { true: 'limited', false: 'unlimited', } as const /** * Authorizes an access key by signing a key authorization and sending a transaction. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions, Account } from 'viem/tempo' * import { generatePrivateKey } from 'viem/accounts' * * const account = Account.from({ privateKey: '0x...' }) * const client = createClient({ * account, * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const accessKey = Account.fromP256(generatePrivateKey(), { * access: account, * }) * * const hash = await Actions.accessKey.authorize(client, { * accessKey, * expiry: Math.floor((Date.now() + 30_000) / 1000), * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function authorize< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: authorize.Parameters<chain, account>, ): Promise<authorize.ReturnValue> { return authorize.inner(sendTransaction, client, parameters) } export namespace authorize { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = WriteParameters<chain, account> & Args export type Args = { /** The access key to authorize. */ accessKey: resolveAccessKey.Parameters /** * Whether to authorize the key as an admin key. Admin keys are unrestricted * and can manage the account's other access keys; `expiry`, `limits`, and * `scopes` are ignored. Requires the T6 hardfork. * * [TIP-1049](https://tips.sh/1049) */ admin?: boolean | undefined /** The chain ID. */ chainId?: number | undefined /** Unix timestamp when the key expires. */ expiry?: number | undefined /** Spending limits per token. */ limits?: | { token: Address; limit: bigint; period?: number | undefined }[] | undefined /** Call scopes restricting which contracts/selectors this key can call. */ scopes?: KeyAuthorization.Scope[] | undefined /** * Optional 32-byte witness bound into the authorization's signing hash. * * Applications use this to bind a single signature to an arbitrary offchain * context (e.g. a server-issued challenge), or as a revocation handle that * can be burned onchain (see {@link burnWitness}) to invalidate the * authorization before it is submitted. * * [TIP-1053](https://tips.sh/1053) */ witness?: Hex | undefined } export type ReturnValue = WriteContractReturnType // TODO: exhaustive error type export type ErrorType = BaseErrorType /** @internal */ export async function inner< action extends typeof sendTransaction | typeof sendTransactionSync, chain extends Chain | undefined, account extends Account | undefined, >( action: action, client: Client<Transport, chain, account>, parameters: authorize.Parameters<chain, account>, ): Promise<ReturnType<action>> { const { accessKey, admin, chainId = client.chain?.id, expiry, limits, scopes, witness, ...rest } = parameters const account_ = rest.account ?? client.account if (!account_) throw new Error('account is required.') if (!chainId) throw new Error('chainId is required.') const parsed = parseAccount(account_) const keyAuthorization = await signKeyAuthorization(parsed as never, { chainId: BigInt(chainId), key: accessKey, admin, expiry, limits, scopes, witness, }) return (await action(client, { ...rest, keyAuthorization, } as never)) as never } export function extractEvent(logs: Log[]) { const [log] = parseEventLogs({ abi: Abis.accountKeychain, logs, eventName: 'KeyAuthorized', strict: true, }) if (!log) throw new Error('`KeyAuthorized` event not found.') return log } } /** * Authorizes an access key and waits for the transaction receipt. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions, Account } from 'viem/tempo' * import { generatePrivateKey } from 'viem/accounts' * * const account = Account.from({ privateKey: '0x...' }) * const client = createClient({ * account, * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const accessKey = Account.fromP256(generatePrivateKey(), { * access: account, * }) * * const { receipt, ...result } = await Actions.accessKey.authorizeSync(client, { * accessKey, * expiry: Math.floor((Date.now() + 30_000) / 1000), * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction receipt and event data. */ export async function authorizeSync< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: authorizeSync.Parameters<chain, account>, ): Promise<authorizeSync.ReturnValue> { const { throwOnReceiptRevert = true, ...rest } = parameters const receipt = await authorize.inner(sendTransactionSync, client, { ...rest, throwOnReceiptRevert, } as never) const { args } = authorize.extractEvent(receipt.logs) return { ...args, receipt, } as never } export namespace authorizeSync { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = authorize.Parameters<chain, account> export type Args = authorize.Args export type ReturnValue = Compute< GetEventArgs< typeof Abis.accountKeychain, 'KeyAuthorized', { IndexedOnly: false; Required: true } > & { receipt: TransactionReceipt } > // TODO: exhaustive error type export type ErrorType = BaseErrorType } /** * Burns a key-authorization witness, invalidating any authorization bound to * it before it is submitted onchain. * * Once burned, an `authorizeKey` call carrying the same `witness` will revert. * This lets applications issue a signed authorization offchain (see * {@link authorize}) while retaining the ability to revoke it. * * [TIP-1053](https://tips.sh/1053) * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ * account: privateKeyToAccount('0x...'), * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const hash = await Actions.accessKey.burnWitness(client, { * witness: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function burnWitness< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: burnWitness.Parameters<chain, account>, ): Promise<burnWitness.ReturnValue> { return burnWitness.inner(writeContract, client, parameters) } export namespace burnWitness { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = WriteParameters<chain, account> & Args export type Args = { /** The 32-byte witness to burn. */ witness: Hex } export type ReturnValue = WriteContractReturnType // TODO: exhaustive error type export type ErrorType = BaseErrorType /** @internal */ export async function inner< action extends typeof writeContract | typeof writeContractSync, chain extends Chain | undefined, account extends Account | undefined, >( action: action, client: Client<Transport, chain, account>, parameters: burnWitness.Parameters<chain, account>, ): Promise<ReturnType<action>> { const { witness, ...rest } = parameters const call = burnWitness.call({ witness }) return (await action(client, { ...rest, ...call, } as never)) as never } /** * Defines a call to the `burnKeyAuthorizationWitness` function. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls * * @example * ```ts * import { createClient, http, walletActions } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }).extend(walletActions) * * const hash = await client.sendTransaction({ * calls: [ * Actions.accessKey.burnWitness.call({ witness: '0x...' }), * ], * }) * ``` * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { witness } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'burnKeyAuthorizationWitness', args: [witness], }) } export function extractEvent(logs: Log[]) { const [log] = parseEventLogs({ abi: Abis.accountKeychain, logs, eventName: 'KeyAuthorizationWitnessBurned', strict: true, }) if (!log) throw new Error('`KeyAuthorizationWitnessBurned` event not found.') return log } } /** * Burns a key-authorization witness and waits for the transaction receipt. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ * account: privateKeyToAccount('0x...'), * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const { receipt, ...result } = await Actions.accessKey.burnWitnessSync(client, { * witness: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction receipt and event data. */ export async function burnWitnessSync< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: burnWitnessSync.Parameters<chain, account>, ): Promise<burnWitnessSync.ReturnValue> { const { throwOnReceiptRevert = true, ...rest } = parameters const receipt = await burnWitness.inner(writeContractSync, client, { ...rest, throwOnReceiptRevert, } as never) const { args } = burnWitness.extractEvent(receipt.logs) return { ...args, receipt, } as never } export namespace burnWitnessSync { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = burnWitness.Parameters<chain, account> export type Args = burnWitness.Args export type ReturnValue = Compute< GetEventArgs< typeof Abis.accountKeychain, 'KeyAuthorizationWitnessBurned', { IndexedOnly: false; Required: true } > & { receipt: TransactionReceipt } > // TODO: exhaustive error type export type ErrorType = BaseErrorType } /** * Gets access key information. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const key = await Actions.accessKey.getMetadata(client, { * account: '0x...', * accessKey: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The key information. */ export async function getMetadata< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: getMetadata.Parameters<account>, ): Promise<getMetadata.ReturnValue> { const { account: account_ = client.account, accessKey, ...rest } = parameters if (!account_) throw new Error('account is required.') const account = parseAccount(account_) const result = await readContract(client, { ...rest, account: null as never, ...getMetadata.call({ account: account.address, accessKey }), }) return { address: result.keyId, keyType: signatureTypes[result.signatureType as keyof typeof signatureTypes] ?? 'secp256k1', expiry: result.expiry, spendPolicy: spendPolicies[`${result.enforceLimits}`], isRevoked: result.isRevoked, } } export namespace getMetadata { export type Parameters< account extends Account | undefined = Account | undefined, > = ReadParameters & GetAccountParameter<account> & Pick<Args, 'accessKey'> export type Args = { /** Account address. */ account: Address /** The access key. */ accessKey: Address | AccessKeyAccount } export type ReturnValue = { /** The access key address. */ address: Address /** The key type. */ keyType: 'secp256k1' | 'p256' | 'webAuthn' /** The expiry timestamp. */ expiry: bigint /** The spending policy. */ spendPolicy: 'limited' | 'unlimited' /** Whether the key is revoked. */ isRevoked: boolean } /** * Defines a call to the `getKey` function. * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { account, accessKey } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'getKey', args: [account, resolveAccessKeyAddress(accessKey)], }) } } /** * Gets the remaining spending limit for a key-token pair. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const { remaining, periodEnd } = await Actions.accessKey.getRemainingLimit(client, { * account: '0x...', * accessKey: '0x...', * token: '0x...', * }) * * console.log(remaining, periodEnd) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The remaining spending amount and period end timestamp. */ export async function getRemainingLimit< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: getRemainingLimit.Parameters<account>, ): Promise<getRemainingLimit.ReturnValue> { const { account: account_ = client.account, accessKey, token, ...rest } = parameters if (!account_) throw new Error('account is required.') const account = parseAccount(account_) // TODO: remove pre-t3 branch once mainnet is on t3. const hardfork = (client.chain as { hardfork?: string } | undefined)?.hardfork if (hardfork && Hardfork.lt(hardfork, 't3')) { const remaining = await readContract(client, { ...rest, ...getRemainingLimit.call({ account: account.address, accessKey, token }), }) return { remaining, periodEnd: undefined } } const [remaining, periodEnd] = await readContract(client, { ...rest, ...getRemainingLimit.callWithPeriod({ account: account.address, accessKey, token, }), }) return { remaining, periodEnd } } export namespace getRemainingLimit { export type Parameters< account extends Account | undefined = Account | undefined, > = ReadParameters & GetAccountParameter<account> & Pick<Args, 'accessKey' | 'token'> export type Args = { /** Account address. */ account: Address /** The access key. */ accessKey: Address | AccessKeyAccount /** The token address. */ token: Address } export type ReturnValue = { remaining: bigint periodEnd: bigint | undefined } /** * Defines a call to the `getRemainingLimit` function (pre-T3). * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { account, accessKey, token } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'getRemainingLimit', args: [account, resolveAccessKeyAddress(accessKey), token], }) } /** * Defines a call to the `getRemainingLimitWithPeriod` function (T3+). * * @param args - Arguments. * @returns The call. */ export function callWithPeriod(args: Args) { const { account, accessKey, token } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'getRemainingLimitWithPeriod', args: [account, resolveAccessKeyAddress(accessKey), token], }) } } /** * Checks whether an access key is an admin key for an account. * * Returns `true` for the account's root key or for an active admin access key * (see {@link authorize} with `admin: true`). * * [TIP-1049](https://tips.sh/1049) * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const isAdmin = await Actions.accessKey.isAdmin(client, { * account: '0x...', * accessKey: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns Whether the access key is an admin key. */ export async function isAdmin< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: isAdmin.Parameters<account>, ): Promise<isAdmin.ReturnValue> { const { account: account_ = client.account, accessKey, ...rest } = parameters if (!account_) throw new Error('account is required.') const account = parseAccount(account_) return readContract(client, { ...rest, account: null as never, ...isAdmin.call({ account: account.address, accessKey }), }) } export namespace isAdmin { export type Parameters< account extends Account | undefined = Account | undefined, > = ReadParameters & GetAccountParameter<account> & Pick<Args, 'accessKey'> export type Args = { /** Account address. */ account: Address /** The access key. */ accessKey: Address | AccessKeyAccount } export type ReturnValue = ReadContractReturnType< typeof Abis.accountKeychain, 'isAdminKey', never > /** * Defines a call to the `isAdminKey` function. * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { account, accessKey } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'isAdminKey', args: [account, resolveAccessKeyAddress(accessKey)], }) } } /** * Checks whether a key-authorization witness has been burned for an account. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const isBurned = await Actions.accessKey.isWitnessBurned(client, { * account: '0x...', * witness: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns Whether the witness has been burned. */ export async function isWitnessBurned< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: isWitnessBurned.Parameters<account>, ): Promise<isWitnessBurned.ReturnValue> { const { account: account_ = client.account, witness, ...rest } = parameters if (!account_) throw new Error('account is required.') const account = parseAccount(account_) return readContract(client, { ...rest, account: null as never, ...isWitnessBurned.call({ account: account.address, witness }), }) } export namespace isWitnessBurned { export type Parameters< account extends Account | undefined = Account | undefined, > = ReadParameters & GetAccountParameter<account> & Pick<Args, 'witness'> export type Args = { /** Account address. */ account: Address /** The 32-byte witness to check. */ witness: Hex } export type ReturnValue = ReadContractReturnType< typeof Abis.accountKeychain, 'isKeyAuthorizationWitnessBurned', never > /** * Defines a call to the `isKeyAuthorizationWitnessBurned` function. * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { account, witness } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'isKeyAuthorizationWitnessBurned', args: [account, witness], }) } } /** * Revokes an authorized access key. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ * account: privateKeyToAccount('0x...'), * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const hash = await Actions.accessKey.revoke(client, { * accessKey: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function revoke< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: revoke.Parameters<chain, account>, ): Promise<revoke.ReturnValue> { return revoke.inner(writeContract, client, parameters) } export namespace revoke { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = WriteParameters<chain, account> & Args export type Args = { /** The access key to revoke. */ accessKey: Address | AccessKeyAccount } export type ReturnValue = WriteContractReturnType // TODO: exhaustive error type export type ErrorType = BaseErrorType /** @internal */ export async function inner< action extends typeof writeContract | typeof writeContractSync, chain extends Chain | undefined, account extends Account | undefined, >( action: action, client: Client<Transport, chain, account>, parameters: revoke.Parameters<chain, account>, ): Promise<ReturnType<action>> { const { accessKey, ...rest } = parameters const call = revoke.call({ accessKey }) return (await action(client, { ...rest, ...call, } as never)) as never } /** * Defines a call to the `revokeKey` function. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls * * @example * ```ts * import { createClient, http, walletActions } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }).extend(walletActions) * * const hash = await client.sendTransaction({ * calls: [ * Actions.accessKey.revoke.call({ accessKey: '0x...' }), * ], * }) * ``` * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { accessKey } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'revokeKey', args: [resolveAccessKeyAddress(accessKey)], }) } export function extractEvent(logs: Log[]) { const [log] = parseEventLogs({ abi: Abis.accountKeychain, logs, eventName: 'KeyRevoked', strict: true, }) if (!log) throw new Error('`KeyRevoked` event not found.') return log } } /** * Revokes an authorized access key and waits for the transaction receipt. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ * account: privateKeyToAccount('0x...'), * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const result = await Actions.accessKey.revokeSync(client, { * accessKey: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction receipt and event data. */ export async function revokeSync< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: revokeSync.Parameters<chain, account>, ): Promise<revokeSync.ReturnValue> { const { throwOnReceiptRevert = true, ...rest } = parameters const receipt = await revoke.inner(writeContractSync, client, { ...rest, throwOnReceiptRevert, } as never) const { args } = revoke.extractEvent(receipt.logs) return { ...args, receipt, } as never } export namespace revokeSync { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = revoke.Parameters<chain, account> export type Args = revoke.Args export type ReturnValue = Compute< GetEventArgs< typeof Abis.accountKeychain, 'KeyRevoked', { IndexedOnly: false; Required: true } > & { receipt: TransactionReceipt } > // TODO: exhaustive error type export type ErrorType = BaseErrorType } /** * Signs a key authorization for an access key. * * @example * ```ts * import { generatePrivateKey } from 'viem/accounts' * import { Account, Actions } from 'viem/tempo' * * const account = Account.from({ privateKey: '0x...' }) * const accessKey = Account.fromP256(generatePrivateKey(), { * access: account, * }) * * const keyAuthorization = await Actions.accessKey.signAuthorization( * client, * { * account, * accessKey, * expiry: Math.floor((Date.now() + 30_000) / 1000), * }, * ) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The signed key authorization. */ export async function signAuthorization< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: signAuthorization.Parameters<account>, ): Promise<signAuthorization.ReturnValue> { const { accessKey, chainId = client.chain?.id, ...rest } = parameters const account_ = rest.account ?? client.account if (!account_) throw new Error('account is required.') if (!chainId) throw new Error('chainId is required.') const parsed = parseAccount(account_) return signKeyAuthorization(parsed as never, { chainId: BigInt(chainId), key: accessKey, ...rest, }) } export namespace signAuthorization { export type Parameters< account extends Account | undefined = Account | undefined, > = GetAccountParameter<account> & { /** The access key to authorize. */ accessKey: resolveAccessKey.Parameters /** * Whether to authorize the key as an admin key. Admin keys are unrestricted * and can manage the account's other access keys; `expiry`, `limits`, and * `scopes` are ignored. Requires the T6 hardfork. * * [TIP-1049](https://tips.sh/1049) */ admin?: boolean | undefined /** The chain ID. */ chainId?: number | undefined /** Unix timestamp when the key expires. */ expiry?: number | undefined /** Spending limits per token. */ limits?: | { token: Address; limit: bigint; period?: number | undefined }[] | undefined /** Call scopes restricting which contracts/selectors this key can call. */ scopes?: KeyAuthorization.Scope[] | undefined /** * Optional 32-byte witness bound into the authorization's signing hash. * * Applications use this to bind a single signature to an arbitrary offchain * context (e.g. a server-issued challenge), or as a revocation handle that * can be burned onchain (see {@link burnWitness}) to invalidate the * authorization before it is submitted. * * [TIP-1053](https://tips.sh/1053) */ witness?: Hex | undefined } export type ReturnValue = Awaited<ReturnType<typeof signKeyAuthorization>> } /** * Updates the spending limit for a specific token on an authorized access key. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ * account: privateKeyToAccount('0x...'), * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const hash = await Actions.accessKey.updateLimit(client, { * accessKey: '0x...', * token: '0x...', * limit: 1000000000000000000n, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function updateLimit< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: updateLimit.Parameters<chain, account>, ): Promise<updateLimit.ReturnValue> { return updateLimit.inner(writeContract, client, parameters) } export namespace updateLimit { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = WriteParameters<chain, account> & Args export type Args = { /** The access key to update. */ accessKey: Address | AccessKeyAccount /** The token address. */ token: Address /** The new spending limit. */ limit: bigint } export type ReturnValue = WriteContractReturnType // TODO: exhaustive error type export type ErrorType = BaseErrorType /** @internal */ export async function inner< action extends typeof writeContract | typeof writeContractSync, chain extends Chain | undefined, account extends Account | undefined, >( action: action, client: Client<Transport, chain, account>, parameters: updateLimit.Parameters<chain, account>, ): Promise<ReturnType<action>> { const { accessKey, token, limit, ...rest } = parameters const call = updateLimit.call({ accessKey, token, limit }) return (await action(client, { ...rest, ...call, } as never)) as never } /** * Defines a call to the `updateSpendingLimit` function. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate the gas cost of the call * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the call * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): send multiple calls * * @example * ```ts * import { createClient, http, walletActions } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }).extend(walletActions) * * const hash = await client.sendTransaction({ * calls: [ * Actions.accessKey.updateLimit.call({ * accessKey: '0x...', * token: '0x...', * limit: 1000000000000000000n, * }), * ], * }) * ``` * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { accessKey, token, limit } = args return defineCall({ address: Addresses.accountKeychain, abi: Abis.accountKeychain, functionName: 'updateSpendingLimit', args: [resolveAccessKeyAddress(accessKey), token, limit], }) } export function extractEvent(logs: Log[]) { const [log] = parseEventLogs({ abi: Abis.accountKeychain, logs, eventName: 'SpendingLimitUpdated', strict: true, }) if (!log) throw new Error('`SpendingLimitUpdated` event not found.') return log } } /** * Updates the spending limit and waits for the transaction receipt. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * import { privateKeyToAccount } from 'viem/accounts' * * const client = createClient({ * account: privateKeyToAccount('0x...'), * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const result = await Actions.accessKey.updateLimitSync(client, { * accessKey: '0x...', * token: '0x...', * limit: 1000000000000000000n, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction receipt and event data. */ export async function updateLimitSync< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: updateLimitSync.Parameters<chain, account>, ): Promise<updateLimitSync.ReturnValue> { const { throwOnReceiptRevert = true, ...rest } = parameters const receipt = await updateLimit.inner(writeContractSync, client, { ...rest, throwOnReceiptRevert, } as never) const { args } = updateLimit.extractEvent(receipt.logs) return { account: args.account, publicKey: args.publicKey, token: args.token, limit: args.newLimit, receipt, } } export namespace updateLimitSync { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = updateLimit.Parameters<chain, account> export type Args = updateLimit.Args export type ReturnValue = { /** The account that owns the key. */ account: Address /** The access key address. */ publicKey: Address /** The token address. */ token: Address /** The new spending limit. */ limit: bigint /** The transaction receipt. */ receipt: TransactionReceipt } // TODO: exhaustive error type export type ErrorType = BaseErrorType } /** * Watches for admin key authorization events. * * Emitted when an admin key is authorized (see {@link authorize} with * `admin: true`). * * [TIP-1049](https://tips.sh/1049) * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const unwatch = Actions.accessKey.watchAdminAuthorized(client, { * onAuthorized: (args, log) => { * console.log('Admin key authorized:', args) * }, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns A function to unsubscribe from the event. */ export function watchAdminAuthorized< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: watchAdminAuthorized.Parameters, ) { const { onAuthorized, ...rest } = parameters return watchContractEvent(client, { ...rest, address: Addresses.accountKeychain, abi: Abis.accountKeychain, eventName: 'AdminKeyAuthorized', onLogs: (logs) => { for (const log of logs) onAuthorized(log.args, log) }, strict: true, }) } export declare namespace watchAdminAuthorized { export type Args = Compute< GetEventArgs< typeof Abis.accountKeychain, 'AdminKeyAuthorized', { IndexedOnly: false; Required: true } > > export type Log = viem_Log< bigint, number, false, ExtractAbiItem<typeof Abis.accountKeychain, 'AdminKeyAuthorized'>, true > export type Parameters = UnionOmit< WatchContractEventParameters< typeof Abis.accountKeychain, 'AdminKeyAuthorized', true >, 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' > & { /** Callback to invoke when an admin key is authorized. */ onAuthorized: (args: Args, log: Log) => void } } /** * Watches for key-authorization witness events. * * Emitted when a key is authorized with a `witness` (see {@link authorize}). * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const unwatch = Actions.accessKey.watchWitness(client, { * onWitness: (args, log) => { * console.log('Witness used:', args) * }, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns A function to unsubscribe from the event. */ export function watchWitness< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: watchWitness.Parameters, ) { const { onWitness, ...rest } = parameters return watchContractEvent(client, { ...rest, address: Addresses.accountKeychain, abi: Abis.accountKeychain, eventName: 'KeyAuthorizationWitness', onLogs: (logs) => { for (const log of logs) onWitness(log.args, log) }, strict: true, }) } export declare namespace watchWitness { export type Args = Compute< GetEventArgs< typeof Abis.accountKeychain, 'KeyAuthorizationWitness', { IndexedOnly: false; Required: true } > > export type Log = viem_Log< bigint, number, false, ExtractAbiItem<typeof Abis.accountKeychain, 'KeyAuthorizationWitness'>, true > export type Parameters = UnionOmit< WatchContractEventParameters< typeof Abis.accountKeychain, 'KeyAuthorizationWitness', true >, 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' > & { /** Callback to invoke when a witness is used. */ onWitness: (args: Args, log: Log) => void } } /** * Watches for key-authorization witness burned events. * * Emitted when a witness is burned (see {@link burnWitness}). * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const unwatch = Actions.accessKey.watchWitnessBurned(client, { * onBurned: (args, log) => { * console.log('Witness burned:', args) * }, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns A function to unsubscribe from the event. */ export function watchWitnessBurned< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: watchWitnessBurned.Parameters, ) { const { onBurned, ...rest } = parameters return watchContractEvent(client, { ...rest, address: Addresses.accountKeychain, abi: Abis.accountKeychain, eventName: 'KeyAuthorizationWitnessBurned', onLogs: (logs) => { for (const log of logs) onBurned(log.args, log) }, strict: true, }) } export declare namespace watchWitnessBurned { export type Args = Compute< GetEventArgs< typeof Abis.accountKeychain, 'KeyAuthorizationWitnessBurned', { IndexedOnly: false; Required: true } > > export type Log = viem_Log< bigint, number, false, ExtractAbiItem< typeof Abis.accountKeychain, 'KeyAuthorizationWitnessBurned' >, true > export type Parameters = UnionOmit< WatchContractEventParameters< typeof Abis.accountKeychain, 'KeyAuthorizationWitnessBurned', true >, 'abi' | 'address' | 'batch' | 'eventName' | 'onLogs' | 'strict' > & { /** Callback to invoke when a witness is burned. */ onBurned: (args: Args, log: Log) => void } } /** * Verifies that a keychain signature was produced by an active access key * for the expected account. * * By default (`admin: true`), returns `true` only if the signature was * produced by the account's root key or an active admin access key. Set * `admin: false` to accept any active access key. * * Returns `false` for account mismatches, unknown, revoked, or expired * access keys. [TIP-1049](https://tips.sh/1049) * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo.extend({ feeToken: '0x20c0000000000000000000000000000000000001' }), * transport: http(), * }) * * const valid = await Actions.accessKey.verifyHash(client, { * account: '0x...', * hash: '0x...', * signature: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns Whether the keychain signature is valid for the account. */ export async function verifyHash< chain extends Chain | undefined, account extends Account | undefined, >( client: Client<Transport, chain, account>, parameters: verifyHash.Parameters, ): Promise<verifyHash.ReturnValue> { const { account, admin, hash, signature, ...rest } = parameters return readContract(client, { ...rest, ...verifyHash.call({ account, admin, hash, signature }), }) } export namespace verifyHash { export type Parameters = ReadParameters & Args export type Args = { /** Account address the signature is expected to belong to. */ account: Address /** * Whether to require the signer to be the account's root key or an * active admin access key. Defaults to `true`. Set to `false` to accept * any active access key. */ admin?: boolean | undefined /** Original message hash that was signed. */ hash: Hex /** Keychain signature envelope (V2). */ signature: Hex } export type ReturnValue = ReadContractReturnType< typeof Abis.signatureVerifier, 'verifyKeychain' | 'verifyKeychainAdmin', never > /** * Defines a call to `verifyKeychain` or `verifyKeychainAdmin` on the * Signature Verifier precompile (controlled by `admin`). * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { const { account, admin = true, hash, signature } = args return defineCall({ address: Addresses.signatureVerifier, abi: Abis.signatureVerifier, functionName: admin ? 'verifyKeychainAdmin' : 'verifyKeychain', args: [account, hash, signature], }) } } /** @internal */ function resolveAccessKeyAddress( accessKey: Address | AccessKeyAccount, ): Address { if (typeof accessKey === 'string') return accessKey return accessKey.accessKeyAddress }