ERC-7535: Native Asset ERC-4626 Vault
ERC-7535 specialises ERC-4626 for vaults that hold the native asset (ETH on Ethereum) rather than an ERC-20. It standardises how deposit() / mint() / withdraw() / redeem() accept and return ETH (via payable and call{value:}) and how the vault reports its underlying asset using the ERC-7528 sentinel (0xEEEE…EEEE). The motivation is simple: thousands of vaults already exist for stETH, USDC, etc., but a uniform pattern for "wrap ETH into a yield-bearing token" was missing. Lido wstETH, Aave aWETH, Yearn yETH all predate this standard with bespoke shapes; ERC-7535 converges them.
Required Interface (delta from ERC-4626)
// Same shape as ERC-4626, but `asset()` returns the native sentinel
// and value-bearing methods are `payable`.
function asset() external view returns (address); // returns 0xEEEE…EEEE
function deposit(uint256 assets, address receiver) external payable returns (uint256 shares);
function mint(uint256 shares, address receiver) external payable returns (uint256 assets);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);The vault validates msg.value == assets (or the equivalent for mint) and uses receiver.call{value: amount}("") to send ETH back on redemption.
Neo Equivalent: NEP-17 Vault Bound to NEO or GAS
Neo's native assets (NEO and GAS) are NEP-17 contracts in their own right — they have well-known contract hashes and behave like any other NEP-17. A "native asset vault" on Neo is just an ERC-4626-style vault parameterised on the NEO or GAS contract hash. There's no special "payable / msg.value" path — the deposit flow is the standard NEP-17 transfer + OnNEP17Payment callback.
| ERC-7535 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
asset() == 0xEEEE…EEEE (sentinel) | Asset() returns NativeContract.GAS.Hash (or NEO.Hash) | Real contract hash, no sentinel needed |
payable deposit(amount, receiver) + msg.value == amount check | Transfer from depositor → vault triggers OnNEP17Payment(from, amount, data) with data encoding the action | Standard NEP-17 callback path |
payable mint(shares, receiver) | Same OnNEP17Payment path with data = encode("mint", shares, receiver) | Single entry-point handles both deposit and mint |
withdraw(assets, receiver, owner) → receiver.call{value: amount}("") | Contract.Call(GAS, "transfer", [vault, receiver, amount, null]) | Standard NEP-17 send |
| Witness model | Runtime.CheckWitness(owner) for the burn-and-pay flow | Native authorization |
Why The ERC Doesn't Need a Separate Standard on Neo
Because NEO and GAS are first-class NEP-17 contracts, the gap that ERC-7535 fills on Ethereum (a uniform spec for "ETH vaults") doesn't exist on Neo — the same vault contract works for NEO, GAS, and any other NEP-17 token with no code paths swapped. The "native asset vault" is just an ERC-4626 vault with asset = NEO.Hash or asset = GAS.Hash.
The mirror page exists to make this concrete for migrating teams: any ERC-7535 vault you're porting becomes a single Solidity-on-Neo or C# vault contract with no native-vs-token branches.
Migration Notes
When porting an ERC-7535 vault to Neo:
- Drop the ERC-7528 sentinel constant — replace with
NativeContract.GAS.HashorNativeContract.NEO.Hash. - Drop
payableandmsg.valuechecks — Neo doesn't have native-asset value forwarding. Deposits arrive via NEP-17transfer→OnNEP17Paymentcallback. - Replace
receiver.call{value:}("")withContract.Call(GAS, "transfer", …). - Replace
WETH9.deposit/withdrawwrap-unwrap if your vault uses WETH — not needed on Neo since GAS is itself a NEP-17.
The Neo port is shorter than the Ethereum source. The asset indirection that ERC-7535 standardises becomes a constructor argument, not a code path.
