transparent proxy
The transparent proxy is one specific answer to a nasty corner of the proxy pattern: what if the implementation happens to have a function whose 4-byte selector matches the proxy's own upgrade function? Without a rule, a user call could be ambiguous. The transparent pattern resolves it by deciding based on who is calling.
The proxy inspects msg.sender on every call. If the caller is the admin (conventionally a separate ProxyAdmin contract), the call is routed to the proxy's own administrative functions such as upgradeTo and changeAdmin, and is never forwarded to the implementation. Every other caller is delegatecalled straight through to the implementation, even when a selector would otherwise collide with an admin function. This cleanly guarantees that ordinary users can never accidentally — or maliciously — trigger an admin function, and the admin can never accidentally execute implementation logic.
The trade-off is gas: every single user call pays an extra cold SLOAD to read the admin slot plus a comparison, on top of the normal delegatecall. Because of the who-is-calling rule, the admin must be a contract (the ProxyAdmin) or a multisig, not an externally owned account that also wants to use the dapp — an admin EOA literally cannot call the implementation's user functions through the proxy. OpenZeppelin's TransparentUpgradeableProxy is the canonical implementation; the leaner UUPS pattern was designed partly to avoid this per-call overhead.
// inside the proxy, conceptually:
if (msg.sender == admin) {
// run proxy admin functions (upgradeTo, changeAdmin)
} else {
// delegatecall to the implementation
}Routing by caller dodges admin/implementation selector clashes.
Keep the proxy admin separate from any account that uses the contract. An admin EOA cannot reach the implementation's functions, so dapp calls from the admin address silently route into proxy admin logic instead.