viem
Version:
1,912 lines • 56.4 kB
text/typescript
import type { Address } from 'abitype'
import type * as RpcSchema from 'ox/RpcSchema'
import {
KeyAuthorization,
MultisigConfig,
MultisigOperation,
type RpcSchemaTempo,
SignatureEnvelope,
} 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, OneOf, UnionOmit } from '../../types/utils.js'
import { parseEventLogs } from '../../utils/abi/parseEventLogs.js'
import { isAddressEqual } from '../../utils/address/isAddressEqual.js'
import * as Abis from '../Abis.js'
import type {
AccessKeyAccount,
MultisigAccount,
RootAccount,
} from '../Account.js'
import {
fromMultisig,
getKeyAuthorizationSignPayload,
resolveAccessKey,
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 { parseApproval } from '../multisig/Signature.js'
import type { TransactionReceipt } from '../Transaction.js'
import { getConfig } from './multisig.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 keyAuthorization = await signAuthorization(client, {
account: account_,
accessKey,
chainId,
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)
if ((receipt as TransactionReceipt).status === 'pending')
return { receipt } 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)
if ((receipt as TransactionReceipt).status === 'pending')
return { receipt } 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)
if ((receipt as TransactionReceipt).status === 'pending')
return { receipt } 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
}
/**
* Prepares a key authorization for signing.
*
* @example
* ```ts
* const authorization = await Actions.accessKey.prepareAuthorization(client, {
* account,
* accessKey,
* })
* const keyAuthorization = await Actions.accessKey.signAuthorization(
* client,
* authorization,
* )
* ```
*
* @param client - Client.
* @param parameters - Parameters.
* @returns Prepared key authorization parameters.
*/
export async function prepareAuthorization<
chain extends Chain | undefined,
account extends Account | undefined,
>(
client: Client<Transport, chain, account>,
parameters: prepareAuthorization.Parameters<account>,
): Promise<prepareAuthorization.ReturnValue> {
const { chainId = client.chain?.id } = parameters
const account_ = parameters.account ?? client.account
if (!account_) throw new Error('account is required.')
if (!chainId) throw new Error('chainId is required.')
const parsed = parseAccount(account_)
const multisigState = await (async () => {
if ('multisig' in parameters) return parameters.multisig
if (parsed.source !== 'multisig') return undefined
const account = parsed as MultisigAccount
if (!account.config)
throw new Error(
'A multisig config is required to prepare a key authorization.',
)
return { config: account.config }
})()
const authorizationSignPayload = getKeyAuthorizationSignPayload(
parsed as never,
{
...parameters,
chainId: BigInt(chainId),
key: parameters.accessKey,
},
)
const signPayload =
parsed.source === 'multisig' && multisigState
? MultisigConfig.getSignPayload({
account: parsed.address,
config: multisigState.config,
payload: authorizationSignPayload,
})
: authorizationSignPayload
return {
...parameters,
account: parsed,
chainId,
multisig: multisigState,
signPayload,
} as never
}
export namespace prepareAuthorization {
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
/** @internal Prepared multisig state. */
multisig?: signKeyAuthorization.Parameters['multisig']
/** 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 = Compute<
Omit<Parameters<Account | undefined>, 'account' | 'chainId'> & {
account: Account
chainId: number
multisig: signKeyAuthorization.Parameters['multisig']
/** Payload that the authorizing account or multisig owners sign. */
signPayload: Hex
}
>
}
/**
* Signs a key authorization for an access key.
*
* Use {@link prepareAuthorization} before this action when signing requires
* transient user activation.
*
* @param client - Client.
* @param parameters - Authorization fields, or a stored operation hash.
* @returns A signed key authorization with multisig operation metadata when coordinated.
*/
export function signAuthorization<
chain extends Chain | undefined,
account extends Account | undefined,
>(
client: Client<Transport, chain, account>,
parameters: signAuthorization.CoordinatedParameters,
): Promise<signAuthorization.CoordinatedReturnValue>
export function signAuthorization<
chain extends Chain | undefined,
account extends Account | undefined,
>(
client: Client<Transport, chain, account>,
parameters: signAuthorization.LocalParameters<account>,
): Promise<signAuthorization.ReturnValue>
export async function signAuthorization<
chain extends Chain | undefined,
account extends Account | undefined,
>(
client: Client<Transport, chain, account>,
parameters: signAuthorization.Parameters<account>,
): Promise<
signAuthorization.CoordinatedReturnValue | signAuthorization.ReturnValue
> {
const coordinated =
('hash' in parameters && parameters.hash) ||
('owner' in parameters && parameters.owner)
if (coordinated) {
const parameters_ = parameters as signAuthorization.CoordinatedParameters
const owner_ = parameters_.owner
if (!owner_ || typeof owner_ === 'string')
throw new Error(
'A local owner account is required to approve a multisig key authorization.',
)
const owner = parseAccount(owner_)
const sign = owner.sign
if (!sign)
throw new Error(
'A local owner account is required to approve a multisig key authorization.',
)
const request = await (async () => {
if ('hash' in parameters_ && parameters_.hash) {
const signature = SignatureEnvelope.serialize(
SignatureEnvelope.from(await sign({ hash: parameters_.hash })),
)
return { hash: parameters_.hash, signature }
}
const {
account: account_,
owner: _,
...authorization
} = parameters_ as signAuthorization.CoordinatedInitialParameters
const address = typeof account_ === 'string' ? account_ : account_.address
const config = await (async () => {
if (typeof account_ !== 'string' && account_.config)
return account_.config
const config = await getConfig(client, { address })
if (config) return config
throw new Error(
`No current multisig config is cached for account ${address}. Provide the current config.`,
)
})()
const account = (() => {
if (config.version !== 0n) return fromMultisig({ address, ...config })
const { version: _, ...initialConfig } = config
return fromMultisig({ address: 'infer', ...initialConfig })
})()
if (!isAddressEqual(account.address, address))
throw new Error('Initial multisig config does not match the account.')
const prepared = await prepareAuthorization(client, {
...authorization,
account,
} as never)
const signature = SignatureEnvelope.serialize(
SignatureEnvelope.from(await sign({ hash: prepared.signPayload })),
)
const { accessKey, admin, chainId, expiry, limits, scopes, witness } =
prepared
const { accessKeyAddress, keyType: type } = resolveAccessKey(accessKey)
const keyAuthorization = KeyAuthorization.from({
account: account.address,
address: accessKeyAddress,
chainId: BigInt(chainId),
signature: SignatureEnvelope.from({
account: account.address,
config,
signatures: [parseApproval(signature)],
}),
type,
...(witness ? { witness } : {}),
...(admin ? { isAdmin: true } : { expiry, limits, scopes }),
} as never)
return { keyAuthorization: KeyAuthorization.toRpc(keyAuthorization) }
})()
type multisig_approveKeyAuthorization = Extract<
RpcSchema.ToViem<RpcSchemaTempo.Multisig>[number],
{ Method: 'multisig_approveKeyAuthorization' }
>
const operation = await client.request<multisig_approveKeyAuthorization>({
method: 'multisig_approveKeyAuthorization',
params: [request],
})
const multisig = MultisigOperation.fromRpc(operation)
const keyAuthorization = KeyAuthorization.deserialize(
await (async () => {
if (multisig.status === 'success') return multisig.keyAuthorization
const approvals = await MultisigOperation.selectApprovals({
account: multisig.account,
approvals: multisig.approvals,
config: multisig.config,
hash: multisig.hash,
})
return MultisigOperation.serializeKeyAuthorization(
multisig.keyAuthorization,
{
account: multisig.account,
approvals:
approvals.selectedApprovals.length > 0
? approvals.selectedApprovals
: approvals.approvals.slice(0, 1),
config: multisig.config,
},
)
})(),
)
if (!keyAuthorization.signature)
throw new Error('Expected a signed multisig key authorization.')
return {
...keyAuthorization,
hash: multisig.hash,
multisig,
status: multisig.status,
} as never
}
const prepared = (
'signPayload' in parameters
? parameters
: await prepareAuthorization(
client,
parameters as signAuthorization.LocalParameters<account>,
)
) as prepareAuthorization.ReturnValue
const {
accessKey,
account: account_,
chainId,
multisig: multisigState,
signPayload: _,
...rest
} = prepared
return signKeyAuthorization(account_ as never, {
chainId: BigInt(chainId),
key: accessKey,
multisig: multisigState,
...rest,
}) as never
}
export namespace signAuthorization {
/** Initial coordinated key authorization parameters. */
export type CoordinatedInitialParameters = Omit<
prepareAuthorization.Parameters<Account>,
'account' | 'multisig'
> & {
/** Multisig account being authorized. */
account: Address | MultisigAccount
/** Local owner that approves the authorization. */
owner: RootAccount
}
/** Coordinated key authorization parameters. */
export type CoordinatedParameters = OneOf<
| CoordinatedInitialParameters
| {
/** Stored multisig operation hash. */
hash: Hex
/** Local owner that approves the authorization. */
owner: RootAccount
}
>
/** Locally signed key authorization parameters. */
export type LocalParameters<
account extends Account | undefined = Account | undefined,
> = prepareAuthorization.Parameters<account> & {
/** Serialized approvals from external multisig owners. */
signatures?: readonly SignatureEnvelope.Serialized[] | undefined
}
/** Parameters for {@link signAuthorization}. */
export type Parameters<
account extends Account | undefined = Account | undefined,
> = CoordinatedParameters | LocalParameters<account>
/** Local return value for {@link signAuthorization}. */
export type ReturnValue = Awaited<ReturnType<typeof signKeyAuthorization>>
/** Coordinated return value for {@link signAuthorization}. */
export type CoordinatedReturnValue = Compute<
ReturnValue & {
/** Deterministic multisig operation hash. */
hash: Hex
/** Current multisig operation. */
multisig: MultisigOperation.KeyAuthorizationOperation
/** Current multisig operation status. */
status: MultisigOperation.KeyAuthorizationOperation['status']
}
>
/** Error type for {@link signAuthorization}. */
export type ErrorType = BaseErrorType
}
/**
* 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)
if ((receipt as TransactionReceipt).status === 'pending')
return { receipt } 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
}