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

# Storage Best Practices

> How to persist wallet state in your Rust backend — what to cache, what to vault.

The Rust SDK is **stateless**. It does not hold wallet state between calls. After `create_wallet_account()`, you receive two distinct pieces of state — each belongs in a different storage tier:

| Returned value        | Sensitivity                  | Where to store                                                       | How it's used                                                                                          |
| --------------------- | ---------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `WalletProperties`    | Non-sensitive                | Normal cache: Redis, Postgres, etc. Read on every request.           | Identifies the wallet; passed to every sign / export operation.                                        |
| `Vec<ServerKeyShare>` | Sensitive (MPC key material) | Secrets vault: HSM, KMS-wrapped DB column, AWS Secrets Manager, etc. | The customer-side half of MPC signing. Combined with your Dynamic API key, provides signing authority. |

Both must be persisted from creation. The SDK provides no compatibility shim — every subsequent operation requires `WalletProperties` explicitly, and operations without cached `Vec<ServerKeyShare>` (and without backup fallback) will fail.

## Persisting `WalletProperties`

`WalletProperties` is non-sensitive identity + backup-pointer metadata. It's `serde::Serialize` / `Deserialize`, so cache it like any other per-user record:

```rust theme={"system"}
let (wallet_properties, external_server_key_shares) = evm
    .create_wallet_account(
        ThresholdSignatureScheme::TwoOfTwo,
        Some("user-password".to_string()),
        /* back_up_to_dynamic */ true,
    )
    .await?;

// Cache the full struct — including external_server_key_shares_backup_info.
redis.set(
    format!("wallet:{}", wallet_properties.account_address),
    serde_json::to_string(&wallet_properties)?,
).await?;
```

**Cache the full struct.** The `external_server_key_shares_backup_info` field on `WalletProperties` is *not* recoverable via SDK-scoped endpoints — `fetch_wallet_metadata(account_address)` returns identity only. Operations that need it (password verification, share recovery) will fail with `Error::StaleWalletProperties` if it's missing.

## Storing `Vec<ServerKeyShare>`

When you set `back_up_to_dynamic: false` during wallet creation, you are responsible for securely storing the `Vec<ServerKeyShare>` returned by `create_wallet_account`. These key shares, combined with your Dynamic developer API key, provide signing authority and must be protected with defense-in-depth strategies.

`ServerKeyShare` is `serde::Serialize` / `Deserialize` — serialize to JSON or your vault's native format before persisting.

## Recommended Storage Approaches

<AccordionGroup>
  <Accordion title="1. Envelope Encryption with Cloud KMS (Recommended)">
    Use a cloud Key Management Service to encrypt the key shares before storing them in your database.

    **AWS KMS Example (using `aws-sdk-kms`):**

    ```rust theme={"system"}
    use aws_sdk_kms::{primitives::Blob, Client};

    async fn encrypt_with_kms(
        kms: &Client,
        key_id: &str,
        plaintext: &[u8],
    ) -> anyhow::Result<Vec<u8>> {
        let out = kms
            .encrypt()
            .key_id(key_id)
            .plaintext(Blob::new(plaintext))
            .send()
            .await?;
        Ok(out.ciphertext_blob.unwrap().into_inner())
    }

    async fn decrypt_with_kms(kms: &Client, ciphertext: &[u8]) -> anyhow::Result<Vec<u8>> {
        let out = kms
            .decrypt()
            .ciphertext_blob(Blob::new(ciphertext))
            .send()
            .await?;
        Ok(out.plaintext.unwrap().into_inner())
    }
    ```

    **Benefits:**

    * Centralized key management with automatic rotation
    * Hardware-backed security (FIPS 140-2 Level 3)
    * Audit logging of all encryption/decryption operations
    * Fine-grained IAM policies
  </Accordion>

  <Accordion title="2. Google Cloud KMS & Secret Manager">
    Use the `google-cloud-secretmanager` crate (or `gcloud-sdk`) — same pattern as AWS KMS, integrated with Google Cloud's IAM and audit logging.
  </Accordion>

  <Accordion title="3. Azure Key Vault">
    Use the `azure_security_keyvault` crate — Azure-managed secrets with Managed Identity auth.
  </Accordion>

  <Accordion title="4. HashiCorp Vault">
    Use the `vaultrs` crate to read/write secrets to a Vault KV mount. Works well if your stack already runs Vault.
  </Accordion>
</AccordionGroup>

## Security Requirements Checklist

Regardless of your storage method, follow these requirements:

* **Never log plaintext key shares** — redact `external_server_key_shares` from all logs, error messages, and monitoring. The SDK's `tracing` instrumentation already skips these args; do the same in your own code.
* **Encrypt at rest** — use AES-256-GCM or equivalent; ensure database/storage has encryption enabled
* **Encrypt in transit** — all communication must use TLS 1.3
* **Implement access controls** — restrict which services and roles can decrypt key shares
* **Enable audit logging** — track all access to encrypted materials with timestamps and actor identity
* **Separate encryption keys** — don't reuse keys across environments (dev/staging/prod)
* **Use unique encryption per record** — generate new IVs for each encryption operation
* **Implement key rotation** — rotate encryption keys periodically (e.g., every 90 days)
* **Plan for key compromise** — document incident response for key material exposure
* **Secure deletion** — `zeroize::Zeroize` your decrypted shares after use

## Storage Schema Example

```sql theme={"system"}
CREATE TABLE server_wallet_key_shares (
  id UUID PRIMARY KEY,
  wallet_id VARCHAR(255) NOT NULL,
  account_address VARCHAR(255) NOT NULL,

  -- Encrypted with KMS/Vault
  encrypted_key_shares BYTEA NOT NULL,

  -- Metadata for key management
  encryption_key_id VARCHAR(255) NOT NULL,
  encryption_algorithm VARCHAR(50) DEFAULT 'AES-256-GCM',

  -- Audit fields
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  last_accessed_at TIMESTAMP,
  access_count INTEGER DEFAULT 0
);
```

## What NOT to Do

<Warning>
  * **Never store plaintext key shares in databases, files, or environment variables**
  * **Never commit encryption keys or key shares to version control**
  * **Never use the same encryption key across all wallets**
  * **Never rely solely on database encryption without application-level encryption**
  * **Never expose key shares through APIs or logs**
</Warning>
