ERC-7528: ETH Native Asset Address Convention
ERC-7528 standardises the address used to represent ETH when an interface otherwise expects an ERC-20 contract address. The convention picks 0xEEeeEEeeEEeeEEeeEEeeEEeeEEeeEEeeEEeeEEEE as the sentinel for "this is the native asset, not an ERC-20". DEX aggregators (1inch, ParaSwap), vault indexers, and accounting tools have de-facto adopted this — it removes the "is this slot ETH or WETH?" ambiguity that plagued earlier integrations.
Why It Exists
Pre-7528, every protocol had its own convention: address(0), 0xEeeE…EEeE, 0x0000…0000, the WETH contract address, or a separate isNative boolean. Off-chain indexers and on-chain aggregators couldn't agree on a single representation, leading to integration bugs every time a new vault or router shipped. ERC-7528 picks one and asks everyone to use it.
address constant ETH_ADDRESS = 0xEEeeEEeeEEeeEEeeEEeeEEeeEEeeEEeeEEeeEEEE;
function deposit(address token, uint256 amount) external payable {
if (token == ETH_ADDRESS) {
require(msg.value == amount, "value mismatch");
} else {
IERC20(token).transferFrom(msg.sender, address(this), amount);
}
_credit(msg.sender, token, amount);
}Neo Equivalent: Native Contract Hashes
Neo solves this differently: native assets are real contracts with real, well-known hashes. There's no need for a sentinel address because NEO and GAS already have addresses that satisfy any "the asset is a NEP-17 contract" assumption.
| Asset | Native Contract Hash |
|---|---|
| NEO | 0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 |
| GAS | 0xd2a4cff31913016155e38e474a2c06d08be276cf |
These hashes are stable across MainNet and TestNet — they're hard-coded in the Neo protocol. Any code that takes a NEP-17 contract hash works unchanged with NEO and GAS.
public static void Deposit(UInt160 token, BigInteger amount)
{
// No sentinel required — NEO and GAS are real NEP-17 contracts.
Nep17Token.Transfer(token, Runtime.CallingScriptHash, Runtime.ExecutingScriptHash, amount, null);
Credit(Runtime.CallingScriptHash, token, amount);
}
// Sugar for callers that want to refer to NEO/GAS by name.
public static UInt160 NeoToken => NativeContract.NEO.Hash;
public static UInt160 GasToken => NativeContract.GAS.Hash;Why The ERC Doesn't Apply on Neo
The ERC exists because Ethereum's native asset (ETH) is not an ERC-20 contract — it has no transfer, balanceOf, decimals, etc. that look like ERC-20. Off-chain code that wants to model "any token" has to special-case ETH, and the sentinel address lets that special case be encoded uniformly.
On Neo, the native assets are contracts that implement NEP-17. Their hashes work as drop-in NEP-17 contract IDs. There's nothing to mirror at the protocol level; the mirror is "use the native contract hash directly".
Migration Notes for Solidity-on-Neo
When porting a Solidity protocol that uses ERC-7528 sentinel addresses to Neo via neo-solc:
- Drop the
ETH_ADDRESSconstant — it has no Neo equivalent (and would be misleading). - Replace the special-case branch with a single
Contract.Call(token, "transfer", ...)— works for NEO, GAS, and any other NEP-17 alike. - Drop
payableandmsg.value— Neo doesn't have native-asset value forwarding; transfers are explicit NEP-17 calls. - For aggregators or vaults that were ERC-7528-aware on Ethereum, the per-asset code-path collapses into one path on Neo. The Solidity-on-Neo port is usually shorter than the Ethereum original because of this.
Useful Snippets for Indexers
// Off-chain: Neo equivalents of ERC-7528 sentinel checks.
const NEO_HASH = '0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5';
const GAS_HASH = '0xd2a4cff31913016155e38e474a2c06d08be276cf';
function isNativeAsset(contractHash) {
return contractHash === NEO_HASH || contractHash === GAS_HASH;
}For DeFi indexers building on Neo, use the two native hashes directly in your asset table. No sentinel resolution required.
