Skip to main content

Function Signature

delegatedSignTransaction(
  client: DelegatedEvmWalletClient,
  params: {
    walletId: string;
    walletApiKey: string;
    keyShare: ServerKeyShare;
    transaction: TransactionSerializable;
  }
): Promise<string>

Description

Signs an EVM transaction on behalf of a user who has granted delegation permission. This function requires delegation credentials (wallet ID, wallet API key, and key share) that are provided to your webhook endpoint when a user approves delegation. The function serializes the transaction, signs it using MPC, and returns a fully signed transaction ready for broadcast.

Parameters

Required Parameters

  • client (DelegatedEvmWalletClient) - The delegated client instance created with createDelegatedEvmWalletClient()
  • walletId (string) - The wallet ID from the delegation webhook
  • walletApiKey (string) - The wallet-specific API key from the delegation webhook
  • keyShare (ServerKeyShare) - The server key share from the delegation webhook
  • transaction (TransactionSerializable) - The transaction object to sign (must be a valid Viem transaction)

Returns

Promise<string> - A hex-encoded signed transaction ready for broadcast

Example

Basic Transaction Signing

import { 
  createDelegatedEvmWalletClient,
  delegatedSignTransaction 
} from '@dynamic-labs-wallet/node-evm';
import type { TransactionSerializable } from 'viem';

const delegatedClient = createDelegatedEvmWalletClient({
  environmentId: 'your-environment-id',
  apiKey: 'your-server-api-key',
});

const transaction: TransactionSerializable = {
  to: '0xRecipientAddress',
  value: BigInt('1000000000000000000'), // 1 ETH
  chainId: 1,
  nonce: 0,
  gasLimit: BigInt('21000'),
  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,
});

console.log('Signed transaction:', signedTx);

Complete Flow with Broadcasting

import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(),
});

async function sendDelegatedTransaction(
  credentials: DelegationCredentials,
  to: string,
  value: bigint
) {
  const fromAddress = await getWalletAddress(credentials.walletId);

  const nonce = await publicClient.getTransactionCount({
    address: fromAddress,
  });

  const gasEstimate = await publicClient.estimateGas({
    account: fromAddress,
    to,
    value,
  });

  const feeData = await publicClient.estimateFeesPerGas();

  const transaction: TransactionSerializable = {
    to,
    value,
    chainId: mainnet.id,
    nonce,
    gasLimit: gasEstimate,
    maxFeePerGas: feeData.maxFeePerGas!,
    maxPriorityFeePerGas: feeData.maxPriorityFeePerGas!,
  };

  const signedTx = await delegatedSignTransaction(delegatedClient, {
    walletId: credentials.walletId,
    walletApiKey: credentials.walletApiKey,
    keyShare: credentials.keyShare,
    transaction,
  });

  const txHash = await publicClient.sendRawTransaction({
    serializedTransaction: signedTx as `0x${string}`,
  });

  return txHash;
}

EIP-1559 Transaction

const transaction: TransactionSerializable = {
  to: '0xRecipientAddress',
  value: BigInt('1000000000000000000'),
  chainId: 1,
  nonce: 5,
  gasLimit: BigInt('21000'),
  maxFeePerGas: BigInt('50000000000'), // 50 gwei (important-comment)
  maxPriorityFeePerGas: BigInt('2000000000'), // 2 gwei (important-comment)
  type: 'eip1559',
};

const signedTx = await delegatedSignTransaction(delegatedClient, {
  walletId: credentials.walletId,
  walletApiKey: credentials.walletApiKey,
  keyShare: credentials.keyShare,
  transaction,
});

Contract Interaction

import { encodeFunctionData } from 'viem';

const data = encodeFunctionData({
  abi: contractAbi,
  functionName: 'transfer',
  args: [recipientAddress, amount],
});

const transaction: TransactionSerializable = {
  to: contractAddress,
  data,
  value: BigInt(0),
  chainId: 1,
  nonce: 10,
  gasLimit: BigInt('100000'),
  maxFeePerGas: BigInt('30000000000'),
  maxPriorityFeePerGas: BigInt('1500000000'),
};

const signedTx = await delegatedSignTransaction(delegatedClient, {
  walletId: credentials.walletId,
  walletApiKey: credentials.walletApiKey,
  keyShare: credentials.keyShare,
  transaction,
});

Type Definitions

type ServerKeyShare = {
  pubkey: {
    pubkey: Uint8Array;
  };
  secretShare: string;
};

type TransactionSerializable = {
  to?: `0x${string}`;
  from?: `0x${string}`;
  nonce?: number;
  value?: bigint;
  data?: `0x${string}`;
  gasLimit?: bigint;
  gasPrice?: bigint;
  maxFeePerGas?: bigint;
  maxPriorityFeePerGas?: bigint;
  chainId?: number;
  type?: 'legacy' | 'eip1559' | 'eip2930';
  accessList?: any[];
};

Common Use Cases

Automated Payments

async function processAutomatedPayment(
  userId: string,
  recipient: string,
  amount: bigint
): Promise<string> {
  const credentials = await getDelegationCredentials(userId);
  const walletAddress = await getWalletAddress(credentials.walletId);
  
  const nonce = await publicClient.getTransactionCount({
    address: walletAddress,
  });

  const transaction: TransactionSerializable = {
    to: recipient,
    value: amount,
    chainId: 1,
    nonce,
    gasLimit: BigInt('21000'),
    maxFeePerGas: BigInt('20000000000'),
    maxPriorityFeePerGas: BigInt('1000000000'),
  };

  const signedTx = await delegatedSignTransaction(delegatedClient, {
    walletId: credentials.walletId,
    walletApiKey: credentials.walletApiKey,
    keyShare: credentials.keyShare,
    transaction,
  });

  return await publicClient.sendRawTransaction({
    serializedTransaction: signedTx as `0x${string}`,
  });
}

Batch Transactions

async function processBatchTransactions(
  userId: string,
  transactions: Array<{ to: string; value: bigint }>
): Promise<string[]> {
  const credentials = await getDelegationCredentials(userId);
  const walletAddress = await getWalletAddress(credentials.walletId);
  
  let nonce = await publicClient.getTransactionCount({
    address: walletAddress,
  });

  const txHashes: string[] = [];

  for (const tx of transactions) {
    const transaction: TransactionSerializable = {
      to: tx.to,
      value: tx.value,
      chainId: 1,
      nonce: nonce++,
      gasLimit: BigInt('21000'),
      maxFeePerGas: BigInt('20000000000'),
      maxPriorityFeePerGas: BigInt('1000000000'),
    };

    const signedTx = await delegatedSignTransaction(delegatedClient, {
      walletId: credentials.walletId,
      walletApiKey: credentials.walletApiKey,
      keyShare: credentials.keyShare,
      transaction,
    });

    const txHash = await publicClient.sendRawTransaction({
      serializedTransaction: signedTx as `0x${string}`,
    });

    txHashes.push(txHash);
  }

  return txHashes;
}

Error Handling

The function throws an error if:
  • Any required parameter is missing or invalid
  • The transaction object is malformed
  • The delegation credentials are expired or revoked
  • The MPC signing operation fails
  • Transaction serialization fails
try {
  const signedTx = await delegatedSignTransaction(delegatedClient, params);
  console.log('Transaction signed successfully');
} catch (error) {
  if (error.message.includes('serialized')) {
    console.error('Invalid transaction format');
  } else if (error.message.includes('key share')) {
    console.error('Invalid or expired delegation credentials');
  } else {
    console.error('Signing failed:', error);
  }
  throw error;
}

Security Considerations

  • Transaction Validation: Always validate transaction parameters before signing
  • Gas Limits: Set appropriate gas limits to prevent excessive gas consumption
  • Amount Validation: Validate transaction amounts to prevent unauthorized transfers
  • Recipient Validation: Verify recipient addresses are correct and trusted
  • Nonce Management: Properly manage nonces to prevent transaction conflicts
  • Audit Logging: Log all transaction signing operations
I