Skip to main content
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 valueSensitivityWhere to storeHow it’s used
WalletPropertiesNon-sensitiveNormal 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:
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.
Use google-cloud-kms or google-cloud-secretmanager — same envelope pattern as AWS KMS, integrated with Google Cloud IAM and audit logging.
Use azure-security-keyvault-secrets — Azure-managed secrets with Managed Identity auth.
Use vault-java-driver or spring-cloud-vault to read/write secrets to a Vault KV mount. Works well if your stack already runs Vault.

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

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

  • 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