# Configurable Accounts

:::info
This guide is intended to be low-level. If you are looking for a high-level abstraction, check out
[Viem's Multisig Transactions guide](https://viem.sh/tempo/guides/multisig-transactions).
:::

## Overview

A Tempo configurable account has weighted owners and an approval threshold that can change without
changing the account address. It can have a single owner or several. Ox models it with an
[`AccountConfig`](/tempo/reference/AccountConfig) and a top-level
[`SignatureEnvelope`](/tempo/reference/SignatureEnvelope) of type `configurable`. The initial,
version-0 config derives a stable account address. Owners approve an account-specific digest, and
their combined weight must meet the configured threshold.

Every configurable account signature carries the complete applicable config. A version-0 config authorizes the
counterfactual account while its onchain config commitment is zero. After an owner update, the
signature carries the current nonzero version whose commitment is stored by the network.

:::warning
Configurable account support is experimental. These examples describe Ox's current API. TIP-1107
is a draft and its protocol shape may change, so confirm that your Ox and Tempo node versions agree
before producing signatures.
:::

[See the TIP-1107 draft](https://tips.sh/1107)

## Recipes

### Derive a Configurable Account

Normalize the initial config with
[`AccountConfig.from`](/tempo/reference/AccountConfig/from), then derive its permanent account
with [`AccountConfig.getAddress`](/tempo/reference/AccountConfig/getAddress). Supply the recovery
factory configured by your chain. The pinned experimental node has no public-network default.

```ts twoslash
import { Address, Hex, Secp256k1 } from 'ox'
import { AccountConfig } from 'ox/tempo'

const ownerPrivateKeys = [
  Secp256k1.randomPrivateKey(),
  Secp256k1.randomPrivateKey(),
  Secp256k1.randomPrivateKey(),
]
const owners = ownerPrivateKeys.map((privateKey) => ({
  owner: Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })),
  weight: 1,
}))

// [!code focus:start]
const initialConfig = AccountConfig.from({
  owners,
  salt: Hex.random(32),
  threshold: 2, // [!code hl]
})
// Replace this development factory with your chain's configured factory.
const factory = '0x7171717171717171717171717171717171717171'
const account = AccountConfig.getAddress(initialConfig, { factory })
// @log: '0x...'
// [!code focus:end]
```

`AccountConfig.from` sorts owners by ascending address, applies the zero salt and version when
omitted, and rejects invalid thresholds, weights, versions, salts, or owner lists.

### Sign an Initial Transaction

Build a nonempty transaction, derive its owner-approval digest with
[`AccountConfig.getSignPayload`](/tempo/reference/AccountConfig/getSignPayload), and collect
enough owner signatures to meet the threshold. Use
[`SignatureEnvelope.sortApprovals`](/tempo/reference/SignatureEnvelope/sortApprovals)
to put approvals in the order required by Tempo.

Pass the complete initial config to
[`SignatureEnvelope.from`](/tempo/reference/SignatureEnvelope/from). Supply both the explicit `account` and the
version-0 configuration.

```ts twoslash
import { Address, Hex, Secp256k1 } from 'ox'
import { AccountConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'

// 1. Set up the owner keys and initial config.
const ownerPrivateKeys = [
  Secp256k1.randomPrivateKey(),
  Secp256k1.randomPrivateKey(),
  Secp256k1.randomPrivateKey(),
]
const initialConfig = AccountConfig.from({
  owners: ownerPrivateKeys.map((privateKey) => ({
    owner: Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })),
    weight: 1,
  })),
  salt: Hex.random(32),
  threshold: 2,
})
// Replace this development factory with your chain's configured factory.
const factory = '0x7171717171717171717171717171717171717171'
const account = AccountConfig.getAddress(initialConfig, { factory })

// 2. Build the initial transaction.
const transaction = TxEnvelopeTempo.from({
  calls: [
    {
      to: '0x0000000000000000000000000000000000000000',
    },
  ],
  chainId: 4217,
  nonce: 0n,
})
// [!code focus:start]
// 3. Derive the owner approval payload.
const payload = TxEnvelopeTempo.getSignPayload(transaction)
const approvalPayload = AccountConfig.getSignPayload({
  account,
  config: initialConfig,
  payload,
})

// 4. Collect sufficient approval weight and sort the approvals.
const approvals = ownerPrivateKeys.slice(0, 2).map((privateKey) =>
  SignatureEnvelope.from(
    Secp256k1.sign({
      payload: approvalPayload,
      privateKey,
    }),
  ),
)
const orderedApprovals = SignatureEnvelope.sortApprovals({
  account,
  config: initialConfig,
  payload,
  signatures: approvals,
})

// 5. Attach the complete config and serialize the transaction.
const bootstrapSignature = SignatureEnvelope.from({
  account,
  config: initialConfig, // [!code hl]
  signatures: orderedApprovals,
})
const serialized = TxEnvelopeTempo.serialize(transaction, {
  signature: bootstrapSignature,
})
// @log: '0x76...'
// [!code focus:end]
```

The version-0 config lives in the signature. The first accepted transaction registers its config
commitment. Use that configuration until an owner update stores a new commitment.

### Sign a Later Transaction

Reuse the original `initialConfig` to derive the permanent account, even if the owners have changed.
Owners change the config by calling the `updateConfig` precompile, which keeps the salt and
increments the version. Derive the same next config offchain with
[`AccountConfig.update`](/tempo/reference/AccountConfig/update), and track it because the network
stores only its commitment. Use that config for the approval digest and signature witness.

```ts twoslash
import { Address, Hex, Secp256k1 } from 'ox'
import { AccountConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'

// 1. Set up the owner keys and initial config.
const ownerPrivateKeys = [
  Secp256k1.randomPrivateKey(),
  Secp256k1.randomPrivateKey(),
  Secp256k1.randomPrivateKey(),
]
const initialConfig = AccountConfig.from({
  owners: ownerPrivateKeys.map((privateKey) => ({
    owner: Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })),
    weight: 1,
  })),
  salt: Hex.random(32),
  threshold: 2,
})
// Replace this development factory with your chain's configured factory.
const factory = '0x7171717171717171717171717171717171717171'
const account = AccountConfig.getAddress(initialConfig, { factory })

// 2. Derive the config committed by an owner update that removed the first owner.
const currentConfig = AccountConfig.update(initialConfig, {
  owners: ownerPrivateKeys.slice(1).map((privateKey) => ({
    owner: Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })),
    weight: 1,
  })),
  threshold: 2,
})

// 3. Build a later transaction.
const transaction = TxEnvelopeTempo.from({
  calls: [
    {
      to: '0xcafebabecafebabecafebabecafebabecafebabe',
      value: 1n,
    },
  ],
  chainId: 4217,
  nonce: 1n,
})
// [!code focus:start]
// 4. Derive the owner approval payload with the current config version.
const payload = TxEnvelopeTempo.getSignPayload(transaction)
const approvalPayload = AccountConfig.getSignPayload({
  account,
  config: currentConfig,
  payload,
})

// 5. Collect sufficient approval weight and sort the approvals.
const approvals = ownerPrivateKeys.slice(1, 3).map((privateKey) =>
  SignatureEnvelope.from(
    Secp256k1.sign({
      payload: approvalPayload,
      privateKey,
    }),
  ),
)
const orderedApprovals = SignatureEnvelope.sortApprovals({
  account,
  config: currentConfig,
  payload,
  signatures: approvals,
})

// 6. Attach the current config and approvals.
const signature = SignatureEnvelope.from({
  account,
  config: currentConfig, // [!code hl]
  signatures: orderedApprovals, // [!code hl]
})
const serialized = TxEnvelopeTempo.serialize(transaction, {
  signature,
})
// @log: '0x76...'
// [!code focus:end]
```

The network hashes `currentConfig` and checks that its commitment matches the value stored for
`account` before validating the approvals.

## Best Practices

### Persist Config Witnesses

Store the normalized initial config with the account because it remains the source for deriving the
permanent address. After each owner update, store or index the complete new config returned by
`AccountConfig.update`. The precompile stores only the config commitment, not the owner list.

### Sort Both Config Owners and Approvals

Construct configs with `AccountConfig.from` and order every approval set with
`sortApprovals`. These are separate ordering requirements.

### Check Approval Weight

Ox validates the config, but the network decides whether the supplied approvals meet the active
threshold. Count weights from the current config witness before collecting and broadcasting a
signature.

### Authorize Access Keys with Owner Approvals

A configurable account can provision an account-bound non-admin access key. Set the
authorization's `account` to the configurable account address and `isAdmin` to `false`, then sign
`KeyAuthorization.getSignPayload` with a configurable envelope whose owner approvals use the current
config. Attach the signed authorization as `keyAuthorization` when provisioning the key; later
transactions omit it and use the account-bound keychain signature flow.

When both the outer transaction and the key authorization use configurable account signatures,
each signature carries and validates its own complete applicable config witness.

### Use the Applicable Config

Use the version-0 config to register the account and until its first owner update. After an update,
use the complete config whose commitment is currently stored for the account.

## See More

<Cards>
  <Card icon="lucide:mail-open" title="Transaction Envelopes" description="Construct and sign Tempo transaction envelopes." to="/tempo/guides/transaction-envelopes" />

  <Card icon="lucide:signature" title="Signature Envelopes" description="Work with Tempo's primitive and stateful signature formats." to="/tempo/guides/signature-envelopes" />

  <Card icon="lucide:square-function" title="AccountConfig" description="Review the complete experimental configurable account API." to="/tempo/reference/AccountConfig" />
</Cards>
