> ## 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 when managing storage yourself — what to cache, what to vault, and how to recover.

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

| Returned value                                            | Sensitivity                  | Where to store                                                       | How it's used                                                                                          |
| --------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [`walletMetadata`](/node/reference/types/wallet-metadata) | Non-sensitive                | Normal cache: Redis, Postgres, etc. Read on every request.           | Identifies the wallet; passed to every sign / export / backup operation.                               |
| `externalServerKeyShares`                                 | 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 `walletMetadata` explicitly, and any operation that doesn't have a cached `externalServerKeyShares[]` (or fallback backup) will fail.

## Persisting `walletMetadata`

`walletMetadata` is non-sensitive identity + backup-pointer metadata. Cache it like you would any other per-user record:

```ts theme={"system"}
const { walletMetadata, externalServerKeyShares } = await client.createWalletAccount({
  thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
  backUpToDynamic: true,
  password: 'user-password',
});

// Cache the full object — including externalServerKeySharesBackupInfo.
await redis.set(
  `wallet:${walletMetadata.accountAddress}`,
  JSON.stringify(walletMetadata)
);
```

**Cache the full object.** The `externalServerKeySharesBackupInfo` field on `walletMetadata` is *not* recoverable via SDK-scoped endpoints — `fetchWalletMetadata(accountAddress)` returns identity only. Operations that need it — sign (`signMessage` / `signTransaction` / `signTypedData`), export (`exportKey` / `exportPrivateKey`), password verification, share recovery, refresh, reshare, updatePassword — will throw if it's missing.

<Warning>
  **There is no lost-cache recovery for signing or exporting.** If you only persist `accountAddress`, you can call `fetchWalletMetadata` to re-identify a wallet but you cannot sign or export from it — those methods reject identity-only metadata when paired with caller-supplied `externalServerKeyShares`. Treat the full `walletMetadata` object as recovery-critical.
</Warning>

**BTC wallets must persist `addressType`.** Bitcoin signing and backup operations read `addressType` (`taproot` or `native_segwit`) from `walletMetadata`. `fetchWalletMetadata` does not return it, and `storeEncryptedBackupByWallet` cannot infer it server-side. If your BTC cache omits `addressType`, subsequent backups fail.

**Re-cache after mutating operations.** `updatePassword`, `refreshWalletAccountShares`, and `reshare` return a new `backupInfo` reflecting the updated backup state. Merge it back and persist:

```ts theme={"system"}
const { backupInfo } = await client.updatePassword({ walletMetadata, /* ... */ });
const updated = { ...walletMetadata, externalServerKeySharesBackupInfo: backupInfo };
await redis.set(`wallet:${updated.accountAddress}`, JSON.stringify(updated));
```

## Storing `externalServerKeyShares`

When you set `backUpToDynamic: false` during wallet creation, you are responsible for securely storing the `externalServerKeyShares` 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.

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

    ```ts theme={"system"}
    import { KMSClient, EncryptCommand, DecryptCommand } from '@aws-sdk/client-kms';

    const kmsClient = new KMSClient({ region: 'us-east-1' });

    async function encryptWithKMS(plaintext: string, keyId: string) {
      const command = new EncryptCommand({
        KeyId: keyId,
        Plaintext: Buffer.from(plaintext),
      });
      const response = await kmsClient.send(command);
      return response.CiphertextBlob; // Store this in your database
    }

    async function decryptWithKMS(ciphertext: Uint8Array) {
      const command = new DecryptCommand({
        CiphertextBlob: ciphertext,
      });
      const response = await kmsClient.send(command);
      return Buffer.from(response.Plaintext).toString('utf8');
    }
    ```

    **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">
    Similar to AWS KMS, but integrated with Google Cloud's ecosystem.

    ```ts theme={"system"}
    import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
    import { KeyManagementServiceClient } from '@google-cloud/kms';

    const secretClient = new SecretManagerServiceClient();
    const kmsClient = new KeyManagementServiceClient();

    async function storeSecret(projectId: string, secretId: string, payload: string) {
      const [version] = await secretClient.addSecretVersion({
        parent: `projects/${projectId}/secrets/${secretId}`,
        payload: {
          data: Buffer.from(payload, 'utf8'),
        },
      });
      return version.name;
    }
    ```
  </Accordion>

  <Accordion title="3. Azure Key Vault">
    Microsoft Azure's managed secrets and key management service.

    ```ts theme={"system"}
    import { SecretClient } from '@azure/keyvault-secrets';
    import { DefaultAzureCredential } from '@azure/identity';

    const credential = new DefaultAzureCredential();
    const vaultUrl = `https://${vaultName}.vault.azure.net`;
    const client = new SecretClient(vaultUrl, credential);

    async function storeSecret(name: string, value: string) {
      await client.setSecret(name, value);
    }
    ```
  </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
* **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** — overwrite secrets in memory after use; use secure deletion for storage

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

  -- Indexes
  INDEX idx_wallet_id (wallet_id),
  INDEX idx_account_address (account_address)
);
```

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