Sending and receiving Ether

How ETH moves in and out of contracts — and why you should use call() instead of transfer().

Receiving Ether

A contract can receive ETH in three ways: via a payable function, a receive() function, or a fallback() function.

solidity
contract EtherReceiver {

    // Called when ETH is sent with no calldata (plain ETH transfer)
    receive() external payable {
        // msg.value is the amount of ETH received
        emit Received(msg.sender, msg.value);
    }

    // Called when ETH is sent with calldata that doesn't match any function
    fallback() external payable {
        // Handle unexpected calls
    }

    // Regular payable function
    function deposit() external payable {
        require(msg.value > 0, 'Send ETH');
    }

    event Received(address indexed from, uint256 amount);
}

Sending Ether out

There are three methods to send ETH from a contract. Use call() — the other two are outdated.

solidity
// RECOMMENDED: call() — forwards all available gas, returns success flag
(bool success, ) = recipient.call{value: amount}('');
require(success, 'Transfer failed');

// DEPRECATED: transfer() — hard-coded 2300 gas limit, throws on failure
payable(recipient).transfer(amount);

// DEPRECATED: send() — hard-coded 2300 gas limit, returns bool (easy to misuse)
bool sent = payable(recipient).send(amount);

The 2300 gas limit on transfer() and send() was introduced as a reentrancy protection, but Istanbul and later EIPs changed gas costs in ways that can cause legitimate contracts to fail with this limit. Always use call() and implement reentrancy protection explicitly.

A safe withdrawal pattern

The recommended way to handle ETH withdrawals is the "pull over push" pattern: users withdraw their own funds rather than the contract pushing ETH to them.

solidity
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';

contract SafeBank is ReentrancyGuard {
    mapping(address => uint256) public pendingWithdrawals;

    function deposit() external payable {
        pendingWithdrawals[msg.sender] += msg.value;
    }

    function withdraw() external nonReentrant {
        uint256 amount = pendingWithdrawals[msg.sender];
        require(amount > 0, 'Nothing to withdraw');

        // Zero out before sending — critical for reentrancy safety
        pendingWithdrawals[msg.sender] = 0;

        (bool success, ) = msg.sender.call{value: amount}('');
        require(success, 'Transfer failed');
    }
}

Checking contract balance

solidity
// ETH balance of this contract
uint256 contractBalance = address(this).balance;

// ETH balance of any address
uint256 someBalance = address(someAddress).balance;
←   Interfacesmsg.sender and msg.value   →