> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://accounts.tempo.xyz/api/mcp` to find what you need.

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

#### Spend Permission

[Runnable example source](https://github.com/tempoxyz/accounts/tree/main/examples/access-key)

Create an account, or use an existing one.

Interactive demo: Send a payment. Run it in the browser or use the linked example source.

## 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

[Transfers](/docs/guides/transfers): Send stablecoin transfers from a connected account, initiated by the user or by your server.

[wallet\_authorizeAccessKey](/docs/rpc/wallet_authorizeAccessKey): Read the low-level RPC shape for access key approval, including limits, scopes, expiry, and recipients.

[wallet\_revokeAccessKey](/docs/rpc/wallet_revokeAccessKey): Revoke an access key when a user turns off a spend permission.

[Production](/docs/production): Review HTTPS, trusted host, and adapter-specific production requirements.
