<!--
Sitemap:
- [Tempo Accounts SDK - Getting Started](/docs/): Set up the Tempo Accounts SDK to create, manage, and interact with accounts on Tempo.
- [Deploying to Production](/docs/production): Things to consider before deploying your application with the Tempo Accounts SDK to production.
- [FAQ](/docs/faq): Frequently asked questions about the Tempo Accounts SDK.
- [Connect Accounts](/docs/guides/connect-accounts): Connect a Tempo account in your application.
- [Authentication](/docs/guides/authentication): Authenticate connected accounts against your own server with a signed SIWE challenge.
- [Identity](/docs/guides/identity): Request and verify identity claims about a connected account, starting with a verified email.
- [Transfers](/docs/guides/transfers): Send stablecoin transfers from a connected Tempo account, initiated either by the user or by your server.
- [Spend Permissions](/docs/guides/spend-permissions): Authorize spend limits, call scopes, and expiries so repeat transfers can be signed without a confirmation prompt.
- [React Native](/docs/guides/react-native): Set up Tempo Accounts in a React Native app.
- [Subscriptions](/docs/guides/subscriptions): Charge recurring payments from a connected Tempo account.
- [Fee Sponsorship](/docs/guides/fee-sponsorship): Sponsor transaction fees from a server-controlled policy.
- [Deposits](/docs/guides/deposits): Open the Tempo deposit flow from a connected account and let the user choose deposit details.
- [Swaps](/docs/guides/swaps): Open the Tempo swap flow from a connected account with optional pre-filled intent fields.
- [Theming](/docs/guides/theming): Match embedded account surfaces to your product.
- [CLI](/docs/guides/cli): Authorize and use Tempo accounts from command-line tools.
- [Adapters](/docs/adapters/): Choose the signing adapter for your Tempo Accounts SDK integration.
- [Tempo Wallet Adapter](/docs/adapters/tempo-wallet): Use Tempo Wallet as the hosted universal wallet adapter.
- [WebAuthn Adapter](/docs/adapters/webauthn): Use domain-bound passkeys as the account signing adapter.
- [Turnkey Adapter](/docs/adapters/turnkey): Use Turnkey-managed wallet accounts as the account signing adapter.
- [Privy Adapter](/docs/adapters/privy): Use Privy embedded wallets as the account signing adapter.
- [Private Key Adapter](/docs/adapters/private-key): Sign in-process with a `secp256k1` private key.
- [Custom Adapter](/docs/adapters/custom): Author your own adapter with the `Adapter.define` API.
- [Adapters](/docs/api/adapters): Pluggable adapters for the Tempo Accounts SDK Provider.
- [dialog](/docs/api/dialog): Adapter for the Tempo Wallet dialog, an embedded iframe or popup for account management.
- [local](/docs/api/local): Key-agnostic adapter for defining arbitrary account types and signing mechanisms.
- [mobileWebAuth](/docs/api/mobileWebAuth): Mobile web auth adapter for browser-session wallet requests.
- [postMessage](/docs/api/postMessage): Connect to wallet from anywhere on the web
- [privy](/docs/api/privy): React adapter backed by Privy sessions and embedded Ethereum wallets.
- [secp256k1](/docs/api/secp256k1): Adapter that signs in-process with a `secp256k1` private key.
- [turnkey](/docs/api/turnkey): Adapter backed by Turnkey client sessions.
- [webAuthn](/docs/api/webAuthn): Adapter for passkey-based accounts using WebAuthn registration and authentication.
- [Dialog](/docs/api/dialogs): Dialog modes for embedding the Tempo Wallet.
- [Dialog.iframe](/docs/api/dialog.iframe): Embed the Tempo Wallet auth UI in an iframe dialog element.
- [Dialog.popup](/docs/api/dialog.popup): Open the Tempo Wallet auth UI in a popup window.
- [Expiry](/docs/api/expiry): Utility functions for computing access key expiry timestamps.
- [Provider](/docs/api/provider): Create an EIP-1193 provider for managing accounts on Tempo.
- [Rpc](/docs/api/rpc): Per-method Zod schemas and shared building blocks for the Accounts JSON-RPC surface.
- [Schema](/docs/api/schema): Zod-based JSON-RPC schema definitions for the Accounts provider.
- [TrustedHosts](/docs/api/trustedHosts): Trusted host mappings and matching helpers for dialog adapters.
- [WebAuthnCeremony](/docs/api/webauthnceremony): Pluggable strategy for WebAuthn registration and authentication ceremonies.
- [WebAuthnCeremony.from](/docs/api/webauthnceremony.from): Create a WebAuthnCeremony from a custom implementation.
- [WebAuthnCeremony.server](/docs/api/webauthnceremony.server): Server-backed WebAuthn ceremony that delegates to a remote handler.
- [CLI](/docs/cli/provider): Create a Provider for CLI environments.
- [eth_accounts](/docs/rpc/eth_accounts): Get the addresses of the currently connected accounts.
- [eth_chainId](/docs/rpc/eth_chainId): Get the chain ID of the currently active chain.
- [eth_fillTransaction](/docs/rpc/eth_fillTransaction): Fills missing transaction fields and returns wallet-aware metadata.
- [eth_requestAccounts](/docs/rpc/eth_requestAccounts): Request access to user accounts, prompting the user to connect if needed.
- [eth_sendTransaction](/docs/rpc/eth_sendTransaction): Send a transaction from the connected account.
- [eth_sendTransactionSync](/docs/rpc/eth_sendTransactionSync): Send a transaction and wait for the receipt.
- [eth_signTransaction](/docs/rpc/eth_signTransaction): Sign a transaction without broadcasting it.
- [eth_signTypedData_v4](/docs/rpc/eth_signTypedData_v4): Sign EIP-712 typed structured data with the connected account.
- [personal_sign](/docs/rpc/personal_sign): Sign a message with the connected account.
- [wallet_authorizeAccessKey](/docs/rpc/wallet_authorizeAccessKey): Authorize an access key for delegated transaction signing.
- [wallet_connect](/docs/rpc/wallet_connect): Connect account(s) with optional capabilities like access key authorization.
- [wallet_deposit](/docs/rpc/wallet_deposit): Open the wallet deposit flow with optional pre-filled fields.
- [wallet_depositZone](/docs/rpc/wallet_depositZone): Open the wallet zone-deposit flow with optional pre-filled fields.
- [wallet_disconnect](/docs/rpc/wallet_disconnect): Disconnect the connected account(s).
- [wallet_getBalances](/docs/rpc/wallet_getBalances): Get token balances for an account.
- [wallet_getCallsStatus](/docs/rpc/wallet_getCallsStatus): Get the status of a batch of calls sent via wallet_sendCalls.
- [wallet_getCapabilities](/docs/rpc/wallet_getCapabilities): Get account capabilities for specified chains.
- [wallet_revokeAccessKey](/docs/rpc/wallet_revokeAccessKey): Revoke a previously authorized access key.
- [wallet_send](/docs/rpc/wallet_send): Open the wallet send-token flow with optional pre-filled fields.
- [wallet_sendCalls](/docs/rpc/wallet_sendCalls): Send a batch of calls from the connected account.
- [wallet_swap](/docs/rpc/wallet_swap): Open the wallet swap flow with optional pre-filled swap intent fields.
- [wallet_switchEthereumChain](/docs/rpc/wallet_switchEthereumChain): Switch the provider's active chain.
- [wallet_withdrawZone](/docs/rpc/wallet_withdrawZone): Open the wallet zone-withdraw flow with optional pre-filled fields.
- [Remote](/docs/api/remote): Bridge that runs inside the wallet's iframe/popup and serves RPC requests from the host SDK.
- [Remote.create](/docs/api/remote.create): Create a remote context bound to a Messenger and Provider.
- [Remote.useEnsureVisibility](/docs/api/remote.useEnsureVisibility): React hook that monitors iframe visibility and falls back to a popup when occluded.
- [Remote.useState](/docs/api/remote.useState): React hook to subscribe to a remote context's state store.
- [Remote.useTheme](/docs/api/remote.useTheme): React hook that applies theme overrides from URL search params and live messenger updates.
- [Remote.validateSearch](/docs/api/remote.validateSearch): Validate an RPC request payload from URL search params.
- [Tempo Accounts Server Handlers](/docs/server/): Configure server-side Tempo Accounts SDK handlers for relaying wallet RPC requests, composing backends, and managing WebAuthn ceremonies.
- [Handler.auth](/docs/server/handler.auth): Server handler that issues SIWE-based authentication challenges and sessions.
- [Handler.codeAuth](/docs/server/handler.codeAuth): Server handler for the device-code (PKCE) access-key bootstrap flow.
- [Handler.compose](/docs/server/handler.compose): Compose multiple server handlers into a single handler.
- [Handler.exchange](/docs/server/handler.exchange): Server handler that returns Stablecoin DEX quotes and ready-to-submit calls.
- [Handler.relay](/docs/server/handler.relay): Server handler that proxies certain RPC requests with wallet-aware enrichment.
- [Handler.webAuthn](/docs/server/handler.webAuthn): Server-side WebAuthn ceremony handler for registration and authentication.
- [hc](/docs/server/hc): Typed RPC client for handlers built with the Tempo Accounts SDK.
- [Identity.verify](/docs/server/identity.verify): Verify a wallet-issued identity token (verified email) against an issuer's JWKS.
- [Kv](/docs/server/kv): Key-value store adapters for server-side persistence.
- [Kv.cloudflare](/docs/server/kv.cloudflare): Kv adapter backed by a Cloudflare Workers KV namespace.
- [Kv.durableObject](/docs/server/kv.durableObject): Kv adapter backed by a Cloudflare Durable Object with atomic take and create.
- [Kv.from](/docs/server/kv.from): Wrap a custom Kv-shaped object so the SDK accepts it as a Kv.
- [Kv.memory](/docs/server/kv.memory): In-memory Kv adapter for tests and single-process deployments.
- [Keystore](/docs/api/keystore): Pluggable backends for key material.
- [Keystore.p256](/docs/api/keystore.p256): Pure-JS P-256 keystore.
- [Keystore.secp256k1](/docs/api/keystore.secp256k1): Pure-JS secp256k1 keystore.
- [Keystore.webCryptoP256](/docs/api/keystore.webCryptoP256): WebCrypto P-256 keystore.
- [Storage](/docs/api/storage): Pluggable storage adapters for persisting provider state.
- [Storage.combine](/docs/api/storage.combine): Combine multiple Storage adapters into one.
- [Storage.cookie](/docs/api/storage.cookie): Cookie-backed Storage adapter.
- [Storage.from](/docs/api/storage.from): Create a Storage adapter from a custom implementation.
- [Storage.idb](/docs/api/storage.idb): IndexedDB-backed Storage adapter.
- [Storage.localStorage](/docs/api/storage.localStorage): localStorage-backed Storage adapter.
- [Storage.memory](/docs/api/storage.memory): In-memory Storage adapter.
- [asyncStorage](/docs/api/storage.asyncStorage): React Native Storage adapter backed by AsyncStorage.
- [secureMmkv](/docs/api/storage.secureMmkv): Encrypted React Native Storage adapter backed by MMKV.
- [Tempo Accounts SDK](/index): The fastest way to add stablecoins to your application.
- [Secp256k1 Adapter](/docs/adapters/secp256k1): Sign in-process with a `secp256k1` private key.
- [Bring Your Auth](/docs/enterprise/bring-your-auth/): Connect enterprise auth and signing systems to Tempo accounts.
- [Hosted Universal Wallets](/docs/enterprise/hosted-universal-wallets): Stub for hosting a universal wallet on your own domain.
- [Handler.feePayer (Deprecated)](/docs/server/handler.feePayer): Deprecated — use Handler.relay with feePayer option instead.
- [tempoWallet](/docs/wagmi/tempoWallet): Wagmi connector for the Tempo Wallet dialog.
- [webAuthn](/docs/wagmi/webAuthn): Wagmi connector for passkey-based WebAuthn accounts.
- [AWS KMS](/docs/enterprise/bring-your-auth/aws-kms): Stub for integrating AWS KMS-backed signing with the Tempo Accounts SDK.
- [Custom Auth](/docs/enterprise/bring-your-auth/custom): Stub for first-party enterprise auth and signing integrations.
- [Privy](/docs/enterprise/bring-your-auth/privy): Enterprise notes for integrating Privy-backed auth with the Tempo Accounts SDK.
- [Turnkey](/docs/enterprise/bring-your-auth/turnkey): Stub for integrating Turnkey-backed signing with the Tempo Accounts SDK.
-->

# Spend Permissions

Spend permissions let a connected Tempo account approve a scoped access key once, then use that key for repeat activity that stays inside the approved policy.

Use them when an app needs to make the same kind of payment more than once: checkouts, metered APIs, recurring actions, or any flow where asking for a confirmation prompt on every transfer would be noisy. The user still approves the boundary up front: token, amount, allowed call targets, optional recipients, and expiry.

## Demo

By the end of this guide you'll configure a bounded spend permission: the user signs in, approves the policy once, then matching transfers can be signed locally without another Tempo Wallet prompt.

<Demo title="Spend Permission" githubUrl="https://github.com/tempoxyz/accounts/tree/main/examples/access-key" headerAction={<DemoReset />} className="flex flex-col gap-3" wagmiConfig="spendPermissions">
  <Steps.Step label="Create an account, or use an existing one." action={<ConnectAccount />} />

  <SendPayment />
</Demo>

## Walkthrough

::::steps

### Connect an Account

Follow the [Connect Accounts](/docs/guides/connect-accounts) guide to set up Wagmi with a Tempo signing adapter and let the user sign in. This guide uses Tempo Wallet because the example is hosted there, but the `authorizeAccessKey` shape is the same for adapters that support access key authorization.

### Define the Spend Policy

Add `authorizeAccessKey` to the connector. The object describes the permission the user will approve when a matching local key is not already available. Pass a function instead when the policy needs to be computed dynamically.

```tsx twoslash [src/config.ts]
// @noErrors
import { Expiry } from 'accounts'
import { parseUnits } from 'viem'
import { createConfig, http } from 'wagmi'
import { tempo, tempoModerato } from 'wagmi/chains'
import { tempoWallet } from 'wagmi/connectors'

const pathUsd = '0x20c0000000000000000000000000000000000000' as const

export const config = createConfig({
  chains: [tempoModerato, tempo],
  connectors: [
    tempoWallet({
      testnet: true,
      authorizeAccessKey: { // [!code focus]
        expiry: Expiry.days(1), // [!code focus]
        limits: [ // [!code focus]
          { token: pathUsd, limit: parseUnits('100', 6) }, // [!code focus]
        ], // [!code focus]
        scopes: [ // [!code focus]
          { address: pathUsd, selector: 'transfer(address,uint256)' }, // [!code focus]
        ], // [!code focus]
        reuse: { // [!code focus]
          minExpiry: Expiry.hours(1), // [!code focus]
          minLimits: [ // [!code focus]
            { token: pathUsd, limit: parseUnits('10', 6) }, // [!code focus]
          ], // [!code focus]
        }, // [!code focus]
      }, // [!code focus]
    }),
  ],
  multiInjectedProviderDiscovery: false,
  transports: {
    [tempo.id]: http(),
    [tempoModerato.id]: http(),
  },
})
```

:::warning\[Bound permissions are required]

Tempo Wallet approval pages require at least one `limits` entry and one `scopes` entry. `limits` define how much the access key can spend, while `scopes` define which calls it can make. Without both, the approval would be too broad for a user to review safely.

Local adapters may accept broader keys, but production apps should keep the same bounded policy everywhere.

:::

### Sign In with the Policy

Use the same sign-in button from the Connect Accounts guide. If the SDK already has a stored access key that matches the scopes and optional `reuse` policy, sign-in continues without a new access key prompt. Otherwise Tempo Wallet signs the access key authorization returned by `authorizeAccessKey`.

```tsx twoslash [src/Connect.tsx]
// @noErrors
import { useConnect, useConnectors } from 'wagmi'

export function Connect() {
  const connect = useConnect()
  const [connector] = useConnectors()

  return (
    <button
      disabled={connect.isPending}
      onClick={() => connect.connect({ connector })}
    >
      {connect.isPending ? 'Check prompt...' : 'Sign in'}
    </button>
  )
}
```

### Send Within the Permission

Submit calls that match the approved scopes. The SDK selects the access key when the transfer fits the spend policy; otherwise it falls back to the connected account's normal signing path.

If no matching key exists at send time, the SDK checks whether the configured `authorizeAccessKey` scopes would cover the transaction. When they do, it requests the access key first, stores it, then sends the transaction with that key. When they do not, it skips the access key request and uses the normal signing path.

```tsx twoslash [src/Transfer.tsx]
// @noErrors
import { Hooks } from 'wagmi/tempo'

export function Transfer() {
  const transfer = Hooks.wallet.useTransfer()

  return (
    <button
      disabled={transfer.isPending}
      onClick={() =>
        transfer.mutate({
          amount: '1',
          to: '0x0000000000000000000000000000000000000001',
          token: 'pathusd',
        })
      }
    >
      {transfer.isPending ? 'Sending...' : 'Pay $1'}
    </button>
  )
}
```

::::

## Best Practices

### Pair Limits with Scopes

Use `limits` for how much the key may spend, and `scopes` for what calls it may make. A transfer permission should usually include `transfer(address, uint256)` for the token you intend to support.

```tsx twoslash [src/config.ts]
// @noErrors
import { Expiry } from 'accounts'
import { parseUnits } from 'viem'

const pathUsd = '0x20c0000000000000000000000000000000000000' as const

const spendPermission = {
  expiry: Expiry.days(1),
  limits: [{ token: pathUsd, limit: parseUnits('100', 6) }],
  scopes: [
    { address: pathUsd, selector: 'transfer(address,uint256)' },
  ],
  reuse: {
    minExpiry: Expiry.hours(1),
    minLimits: [{ token: pathUsd, limit: parseUnits('10', 6) }],
  },
}
```

The top-level `expiry`, `limits`, and `scopes` define the new key to create when needed. `reuse.minExpiry` and `reuse.minLimits` only decide whether an existing stored key is acceptable to reuse.

### Keep Expiry Short

Start with a short expiry and ask the user to renew when the workflow needs more time. The access key example uses `Expiry.days(1)`, which is a good default for checkout and metered usage flows.

```ts twoslash
import { Expiry } from 'accounts'

const expiry = Expiry.days(1)
```

### Use Recipient Scopes

If your app always pays one recipient, include `recipients` on the scope. Only omit it when the recipient is truly user-selected at transfer time.

```ts twoslash
// @noErrors
const pathUsd = '0x20c0000000000000000000000000000000000000' as const

const scopes = [
  {
    address: pathUsd,
    selector: 'transfer(address,uint256)',
    recipients: ['0x0000000000000000000000000000000000000001'],
  },
]
```

### Match Token Decimals

Build limits with `parseUnits` and the token's real decimal count. pathUSD uses 6 decimals, so `parseUnits('100', 6)` means a 100 pathUSD cap.

```ts twoslash
import { parseUnits } from 'viem'

const limit = parseUnits('100', 6)
```

## Next Steps

<Cards>
  <Card description="Send stablecoin transfers from a connected account, initiated by the user or by your server." icon="lucide:send" to="/docs/guides/transfers" title="Transfers" />

  <Card description="Read the low-level RPC shape for access key approval, including limits, scopes, expiry, and recipients." icon="lucide:shield-check" to="/docs/rpc/wallet_authorizeAccessKey" title="wallet_authorizeAccessKey" />

  <Card description="Revoke an access key when a user turns off a spend permission." icon="lucide:shield-x" to="/docs/rpc/wallet_revokeAccessKey" title="wallet_revokeAccessKey" />

  <Card description="Review HTTPS, trusted host, and adapter-specific production requirements." icon="lucide:rocket" to="/docs/production" title="Production" />
</Cards>
