> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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`](https://docs.dynamic.xyz/docs/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.

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

> [accordion] 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] 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] 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);
> }
> ```

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.