BlockchainSolidity9 min readUpdated

The address Type in Solidity: Wallet and Contract Identity

By Mudassir Khan — Agentic AI Consultant & AI Systems Architect, Islamabad, Pakistan

Cover illustration for: The address Type in Solidity: Wallet and Contract Identity

Section 01 · Definition

What is the address type in Solidity?

An address is a 20 byte identity on Ethereum. It is how the chain refers to a wallet or a contract — and Solidity gives you a first-class type for it.

Quick answer

What is address? address is a Solidity value type that holds a 160-bit Ethereum identifier. It is most often the address of a user's wallet (an externally owned account, or EOA) or the address of another smart contract. Solidity treats it as its own type so the compiler can enforce the difference between an address that is allowed to receive ether (address payable) and one that is not.

Every transaction on Ethereum has a sender address and (for non-deployment transactions) a recipient address. Inside Solidity those values appear as msg.sender and address(this). You will store addresses inside mappings to track who owns what, inside arrays to keep lists of participants, and inside structs alongside other identity fields.

Section 02 · Where it comes from

How an address is derived

An Ethereum address is not random. It is the last 20 bytes of the keccak256 hash of a public key. That fact has practical consequences for security and verification.

Pipeline showing public key hashed through keccak256 with the last 20 bytes forming the Ethereum address.
Public key → keccak256 → take last 20 bytes → that is the address.

Because addresses are derived from public keys (for EOAs) or from a known formula involving the deployer's address and nonce (for contracts), they are not guessable in any practical sense. Two different keys produce two different addresses. Two different deployers, or the same deployer at two different nonces, produce two different contract addresses. Before pasting any address into a contract, run it through the Ethereum Address Validator to confirm shape and EIP-55 checksum.

Section 03 · address payable

The payable modifier on the type itself

address payable is a stricter sub-type. The compiler will not let you call .transfer or .send on a plain address — you have to acknowledge the cast.

solidity
address          public router;     // can read state, can call functions
address payable  public treasury;   // can also receive ether

function pay(uint256 amount) external {
    treasury.transfer(amount);          // ok
    // router.transfer(amount);         // compile error
    payable(router).transfer(amount);   // explicit cast — only do this if you know router is safe
}

The intent behind the split is safety. Sending ether to an address that does not have a payable fallback or receive function will revert, locking funds inside your contract until you fix the call. Forcing the developer to write payable(...) makes the assumption explicit and easier to audit.

Section 04 · Built-in members

What you can do with an address value

The address type comes with a small set of built-in members that you will use constantly. Memorise these — every contract reads or sets at least one of them.

solidity
address user = msg.sender;

uint256 wei_       = user.balance;       // ether (in wei) held by the address
bytes   memory bc  = user.code;          // bytecode at the address (empty for EOAs)
bytes32 hash_      = user.codehash;      // keccak256 of code

// Low level call — the modern way to send ether or call any function
(bool ok, bytes memory data) = payable(user).call{value: 1 ether}("");
require(ok, "transfer failed");

The legacy .transfer(amount) and .send(amount) forward only 2 300 gas, which can fail unexpectedly with contracts that have non-trivial fallback logic. The recommended pattern in modern Solidity is the low-level call shown above, combined with a reentrancy guard modifier when the call could give control back to the caller.

EOA vs contract address

There is no compile-time way to tell whether an address holds a wallet or a contract. The runtime check is `addr.code.length > 0` — non-zero means a contract is deployed there. Be careful: during a contract's own constructor, the address has no code yet, so the check returns false and gives a misleading answer.

Section 05 · Real-world uses

Where address shows up in production code

A short tour of the patterns you will see in the first contracts you read.

solidity
// 1. Owner / admin
address public owner;

// 2. ERC20 balances and allowances
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

// 3. NFT ownership
mapping(uint256 => address) public ownerOf;

// 4. Whitelist
mapping(address => bool) public isWhitelisted;

// 5. Multisig signers
address[] public signers;

// 6. Hardcoded protocol contracts
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

Section 07 · FAQ

Frequently asked questions

What is the difference between address and address payable?

address payable is a sub-type of address. It can receive ether through .transfer, .send, or low-level .call{value:...}. Plain address cannot. The split is enforced by the compiler so accidental ether sends to a non-payable contract are caught at build time.

How do I check whether an address is a contract?

Use addr.code.length > 0. A non-zero result means there is bytecode at that address — i.e. a contract has been deployed. The check returns false for an externally owned account and false for a contract that is still inside its own constructor.

Why should I avoid tx.origin?

tx.origin is always the EOA that started the transaction, even if the call passed through several intermediate contracts. Using it for access control opens phishing-style attacks where a user is tricked into calling a malicious contract that then calls yours, and tx.origin is the user. Use msg.sender instead.

Is address(0) special?

Yes. The zero address (0x0000...0000) is the default value of any uninitialised address variable, and is conventionally used to mean 'no one' or 'burn'. ERC20 transfers to address(0) are how tokens are burnt. Always require recipients are not address(0) on user-facing functions.

Can I send ether without using address payable?

Not directly. You either store the recipient as address payable from the start, or cast at the call site with payable(recipient).transfer(...) or payable(recipient).call{value:...}(""). Modern Solidity prefers the low-level .call form for forwarding all available gas safely.

Written by Mudassir Khan

Agentic AI consultant and AI systems architect based in Islamabad, Pakistan. CEO of Cube A Cloud. 38+ agentic AI launches delivered for global founders and CTOs.

View agentic AI consulting serviceSee ChainTrust case study

Related service

Agentic AI Consulting

See scope & pricing →

Related case study

ChainTrust Compliance Engine

Read case study →

More on this topic

Need an AI systems architect?

Book a 30-minute architecture call. I will sketch the high-level design for your use case and give you an honest view of the trade-offs.

Book a strategy call →