Sending & Receiving Ether
If neither exists (or fallback isn't payable and Ether is sent), the call reverts.
18.1 Receiving: receive and fallback
If neither exists (or fallback isn't payable and Ether is sent), the call reverts. fallback can also take bytes calldata input and return bytes memory: fallback(bytes calldata input) external payable returns (bytes memory).
A contract can receive Ether without executing code: as the block fee recipient (coinbase), via selfdestruct from another contract, or by being pre-funded at a precomputed address. Never assume address(this).balance equals what your code tracked.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Receiver {
event Received(address from, uint256 amount);
event FallbackCalled(address from, uint256 amount, bytes data);
// Called on plain Ether transfers (empty calldata)
receive() external payable {
emit Received(msg.sender, msg.value);
}
// Called when no function matches the selector (or calldata non-empty and no receive)
fallback() external payable {
emit FallbackCalled(msg.sender, msg.value, msg.data);
}
}Ether / call sent to contract
│
is msg.data empty?
┌───────┴───────┐
yes no
│ │
receive() exists? function selector matches? ── yes ──► that function
┌────┴────┐ │ no
yes no ▼
│ │ fallback()
receive() fallback()18.2 Sending: transfer, send, call
avoid (deprecated direction; breaks with smart wallets) returns false a.call{value: x}("") all remaining (or specified) returns (false, data)
recommended, with reentrancy protection The 2,300-gas stipend of transfer/send was a reentrancy defense, but gas costs change across forks (EIP-1884, EIP-2929) and smart-contract wallets need more gas, so use call plus Checks-Effects-Interactions and/or a reentrancy guard.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Sender {
error TransferFailed();
function sendViaCall(address payable to) external payable {
(bool success, ) = to.call{value: msg.value}("");
if (!success) revert TransferFailed();
}
}18.3 Pull over push
Instead of sending Ether to many users in a loop (one failure blocks all), record balances and let each user withdraw:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract PullPayments {
mapping(address => uint256) public owed;
function _credit(address user, uint256 amount) internal { owed[user] += amount; }
function withdraw() external {
uint256 amount = owed[msg.sender];
require(amount > 0, "nothing owed");
owed[msg.sender] = 0; // effects before interaction
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "failed");
}
}