<!-- tempo-docs-context -->
> Source: [/docs/accounts/policies/signing-thresholds](https://tempo.xyz/developers/docs/accounts/policies/signing-thresholds.md)
> Section: [Accounts](https://tempo.xyz/developers/docs/accounts.md). Create accounts, set up signing methods, policies, access keys, key rotation, and fees, and follow guides for users, automations, agents, and teams.
> [Documentation index](https://tempo.xyz/developers/llms.txt) · [Docs skill](https://tempo.xyz/developers/SKILL.md)

> Tempo MCP: Use `search`, `find_pages`, `read_page`, and `code` at `https://mcp.tempo.xyz` for Tempo and related documentation.
> If an indexed page is missing or reflects an older site, read this site’s [documentation index](https://tempo.xyz/developers/llms.txt) and Markdown pages directly.

# Signing Thresholds

Sets the total signer weight a transaction needs before a configurable account can pay.

## Overview

A signing threshold is the [account policy](https://tempo.xyz/developers/docs/accounts/policies) that decides who controls a configurable Tempo account. Each signer is a key with a weight. A transaction runs only when the signers who approve it reach the threshold in total weight.

:::warning
**Multiple signers arrive with the [T14 network upgrade](https://tempo.xyz/developers/docs/protocol/upgrades/t14).** Until T14 activates, an account with several signers and a threshold runs only on the [Tempo Accounts devnet](https://tempo.xyz/developers/docs/accounts/create#connect-to-the-devnet), not on mainnet or testnet, and may still change.
:::

<span id="how-approvals-add-up" />

<span id="signer-weights-and-the-threshold" />

<span id="weights-and-thresholds" />

<span id="how-it-works" />

## Signer Weights

Take a customer account at threshold 2. Its passkey has weight 2, and a backup key and a recovery provider's key have weight 1 each:

```ts [threshold.ts]
import { Account } from 'viem/tempo'

const account = Account.fromConfig({
  address: 'infer', // derive the address from these first signers
  owners: [ // [!code hl]
    { owner: passkey.address, weight: 2 }, // [!code hl]
    { owner: backup.address, weight: 1 }, // [!code hl]
    { owner: recovery.address, weight: 1 }, // [!code hl]
  ], // [!code hl]
  threshold: 2, // [!code hl]
})
```

The passkey approves alone. The backup and recovery keys approve together. Either of those alone reaches 1 of 2 and can't pay.

Signers approve a whole transaction at once, including every call in a batch. The same threshold covers payments, configuration updates, and new [access keys](https://tempo.xyz/developers/docs/accounts/access-keys). Access keys don't count toward it. Each pays alone within its limits.

An [admin access key](https://tempo.xyz/developers/docs/accounts/access-keys/admin-access-keys) has no limits. Once the signers authorize one, it can pay and authorize other access keys without reaching the threshold. Authorize one only if you'd let that single key act for the whole account.

To change signers or the threshold, send a [configuration update](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#add-a-signer), approved at the current threshold.

<span id="what-a-threshold-protects" />

<span id="what-a-threshold-protects-against" />

<span id="what-it-protects-against" />

## Theft and Loss

A signer can be stolen or lost, such as when a key leaks, a phone breaks, or an approver leaves. Weights that survive both meet two tests:

* **Theft:** No single party reaches the threshold alone, unless that party is the customer.
* **Loss:** The other signers still reach the threshold without any one of them ([plan for recovery](https://tempo.xyz/developers/docs/accounts/account-configuration#plan-for-recovery)).

Two keys in the same KMS belong to one party. So do a wallet provider's key and a recovery key it also runs. That party approves with the keys' combined weight.

<span id="recommended-setup" />

<span id="recommended-weights" />

<span id="multi-factor-approval" />

<span id="set-up-multi-factor-approval-in-code" />

<span id="recommended-signer-setups" />

## Recommended Setups

| Setup | Signers (weight) | Threshold | Who can approve |
| --- | --- | --- | --- |
| [Getting started](https://tempo.xyz/developers/docs/accounts/apps/self-custodial-wallets) | Passkey (1), backup key (1) | 1 | Either one |
| [Customer accounts](https://tempo.xyz/developers/docs/accounts/apps/self-custodial-wallets) | Passkey (2), backup key (1), recovery provider (1) | 2 | The passkey, or both keys |
| [Self-custodial wallets with a provider](https://tempo.xyz/developers/docs/accounts/apps/self-custodial-wallets) | Customer's passkey (1), your wallet provider's key (1), customer's backup key (1) | 2 | Any two: the passkey and the provider day to day, or the customer's two keys without you |
| [Team multisig](https://tempo.xyz/developers/docs/accounts/custodians/team-multisig) | Three approvers (1 each) | 2 | Any two |

<span id="recovery-provider" />

A recovery provider holds one key to help replace a lost signer. It's any party other than the customer and your wallet provider. It can be a key in your own KMS or a recovery service you contract. Tempo doesn't limit a signer to recovery. Any signers that reach the threshold together can also pay and send configuration updates.

Getting started survives a lost passkey, but either key can pay alone. Keep both keys with the customer, and move to customer accounts before balances grow.

<span id="team-budgets" />

<span id="set-up-a-team-account" />

<span id="team-account-limits" />

To let a team spend without lowering the treasury's threshold, [give the team a budget](https://tempo.xyz/developers/docs/accounts/custodians/team-multisig). The treasury authorizes the team's own account as an access key. The team's signers then approve payments within that budget.

<span id="collect-and-verify-approvals" />

<span id="collect-approvals-from-several-approvers" />

## Collect Approvals

Each signer's key stays with its holder:

| Signer | Where the key stays |
| --- | --- |
| Passkey | The customer's device |
| Backup key | The customer's hardware wallet |
| Recovery key | The [recovery provider](https://tempo.xyz/developers/docs/accounts/policies/signing-thresholds#recovery-provider), such as your KMS |

Tempo stores only a hash of the account's configuration, which covers its signers, weights, threshold, and version. Tempo checks approvals against that hash but can't read the configuration back. When your app creates the account, save `account.address` and `account.config` with the account record, `saved`.

Before each transaction, rebuild the account from the latest saved record. The examples call that helper [`openAccount`](https://tempo.xyz/developers/docs/accounts/apps/self-custodial-wallets). It passes `saved` to `Account.fromConfig` with the approving keys on this device in place of their addresses. It uses `address: 'infer'` until the first configuration update, then the saved address.

A configuration update raises the configuration's version. Tempo checks every later transaction against the new hash, so save the `config` that `updateConfigSync` returns. An account built from the old configuration fails with `multisig configuration commitment mismatch`.

When the keys on one device reach the threshold, attach them and send the transaction as usual. Here the customer's passkey reaches the threshold alone, so the payment runs in the customer's browser. `client.ts` connects to the devnet ([set up the devnet key](https://tempo.xyz/developers/docs/accounts/create#connect-to-the-devnet)).

:::code-group
```ts [pay.ts]
import { client } from './client'

await client.token.transferSync({ account: openAccount([passkey]), amount, to, token }) // [!code hl]
```

```ts [client.ts]
import { http } from 'viem'
import { tempoDevnet } from 'viem/chains'
import { createClient } from 'viem/tempo'

// The Tempo Accounts devnet. Its RPC needs a key, which Tempo gives you.
const rpcKey = process.env.TEMPO_DEVNET_RPC_KEY

export const client = createClient({
  chain: tempoDevnet,
  transport: http('https://rpc-t14-checkpoint.devnet.tempoxyz.dev', {
    fetchOptions: { headers: { Authorization: `Basic ${btoa(`previews:${rpcKey}`)}` } },
  }),
})
```
:::

`passkey` is the customer's passkey account, such as `Account.fromWebAuthnP256(credential)`. Viem signs with the attached keys, highest weight first, until their weights reach the threshold. If they fall short, it throws `Local owners do not meet the threshold.` and sends nothing.

### Approvers on Separate Devices

When no single device holds enough weight, collect approvals across devices. Your app prepares the transaction once. Each approver signs it on their own device. Your app submits it with their approvals. Here the backup key and the recovery provider's key approve together:

```ts [approve.ts]
import { Actions } from 'viem/tempo'
import { client } from './client'

// 1. Your app: prepare the payment once, from the account with no keys attached.
const request = await client.prepareTransactionRequest({ // [!code hl]
  account: openAccount([]), // [!code hl]
  calls: [Actions.token.transfer.call({ amount, to, token })], // [!code hl]
}) // [!code hl]

// 2. Each approver signs the same request on their own device: the backup key, then the recovery provider.
const backupApproval = await client.signTransaction({ ...request, account: backup }) // [!code hl]
const recoveryApproval = await client.signTransaction({ ...request, account: recovery }) // [!code hl]

// 3. Your app: submit the same request with the approvals.
const receipt = await client.sendTransactionSync({ ...request, signatures: [backupApproval, recoveryApproval] }) // [!code hl]
```

For a configurable account, `signTransaction` returns the signer's approval rather than a signed transaction. An approval covers the exact transaction, the account, and the configuration version. Show each approver what they're approving, such as the recipient and amount. Send every approver the same `request`. Submit the request unchanged. The request contains bigints, so move it between devices in an encoding that keeps them.

Approvals can arrive in any order. Viem sorts them by signer address when it combines them. Submit only the approvals the threshold needs. For this account, that's the passkey's approval alone, or the backup and recovery approvals together. Tempo adds up approvals in signer-address order. It rejects the transaction with `excess multisig owner signatures` if any approval comes after the total reaches the threshold. Too few fail with `multisig signature weight below threshold`.

An approval also covers the transaction's nonce, its place in the account's sequence of transactions. If another transaction from the account lands first, submitting fails with `nonce too low`, so prepare again and collect new approvals. To keep slow approvals valid while the account keeps paying, pass `prepareTransactionRequest` its own `nonceKey`. Use a random `bigint` from `1n` to `2n ** 256n - 2n`, which starts a [separate sequence](https://tempo.xyz/developers/docs/guide/tempo-transaction#concurrent-transactions) for this transaction. The maximum `uint256` selects an [expiring nonce](https://tempo.xyz/developers/docs/guide/tempo-transaction#expiring-nonces) instead.

A configuration update works the same way. Put `Actions.accounts.updateConfig.call({ currentConfig: saved.config, nextConfig })` in `calls`, and collect approvals from the current signers. Once it lands, save `AccountConfig.update(saved.config, nextConfig)` as the account's configuration. That's the same configuration `updateConfigSync` returns. An update voids approvals collected under the old configuration, so collect those again.

<span id="verify-approvals" />

<span id="signature-verification" />

<span id="tempo-approval-verification-methods" />

<span id="verify-an-account-approval-in-solidity" />

<span id="verify-approvals-in-a-contract" />

## Verify in a Contract

A contract checks that an account's signers approved a message with `verifyMultisig`. It's on the [signature verifier](https://tempo.xyz/developers/docs/development/configurable-accounts#verify-owner-approvals-in-a-contract), a contract built into Tempo at `0x5165300000000000000000000000000000000000`. Like configurable accounts, `verifyMultisig` exists only on the devnet until T14. On mainnet and testnet, the call reverts with `UnknownFunctionSelector`.

```solidity [ApprovalCheck.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ISignatureVerifier {
    function verifyMultisig(address account, bytes32 hash, bytes calldata signature) external view returns (bool);
}

contract ApprovalCheck {
    ISignatureVerifier constant VERIFIER = ISignatureVerifier(0x5165300000000000000000000000000000000000);

    function isApproved(address account, bytes32 hash, bytes calldata signature) external view returns (bool) {
        return VERIFIER.verifyMultisig(account, hash, signature); // [!code hl]
    }
}
```

`verifyMultisig` checks the account's signers at call time, so approvals from removed signers fail. An account that hasn't sent a transaction yet verifies against its first signers. The call returns `false` when the signers don't match the account. It reverts with `InvalidSignature()` when approvals fall short. Include your chain ID, contract address, and the account in `hash`, so an approval can't be replayed elsewhere.

To check a single access key's signature instead, call `verifyKeychain`. It also works on mainnet and testnet. It accepts only access keys that sign with one key. A call for a configurable account signing as an access key, such as a team budget, reverts with `InvalidFormat()`.

<span id="security-policy-limits" />

<span id="approval-limits" />

<span id="account-security-limits" />

<span id="multi-factor-approval-limits" />

<span id="signing-threshold-limits" />

## Limits

| Limit | Value |
| --- | --- |
| Signers per account | 1 to 48 |
| Weight per signer | At least 1 |
| Total weight | Up to 255 |
| Approvals per transaction | Up to 8, so the eight largest weights must reach the threshold |

## Related

[Account Policies](https://tempo.xyz/developers/docs/accounts/policies): Covers how signing thresholds and receive policies work together.

[Configuration Updates](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#add-a-signer): Use for changing signers, weights, or the threshold.

[Admin Access Keys](https://tempo.xyz/developers/docs/accounts/access-keys/admin-access-keys): Covers access keys with no limits.

[Set Up a Self-Custodial Wallet With a Provider](https://tempo.xyz/developers/docs/accounts/apps/self-custodial-wallets): Use for building customer accounts with a wallet provider and a backup key.

[Set Up a Team Multisig](https://tempo.xyz/developers/docs/accounts/custodians/team-multisig): Use for a team account and team budgets.
