Migrate ERC-20 to LSP·7 on Ethereum/EVM
Step 1 — map the ERC-20 surface
List every external function your contract exposes: transfer, transferFrom, approve,
allowance, mint, burn, admin roles. Separate protocol compatibility requirements
(must work with existing DeFi pools, bridges) from product UX requirements (rich
metadata, receiver hooks, scoped delegation). If protocol compatibility wins, stop here —
LSP7 is not the right move.
Step 2 — choose which LSP7 features you actually need
Don’t migrate features you won’t use. Real questions:
- Does the recipient need to react onchain? → use LSP1 +
force=falseselectively - Does the transfer need context (memo, intent, source)? → use the
dataparameter - Should the asset carry richer metadata? → store under LSP4 keys via ERC-725Y
- Should the account gate operator authority? → pair with LSP6 on the controller side
Step 3 — decide the migration path
Three real options:
- Fresh deployment — new LSP7 contract, distribute via snapshot mint. Cleanest. Breaks every existing integration.
- Wrap — keep the ERC-20, deploy a wrapper LSP7 backed 1:1. Reversible. Adds a deposit step.
- Side-by-side — both contracts live, choose at integration time. Confusing for users; rarely the right answer.
Step 4 — port the contract
Replace transfer(to, amount) with transfer(from, to, amount, force, data). Replace
approve / transferFrom with authorizeOperator / transfer. Move name/symbol metadata to
ERC-725Y under LSP4 keys. Add LSP4Metadata for the rich JSON.
Step 5 — handle the holders
Snapshot the ERC-20 balances at a known block. Deploy LSP7. Mint to the snapshot list (or publish a merkle claim if the holder set is large). Pause the ERC-20 once the migration window closes.
Step 6 — wire the receivers
Identify which downstream contracts (vaults, gauges, staking) need to react to incoming
LSP7. Add LSP1 handlers there. Use force=false once those handlers ship; use force=true
for any contract that legitimately just holds.
Gotchas.
- LSP7 publishes different function selectors than ERC-20. Contracts that ABI-check for ERC-20 won't dispatch to LSP7 — by design — so anything that consumes your token has to call LSP7 functions explicitly.
- LSP7 operators are amount-scoped — they don't magically solve approval risk. The shift comes from pairing with LSP6 on the controller side.
- force=false transfers revert if the recipient doesn't implement LSP1. Decide deliberately per use case.
- LSP1 fires on every transfer — recipient code can execute. Apply standard reentrancy guards; ERC-20's no-recipient-call model gave you reentrancy safety for free.
- ERC-725Y metadata writes cost storage gas. Don't dump your whole JSON on-chain; use VerifiableURI for off-chain blobs.
Verify before ship.
- Holders mapping migrated 1:1 (or new contract has accurate snapshot mint)
- All historical transfer events replayable from a known block
- LSP4 metadata reads return expected name/symbol/JSON
- Universal Profile recipients receive LSP1 hooks on transfer
- Operator authorize/revoke fires the expected events
- force=false revert behavior covered in tests