migration · ERC-20 → LSP·7

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=false selectively
  • Does the transfer need context (memo, intent, source)? → use the data parameter
  • 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

Continue at docs.lukso.tech.