Delegated access lets your server perform wallet operations (signing messages, signing transactions, exporting keys) on behalf of users with their permission. This is useful for automating workflows while maintaining security through user-approved delegation.This guide covers the server-side implementation using the Java SDK. For conceptual background, see Delegated Access Overview.
Dynamic emits two encrypted blobs in each delegation webhook. Both use RSA-OAEP-SHA256 + AES-256-GCM hybrid encryption. Decrypt with the RSA private key whose public counterpart is configured on your environment:
import xyz.dynamic.waas.delegated.DecryptedWebhookData;import xyz.dynamic.waas.delegated.EncryptedDelegatedPayload;import xyz.dynamic.waas.delegated.Webhook;// Load from a secrets manager — never hardcode.String rsaPrivateKeyPem = System.getenv("DYNAMIC_DELEGATED_RSA_PRIVATE_KEY_PEM");EncryptedDelegatedPayload encShare = payload.encryptedDelegatedKeyShare();EncryptedDelegatedPayload encApiKey = payload.encryptedWalletApiKey();DecryptedWebhookData decrypted = Webhook.decryptDelegatedWebhookData( rsaPrivateKeyPem, encShare, encApiKey);// decrypted.serverKeyShare() — ServerKeyShare, the MPC server key share (sensitive)// decrypted.walletApiKey() — String, the per-wallet API key (sensitive)
DecryptedWebhookData.toString() is redacted — log.debug("{}", decrypted) won’t spill key material into logs.The envelope is RSA-OAEP-SHA256-wrapped AES-256-GCM (alg = "HYBRID-RSA-AES-256"). The SDK also accepts the legacy "RSA-OAEP" alg for backward compatibility.
The delegated client takes the per-wallet credentials (walletId, walletApiKey, keyShare, walletProperties) plus the operation-specific inputs. walletProperties is not in the webhook payload — it’s the same WalletProperties you persisted when the wallet was first created. If you don’t have it cached, rebuild identity via client.fetchWalletMetadata(accountAddress) (note: that returns identity only — no externalServerKeySharesBackupInfo, which is fine for sign / export but not for password rotation or share recovery).
import xyz.dynamic.waas.evm.delegated.opts.DelegatedSignMessageOpts;// Load WalletProperties from your own cache (Redis/Postgres), keyed by walletId.WalletProperties walletProperties = walletCache.get(walletId);String signature = evm.signMessage(DelegatedSignMessageOpts.builder() .walletId(walletId) .walletApiKey(decrypted.walletApiKey()) .keyShare(decrypted.serverKeyShare()) .walletProperties(walletProperties) .message("Hello, delegated".getBytes(java.nio.charset.StandardCharsets.UTF_8)) .build()).join();
The server endpoint for revokeDelegation is not yet live. The SDK validates inputs and returns a successful future without issuing an HTTP call — so a successful join() does NOT mean the delegation was revoked server-side. Node v1 / Python / Go / Rust all stub this identically pending the live route. Until the endpoint ships, treat revocation as a client-side audit signal only: drop the credentials from your vault locally, and do not rely on Dynamic blocking subsequent sign calls.