Understanding how to send Ether (ETH) in Solidity is essential for any Ethereum smart contract developer. Whether you're building decentralized applications (dApps), payment systems, or token contracts, knowing the correct and secure way to transfer ETH between addresses is crucial. In this guide, we’ll explore the three primary methods for sending ETH in Solidity: transfer(), send(), and call(). We'll compare their behaviors, gas limitations, error handling mechanisms, and best practices—helping you write safer and more efficient code.
Core Keywords
- Solidity send ETH
- transfer vs send vs call
- Ethereum smart contract payments
- payable functions in Solidity
- call method in Solidity
- secure ETH transfer
- receive function in Solidity
- fallback function
These keywords naturally align with common search queries from developers learning Solidity or debugging transaction issues. We'll integrate them throughout the article to enhance SEO without compromising readability.
Receiving Ether: The ReceiveETH Contract
Before we can send ETH, we need a contract that can receive it. Let’s start by creating a simple ReceiveETH contract that logs incoming transactions and allows us to check its balance.
contract ReceiveETH {
// Event to log amount received and remaining gas
event Log(uint amount, uint gas);
// Triggered when ETH is sent to the contract
receive() external payable {
emit Log(msg.value, gasleft());
}
// Returns the current ETH balance of the contract
function getBalance() public view returns (uint) {
return address(this).balance;
}
}This contract includes:
- A
receive()function markedpayable, which executes when someone sends ETH directly to the contract. - An event
Logthat records the amount of ETH received and the remaining gas—useful for debugging. - A
getBalance()function to query the contract’s current ETH balance.
After deploying this contract, you’ll notice its initial balance is 0 ETH. Once we send funds using one of the sending methods below, this value will update accordingly.
👉 Learn how to securely test ETH transfers on a real blockchain environment.
Sending Ether: The SendETH Contract
To demonstrate sending ETH, we’ll create a SendETH contract with a payable constructor and receive function so it can hold and forward ETH.
contract SendETH {
constructor() payable {}
receive() external payable {}
}With this foundation, we can now implement the three methods of sending ETH: transfer(), send(), and call().
Method 1: Using transfer()
The transfer() function sends a specified amount of ETH to another address with a built-in gas limit of 2300 gas—enough to cover a basic transfer but not enough for complex logic in the recipient’s fallback or receive functions.
Key Features:
- Gas fixed at 2300.
- Automatically reverts on failure (safe default behavior).
- Simpler error handling due to automatic rollback.
Example Code:
function transferETH(address payable _to, uint256 amount) external payable {
_to.transfer(amount);
}⚠️ Note: This function will revert if _to is not a valid payable address or if the contract doesn't have sufficient balance.When testing:
- If
amount = 10and the transaction value is less than 10 ETH (value < amount), the transaction fails and automatically reverts. - If
value >= amount, the transfer succeeds.
After success, calling getBalance() on ReceiveETH confirms the received amount.
While convenient, transfer() is considered deprecated in modern Solidity due to its rigid gas model and potential for unexpected failures with complex contracts.
Method 2: Using send()
The send() method works similarly to transfer() but returns a boolean instead of reverting automatically on failure.
Key Features:
- Also limited to 2300 gas.
- Does not revert on failure—returns
false. - Requires manual error checking.
Example Code:
function sendETH(address payable _to, uint256 amount) external payable {
bool success = _to.send(amount);
if (!success) {
revert SendFailed();
}
}Here, we must explicitly check the return value. If send() fails (e.g., due to out-of-gas or invalid recipient), we manually revert using a custom error.
Despite offering more control, send() shares the same limitations as transfer() and is rarely used in new projects because of its inflexibility and safety risks if error handling is missing.
Method 3: Using call() (Recommended)
The most flexible and currently recommended method for sending ETH is call().
Key Features:
- No hardcoded gas limit—you can forward all available gas or specify an amount.
- Supports complex logic in recipient contracts.
- Returns
(bool success, bytes memory data)—requires explicit success check. - Must handle errors manually.
Example Code:
function callETH(address payable _to, uint256 amount) external payable {
(bool success,) = _to.call{value: amount}("");
if (!success) {
revert CallFailed();
}
}Using .call{value: amount}("") allows full control over gas and ensures compatibility with modern contracts that may perform non-trivial operations upon receiving funds.
✅ Best Practice: Always verify the boolean result and revert if needed. This pattern gives both flexibility and safety.
👉 Discover how advanced developers use call() for cross-contract interactions.
Frequently Asked Questions (FAQ)
Q: Why is call() preferred over transfer() and send()?
A: Because call() removes the rigid 2300 gas stipend, allowing recipients to execute more complex logic. It's future-proof and aligns with current Solidity security recommendations.
Q: Is transfer() still safe to use?
A: While transfer() automatically reverts on failure (a safety feature), it’s discouraged since Solidity 0.8.x due to potential issues with contracts relying on higher gas. Use only if you fully understand the constraints.
Q: What happens if I forget to check the return value of send() or call()?
A: The transaction may proceed even if the ETH transfer failed, leading to silent failures. Always validate the result and revert appropriately.
Q: Can a contract reject incoming ETH?
A: Yes. If a contract lacks a receive() or payable fallback() function, sending ETH will fail. Additionally, these functions can include conditions to reject payments.
Q: How do I test ETH transfers locally before going live?
A: Use development tools like Hardhat or Foundry with local networks or testnets (e.g., Sepolia). Fund your contracts via faucets and simulate various scenarios including failures.
Q: What is the difference between receive() and fallback() functions?
A: The receive() function handles plain ETH transfers with no data. The fallback() handles calls with data or ETH when no other function matches. Only one of each can exist, and both must be external and payable to accept value.
Summary: Choosing the Right Method
| Method | Gas Limit | Reverts on Failure? | Recommended? | Use Case |
|---|---|---|---|---|
transfer() | 2300 | Yes | ❌ | Legacy code only |
send() | 2300 | No (returns bool) | ❌ | Rare edge cases |
call() | Unlimited | No (manual check) | ✅ | All modern applications |
In modern Solidity development:
- Use
call()for sending ETH—it's flexible, safe when properly handled, and future-compatible. - Avoid
transfer()andsend()unless maintaining legacy systems. - Always include proper error handling when using low-level calls.
By understanding these differences, you can build more robust and secure smart contracts that interact safely with Ether transfers.
👉 Start building secure Solidity contracts with real-time blockchain tools.