WalletMetadata describes a wallet’s non-sensitive identity and backup-pointer state. It is returned alongside externalServerKeyShares from wallet-creation methods and must be passed as an explicit argument to every subsequent sign, export, backup, refresh, reshare, and password operation.
The Node SDK is stateless — it does not hold wallet state between calls. Persist walletMetadata in a cache (Redis, Postgres, etc.) and externalServerKeyShares in a secrets vault (HSM, KMS-wrapped DB column, Secret Manager). See Storage Best Practices for the recommended split.
Interface Definition
interface WalletMetadata {
walletId: string;
accountAddress: string;
chainName: string;
thresholdSignatureScheme: ThresholdSignatureScheme;
derivationPath?: string;
addressType?: string; // BTC only
externalServerKeySharesBackupInfo?: KeyShareBackupInfo;
}
Properties
Required Properties
walletId (string) — Unique identifier for the wallet
accountAddress (string) — The wallet’s account address
chainName (string) — SDK-internal chain name (EVM, SVM, BTC, TON)
thresholdSignatureScheme (ThresholdSignatureScheme) — The threshold signature scheme used for this wallet
Optional Properties
derivationPath (string) — Derivation path for the wallet (e.g. EVM uses a BIP-44 path; encoded as a JSON object)
addressType (string) — Bitcoin address type (taproot or native_segwit). Required for BTC operations; absent for other chains.
externalServerKeySharesBackupInfo (KeyShareBackupInfo) — Per-share pointer metadata describing where each share is backed up. Required for signMessage, signTransaction, signTypedData, exportKey, exportPrivateKey, password verification, share recovery, refreshWalletAccountShares, reshare, and updatePassword whenever you pass caller-held externalServerKeyShares. Operations that need it throw a descriptive error when it’s missing. Caching the full walletMetadata returned from createWalletAccount() / importPrivateKey() ensures you have it.
There are two paths to obtain a walletMetadata value, with different completeness guarantees:
| Source | Includes backupInfo? | Includes addressType? | Use when |
|---|
createWalletAccount() / importPrivateKey() return value | ✅ Yes | ✅ Yes | Wallet creation. Persist the full object in your cache. |
client.fetchWalletMetadata(accountAddress) | ❌ No | ❌ No | Recovery path: you have the address but lost the cached metadata. Returns identity only. |
The recommended flow is to persist the full walletMetadata from creation. The backup-pointer metadata (externalServerKeySharesBackupInfo) is not recoverable via SDK-scoped endpoints — there is no server-side fallback if you lose it.
fetchWalletMetadata(accountAddress) is not a recovery path for signing or exporting. It returns identity only (no externalServerKeySharesBackupInfo, no addressType). signMessage, signTransaction, signTypedData, exportKey, and exportPrivateKey all throw when called with caller-supplied externalServerKeyShares and a walletMetadata that lacks externalServerKeySharesBackupInfo. If you lose the cached object, you cannot sign — only re-identify.
Example
import { DynamicEvmWalletClient } from '@dynamic-labs-wallet/node-evm';
import { ThresholdSignatureScheme } from '@dynamic-labs-wallet/node';
const client = new DynamicEvmWalletClient({ environmentId, baseApiUrl });
await client.authenticateApiToken(apiKey);
// 1. Create the wallet — returns walletMetadata + externalServerKeyShares.
const { walletMetadata, externalServerKeyShares } = await client.createWalletAccount({
thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
backUpToDynamic: true,
password: 'user-password',
});
// 2. Persist both — to your cache and your vault respectively.
await redis.set(
`wallet:${walletMetadata.accountAddress}`,
JSON.stringify(walletMetadata)
);
await vault.write(
`wallet:${walletMetadata.accountAddress}/shares`,
externalServerKeyShares
);
// 3. Later, in any process / pod, pass walletMetadata back in.
const cached = JSON.parse(await redis.get(`wallet:${accountAddress}`));
const shares = await vault.read(`wallet:${accountAddress}/shares`);
const signature = await client.signMessage({
walletMetadata: cached,
externalServerKeyShares: shares,
message: 'Hello',
password: 'user-password',
});
updatePassword, refreshWalletAccountShares, and reshare all return a new backupInfo reflecting the new backup state. You must merge it into your cached walletMetadata — otherwise the next operation reads stale metadata and either silently misbehaves or surfaces a “stale walletMetadata” error.
const { backupInfo } = await client.updatePassword({
walletMetadata,
externalServerKeyShares,
existingPassword: 'old-password',
newPassword: 'new-password',
backUpToDynamic: true,
});
const updated = {
...walletMetadata,
externalServerKeySharesBackupInfo: backupInfo,
};
await redis.set(
`wallet:${updated.accountAddress}`,
JSON.stringify(updated)
);