> ## 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 Java backend — what to cache, what to vault.

The Java SDK is **stateless**. It does not hold wallet state between calls. After `createWalletAccount()`, the returned `KeygenResult` carries 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.                                        |
| `List<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 `List<ServerKeyShare>` (and without backup fallback) will fail.

## Persisting `WalletProperties`

`WalletProperties` is non-sensitive identity + backup-pointer metadata. It exposes `toJson()` / `fromJson(...)` with snake\_case keys (cross-SDK compatible with Python and Rust) and `toNodeJson()` / `fromNodeJson(...)` with camelCase (Node v1 interop). Cache it like any other per-user record:

```java theme={"system"}
KeygenResult result = client.createWalletAccount(
    CreateWalletOpts.builder()
        .password("user-password")
        .backUpToDynamic(true)
        .build()
).join();

WalletProperties walletProperties = result.walletProperties();

// Cache the full struct — including backup-pointer info.
redis.set(
    "wallet:" + walletProperties.accountAddress(),
    walletProperties.toJson()
);
```

**Cache the full record.** The `externalServerKeySharesBackupInfo` field on `WalletProperties` is *not* recoverable via SDK-scoped endpoints — `fetchWalletMetadata(accountAddress)` returns identity only. Operations that need it (password verification, share recovery) will fail if it's missing.

Mutating operations (`updatePassword`, refresh, reshare) return new backup info — merge it into the cached `WalletProperties` via `WalletProperties.withBackupInfo(...)` before writing back to your cache.

## Storing `List<ServerKeyShare>`

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

`ServerKeyShare` is JSON-serializable — use `ServerKeyShare.toJson()` / `fromJson(...)` 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 (AWS SDK for Java v2):**

    ```java theme={"system"}
    import software.amazon.awssdk.core.SdkBytes;
    import software.amazon.awssdk.services.kms.KmsClient;
    import software.amazon.awssdk.services.kms.model.*;

    public final class KmsEnvelope {
        private final KmsClient kms;
        private final String keyId;

        public KmsEnvelope(KmsClient kms, String keyId) {
            this.kms = kms;
            this.keyId = keyId;
        }

        public byte[] encrypt(byte[] plaintext) {
            EncryptResponse out = kms.encrypt(EncryptRequest.builder()
                .keyId(keyId)
                .plaintext(SdkBytes.fromByteArray(plaintext))
                .build());
            return out.ciphertextBlob().asByteArray();
        }

        public byte[] decrypt(byte[] ciphertext) {
            DecryptResponse out = kms.decrypt(DecryptRequest.builder()
                .keyId(keyId)
                .ciphertextBlob(SdkBytes.fromByteArray(ciphertext))
                .build());
            return out.plaintext().asByteArray();
        }
    }
    ```

    **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 [`google-cloud-kms`](https://cloud.google.com/java/docs/reference/google-cloud-kms/latest) or [`google-cloud-secretmanager`](https://cloud.google.com/java/docs/reference/google-cloud-secretmanager/latest) — same envelope pattern as AWS KMS, integrated with Google Cloud IAM and audit logging.
  </Accordion>

  <Accordion title="3. Azure Key Vault">
    Use [`azure-security-keyvault-secrets`](https://learn.microsoft.com/en-us/java/api/overview/azure/security-keyvault-secrets-readme) — Azure-managed secrets with Managed Identity auth.
  </Accordion>

  <Accordion title="4. HashiCorp Vault">
    Use [`vault-java-driver`](https://github.com/BetterCloud/vault-java-driver) or [`spring-cloud-vault`](https://spring.io/projects/spring-cloud-vault) 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 `externalServerKeyShares` from all logs, error messages, and monitoring. `ServerKeyShare.toString()` is redacted by the SDK; mirror that 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** — null out decrypted byte arrays after use; prefer `char[]` for passwords so you can clear them deterministically

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