Sending ETH to a specific function within a smart contract is a common requirement when building decentralized applications (dApps) with ethers.js, one of the most popular libraries for interacting with Ethereum. Whether you're funding a payable function, participating in a token sale, or triggering a reward mechanism, understanding how to correctly send Ether during a contract call is essential.
This guide walks you through the complete process of sending ETH to a payable smart contract function using ethers.js, including setup, code implementation, best practices, and real-world considerations.
Setting Up ethers.js
Before interacting with any smart contract, ensure that ethers.js is installed in your project:
npm install ethersOnce installed, import the library into your JavaScript or TypeScript file:
const { ethers } = require("ethers");👉 Learn how to securely manage Ethereum transactions using modern Web3 tools.
Step-by-Step: Sending ETH to a Payable Contract Function
1. Initialize Your Wallet
To sign and send transactions, you need an Ethereum wallet controlled by a private key. In ethers.js, this is done using the Wallet class.
const privateKey = '0xYourPrivateKey'; // Replace with your actual private key
const wallet = new ethers.Wallet(privateKey);🔐 Security Tip: Never hardcode private keys in production code. Use environment variables or secure secret management tools.
2. Connect to an Ethereum Network
Use a provider to connect to the Ethereum blockchain. You can use public providers like Alchemy, Infura, or the default provider from ethers.js.
const provider = ethers.getDefaultProvider('sepolia'); // Or 'mainnet', 'goerli', etc.You can also connect via custom RPC endpoints for better reliability:
const provider = new ethers.JsonRpcProvider('https://ethereum-sepolia-rpc.publicnode.com');Then connect the wallet to the provider:
const signer = wallet.connect(provider);3. Create a Contract Instance
To interact with a smart contract, you’ll need:
- The contract address
- The ABI (Application Binary Interface)
The ABI defines which functions are available and how to call them.
const contractAddress = '0xYourContractAddress';
const contractABI = [
"function payFunction() external payable"
]; // Minimal ABI for payable function
const contract = new ethers.Contract(contractAddress, contractABI, signer);Ensure the ABI includes the function you're calling and marks it as payable if it accepts ETH.
4. Send ETH to the Payable Function
Now, invoke the payable function and include the { value } option to send Ether:
const valueToSend = ethers.parseEther("0.1"); // Sends 0.1 ETH
try {
const tx = await contract.payFunction({ value: valueToSend });
console.log("Transaction sent:", tx.hash);
const receipt = await tx.wait();
console.log("Transaction confirmed in block:", receipt.blockNumber);
} catch (error) {
console.error("Transaction failed:", error.message);
}Here’s what happens:
ethers.parseEther("0.1")converts human-readable ETH (e.g., 0.1) into wei (the smallest unit).{ value: valueToSend }attaches ETH to the transaction.tx.wait()waits for blockchain confirmation.
👉 Discover how to build scalable dApps with reliable Web3 infrastructure.
Understanding Payable Functions in Solidity
For this to work, the target function in your Solidity smart contract must be marked payable. Here's an example:
pragma solidity ^0.8.0;
contract PaymentReceiver {
event Received(address sender, uint amount);
function payFunction() external payable {
emit Received(msg.sender, msg.value);
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}In this contract:
payFunction()accepts ETH because it's markedpayable.- It emits an event upon receiving funds.
getBalance()lets you check the contract's current balance.
Without the payable modifier, sending ETH will result in a transaction failure.
Common Mistakes and Best Practices
❌ Don't Forget the payable Modifier
If the Solidity function isn’t marked payable, sending ETH will revert.
❌ Avoid Hardcoding Sensitive Data
Never expose private keys in source code. Use .env files:
PRIVATE_KEY=0xabc123...And load it safely:
require('dotenv').config();
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);✅ Use Testnets First
Test your logic on Sepolia or Holesky before deploying to mainnet.
✅ Handle Errors Gracefully
Wrap transactions in try-catch blocks to handle reverts, gas issues, or network errors.
Frequently Asked Questions (FAQ)
Q: Can I send ETH to any smart contract function?
A: No — only functions marked as payable in Solidity can receive Ether. Attempting to send ETH to non-payable functions will cause the transaction to fail.
Q: What happens if I send ETH to a non-payable function?
A: The transaction will be reverted by the EVM (Ethereum Virtual Machine), and you’ll lose the gas fees.
Q: How do I check if a function is payable?
A: Review the contract’s ABI — payable functions include "payable": true. Alternatively, inspect the Solidity source code for the payable keyword.
Q: Can I send ETH without calling a function?
A: Yes — you can send ETH directly to a contract’s address if it has a receive() or fallback() function defined. Otherwise, the transfer will fail.
Q: Is there a difference between parseEther and parseUnits?
A: parseEther("1") converts 1 ETH into wei (1e18). parseUnits("1", 6) would convert 1 USDC (6 decimals) into its base unit. Use accordingly based on token decimals.
Q: Do I need gas fees in addition to the sent value?
A: Yes — gas fees are paid separately in ETH and are required to execute any transaction on Ethereum, regardless of whether you're sending additional ETH in value.
Advanced Example: Dynamic Value and Error Handling
Here’s a more robust version of the script:
async function sendEthToContract(amountEth, contractAddr, abi, privateKey, network) {
const provider = ethers.getDefaultProvider(network);
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(contractAddr, abi, wallet);
const value = ethers.parseEther(amountEth.toString());
try {
const tx = await contract.payFunction({ value, gasLimit: 100000 });
console.log(`Transaction hash: ${tx.hash}`);
await tx.wait();
console.log("Transaction confirmed!");
} catch (err) {
console.error("Error sending transaction:", err.message);
}
}This modular approach improves reusability and error transparency.
Final Thoughts
Sending ETH to a specific smart contract function using ethers.js is straightforward once you understand the core components: wallet setup, provider connection, contract instantiation, and proper use of the { value } option.
Key core keywords naturally integrated throughout this article include:
- ethers.js
- send ETH
- smart contract function
- payable function
- Ethereum transactions
- contract interaction
- Web3 development
- Solidity
By following best practices — such as securing private keys, testing on testnets, and validating function payability — you can build reliable and secure dApps that interact seamlessly with Ethereum.
👉 Start building secure and efficient Ethereum applications today.