> For AI agents: the documentation index is at https://docs.umbraprivacy.com/llms.txt, and every page is available as Markdown at its URL with .md appended.

# The platform wallet (/docs/account/platform-wallet)

> **Umbra's clients, not the protocol**
>
> The pool requires one thing here: `register` must be signed by the Solana wallet whose address the identity commitment holds in slot 4. Deriving that wallet from your account signature, and binding it to your main wallet in the user registry, is what Umbra's clients do. The pool's `register` does not read the user registry, and another client could use any wallet it likes.

Your platform wallet is an ordinary Solana keypair that the Umbra SDK derives from your account signature and holds on your device. It is the address your identity commitment is bound to, so it signs where the protocol needs proof that you control that address, and your main wallet is publicly bound to it.

## How it is derived

The account context derived from your signature becomes 32 bytes of BIP-39 entropy through HKDF-SHA512 in RFC 9180's labelled form, under a domain that names the chain, the deployment, the application `com.umbraprivacy.nimbus-solana`, the purpose `platform-wallet` and a generation. Those bytes are a 24-word phrase, and the phrase's BIP-39 seed feeds a SLIP-0010 ed25519 derivation along `m/44'/501'/0'/0'`, Solana's usual four-level hardened path. The SDK derives the seed once per session; it can derive further accounts by changing the third level, and account 0 is the default.

These are the standard steps, so the phrase restores the same addresses in any wallet that follows BIP-39 and SLIP-0010, `solana-keygen` included. The SDK does not show the phrase. The domain is part of the derivation, so the same signature gives a different wallet on another chain or deployment and in any other application, and importing your main wallet's phrase elsewhere does not reproduce this address. See [the account signature](https://docs.umbraprivacy.com/docs/account/account-signature).

Your identity is derived after the platform wallet and binds its public key: the keys behind your identity commitment are derived for this one address, and a commitment that names any other is refused before anything is proved.

## What it signs

Two wallets sign for you, and they sign different things. The table compares, for each instruction that involves you, what your main wallet does and what your platform wallet does.

| Instruction                                  | Main wallet               | Platform wallet                                        |
| -------------------------------------------- | ------------------------- | ------------------------------------------------------ |
| `bind_platform_wallet` (user registry)       | Signs as the owner        | Signs as the platform wallet                           |
| `register` (shielded pool)                   | Signs and pays the fee    | Signs as the bound wallet                              |
| A deposit (`process_3x3x2x2` with an inflow) | Funds the platform wallet | Signs as the depositor; tokens leave its token account |
| A relayed transfer or withdrawal             | Nothing                   | Nothing                                                |

At registration, the pool splits the bound wallet's address into two halves and feeds them to the registration proof, which checks that they match slot 4 of your identity commitment. The signature is what turns a claim to that address into proof of holding it. An Ed25519 check inside the circuit would cost more than the rest of the circuit, so the protocol uses the Solana signature instead.

In a deposit, the pool requires the depositor to sign and moves the tokens from the depositor's token account. The depositor's address is public in the transaction. In the SDK's default single-transaction deposit, the main wallet transfers the tokens to the platform wallet and pays the Solana fee in the same transaction, so both land or neither does. The older two-transaction form funds the platform wallet first and has the platform wallet pay the deposit's fee.

Inside the transaction proof, slot 4 is opened again, privately, so the circuit can recognise a note owned by your own platform wallet's address as yours when it applies the self-transfer fee rate. See [the fee model](https://docs.umbraprivacy.com/docs/fees/fee-model).

## The binding

The user registry program has one instruction, `bind_platform_wallet`. It links a main wallet to the platform wallet that acts for it. It takes three accounts: the owner (your main wallet), the platform wallet and the owner's binding address; the reference section at the bottom of this page lists them with the role the program checks for each.

It checks, in order, that:

1. exactly three accounts are passed and the payload is empty;
2. it is listed directly by the transaction, not reached through another program (a program holding the owner's signature could otherwise rebind the owner unseen);
3. both the owner and the platform wallet signed, so neither can be named without its consent;
4. the two are different addresses;
5. the third account is the owner's binding address, a program-derived address seeded by the owner.

It stores nothing, creates nothing and charges no rent. The successful instruction is the binding. Anyone can list an owner's binds by asking an RPC node for the signatures that touched the owner's binding address, and the owner's latest successful bind is the current one. Binding again replaces the earlier binding without an error. The errors each failed check returns are in the reference section.

The shielded pool's `register` does not read the user registry. The SDK sends both instructions in one transaction the first time, and refuses to register if the registry already binds your main wallet to a different platform wallet: the pool would bind your identity to one address while the public record named another. Repairing that takes a new bind signed by both wallets, and the SDK does not do it automatically.

## What the binding makes public

The binding is how anyone can pay you at your main wallet address (see [receiving payments](https://docs.umbraprivacy.com/docs/account/receiving)). It also means these links are public and permanent:

- main wallet to platform wallet, through the bind;
- platform wallet to identity commitment, through the registration it signed;
- platform wallet to every deposit it made, as the depositor.

A chain observer can therefore attribute every deposit to your main wallet. What happens to the value after it enters the pool is not visible. See [what is private and what is visible](https://docs.umbraprivacy.com/docs/privacy/visibility).

## Reference

**bind_platform_wallet accounts**

| #   | Account           | Role   |
| --- | ----------------- | ------ |
| `0` | `owner`           | signer |
| `1` | `platform_wallet` | signer |
| `2` | `binding`         | pda    |

Read at build time from `contracts/solana/tools/umbra-manifest/manifest/user-registry.json`.

**Errors**

| Code | Error                    | Meaning                                                                                                                                                                                                                                                                                                                                                                                          |
| ---- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `2`  | `PdaMismatch`            | Returned when an account is not the canonical PDA for the seeds it should derive from.                                                                                                                                                                                                                                                                                                           |
| `3`  | `MissingSigner`          | Returned when a required signer did not sign.                                                                                                                                                                                                                                                                                                                                                    |
| `4`  | `InvalidPayload`         | Returned when the payload has the wrong length.                                                                                                                                                                                                                                                                                                                                                  |
| `5`  | `MismatchingAccountKeys` | The instruction supplied a number of accounts the handler does not name. Every destructure binds an exact arity, so this is returned for too MANY accounts as well as too few. A call carrying slots the handler ignores is refused rather than silently accepted, so a later release that gives one of those positions a meaning cannot reinterpret filler somebody has been sending all along. |
| `6`  | `InvokedThroughCpi`      | Returned when another program invokes the bind, which the transaction must list itself.                                                                                                                                                                                                                                                                                                          |
| `7`  | `PlatformWalletIsOwner`  | Returned when the platform wallet is the owner's own address.                                                                                                                                                                                                                                                                                                                                    |

Read at build time from `libs/typescript/errors/error-manifest.json`.

## Related

- [Register](https://docs.umbraprivacy.com/docs/operations/register): The transaction that carries the bind and the registration.
- [Your identity commitment](https://docs.umbraprivacy.com/docs/account/identity): Slot 4 and the other seven slots.
- [Receiving payments](https://docs.umbraprivacy.com/docs/account/receiving): How a sender finds you from your main wallet.


Last updated 12 October 2026
