Introduction
In this guide, we'll explore the three primary methods for sending ETH between contracts in Solidity: transfer(), send(), and call(). Each method has distinct characteristics, making them suitable for different scenarios. We'll focus on best practices and security considerations while ensuring you understand their practical applications.
ReceiveETH Contract
First, let's deploy a ReceiveETH contract to test our ETH transfers. This contract logs incoming ETH transactions and allows us to check its balance.
contract ReceiveETH {
event Log(uint amount, uint gas); // Logs received ETH and remaining gas
receive() external payable {
emit Log(msg.value, gasleft()); // Triggered when ETH is received
}
function getBalance() public view returns (uint) {
return address(this).balance; // Returns the contract's ETH balance
}
}After deployment, getBalance() will return 0 since no ETH has been sent yet.
Sending ETH Using Transfer, Send, and Call
1. transfer()
- Usage:
transfer(amount) - Gas Limit: 2300 (sufficient for basic transfers)
- Behavior: Automatically reverts on failure.
- Best For: Simple transfers where gas efficiency and automatic rollback are priorities.
function transferETH(address payable _to, uint256 amount) external payable {
_to.transfer(amount);
}2. send()
- Usage:
send(amount) - Gas Limit: 2300
- Behavior: Returns
boolindicating success/failure; does not revert on failure. - Best For: Rarely recommended due to manual error handling.
function sendETH(address payable _to, uint256 amount) external payable {
bool success = _to.send(amount);
if (!success) {
revert SendFailed(); // Custom revert on failure
}
}3. call()
- Usage:
call{value: amount}("") - Gas Limit: None (flexible for complex logic)
- Behavior: Returns
(bool, bytes); does not revert on failure. - Best For: Most flexible and recommended method.
function callETH(address payable _to, uint256 amount) external payable {
(bool success, ) = _to.call{value: amount}("");
if (!success) {
revert CallFailed(); // Custom revert on failure
}
}👉 Learn more about ETH transfers in Solidity
Key Differences
| Method | Gas Limit | Reverts on Failure | Recommended Use Case |
|---|---|---|---|
transfer | 2300 | Yes | Simple transfers |
send | 2300 | No | Legacy code |
call | None | No | Complex interactions |
Best Practices
- Prefer
call(): No gas restrictions and maximum flexibility. - Use
transfer()for Simplicity: When you need automatic reverts. - Avoid
send(): Manual error handling makes it prone to mistakes.
👉 Explore advanced Solidity techniques
FAQ
Q: Why is call() preferred over transfer()?
A: call() offers no gas restrictions, allowing recipient contracts to execute complex logic. However, always handle its return value to manage failures.
Q: Does transfer() guarantee transaction reverts on failure?
A: Yes, unlike send and call, transfer() automatically reverts, simplifying error handling.
Q: When should I use send()?
A: Almost never—its lack of auto-revert and gas restrictions make it inferior to call() and transfer().
Conclusion
call(): Most flexible; no gas limits.transfer(): Simple and secure; auto-reverts.send(): Legacy method; avoid when possible.
Always test ETH transfer methods in a controlled environment before deploying to mainnet. For further reading, check out our detailed Solidity guides!