Skip to main content

Function Signature

delegatedSignTransaction(
  client: DelegatedSvmWalletClient,
  params: {
    walletId: string;
    walletApiKey: string;
    keyShare: ServerKeyShare | ServerKeyShare[];
    transaction: VersionedTransaction | Transaction;
    shareSetId?: string;
    signerAddress?: string;
    derivationPath?: Uint32Array;
  }
): Promise<VersionedTransaction | Transaction>

Description

Signs a Solana transaction on behalf of a user who has granted delegation permission. This function supports both legacy Transaction and VersionedTransaction types from @solana/web3.js. The function returns the signed transaction object with the signature added, ready to be sent to the Solana network. For gasless transactions where a separate fee payer covers gas, set transaction.feePayer to the fee payer’s public key and pass signerAddress to identify the actual signer.

Parameters

Required Parameters

  • client (DelegatedSvmWalletClient) - The delegated client instance created with createDelegatedSvmWalletClient()
  • walletId (string) - The wallet ID from the delegation webhook
  • walletApiKey (string) - The wallet-specific API key from the delegation webhook
  • keyShare (ServerKeyShare | ServerKeyShare[]) - The server key share(s) from the delegation webhook
  • transaction (VersionedTransaction | Transaction) - The Solana transaction to sign

Optional Parameters

  • shareSetId (string) - The shareSetId from the wallet.delegation.created webhook payload. If omitted, the server resolves the correct share set by walletId and share-set type.
  • signerAddress (string) - The address that should sign the transaction. If not provided, defaults to the first signer (VersionedTransaction) or fee payer (Transaction). Use this for gasless transactions where a separate fee payer pays the fees.
  • derivationPath (Uint32Array) - Custom derivation path for the signing key.

Returns

Promise<VersionedTransaction | Transaction> - The signed transaction with the signature added

Example

Basic Transaction Signing

import {
  createDelegatedSvmWalletClient,
  delegatedSignTransaction
} from '@dynamic-labs-wallet/node-svm';
import { Transaction, SystemProgram, PublicKey, Connection } from '@solana/web3.js';

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

const connection = new Connection('https://api.mainnet-beta.solana.com');

const transaction = new Transaction().add(
  SystemProgram.transfer({
    fromPubkey: new PublicKey(senderAddress),
    toPubkey: new PublicKey(recipientAddress),
    lamports: 1000000,
  })
);

transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
transaction.feePayer = new PublicKey(senderAddress);

const signedTx = await delegatedSignTransaction(delegatedClient, {
  walletId: 'wallet-id-from-webhook',
  walletApiKey: 'wallet-api-key-from-webhook',
  keyShare: keyShareFromWebhook,
  transaction,
});

console.log('Transaction signed:', signedTx);

Gasless Transaction with Separate Fee Payer

When a separate wallet pays the gas fees, set feePayer to the sponsor and pass signerAddress so the delegated wallet signs only the instruction:
import {
  createDelegatedSvmWalletClient,
  delegatedSignTransaction,
} from '@dynamic-labs-wallet/node-svm';
import { Transaction, SystemProgram, PublicKey, Connection, Keypair, VersionedTransaction } from '@solana/web3.js';
import bs58 from 'bs58';
import nacl from 'tweetnacl';

const delegatedClient = createDelegatedSvmWalletClient({
  environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  apiKey: process.env.DYNAMIC_API_KEY!,
});

const connection = new Connection(process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com');
const feePayer = Keypair.fromSecretKey(bs58.decode(process.env.FEE_PAYER_PRIVATE_KEY!));

const credentials = await getDelegationCredentials(userId);
const sender = new PublicKey(credentials.publicKey);

const transaction = new Transaction();
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
transaction.recentBlockhash = blockhash;
transaction.lastValidBlockHeight = lastValidBlockHeight;
transaction.feePayer = feePayer.publicKey; // Sponsor pays fees

transaction.add(
  SystemProgram.transfer({
    fromPubkey: sender,
    toPubkey: new PublicKey(recipientAddress),
    lamports: 1000000,
  })
);

// Sign with delegated wallet — signerAddress identifies the sender
let signedTx = await delegatedSignTransaction(delegatedClient, {
  walletId: credentials.walletId,
  walletApiKey: credentials.walletApiKey,
  keyShare: credentials.keyShare,
  transaction,
  signerAddress: sender.toString(),
});

// Add fee payer signature
if (signedTx instanceof VersionedTransaction) {
  signedTx.sign([feePayer]);
} else {
  const message = signedTx.serializeMessage();
  const feePayerSig = nacl.sign.detached(message, feePayer.secretKey);
  signedTx.addSignature(feePayer.publicKey, Buffer.from(feePayerSig));
}

const signature = await connection.sendRawTransaction(signedTx.serialize());
await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight });
console.log('Gasless transaction confirmed:', signature);

Complete Transaction Flow

import {
  createDelegatedSvmWalletClient,
  delegatedSignTransaction
} from '@dynamic-labs-wallet/node-svm';
import {
  Transaction,
  SystemProgram,
  PublicKey,
  Connection,
  sendAndConfirmTransaction
} from '@solana/web3.js';

const delegatedClient = createDelegatedSvmWalletClient({
  environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  apiKey: process.env.DYNAMIC_API_KEY!,
});

const connection = new Connection('https://api.mainnet-beta.solana.com');

async function sendPayment(
  userId: string,
  recipientAddress: string,
  amount: number
) {
  const credentials = await getDelegationCredentials(userId);
  const senderAddress = credentials.publicKey;

  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: new PublicKey(senderAddress),
      toPubkey: new PublicKey(recipientAddress),
      lamports: amount,
    })
  );

  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;
  transaction.feePayer = new PublicKey(senderAddress);

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

  const signature = await connection.sendRawTransaction(
    signedTx.serialize()
  );

  await connection.confirmTransaction(signature);

  console.log('Transaction confirmed:', signature);
  return signature;
}

Versioned Transaction

import { VersionedTransaction, TransactionMessage } from '@solana/web3.js';

const message = new TransactionMessage({
  payerKey: new PublicKey(senderAddress),
  recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
  instructions: [
    SystemProgram.transfer({
      fromPubkey: new PublicKey(senderAddress),
      toPubkey: new PublicKey(recipientAddress),
      lamports: 1000000,
    })
  ],
}).compileToV0Message();

const versionedTx = new VersionedTransaction(message);

const signedTx = await delegatedSignTransaction(delegatedClient, {
  walletId: 'wallet-id-from-webhook',
  walletApiKey: 'wallet-api-key-from-webhook',
  keyShare: keyShareFromWebhook,
  transaction: versionedTx,
});

const signature = await connection.sendTransaction(signedTx);

Type Definitions

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

Error Handling

The function throws an error if:
  • Any required parameter is missing or invalid
  • The transaction is malformed
  • The delegation credentials are expired or revoked
  • The MPC signing operation fails
  • Network communication with Dynamic services fails
try {
  const signedTx = await delegatedSignTransaction(delegatedClient, {
    walletId: credentials.walletId,
    walletApiKey: credentials.walletApiKey,
    keyShare: credentials.keyShare,
    transaction,
  });

  const signature = await connection.sendTransaction(signedTx);
  console.log('Transaction sent:', signature);
} catch (error) {
  if (error.message.includes('key share')) {
    console.error('Invalid or expired delegation credentials');
  } else if (error.message.includes('transaction')) {
    console.error('Invalid transaction format');
  } else {
    console.error('Transaction signing failed:', error);
  }
}

Security Considerations

  • Transaction Validation: Always validate transaction contents before signing
  • Recipient Verification: Verify recipient addresses to prevent sending to wrong addresses
  • Amount Validation: Implement checks for transaction amounts to prevent accidental large transfers
  • Audit Logging: Log all transaction signing operations for security auditing
  • Credential Storage: Never log or expose wallet API keys or key shares
  • Rate Limiting: Implement rate limiting to prevent abuse

Common Use Cases

Automated Payments

async function processAutomatedPayment(
  userId: string,
  recipient: string,
  amount: number
) {
  const credentials = await getDelegationCredentials(userId);

  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: new PublicKey(credentials.publicKey),
      toPubkey: new PublicKey(recipient),
      lamports: amount,
    })
  );

  const { blockhash } = await connection.getLatestBlockhash();
  transaction.recentBlockhash = blockhash;
  transaction.feePayer = new PublicKey(credentials.publicKey);

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

  return await connection.sendTransaction(signedTx);
}

Batch Transactions

async function signMultipleTransactions(
  userId: string,
  transactions: Transaction[]
): Promise<Transaction[]> {
  const credentials = await getDelegationCredentials(userId);

  const signedTransactions = await Promise.all(
    transactions.map(transaction =>
      delegatedSignTransaction(delegatedClient, {
        walletId: credentials.walletId,
        walletApiKey: credentials.walletApiKey,
        keyShare: credentials.keyShare,
        transaction,
      })
    )
  );

  return signedTransactions;
}
Last modified on June 23, 2026