# Wallet

**Status:** Dark-flag — live behind a cohort or flag

A non-custodial Tempo balance — deposits, sends and conversions. Nothing on this surface can sign; the holder's own passkey is the only signer, on their own device.

> **Mixed status by resource**
>
> Reads (`wallets`, `balances`, `transactions`) are `available` today. Everything that prepares a money movement (`deposit_intents`, `send_intents`, `offramp_intents`, `conversions`) is `dark-flag` — live behind a cohort, answering `capability_unavailable` until it opens for a given account. Read the [reference](/reference) for each operation's own status rather than assuming from this page.

## Concept

The wallet is the holder's own Tempo balance. The API can read it, price a deposit or a send, and build the unsigned steps for one. It holds no wallet key, so it cannot sign a send for you, and it cannot recover the wallet if no usable passkey or synced copy remains. Provider and token restrictions may still apply.

## Resources

| Object | What it is |
|---|---|
| `wallets` / `balances` / `transactions` | The holder's address, per-stablecoin balances plus a computed USD total, and a bounded recent-activity window. |
| `virtual_accounts` | Bank details the holder can receive a transfer into. What arrives is delivered to the wallet as stablecoin. See [Receive by bank](#receive-by-bank). |
| `deposit_intents` | A one-time, amount-bound cross-chain deposit address — single-use, expires. |
| `send_intents` | A prepared cross-chain withdrawal — the ordered steps the holder's passkey signs, quoted net of fees. |
| `offramp_intents` / `external_accounts` | A prepared payout to the holder's **own** verified bank account — never a third party's, on anyone's behalf. |
| `conversions` | An unsigned on-chain swap transaction, built but never sent by the API. |

## Minimal flow (a cross-chain send)

1. `GET /v1/wallet/balances` — or the MCP tool `get_wallet_balance`.
2. `POST /v1/wallet/send_intents` — prepare the withdrawal (`prepare_wallet_withdrawal`); nothing leaves the wallet yet.
3. The holder reviews the quoted amount and signs the returned steps with their own passkey.
4. `POST /v1/wallet/send_intents/{id}/source_tx` — record the broadcast hash (`confirm_wallet_withdrawal`) so settlement can be tracked.
5. `GET /v1/wallet/send_intents/{id}` — or `get_transfer_status` — to watch it settle.

## Receive by bank

A virtual account gives the holder bank details to receive a transfer into. `GET /v1/wallet/virtual_accounts` lists them and `POST /v1/wallet/virtual_accounts` creates one. Two fields say what kind of account a row is:

- **`destination.rail`** is the bank rail: `ach`, `sepa`, `spei`, `faster_payments`, `pix`, `wire` or `bre_b`. It has one spelling per rail: an account the provider sync recorded as `ach_push` is published as `ach`, as an account created through `/v1` always was. It is an open enum, so a client tolerates a rail it does not know, and a stored rail outside the list is published as stored.
- **`provider_environment`** is `sandbox`, `production` or `null`. `sandbox` means a provider test account: its bank details are test data and no real bank transfer arrives. `null` means no environment was recorded and is never evidence that an account is live. Only `sandbox` marks a test account. Swaps does not work it out from the host or from `livemode`, which stays the caller's own mode.

Not every currency can be opened. When a Payment links collection account already receives a currency into the same wallet, `GET /v1/capabilities?product=wallet_bank` reads that corridor `not_enabled` with `blocked_reason: collection_account_uses_currency` and `action: none`, and `POST /v1/wallet/virtual_accounts` for it answers `409 conflict`. Read the capability before offering the account. See [Providers and coverage](/providers-coverage).

## Where a transfer came from

Each transfer in `GET /v1/wallet/transactions` (and `GET /v1/wallet/transactions/{hash}`, or the MCP tool `list_wallet_activity`) carries `origin`. On the list every row has it: `null`, or an object.

| `origin` | Meaning |
|---|---|
| `{ "kind": "bank_deposit", "virtual_account_id": "va_…", "currency", "rail" }` | An incoming transfer that one of the caller's own wallet virtual accounts recorded as the delivery of a bank deposit. `currency` and `rail` are that account's `destination.currency` and `destination.rail`, spelled the same. The deposit is still one transfer row. |
| `null` | Any other transfer, and any transfer Swaps cannot tie to one account: a hash claimed by two accounts, an account you may not read, or an events lookup that failed. `null` is not proof that a transfer is not a bank deposit. |

Only an owner or admin who may read the account sees `bank_deposit`; a business key or another member reads `null`. It is never inferred from the sender, the amount or the timing, and it never carries a provider id or a bank account number. `kind` and `rail` are open enums; tolerate a value you do not know. On the single read, a missing `origin` means the same as `null`.

> **Degraded means unknown, never zero**
>
> A rate-limited chain read returns `degraded: true` with an empty list — never read that as "no activity" or a zero balance. History here is a bounded recent window, not a full ledger.

## What an agent can and cannot do here

Every wallet-write tool *prepares* — `prepare_wallet_withdrawal`, `create_deposit_address`, `prepare_bank_withdrawal` — and returns unsigned steps or a deposit address; none of them broadcasts anything. Adding a bank destination is own-account only and re-verified against the provider at spend time — an agent can never add one on someone else's behalf.

Next: the [reference](/reference) for the full intent schemas · [Pay an invoice](/products/pay-invoice) for the fiat-out counterpart that doesn't touch the wallet.
