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

# Delegated Access for EVM Wallets

> Sign messages on behalf of users with their permission using delegated access

## Overview

Delegated access lets your server perform wallet operations (signing messages) 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 Rust SDK. For conceptual background, see [Delegated Access Overview](/overview/wallets/embedded-wallets/mpc/delegated-access).

## Prerequisites

* A Dynamic environment with embedded wallets (MPC) enabled
* An RSA private key registered with Dynamic for webhook decryption (PEM-encoded PKCS#8)
* [Access to delegated shares, wallet API etc. via Delegated Access](/overview/wallets/embedded-wallets/mpc/delegated-access)

## How Delegation Works

1. User approves delegation request in your application (client-side).
2. Dynamic sends an encrypted webhook to your server containing:
   * `walletId` — the user's wallet ID
   * `encryptedWalletApiKey` — wallet-scoped API key for outbound signing requests, RSA-OAEP-wrapped + AES-GCM encrypted
   * `encryptedShare` — the customer-side MPC share, encrypted with the same envelope
3. Your server decrypts both fields with your RSA private key.
4. Your server stores the credentials securely (vault).
5. Your server uses these credentials to sign on behalf of the user via `DelegatedEvmWalletClient`.

## Decrypting the Webhook Payload

```rust theme={"system"}
use dynamic_waas_sdk::decrypt_delegated_webhook_data;

// Your RSA private key (PEM-encoded PKCS#8). Load from a secrets manager — never hardcode.
let rsa_private_key_pem = std::env::var("DYNAMIC_DELEGATED_RSA_PRIVATE_KEY_PEM")?;

let decrypted = decrypt_delegated_webhook_data(
    &rsa_private_key_pem,
    &payload.encrypted_share,
    &payload.encrypted_wallet_api_key,
)?;

// decrypted.share         — Vec<u8>, the MPC server key share (sensitive)
// decrypted.wallet_api_key — String, the per-wallet API key (sensitive)
```

`DecryptedWebhookData` has a custom `Debug` impl that redacts both fields — safe to include in `tracing` spans.

The envelope is RSA-OAEP-SHA256-wrapped AES-256-GCM (`alg = "HYBRID-RSA-AES-256"`). The SDK also accepts legacy `"RSA-OAEP"` for backward compatibility.

## Creating the Delegated Client

```rust theme={"system"}
use dynamic_waas_sdk::{DelegatedWalletClient, DelegatedWalletClientOpts};
use dynamic_waas_sdk_evm::DelegatedEvmWalletClient;

let delegated = DelegatedWalletClient::new(
    DelegatedWalletClientOpts::new(env_id, decrypted.wallet_api_key.clone()),
)?;
let evm = DelegatedEvmWalletClient::new(&delegated);
```

### Configuration Options

```rust theme={"system"}
let opts = DelegatedWalletClientOpts::new(env_id, wallet_api_key)
    .base_api_url("https://app.dynamic-preprod.xyz") // optional
    .base_mpc_relay_url("https://mpc.dynamic-preprod.xyz"); // optional
```

## Signing Messages

The delegated client takes the same arguments as the org-token EVM client. Build a `WalletProperties` using identity from the webhook payload and the decrypted share:

```rust theme={"system"}
use dynamic_waas_sdk::{ServerKeyShare, WalletProperties};

let wallet_properties = WalletProperties {
    chain_name: "EVM".to_string(),
    wallet_id: payload.wallet_id.clone(),
    account_address: payload.account_address.clone(),
    threshold_signature_scheme: Default::default(),
    derivation_path: None,
    address_type: None,
    external_server_key_shares_backup_info: None,
};

let external_server_key_shares = vec![ServerKeyShare {
    /* fields populated from decrypted.share — see docs.rs for the exact shape */
    ..Default::default()
}];

let signature = evm
    .sign_message(
        &wallet_properties,
        &external_server_key_shares,
        "Hello, World!",
    )
    .await?;
```

## Revoking Delegation

When a user revokes consent (or your server detects misuse), call `revoke_delegation` on the base delegated client:

```rust theme={"system"}
delegated.revoke_delegation(&wallet_id).await?;
```

After revocation, the wallet API key + share are no longer accepted by Dynamic — outbound `sign_message` calls will fail with `Error::Auth`.

## Security Considerations

### Credential Storage

* **Never log or expose** delegation credentials (wallet API key, key share). The SDK redacts them in `Debug` output; mirror that in your own code.
* **Store credentials encrypted at rest** in your database (envelope encryption via cloud KMS recommended — see [Storage Best Practices](/rust/storage-best-practices)).
* **Use secure environment variables** for the RSA private key. Load it from AWS Secrets Manager / GCP Secret Manager / Vault, not `.env`.
* **Implement credential rotation policies** — revoke wallet API keys when no longer needed.

### Defense-in-Depth Skeleton

```rust theme={"system"}
use dynamic_waas_sdk::{DelegatedWalletClient, DelegatedWalletClientOpts};
use dynamic_waas_sdk_evm::DelegatedEvmWalletClient;

pub struct DelegatedWalletService {
    env_id: String,
    rsa_private_key_pem: String,
    vault: VaultClient,
    audit: AuditLog,
}

impl DelegatedWalletService {
    pub async fn handle_webhook(&self, payload: WebhookPayload) -> anyhow::Result<()> {
        let decrypted = dynamic_waas_sdk::decrypt_delegated_webhook_data(
            &self.rsa_private_key_pem,
            &payload.encrypted_share,
            &payload.encrypted_wallet_api_key,
        )?;

        // Vault both pieces — encrypted at rest, KMS-wrapped.
        self.vault
            .write_envelope(
                &format!("delegated:{}", payload.wallet_id),
                &decrypted,
            )
            .await?;

        self.audit.log("delegation_granted", &payload.wallet_id).await?;
        Ok(())
    }

    pub async fn sign_message(
        &self,
        wallet_id: &str,
        message: &str,
    ) -> anyhow::Result<String> {
        let creds = self.vault.read_envelope(&format!("delegated:{wallet_id}")).await?;

        let delegated = DelegatedWalletClient::new(
            DelegatedWalletClientOpts::new(&self.env_id, creds.wallet_api_key),
        )?;
        let evm = DelegatedEvmWalletClient::new(&delegated);

        let signature = evm
            .sign_message(&creds.wallet_properties, &creds.shares, message)
            .await?;

        self.audit.log("sign_message", wallet_id).await?;
        Ok(signature)
    }
}
```

## Related

* [Delegated Access Overview](/overview/wallets/embedded-wallets/mpc/delegated-access) — conceptual background and flow diagram
* [Solana Delegated Access](/rust/svm/delegated-access)
