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.
Overview
Delegated access allows your server to perform wallet operations (signing messages and transactions) 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 Node EVM SDK. For conceptual background on delegated access and the delegation workflow, see Delegated Access Overview.
Prerequisites
Before implementing delegated access, you need:
How Delegation Works
- User approves delegation request in your application (client-side)
- Dynamic sends delegation credentials to your webhook endpoint:
walletId: The user’s wallet ID
walletApiKey: Temporary API key for this wallet
keyShare: Server key share for signing operations
- Your server stores these credentials securely
- Your server uses these credentials to perform signing operations
Creating the Delegated Client
First, create a delegated EVM wallet client using your server API key:
import { createDelegatedEvmWalletClient } from '@dynamic-labs-wallet/node-evm';
const delegatedClient = createDelegatedEvmWalletClient({
environmentId: 'your-environment-id',
apiKey: 'your-server-api-key',
});
Configuration Options
const delegatedClient = createDelegatedEvmWalletClient({
environmentId: 'your-environment-id', // Required
apiKey: 'your-server-api-key', // Required
baseMPCRelayApiUrl: 'https://mpc-relay.dynamic.xyz', // Optional: custom MPC relay URL
});
Signing Messages
Use the delegated client to sign messages on behalf of users. You’ll need the delegation credentials received from your webhook.
Basic Message Signing
import { delegatedSignMessage } from '@dynamic-labs-wallet/node-evm';
const signature = await delegatedSignMessage(delegatedClient, {
walletId: 'wallet-id-from-webhook',
walletApiKey: 'wallet-api-key-from-webhook',
keyShare: keyShareFromWebhook, // ServerKeyShare object
message: 'Hello, World!',
});
console.log('Message signed:', signature);
Signing Transactions
Sign EVM transactions using delegated access. The transaction must be a valid Viem TransactionSerializable object.
Basic Transaction Signing
import { delegatedSignTransaction } from '@dynamic-labs-wallet/node-evm';
import type { TransactionSerializable } from 'viem';
const transaction: TransactionSerializable = {
to: '0xRecipientAddress',
value: BigInt('1000000000000000000'), // 1 ETH in wei
chainId: 1
};
const signedTx = await delegatedSignTransaction(delegatedClient, {
walletId: 'wallet-id-from-webhook',
walletApiKey: 'wallet-api-key-from-webhook',
keyShare: keyShareFromWebhook,
transaction,
});
console.log('Signed transaction:', signedTx);
Complete Transaction Flow
import { delegatedSignTransaction } from '@dynamic-labs-wallet/node-evm';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
});
const transaction = {
to: '0xRecipientAddress',
value: BigInt('1000000000000000000'),
chainId: 1,
};
const nonce = await publicClient.getTransactionCount({
address: walletAddress,
});
const gasEstimate = await publicClient.estimateGas({
account: walletAddress,
to: transaction.to,
value: transaction.value,
});
const fullTransaction = {
...transaction,
nonce,
gasLimit: gasEstimate,
maxFeePerGas: BigInt('20000000000'),
maxPriorityFeePerGas: BigInt('1000000000'),
};
const signedTx = await delegatedSignTransaction(delegatedClient, {
walletId: 'wallet-id-from-webhook',
walletApiKey: 'wallet-api-key-from-webhook',
keyShare: keyShareFromWebhook,
transaction: fullTransaction,
});
const txHash = await publicClient.sendRawTransaction({
serializedTransaction: signedTx as `0x${string}`,
});
console.log('Transaction hash:', txHash);
Security Considerations
Credential Storage
- Never log or expose delegation credentials (wallet API key, key share)
- Store credentials encrypted at rest in your database
- Use secure environment variables for server API keys
- Implement credential rotation policies
Best Practices
class DelegatedWalletService {
private client: DelegatedEvmWalletClient;
constructor() {
this.client = createDelegatedEvmWalletClient({
environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
apiKey: process.env.DYNAMIC_API_KEY!,
debug: process.env.NODE_ENV === 'development',
});
}
async signMessage(userId: string, message: string): Promise<string> {
const credentials = await this.getEncryptedCredentials(userId);
if (!credentials) {
throw new Error('No delegation credentials found');
}
try {
const signature = await delegatedSignMessage(this.client, {
walletId: credentials.walletId,
walletApiKey: credentials.walletApiKey,
keyShare: credentials.keyShare,
message,
});
await this.auditLog({
userId,
action: 'sign_message',
message,
timestamp: new Date(),
});
return signature;
} finally {
this.clearSensitiveData(credentials);
}
}
private async getEncryptedCredentials(userId: string) {
// Implementation (important-comment)
}
private clearSensitiveData(credentials: any) {
// Implementation (important-comment)
}
private async auditLog(data: any) {
// Implementation (important-comment)
}
}
If you want to sponsor gas for delegated transactions from your server, see Sponsor Gas for Server Wallets (EVM).