<!-- tempo-docs-context -->
> Source: [/docs/accounts/apps/self-custodial-wallets](https://tempo.xyz/developers/docs/accounts/apps/self-custodial-wallets.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.

# Set Up a Self-Custodial Wallet With a Provider

Builds an account where your provider's key and the user's passkey approve each payment together.

## Overview

This guide builds a three-signer Tempo account for each user of a consumer app with its own login. A wallet provider, such as Privy or Turnkey, holds one signer. Only the user can recover the account or leave without you.

Self-custody rules in some markets require two properties:

* **No Unilateral Control:** Neither your app nor your provider can move or recover the funds alone.
* **Access During Outages:** The user reaches the funds when your systems are down or breached.

A key export meets the first requirement only. It also leaves most users with key material they can't safely use.

:::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.
:::

Each user's account has three signers:

* **Passkey:** The user's passkey, on their phone.
* **Provider Key:** A key your provider manages.
* **Backup Key:** A second key the user keeps.

Any two approve a transaction. The passkey and backup reach the [threshold](https://tempo.xyz/developers/docs/accounts/policies/signing-thresholds) without your app or the provider.

<span id="who-can-move-the-funds" />

## Fund Control

| Who | What they can do alone |
| --- | --- |
| The user, with their passkey and backup | Pay, recover, remove the provider's key, or leave |
| Your wallet provider | Nothing. Its key is one approval of the two each transaction needs. |
| Your app | Nothing. It holds no key; it asks the provider to sign only for a signed-in user. |

<span id="choose-the-controls-your-application-needs" />

## Setup

::::steps
<span id="build-with-privy" />

<span id="build-with-turnkey" />

### Connect Privy or Turnkey

Your provider signs through its own API, and only for a user your app has signed in.

* [Privy](https://docs.privy.io/wallets/using-wallets/tempo/send-a-transaction): Start from the [Privy Tempo example](https://github.com/privy-io/examples/tree/main/examples/privy-next-tempo). Sign transaction hashes with `secp256k1_sign`, or `raw_sign` on server wallets.
* [Turnkey](https://docs.turnkey.com/features/networks/tempo): Start from the [Turnkey Tempo example](https://github.com/tkhq/sdk/tree/main/examples/chain-integrations/with-tempo). Sign transaction hashes with `sign_raw_payload` and no hash function.

Create a wallet for the user with your provider. Wrap it as a Viem account that asks the provider to sign, as in [Pay from a user's embedded wallet](https://tempo.xyz/developers/docs/accounts/automations/pay-from-an-embedded-wallet#wrap-the-providers-signing-as-an-account). Call it `providerAccount`.

<span id="make-the-providers-key-one-signer" />

<span id="create-the-account-with-three-signers" />

### Create the Account

Give the [passkey](https://tempo.xyz/developers/docs/accounts/authentication/passkeys), the provider's key, and the backup key weight 1 each, with a [threshold](https://tempo.xyz/developers/docs/accounts/policies/signing-thresholds) of 2.

The backup is a second passkey on another device, or a hardware key. The user sets it up during onboarding, and the account lists it by address. `client.ts` connects to the Tempo Accounts devnet ([set up the devnet key](https://tempo.xyz/developers/docs/accounts/create#connect-to-the-devnet)).

:::code-group
```ts [account.ts]
import { Account, WebAuthnP256 } from 'viem/tempo'
import { providerAccount } from './provider-account'

const credential = await WebAuthnP256.createCredential({ label: 'My account' })
const passkey = Account.fromWebAuthnP256(credential)

export const account = Account.fromConfig({
  address: 'infer', // derive the address from these first signers
  owners: [ // [!code hl]
    { owner: passkey, weight: 1 }, // [!code hl]
    { owner: providerAccount, weight: 1 }, // [!code hl]
    { owner: backupAddress, weight: 1 }, // [!code hl]
  ], // [!code hl]
  threshold: 2, // [!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}`)}` } },
  }),
})
```
:::

Creating the account sends nothing. The address receives stablecoins right away, and its first transaction registers it onchain.

Save these with the user's record, and rebuild the account from them each time ([keep the current configuration](https://tempo.xyz/developers/docs/development/configurable-accounts#keep-the-current-configuration)):

* `account.address`
* `account.config`
* The passkey's [`credential.id` and `credential.publicKey`](https://tempo.xyz/developers/docs/accounts/authentication/passkeys#passkey-limits)

<span id="pay-with-the-passkey-and-the-provider" />

### Sign Each Payment

Viem signs with the account's attached keys until their weights reach the threshold. With the passkey and `providerAccount` attached, each payment prompts for the passkey and requests one provider signature.

```ts [pay.ts]
import { parseUnits } from 'viem'
import { account } from './account'
import { client } from './client'

// A test stablecoin on the devnet
const token = '0x20c0000000000000000000000000000000000001'

await client.faucet.fundSync({ account: account.address }) // devnet test stablecoins
await client.token.transferSync({ account, amount: parseUnits('1', 6), to: recipient, token }) // [!code hl]
```

The provider signs only the transaction's hash, so its policy engine can't read the payment. Build each transaction in your backend and check it before the provider signs ([route raw signing through one backend](https://tempo.xyz/developers/docs/accounts/apps#route-raw-signing-through-one-backend)).

To skip the passkey prompt on small payments, authorize a [device key](https://tempo.xyz/developers/docs/accounts/authentication/device-keys) with a spending limit. The passkey and provider sign that authorization.

### Back Up the User's Account

Have the user test their backup before they fund the account. They sign a test message with it, or approve one small payment with the passkey and backup together.

Without the passkey and backup, the user depends on your provider. If the provider is also unreachable, no one can approve until it's back.
::::

<span id="if-something-goes-wrong" />

## Failure Cases

| What happened | Who approves | What they do |
| --- | --- | --- |
| The user lost their phone | The provider and the backup | [Replace the passkey](https://tempo.xyz/developers/docs/accounts/apps/recover-an-account) with one on the new phone |
| Your app or the provider is down or breached | The passkey and the backup | Pay as usual, or [remove the provider's key](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#remove-a-lost-or-compromised-signer) |
| The user wants to leave | The passkey and the backup | [Remove the provider's key](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#remove-a-signer), or swap it for another signer the user holds ([replace a signer](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates#replace-a-signer-in-one-update)), at the same address |

A configuration update keeps the account's address, balances, and [Earn](https://tempo.xyz/developers/docs/earn) positions. Save the new configuration each update returns.

<span id="get-funds-out-without-your-app" />

## Leave Without Your App

The passkey and backup sign from any Tempo client, not only your app. To leave, the user [withdraws from Earn](https://tempo.xyz/developers/docs/earn/withdraw), then sends their stablecoins to another account or an [offramp](https://tempo.xyz/developers/docs/partners/payments-and-ramps).

## Add a Recovery Service

A user who loses both the passkey and the backup needs a fourth signer. Add one at weight 1, held by a recovery service you contract.

The service approves with one of the user's keys or the provider. Any two signers can also pay, so choose a service you'd trust to approve payments ([recovery provider](https://tempo.xyz/developers/docs/accounts/policies/signing-thresholds#recovery-provider)).

## Related

[Recover a User's Account](https://tempo.xyz/developers/docs/accounts/apps/recover-an-account): Use for replacing a lost passkey or backup at the same address.

[Pay From a User's Embedded Wallet](https://tempo.xyz/developers/docs/accounts/automations/pay-from-an-embedded-wallet): Use for wrapping the provider's signing and spending within limits.

[Signing Thresholds](https://tempo.xyz/developers/docs/accounts/policies/signing-thresholds): Covers signer weights, thresholds, and recovery providers.

[Configuration Updates](https://tempo.xyz/developers/docs/accounts/account-configuration/configuration-updates): Use for adding, removing, or replacing a signer.
