Overview
Delegated signing lets your backend sign transactions on behalf of end users without requiring them to be online or have an active session. The end user grants a time-bound delegation from your frontend, and your server can then take actions on their account using only your CDP API key and Wallet Secret.
This is useful for:
- Automated transactions - Execute transactions triggered by webhooks or onchain events when the user is offline
- Agentic wallets - Allow a backend service or agent to operate a user’s wallet with scoped permissions
- Background operations - Process user-initiated flows that complete asynchronously after the user has left your app
How it works
Two parties are involved:
- End user - Authenticates on your frontend and grants a time-bound delegation to your app
- Developer - Uses their CDP API key, CDP Wallet Secret and the active delegation to sign transactions from the backend - no user interaction required
The flow:
- Grant - The authenticated end user calls
createDelegation on the frontend, specifying an expiry time
- Sign - Your backend uses the CDP SDK with your API key to perform actions on behalf of the user
- Revoke - The delegation can be revoked before expiry by the end user or the developer
Available delegated signing methods
With an active delegation, your backend can call the following methods via cdp.endUser.* on behalf of an end user:
EVM - Sign
| Method | Description |
|---|
signEvmTransaction({ userId, address, transaction }) | Sign an EVM transaction |
signEvmMessage({ userId, address, message }) | Sign an EIP-191 message |
signEvmTypedData({ userId, address, typedData }) | Sign EIP-712 typed data |
EVM - Send
| Method | Description |
|---|
sendEvmTransaction({ userId, address, transaction, network }) | Send a signed EVM transaction |
sendEvmAsset({ userId, address, to, amount, network }) | Send an EVM asset (e.g. USDC) |
sendUserOperation({ userId, address, network, calls }) | Send a smart account user operation |
createEvmEip7702Delegation({ userId, address, network }) | Create an EIP-7702 delegation |
Solana - Sign
| Method | Description |
|---|
signSolanaMessage({ userId, address, message }) | Sign a Solana message |
signSolanaTransaction({ userId, address, transaction }) | Sign a Solana transaction |
Solana - Send
| Method | Description |
|---|
sendSolanaTransaction({ userId, address, transaction, network }) | Send a signed Solana transaction |
sendSolanaAsset({ userId, address, to, amount, network }) | Send a Solana asset (e.g. USDC) |
Delegated Signing is scoped at the user level. Account-level delegations are coming soon. Operation-specific scoping is available on request.
Prerequisites
- In the CDP Portal, go to Embedded Wallets → Policies and enable the Delegated Signing toggle.
- Install the required packages:
Frontend (@coinbase/cdp-hooks):
Install @coinbase/cdp-{core|hooks}
npm install @coinbase/cdp-core @coinbase/cdp-hooks
Backend (@coinbase/cdp-sdk):
- Install
@coinbase/cdp-sdk
npm install @coinbase/cdp-sdk dotenv
Sign in to the CDP Portal, create a CDP API key and generate a Wallet Secret. Add them to your .env file:
CDP_API_KEY_ID=your-api-key-id
CDP_API_KEY_SECRET=your-api-key-secret
CDP_WALLET_SECRET=your-wallet-secret
Then instantiate the CDP client in your backend:
import { CdpClient } from "@coinbase/cdp-sdk";
import "dotenv/config";
// Automatically loads CDP_API_KEY_ID, CDP_API_KEY_SECRET, and CDP_WALLET_SECRET
// from environment variables
const cdp = new CdpClient();
Set moduleResolution: "node16" or "nodenext" in your tsconfig.json to avoid compilation errors with the CDP SDK.
Step 1: End user creates a delegation (React)
The authenticated end user grants your app a time-bound delegation. This is the only step that requires the user to be present.
Only one active delegation is allowed per user at a time. If a delegation already exists, revoke it or let it expire before creating a new one.
import { useCreateDelegation, useCurrentUser } from "@coinbase/cdp-hooks";
function DelegateButton() {
const { currentUser } = useCurrentUser();
const { createDelegation } = useCreateDelegation();
const handleDelegate = async () => {
// Delegation expires in 24 hours
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
const result = await createDelegation({ expiresAt });
console.log("Delegation ID:", result.delegationId);
};
return (
<button disabled={!currentUser} onClick={handleDelegate}>
Grant Delegation
</button>
);
}
Step 2: Developer signs on behalf of the end user (Node.js)
Once a delegation is active, your backend can submit transactions for the end user using your CDP API key and Wallet Secret - no user session needed.
import { CdpClient } from "@coinbase/cdp-sdk";
// Initialize the client.
const cdp = new CdpClient();
// Look up the end user and their EVM address
const endUser = await cdp.endUser.getEndUser({ userId: "<USER_UUID>" });
const address = endUser.evmAccountObjects[0].address;
// Send an EVM transaction using the client method (developer calls on behalf of end user)
const result = await cdp.endUser.sendEvmTransaction({
userId: endUser.userId,
address: address,
transaction: "0x02...", // RLP-serialized EIP-1559 transaction
network: "base-sepolia",
});
console.log("Transaction hash:", result.transactionHash);
Step 3: Revoke a delegation
A delegation can be revoked before it expires - either by the end user from the frontend, or by the developer from the backend.
End user revokes (React)
import { useRevokeDelegation } from "@coinbase/cdp-hooks";
function RevokeButton() {
const { revokeDelegation } = useRevokeDelegation();
const handleRevoke = async () => {
await revokeDelegation();
console.log("All delegations revoked.");
};
return <button onClick={handleRevoke}>Revoke All Delegations</button>;
}
Developer revokes (Node.js)
import { CdpClient } from "@coinbase/cdp-sdk";
const cdp = new CdpClient();
// Revoke the active delegation for a specific end user
await cdp.endUser.revokeDelegationForEndUser({
userId: endUser.userId,
});