> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Private Wallet

> Add privacy support to an existing Solana wallet for the permissionless confidential Ring. Create private wallets, receive and send private transactions.

A private wallet consists of a Solana keypair for signing, a nullifier key, and a viewing key for encryption – together these keys form the shielded keypair.
Your key management holds the shielded keypair.
The wallet code uses the Privacy Interface and SDK to build private transactions.

<img src="https://mintcdn.com/helius/nNv9k5eJvwuxAzBn/images/privacy/stack.svg?fit=max&auto=format&n=nNv9k5eJvwuxAzBn&q=85&s=0e4087582b1d03c3d29d6a21f33149bb" alt="Key management and the Privacy SDK connect through the Privacy Interface. The Privacy SDK calls the Privacy API. The Privacy API submits the transaction to Solana." width="2156" height="270" data-path="images/privacy/stack.svg" />

The integration enables your users to:

1. Create a self-custodial private wallet
2. Receive private transfers with a Solana address
3. Deposit tokens to a private balance
4. Send privately to a Solana address
5. Withdraw to any Solana address
6. Decrypt private balances and transaction history locally

The integration includes:

1. [**Shielded Keypair**](#integrate-the-shielded-keypair) – in your key management for encryption
2. [**Privacy Interface and SDK**](#integrate-the-privacy-interface-and-sdk) – to build private transactions
3. [**User Flows**](#integrate-user-flows) – to integrate into your wallet

## Context for your AI

<Prompt description="Add a Private Wallet to an existing Solana wallet" actions={["copy", "cursor"]}>
  {`---
    description: Add a Private Wallet to an existing Solana wallet
    allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__helius-docs
    ---

    ## Add a Private Wallet to an existing Solana wallet

    Voice:
    - Point. Then a short, self-contained explanation in a real sentence.
    - No fill words.

    Current Page: https://www.helius.dev/docs/privacy/integration/wallet.md

    Implement in this sequence. Do not skip or rearrange:
    1. Shielded keypair derivation
    2. Privacy Interface in key management
    3. User flows.

    Context:
    - Docs index: https://www.helius.dev/docs/privacy.md
    - Spec: https://github.com/helius-labs/zolana/blob/main/docs/spec.md
    - MCP: https://www.helius.dev/docs/privacy/ai-tools.md
    - Packages: @heliuslabs/zolana, @solana/kit · zolana-client, zolana-interface, zolana-keypair, zolana-transaction
    - Examples: https://github.com/helius-labs/zolana-examples/tree/main/typescript-client · https://github.com/helius-labs/zolana-examples/tree/main/rust-client

    Key APIs: ShieldedKeypairLike, ViewingKeyLike, ShieldedKeypairTrait, ViewingKeyTrait, createZolanaClient. Flow APIs: the pages linked from the docs index.

    ### 1. Index project
    - Grep \`ShieldedKeypairLike|ViewingKeyLike|createZolanaClient|@heliuslabs/zolana|zolana-keypair|TSPP/derive\` across src/
    - Glob \`**/*.{ts,tsx,js,rs}\` · read package.json / Cargo.toml
    - Identify: existing Solana key management, seed vs hardware/MPC, account index, send path, balance/history UI
    - Task subagent if multiple packages

    ### 2. Read references
    - WebFetch the current page — both interface languages, both derivation tabs
    - WebFetch the docs index — follow the pages for the sequence above. Add on-/off-ramp only if selected in step 3
    - WebFetch the spec when protocol behavior is ambiguous (registry, spends, encryption)
    - TaskCreate one todo per phase below

    ### 3. Clarify intention
    - AskUserQuestion: Are you using TypeScript or Rust?
    - AskUserQuestion: Are you a seed-based wallet, creating tests, or a non-seed-based wallet?
    - Seed-based wallet → derive from the seed phrase
    - Tests → derive from a fixed message
    - Non-seed-based wallet → derive from a fixed message
    - AskUserQuestion: Do you want on-/off-ramp?
    - AskUserQuestion: Localnet or devnet?
    - Summarize. Wait for confirmation before planning

    ### 4. Create plan
    - Do not edit unrelated files. Do not add dead code.
    - Take existing code byte for byte. Do not invent APIs, files, or helpers that are not on the fetched pages or in the repo.
    - Every claimed path, symbol, and line number comes from step 1 or 2. If it was not read, do not write it.
    - ASCII tree of files to add or change. One line per file: path, then what that file does.
    - Follow the sequence. If unclear, AskUserQuestion. Present the plan. Wait for approval.

    ### 5. Implement
    - TS: Bash \`pnpm add @heliuslabs/zolana @solana/kit\` if missing
    - Rust: add the crates from the SDK page linked from the docs index
    - Follow the approved plan. Write/Edit. Do not replace the Solana keypair
    - TaskUpdate each step

    ### 6. Verify
    - Every interface method for the chosen language is implemented
    - No secrets in logs or network payloads
    - TS: Bash \`tsc --noEmit\` + existing tests
    - Rust: Bash \`cargo check\` + \`cargo test\` if tests exist
    - TaskUpdate complete

    ### Tools
    - mcp__helius-docs for docs pages
    - mcp__deepwiki__ask_question("helius-labs/zolana", "<q>")
    - WebFetch \`https://www.helius.dev/docs/<path>.md\`
    - Task subagent for parallel Grep/Read/WebFetch
    - TaskList for remaining work

    Invariants:
    - Wallet holds the shielded keypair. Never send key material to the Privacy API.
    - Implement every method on the chosen language's interface. Do not copy Rust-only methods (\`sign_hash\`, \`nullifier_key\`) onto TypeScript.
    - Viewing-key operations are local and synchronous. A sign-only custodian is invalid.
    - Use only APIs from the current page, the docs index, and the flow pages in the sequence. If an API is not there, stop and AskUserQuestion.
    - Do not log mnemonic, nullifier, viewing key, or the \`TSPP/derive/v1\` signature.
    - Language is TypeScript or Rust, not both.
    - Assets are SOL, SPL, and Token-2022.
    - Every Solana public key of the user can add a Private Wallet. One Solana address maps to one Shielded Address.
    - When the recipient of a private transfer does not have a Private Wallet, the Privacy API can resolve the transfer to a private-to-public withdrawal.
    We recommend requesting a separate user signature in the UI to ensure the user does not withdraw to a public balance by accident.`}
</Prompt>

<Accordion title="View prompt">
  ```text theme={"system"}
  description: Add a Private Wallet to an existing Solana wallet
  allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch, AskUserQuestion, Task, TaskCreate, TaskGet, TaskList, TaskUpdate, TaskOutput, mcp__deepwiki, mcp__helius-docs
  ---

  ## Add a Private Wallet to an existing Solana wallet

  Voice:
  - Point. Then a short, self-contained explanation in a real sentence.
  - No fill words.

  Current Page: https://www.helius.dev/docs/privacy/integration/wallet.md

  Implement in this sequence. Do not skip or rearrange:
  1. Shielded keypair derivation
  2. Privacy Interface in key management
  3. User flows.

  Context:
  - Docs index: https://www.helius.dev/docs/privacy.md
  - Spec: https://github.com/helius-labs/zolana/blob/main/docs/spec.md
  - MCP: https://www.helius.dev/docs/privacy/ai-tools.md
  - Packages: @heliuslabs/zolana, @solana/kit · zolana-client, zolana-interface, zolana-keypair, zolana-transaction
  - Examples: https://github.com/helius-labs/zolana-examples/tree/main/typescript-client · https://github.com/helius-labs/zolana-examples/tree/main/rust-client

  Key APIs: ShieldedKeypairLike, ViewingKeyLike, ShieldedKeypairTrait, ViewingKeyTrait, createZolanaClient. Flow APIs: the pages linked from the docs index.

  ### 1. Index project
  - Grep \`ShieldedKeypairLike|ViewingKeyLike|createZolanaClient|@heliuslabs/zolana|zolana-keypair|TSPP/derive\` across src/
  - Glob \`**/*.{ts,tsx,js,rs}\` · read package.json / Cargo.toml
  - Identify: existing Solana key management, seed vs hardware/MPC, account index, send path, balance/history UI
  - Task subagent if multiple packages

  ### 2. Read references
  - WebFetch the current page — both interface languages, both derivation tabs
  - WebFetch the docs index — follow the pages for the sequence above. Add on-/off-ramp only if selected in step 3
  - WebFetch the spec when protocol behavior is ambiguous (registry, spends, encryption)
  - TaskCreate one todo per phase below

  ### 3. Clarify intention
  - AskUserQuestion: Are you using TypeScript or Rust?
  - AskUserQuestion: Are you a seed-based wallet, creating tests, or a non-seed-based wallet?
    - Seed-based wallet → derive from the seed phrase
    - Tests → derive from a fixed message
    - Non-seed-based wallet → derive from a fixed message
  - AskUserQuestion: Do you want on-/off-ramp?
  - AskUserQuestion: Localnet or devnet?
  - Summarize. Wait for confirmation before planning

  ### 4. Create plan
  - Do not edit unrelated files. Do not add dead code.
  - Take existing code byte for byte. Do not invent APIs, files, or helpers that are not on the fetched pages or in the repo.
  - Every claimed path, symbol, and line number comes from step 1 or 2. If it was not read, do not write it.
  - ASCII tree of files to add or change. One line per file: path, then what that file does.
  - Follow the sequence. If unclear, AskUserQuestion. Present the plan. Wait for approval.

  ### 5. Implement
  - TS: Bash \`pnpm add @heliuslabs/zolana @solana/kit\` if missing
  - Rust: add the crates from the SDK page linked from the docs index
  - Follow the approved plan. Write/Edit. Do not replace the Solana keypair
  - TaskUpdate each step

  ### 6. Verify
  - Every interface method for the chosen language is implemented
  - No secrets in logs or network payloads
  - TS: Bash \`tsc --noEmit\` + existing tests
  - Rust: Bash \`cargo check\` + \`cargo test\` if tests exist
  - TaskUpdate complete

  ### Tools
  - mcp__helius-docs for docs pages
  - mcp__deepwiki__ask_question("helius-labs/zolana", "<q>")
  - WebFetch \`https://www.helius.dev/docs/<path>.md\`
  - Task subagent for parallel Grep/Read/WebFetch
  - TaskList for remaining work

  Invariants:
  - Wallet holds the shielded keypair. Never send key material to the Privacy API.
  - Implement every method on the chosen language's interface. Do not copy Rust-only methods (\`sign_hash\`, \`nullifier_key\`) onto TypeScript.
  - Viewing-key operations are local and synchronous. A sign-only custodian is invalid.
  - Use only APIs from the current page, the docs index, and the flow pages in the sequence. If an API is not there, stop and AskUserQuestion.
  - Do not log mnemonic, nullifier, viewing key, or the \`TSPP/derive/v1\` signature.
  - Language is TypeScript or Rust, not both.
  - Assets are SOL, SPL, and Token-2022.
  - Every Solana public key of the user can add a Private Wallet. One Solana address maps to one Shielded Address.
  - When the recipient of a private transfer does not have a Private Wallet, the Privacy API can resolve the transfer to a private-to-public withdrawal.
  We recommend requesting a separate user signature in the UI to ensure the user does not withdraw to a public balance by accident.
  ```
</Accordion>

<Steps>
  <Step>
    ## Integrate the Shielded Keypair

    A private wallet integration adds a shielded keypair to your key management.
    A shielded keypair signs, encrypts and decrypts transactions.

    The shielded keypair is a bundle of the signing, nullifier, and viewing keys.

    * **Signing key:** The wallet's Ed25519 Solana keypair to sign transactions.
    * **Viewing key:** A P-256 keypair, used to encrypt and decrypt transactions.
    * **Nullifier key:** <Tooltip tip="A nullifier ensures a private balance cannot be spent twice. A private balance is the sum of all UTXOs (unspent transaction outputs) owned by an address. Each transaction spends (nullifies) input UTXOs and creates new outputs.">A key used to derive nullifiers.</Tooltip>

    A wallet can create the shielded keypair in two ways:

    1. For seed-based wallets choose **Derive from seed phrase**.
    2. For non-seed-based wallets choose **Derive from EdDSA signature**.

    <Tabs>
      <Tab title="Derive from seed phrase" id="derive-from-seed-phrase">
        A seed-based wallet derives the shielded keypair from one BIP-39 mnemonic and an account index (`0`, `1`, …).

        Each account index maps one Solana keypair to one shielded keypair.
        You can recover the shielded keypair the same way you recover the Solana keypair.

        For example:

        * Account 0: Solana keypair 0 and shielded keypair 0
        * Account 1: Solana keypair 1 and shielded keypair 1

        | Key       | Derivation Path                    | Scheme                    |
        | --------- | ---------------------------------- | ------------------------- |
        | Signing   | `m/44'/501'/account'/0'`           | SLIP-0010 Ed25519         |
        | Nullifier | `m/44'/1392955331'/account'/1'/0'` | SLIP-0010 Ed25519\[1..32] |
        | Viewing   | `m/44'/1392955331'/account'/2'/0'` | SLIP-0010 NIST P-256      |

        The shielded signing key is that account's Solana Ed25519 key.
        The nullifier and viewing keys are additional keys derived from the same seed.

        <Info>
          Example Implementation: [TypeScript example](https://github.com/helius-labs/zolana/blob/cad0664e374f6a0e5c532169c5895203e366eb56/sdk-libs/ts/test/seed-based-keypair.test.ts#L60) · [Rust example](https://github.com/helius-labs/zolana/blob/cad0664e374f6a0e5c532169c5895203e366eb56/sdk-libs/keypair/tests/seed_based_keypair.rs#L111)
        </Info>
      </Tab>

      <Tab title="Derive from EdDSA signature" id="derive-from-signature">
        Derive the shielded keypair from an EdDSA signature over a fixed message.
        EdDSA signatures are deterministic, so the signature of the same Solana keypair for the same message always produces the same shielded keypair.
        We use the signature as a seed to derive a shielded keypair.

        The fixed message payload is `"TSPP/derive/v1"`, encoded as a Solana off-chain message v0.

        The shielded signing key is the wallet’s Solana EdDSA key.
        The nullifier and viewing keys are additional keys derived from the signature.

        <Warning>
          The signature is the derivation seed.
          Whoever holds it can derive the nullifier and viewing keys and can decrypt user transactions.
          Reject any sign request for the `"TSPP/derive/v1"` message from a third party.
        </Warning>

        Pseudocode to derive a shielded keypair from a Solana signature:

        ```jsx theme={"system"}
        function deriveShieldedKeypair(solanaKeypair):
              solanaPubkey = solanaKeypair.publicKeyBytes() // 32 bytes
        	
              payload = UTF8("TSPP/derive/v1")

              // Solana off-chain message v0 envelope
              derivationMessage =
                  bytes("\xffsolana offchain")            // 16 bytes
                  || u8(0)                                // version
                  || SHA256(payload)                      // application domain
                  || u8(0)                                // restricted ASCII format
                  || u8(1)                                // one signer
                  || solanaPubkey
                  || u16LE(length(payload))
                  || payload

              // Ed25519 signatures are deterministic, so the same wallet produces
              // the same 64-byte seed every time.
              derivationSeed = ED25519_SIGN(
                  solanaKeypair.secretKey,
                  derivationMessage
              )

              // HKDF-Extract with no salt
              prk = HKDF_SHA256_EXTRACT(
                  salt = empty,
                  ikm  = derivationSeed
              )

              nullifierSecret = HKDF_SHA256_EXPAND(
                  prk,
                  info   = UTF8("TSPP/nf_key/ed25519/v1"),
                  length = 31
              )

              viewingKeyMaterial = HKDF_SHA256_EXPAND(
                  prk,
                  info   = UTF8("TSPP/view_key/ed25519/v1"),
                  length = 48
              )

              // RFC hash-to-field reduction into a non-zero P-256 scalar
              viewingSecret = P256_SCALAR_FROM_OKM(viewingKeyMaterial)

              return ShieldedKeypair {
                  signingKey:    solanaKeypair,
                  nullifierKey:  NullifierKey(nullifierSecret),
                  viewingKey:    P256Keypair(viewingSecret)
        }
        ```

        <Info>
          Example Implementation: [TypeScript](https://github.com/helius-labs/zolana/blob/cad0664e374f6a0e5c532169c5895203e366eb56/sdk-libs/ts/src/keypair/shielded.ts#L201) · [Rust](https://github.com/helius-labs/zolana/blob/cad0664e374f6a0e5c532169c5895203e366eb56/sdk-libs/keypair/src/shielded.rs#L126)
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ## Integrate the Privacy Interface and SDK

    The Privacy Interface enables your code to use the Privacy SDK to build private transactions without accessing the key material directly.
    Your key management implements the Privacy Interface in two parts.

    1. Signing Interface for Transaction Building with the Privacy SDK. View Source: [TypeScript](https://github.com/helius-labs/zolana/blob/236e6cd6cee4d07e567b316210e018f31dfa4d90/sdk-libs/ts/src/keypair/shielded.ts#L132)/ [Rust](https://github.com/helius-labs/zolana/blob/236e6cd6cee4d07e567b316210e018f31dfa4d90/sdk-libs/keypair/src/traits/shielded_keypair.rs)
    2. Encryption & Decryption Interface. View Source: [TypeScript](https://github.com/helius-labs/zolana/blob/236e6cd6cee4d07e567b316210e018f31dfa4d90/sdk-libs/ts/src/keypair/shielded.ts#L154)/ [Rust](https://github.com/helius-labs/zolana/blob/236e6cd6cee4d07e567b316210e018f31dfa4d90/sdk-libs/keypair/src/traits/view_key.rs)

    <Info>
      The Privacy Interface is stateless and therefore does not store balances or transaction history.
      For performance, we recommend caching user balances and transaction history locally.
    </Info>

    ### On Proof Generation

    Zero-knowledge proof generation requires plaintext inputs of the complete user transaction data and nullifier keys.

    Currently, server proof generation is available through the standard Helius RPC URL.

    Local proof generation is coming soon.
    End-to-end encrypted server proof generation on AWS Nitro is planned before mainnet launch.
  </Step>

  <Step>
    ## Integrate User Flows

    ### Create Private Wallet

    A private wallet is created in two steps:

    1. Derive the shielded keypair
    2. Register the Solana address in the on-chain registry.

    The registry links the signing public key to the nullifier and viewing public keys – together these public keys form the shielded address.

    ```mermaid theme={"system"}
    %%{init: {
      'theme': 'base',
      'themeVariables': {
        'lineColor':           '#FF6B35',
        'primaryTextColor':    '#737373',
        'primaryBorderColor':  '#9CA3AF',
        'actorBkg':            '#FFFFFF',
        'actorBorder':         '#9CA3AF',
        'actorTextColor':      '#737373',
        'signalColor':         '#FF6B35',
        'signalTextColor':     '#737373',
        'labelBoxBkgColor':    '#FF6B351F',
        'labelBoxBorderColor': '#FF6B35',
        'noteBkgColor':        '#F5F5F5',
        'noteTextColor':       '#737373',
        'noteBorderColor':     '#9CA3AF'
      }
    }}%%
    sequenceDiagram
        participant User
        participant Privacy SDK
        participant Solana

        User->>Privacy SDK: Create Private Wallet
        Privacy SDK->>Solana: Register Solana address

    ```

    <Info>
      Each registry record is a PDA owned by the <Tooltip tip="regyS5rkAcw2YzDJCmTwCTHs2s246FXxbmuRZ42u2PD">[registry program](https://orbmarkets.io/address/regyS5rkAcw2YzDJCmTwCTHs2s246FXxbmuRZ42u2PD)</Tooltip>, which anyone can look up to see whether a Solana address can receive private transfers.
    </Info>

    ### Sync Private Balance & Transaction History

    Balance sync fetches encrypted state from the RPC and decrypts it in the wallet.

    The RPC does not decrypt. Only the owner can decrypt a private balance with a viewing key.

    <Info>
      A wallet should decrypt and update balances before private transactions and wherever it runs backfills: on wallet unlock, private wallet open, app resume, network reconnect, stream gap, or wallet restore.
    </Info>

    <CardGroup cols={2}>
      <Card title="Read a Private Balance" icon="wallet" href="/docs/privacy/guides/read-balance" horizontal />

      <Card title="Read Private Transaction History" icon="clock-rotate-left" href="/docs/privacy/guides/read-history" horizontal />
    </CardGroup>

    ### Deposit to a Private Balance

    Users can deposit to a private balance in two ways:

    1. On-ramp from a fiat balance to a private crypto balance
    2. Deposit from a public SPL account to a private balance

    On-ramp and deposit differ in their privacy guarantees.
    We recommend using on-ramp over deposit for better privacy.

    | Field               | On-ramp from Fiat | Deposit from SPL |
    | ------------------- | ----------------- | ---------------- |
    | Asset               | Private           | Public           |
    | Amount              | Private           | Public           |
    | Source              | Not onchain       | Public           |
    | Destination address | Public            | Public           |

    <CardGroup cols={2}>
      <Card title="On-/Off-Ramp" icon="circle-dollar-to-slot" href="/docs/privacy/integration/on-off-ramp">
        On-/Off-ramp fiat directly to and from a private balance.
      </Card>

      <Card title="Deposit" icon="arrow-down-to-bracket" href="/docs/privacy/guides/deposit">
        Deposit from a public crypto balance.
      </Card>
    </CardGroup>

    ### Private Transfers

    Users can send private transfers to any Solana wallet address.

    For every private transfer, the Privacy SDK looks up the wallet address in the onchain registry to check if the recipient has a private wallet.

    The Privacy SDK can resolve a private transfer to a private-to-public withdrawal, when the recipient does not have a Private Wallet.

    <Info>
      We recommend labeling the transaction in the UI as a withdrawal to ensure the user does not withdraw to a public balance by accident.
    </Info>

    | Field                 | Visibility | Why                                                                  |
    | --------------------- | ---------- | -------------------------------------------------------------------- |
    | Asset                 | Private    | The asset is encrypted onchain                                       |
    | Amount                | Private    | The transferred amount is encrypted onchain                          |
    | Source private wallet | Public     | In a confidential Ring the source private wallet is visible onchain. |
    | Recipient             | Public     | In a confidential Ring the recipient is visible onchain.             |

    <Card title="Private Transfer" icon="arrow-right-arrow-left" href="/docs/privacy/guides/transfer">
      Transfer privately to a private balance.
    </Card>

    ### Withdraw to a Public Balance

    Withdrawals can be used in two ways:

    1. As fallback for private transfers to recipients that don't have a private wallet, or
    2. Direct withdrawal to a public SPL token account.

    For withdrawals, we recommend clearly labeling that the user is leaving the private balance, similar to a seed phrase or private key export.

    | Field                      | Visibility | Why                                                                  |
    | -------------------------- | ---------- | -------------------------------------------------------------------- |
    | Asset                      | Public     | The asset is visible onchain                                         |
    | Amount                     | Public     | The withdrawn amount is visible onchain                              |
    | Source private wallet      | Public     | In a confidential Ring the source private wallet is visible onchain. |
    | Destination public wallet  | Public     | The destination wallet address is visible onchain                    |
    | Destination public balance | Public     | The resulting public balance is visible onchain                      |
    | Remaining private balance  | Private    | The remaining balance is encrypted onchain                           |

    <Card title="Withdraw" icon="arrow-up-to-bracket" href="/docs/privacy/guides/withdraw">
      Withdraw to a public crypto balance.
    </Card>
  </Step>
</Steps>

## Didn't find what you were looking for?

<Callout type="info">
  Reach out! [Telegram](https://t.me/tilo_light) | [E-Mail](mailto:sales@helius.xyz) | [Contact](https://www.helius.dev/contact)
</Callout>
