Skip to main content

eth_sendRawTransaction

Submits a pre-signed transaction to the network for broadcast and execution. Returns the transaction hash if successful.

When to Use This Method​

eth_sendRawTransaction is required for:

  • Sending ETH - Transfer value between accounts
  • Token Transfers - Execute ERC20/NFT transfers
  • Contract Interactions - Call state-changing functions
  • Contract Deployment - Deploy new smart contracts

Parameters​

  1. Signed Transaction Data - DATA
    • The signed transaction data in RLP-encoded format
    • Must be prefixed with 0x
{
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": [
"0xf86c0185046110...signed_transaction_data...90a0cf5e"
],
"id": 1
}

Returns​

DATA - 32 Bytes - The transaction hash, or error if transaction invalid.

Implementation Examples​

import { JsonRpcProvider, Wallet, parseEther, parseUnits } from 'ethers';

const provider = new JsonRpcProvider('https://api-polygon-mainnet-full.n.dwellir.com/YOUR_API_KEY');

// Transaction builder and sender
class TransactionManager {
constructor(provider, privateKey) {
this.provider = provider;
this.wallet = new Wallet(privateKey, provider);
}

async sendETH(to, amount) {
try {
// Build transaction
const tx = {
to: to,
value: parseEther(amount),
// Base uses EIP-1559 transactions
type: 2,
chainId: 137, // Base mainnet
};

// Estimate gas and get fee data
const [gasLimit, feeData] = await Promise.all([
this.provider.estimateGas({...tx, from: this.wallet.address}),
this.provider.getFeeData()
]);

tx.gasLimit = gasLimit;
tx.maxFeePerGas = feeData.maxFeePerGas;
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;

// Sign and send
const signedTx = await this.wallet.signTransaction(tx);
const txHash = await this.provider.send('eth_sendRawTransaction', [signedTx]);

console.log('Transaction sent:', txHash);

// Wait for confirmation
const receipt = await this.provider.waitForTransaction(txHash);

return {
hash: txHash,
status: receipt.status === 1 ? 'success' : 'failed',
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString(),
effectiveGasPrice: receipt.effectiveGasPrice.toString()
};
} catch (error) {
console.error('Transaction failed:', error);
throw error;
}
}

async sendToken(tokenAddress, to, amount, decimals = 18) {
const ERC20_ABI = [
"function transfer(address to, uint256 amount) returns (bool)"
];

const token = new Contract(tokenAddress, ERC20_ABI, this.wallet);

// Build transaction
const amountWei = parseUnits(amount, decimals);
const tx = await token.transfer.populateTransaction(to, amountWei);

// Add gas settings
const [gasLimit, feeData] = await Promise.all([
this.provider.estimateGas({...tx, from: this.wallet.address}),
this.provider.getFeeData()
]);

tx.gasLimit = gasLimit * 110n / 100n; // Add 10% buffer
tx.maxFeePerGas = feeData.maxFeePerGas;
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
tx.type = 2;
tx.chainId = 8453;

// Sign and send
const signedTx = await this.wallet.signTransaction(tx);
const txHash = await this.provider.send('eth_sendRawTransaction', [signedTx]);

return txHash;
}

async deployContract(bytecode, abi, constructorArgs = []) {
const factory = new ContractFactory(abi, bytecode, this.wallet);

// Estimate deployment cost
const deployTx = factory.getDeployTransaction(...constructorArgs);
const gasLimit = await this.provider.estimateGas({
...deployTx,
from: this.wallet.address
});

// Deploy with proper gas settings
const contract = await factory.deploy(...constructorArgs, {
gasLimit: gasLimit * 120n / 100n, // 20% buffer for deployment
maxFeePerGas: (await this.provider.getFeeData()).maxFeePerGas,
maxPriorityFeePerGas: (await this.provider.getFeeData()).maxPriorityFeePerGas
});

await contract.waitForDeployment();

return {
address: await contract.getAddress(),
deploymentHash: contract.deploymentTransaction().hash,
gasUsed: (await contract.deploymentTransaction().wait()).gasUsed.toString()
};
}

async batchSend(transactions) {
const results = [];
const nonce = await this.provider.getTransactionCount(this.wallet.address);

for (let i = 0; i < transactions.length; i++) {
const tx = {
...transactions[i],
nonce: nonce + i,
type: 2,
chainId: 137,
gasLimit: await this.provider.estimateGas({
...transactions[i],
from: this.wallet.address
})
};

// Add fee data
const feeData = await this.provider.getFeeData();
tx.maxFeePerGas = feeData.maxFeePerGas;
tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;

// Sign and send
const signedTx = await this.wallet.signTransaction(tx);
const txHash = await this.provider.send('eth_sendRawTransaction', [signedTx]);

results.push({
index: i,
hash: txHash,
nonce: tx.nonce
});
}

return results;
}
}

// Advanced transaction with retry logic
async function sendWithRetry(provider, signedTx, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const txHash = await provider.send('eth_sendRawTransaction', [signedTx]);
return txHash;
} catch (error) {
if (error.message.includes('already known')) {
// Transaction already in mempool
const tx = ethers.Transaction.from(signedTx);
return tx.hash;
}

if (error.message.includes('replacement transaction underpriced')) {
// Need to increase gas price
throw new Error('Gas price too low, please rebuild transaction with higher fees');
}

if (i === maxRetries - 1) throw error;

// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}

// Monitor mempool for transaction
async function monitorTransaction(provider, txHash) {
const startTime = Date.now();
const timeout = 60000; // 1 minute timeout

while (Date.now() - startTime < timeout) {
// Check if transaction is mined
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt) {
return {
status: 'confirmed',
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString()
};
}

// Check if still in mempool
const tx = await provider.getTransaction(txHash);
if (!tx) {
return { status: 'dropped' };
}

await new Promise(resolve => setTimeout(resolve, 2000));
}

return { status: 'timeout' };
}

Common Use Cases​

1. Safe Transaction Broadcasting​

// Broadcast with safety checks
async function safeBroadcast(wallet, transaction) {
// Pre-flight checks
const balance = await wallet.provider.getBalance(wallet.address);
const estimatedCost = transaction.gasLimit * transaction.maxFeePerGas;

if (balance < estimatedCost + transaction.value) {
throw new Error('Insufficient balance for transaction');
}

// Check nonce
const expectedNonce = await wallet.provider.getTransactionCount(wallet.address);
if (transaction.nonce && transaction.nonce !== expectedNonce) {
console.warn(`Nonce mismatch: expected ${expectedNonce}, got ${transaction.nonce}`);
}

// Sign transaction
const signedTx = await wallet.signTransaction(transaction);

// Broadcast with retry
let attempts = 0;
while (attempts < 3) {
try {
const txHash = await wallet.provider.send('eth_sendRawTransaction', [signedTx]);
return txHash;
} catch (error) {
if (error.message.includes('already known')) {
// Transaction already sent
return ethers.Transaction.from(signedTx).hash;
}
attempts++;
if (attempts === 3) throw error;
await new Promise(r => setTimeout(r, 1000 * attempts));
}
}
}

2. Optimized Gas Strategy​

// Dynamic gas price management
async function optimizedSend(wallet, to, value) {
const provider = wallet.provider;

// Get network conditions
const [block, feeData, gasPrice] = await Promise.all([
provider.getBlock('latest'),
provider.getFeeData(),
provider.getGasPrice()
]);

// Calculate optimal fees
const baseFee = block.baseFeePerGas;
const priorityFee = feeData.maxPriorityFeePerGas;

// Adjust based on network congestion
const congestionFactor = Number(baseFee) / Number(parseUnits('10', 'gwei'));
const adjustedPriorityFee = priorityFee * BigInt(Math.ceil(congestionFactor));

const transaction = {
to: to,
value: parseEther(value),
type: 2,
chainId: 137,
maxFeePerGas: baseFee * 2n + adjustedPriorityFee,
maxPriorityFeePerGas: adjustedPriorityFee,
gasLimit: 21000n
};

const signedTx = await wallet.signTransaction(transaction);
return await provider.send('eth_sendRawTransaction', [signedTx]);
}

3. Transaction Queue Management​

// Manage transaction queue
class TransactionQueue {
constructor(wallet) {
this.wallet = wallet;
this.queue = [];
this.processing = false;
}

async add(transaction) {
this.queue.push(transaction);
if (!this.processing) {
this.process();
}
}

async process() {
this.processing = true;

while (this.queue.length > 0) {
const tx = this.queue.shift();

try {
// Add nonce
tx.nonce = await this.wallet.provider.getTransactionCount(this.wallet.address);

// Sign and send
const signedTx = await this.wallet.signTransaction(tx);
const hash = await this.wallet.provider.send('eth_sendRawTransaction', [signedTx]);

console.log(`Transaction sent: ${hash}`);

// Wait for confirmation before next
await this.wallet.provider.waitForTransaction(hash);
} catch (error) {
console.error('Transaction failed:', error);
// Optionally re-queue or handle error
}
}

this.processing = false;
}
}

Error Handling​

Error CodeDescriptionSolution
-32000Invalid transactionCheck parameters and signature
-32001Insufficient fundsEnsure account has enough ETH
-32002Nonce too lowTransaction already processed
-32003Transaction underpricedIncrease gas price
async function handleSendError(error) {
const message = error.message || error.toString();

if (message.includes('insufficient funds')) {
return { error: 'Insufficient balance', recoverable: false };
}

if (message.includes('nonce too low')) {
return { error: 'Transaction already processed', recoverable: false };
}

if (message.includes('replacement transaction underpriced')) {
return { error: 'Gas price too low for replacement', recoverable: true };
}

if (message.includes('transaction already known')) {
return { error: 'Transaction already in mempool', recoverable: false };
}

return { error: message, recoverable: false };
}

Need help? Contact our support team or check the Polygon documentation.