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

delegatedSignTypedData

Function Signature

delegatedSignTypedData(
  client: DelegatedEvmWalletClient,
  params: {
    walletId: string;
    walletApiKey: string;
    keyShare: ServerKeyShare;
    typedData: TypedData;
  }
): Promise<string>

Description

Signs EIP-712 typed data 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. Use this for permit flows, order signing, and other structured data that requires typed-data signing.

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
  • typedData (TypedData) - The EIP-712 typed data to sign (viem’s TypedData type)

Returns

Promise<string> - A hex-encoded ECDSA signature

Example

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

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

const typedData: TypedData = {
  domain: {
    name: 'USD Coin',
    version: '2',
    chainId: 1,
    verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  },
  types: {
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' },
    ],
  },
  primaryType: 'Permit',
  message: {
    owner: '0xOwnerAddress',
    spender: '0xSpenderAddress',
    value: BigInt('1000000'), // 1 USDC (6 decimals)
    nonce: BigInt(0),
    deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
  },
};

const signature = await delegatedSignTypedData(delegatedClient, {
  walletId: 'wallet-id-from-webhook',
  walletApiKey: 'wallet-api-key-from-webhook',
  keyShare: keyShareFromWebhook,
  typedData,
});

console.log('Signature:', signature);

Security Considerations

  • Data Validation: Always validate typed data structure before signing
  • Domain Verification: Verify the domain matches the expected contract
  • Expiry Times: Set appropriate deadlines to prevent replay attacks
  • Nonce Management: Properly track and increment nonces
  • Contract Verification: Ensure verifyingContract addresses are correct
  • Audit Logging: Log all typed data signing operations