import type { Address } from 'abitype' import type * as Hex from 'ox/Hex' import type * as RpcSchema from 'ox/RpcSchema' import { MultisigConfig, MultisigOperation, type RpcSchemaTempo, } from 'ox/tempo' import type { Account } from '../../accounts/types.js' import type { ReadContractReturnType } from '../../actions/public/readContract.js' import { readContract } from '../../actions/public/readContract.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 { Log } from '../../types/log.js' import type { Compute } from '../../types/utils.js' import { parseEventLogs } from '../../utils/abi/parseEventLogs.js' import * as Abis from '../Abis.js' import { fromMultisig, type MultisigAccount } from '../Account.js' import * as Addresses from '../Addresses.js' import type { ReadParameters, WriteParameters } from '../internal/types.js' import { defineCall } from '../internal/utils.js' import type * as Transaction from '../Transaction.js' /** * Gets the current cached config for a multisig account. * * The coordinator reads the account's current onchain commitment and returns * the matching config from its store. It returns `null` when the config is not * cached. * * @example * ```ts * const config = await client.multisig.getConfig({ * address: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The config, or `null` when it is unknown. */ export async function getConfig( client: Client, parameters: getConfig.Parameters, ): Promise { const config = await client.request<{ Method: 'multisig_getConfig' Parameters: [{ address: Address }] ReturnType: MultisigConfig.Rpc | null }>({ method: 'multisig_getConfig', params: [{ address: parameters.address }], }) return config ? MultisigConfig.fromRpc(config) : null } export declare namespace getConfig { /** Parameters for {@link getConfig}. */ export type Parameters = { /** Multisig account address. */ address: Address } /** Return value for {@link getConfig}. */ export type ReturnValue = MultisigConfig.Config | null /** Error type for {@link getConfig}. */ export type ErrorType = BaseErrorType } /** * Gets the current configuration commitment for a native multisig account. * * @example * ```ts * import { createClient, http } from 'viem' * import { tempo } from 'viem/chains' * import { Actions } from 'viem/tempo' * * const client = createClient({ * chain: tempo, * transport: http(), * }) * * const commitment = await Actions.multisig.getConfigCommitment(client, { * account: '0x...', * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The current configuration commitment, or zero when no config has * been committed. */ export async function getConfigCommitment< chain extends Chain | undefined, account extends Account | undefined, >( client: Client, parameters: getConfigCommitment.Parameters, ): Promise { const { account, ...rest } = parameters return readContract(client, { ...rest, ...getConfigCommitment.call({ account }), }) } export namespace getConfigCommitment { export type Parameters = ReadParameters & Args export type Args = { /** Initialized multisig account address. */ account: Address } export type ReturnValue = ReadContractReturnType< typeof Abis.nativeMultisig, 'getConfigCommitment', never > /** * Defines a call to the `getConfigCommitment` function. * * Can be passed to [`multicall`](https://viem.sh/docs/contract/multicall). * * @param args - Arguments. * @returns The call. */ export function call(args: Args) { return defineCall({ address: Addresses.nativeMultisig, abi: Abis.nativeMultisig, args: [args.account], functionName: 'getConfigCommitment', }) } } /** * Gets a coordinated multisig operation by its hash. * * @param client - Client. * @param parameters - Parameters. * @returns The operation, or `null` when it is unknown. */ export async function getOperation( client: Client, parameters: getOperation.Parameters, ): Promise { type multisig_getOperation = Extract< RpcSchema.ToViem[number], { Method: 'multisig_getOperation' } > const operation = await client.request({ method: 'multisig_getOperation', params: [parameters.hash], }) return operation ? MultisigOperation.fromRpc(operation) : null } export declare namespace getOperation { /** Parameters for {@link getOperation}. */ export type Parameters = { /** Multisig operation hash. */ hash: Hex.Hex } /** Return value for {@link getOperation}. */ export type ReturnValue = MultisigOperation.Operation | null /** Error type for {@link getOperation}. */ export type ErrorType = BaseErrorType } /** * Replaces the current configuration for a native multisig account. * * The transaction must be authorized directly by the account's current owner * quorum. Local owner-signing flows can include {@link updateConfig.call} in a * prepared transaction before collecting approvals. * * @example * ```ts * import { createClient, http } from 'viem' * import { sendTransactionSync } from 'viem/actions' * import { tempoLocalnet } from 'viem/chains' * import { Account, Actions } from 'viem/tempo' * * const owner = Account.fromSecp256k1( * '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', * ) * const account = Account.fromMultisig({ * address: 'infer', * owners: [owner], * }) * const client = createClient({ * chain: tempoLocalnet, * transport: http(), * }) * * await sendTransactionSync(client, { * account, * to: account.address, * }) * * const hash = await Actions.multisig.updateConfig(client, { * account, * nextConfig: { * owners: [{ owner: owner.address, weight: 1 }], * threshold: 1, * }, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The transaction hash. */ export async function updateConfig< chain extends Chain | undefined, account extends Account | undefined, >( client: Client, parameters: updateConfig.Parameters, ): Promise { return updateConfig.inner(writeContract, client, parameters) } export namespace updateConfig { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = WriteParameters & { /** Complete current config. Inferred from the account or coordinator when omitted. */ currentConfig?: MultisigConfig.Config | undefined /** Replacement owners and threshold. */ nextConfig: Pick } export type Args = { /** Complete current config. */ currentConfig: MultisigConfig.Config /** Replacement owners and threshold. */ nextConfig: Pick } 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, parameters: Parameters, ): Promise> { const { account: account_, currentConfig: currentConfig_, nextConfig, ...rest } = parameters const accountValue = account_ ?? client.account const account = (() => { if ( typeof accountValue === 'object' && accountValue.source === 'multisig' ) return accountValue as MultisigAccount return undefined })() const config = (() => { if (currentConfig_) return currentConfig_ if (account?.config) return account.config return undefined })() const address = (() => { if (account) return account.address if (typeof accountValue === 'string') return accountValue as Address if (accountValue) return accountValue.address return undefined })() const currentConfig = await (async () => { if (config) return MultisigConfig.from(config) if (!address) throw new Error( 'A multisig account address or current config is required.', ) const cachedConfig = await getConfig(client, { address }) if (!cachedConfig) throw new Error( `No current multisig config is cached for account ${address}. Provide the current config.`, ) return cachedConfig })() const resolvedAccount = (() => { if (account) return { ...account, config: currentConfig } if (typeof accountValue === 'object') return accountValue if (address) return fromMultisig({ address, ...currentConfig }) return undefined })() return (await action(client, { ...rest, ...(resolvedAccount ? { account: resolvedAccount } : {}), ...updateConfig.call({ currentConfig, nextConfig }), } as never)) as never } /** * Defines a call to the `updateConfig` function. * * Can be passed as a parameter to: * - [`estimateContractGas`](https://viem.sh/docs/contract/estimateContractGas): estimate gas * - [`simulateContract`](https://viem.sh/docs/contract/simulateContract): simulate the update * - [`sendCalls`](https://viem.sh/docs/actions/wallet/sendCalls): include the update in a call batch * * @example * ```ts * import { Actions, type MultisigConfig } from 'viem/tempo' * * declare const currentConfig: MultisigConfig.Config * * const call = Actions.multisig.updateConfig.call({ * currentConfig, * nextConfig: { * owners: [{ owner: '0x...', weight: 1 }], * threshold: 1, * }, * }) * ``` * * @param args - Current and replacement multisig configurations. * @returns The call. */ export function call(args: Args) { const currentConfig = MultisigConfig.from(args.currentConfig) const nextConfig = MultisigConfig.from({ owners: args.nextConfig.owners, salt: currentConfig.salt, threshold: args.nextConfig.threshold, version: currentConfig.version + 1n, }) return defineCall({ address: Addresses.nativeMultisig, abi: Abis.nativeMultisig, args: [currentConfig, nextConfig.threshold, nextConfig.owners], functionName: 'updateConfig', }) } /** * Extracts the `MultisigConfigUpdated` event from logs. * * @param logs - Transaction logs. * @returns The configuration update event. */ export function extractEvent(logs: Log[]) { const [log] = parseEventLogs({ abi: Abis.nativeMultisig, logs, eventName: 'MultisigConfigUpdated', strict: true, }) if (!log) throw new Error('`MultisigConfigUpdated` event not found.') return log } } /** * Replaces a native multisig configuration and waits for confirmation. * * @example * ```ts * import { createWalletClient, custom, type EIP1193Provider } from 'viem' * import { tempo } from 'viem/chains' * import { Actions, type MultisigConfig } from 'viem/tempo' * * declare const provider: EIP1193Provider * declare const currentConfig: MultisigConfig.Config * * const client = createWalletClient({ * account: '0x...', * chain: tempo, * transport: custom(provider), * }) * * const { receipt } = await Actions.multisig.updateConfigSync(client, { * currentConfig, * nextConfig: { * owners: [{ owner: '0x...', weight: 1 }], * threshold: 1, * }, * }) * ``` * * @param client - Client. * @param parameters - Parameters. * @returns The updated configuration event and transaction receipt. */ export async function updateConfigSync< chain extends Chain | undefined, account extends Account | undefined, >( client: Client, parameters: updateConfigSync.Parameters, ): Promise { const { throwOnReceiptRevert = true, ...rest } = parameters const receipt = await updateConfig.inner(writeContractSync, client, { ...rest, throwOnReceiptRevert, } as never) if ((receipt as Transaction.TransactionReceipt).status === 'pending') return { receipt } as never const { args } = updateConfig.extractEvent(receipt.logs) return { account: args.account, config: MultisigConfig.from({ owners: args.owners, salt: args.salt, threshold: args.threshold, version: args.version, }), receipt, } as never } export namespace updateConfigSync { export type Parameters< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, > = updateConfig.Parameters export type Args = updateConfig.Args export type ReturnValue = Compute<{ account: Address config: MultisigConfig.Config receipt: Transaction.TransactionReceipt }> export type ErrorType = updateConfig.ErrorType }