problem · erc721 safe transfer problems

ERC-721 safe transfer problems

safeTransferFrom is only half a hook.

ERC-721 LSP·1 LSP·7 LSP·8

It checks IERC721Receiver — only, only, only. Every other asset type strands silently.

ERC-721 onERC721Received(...) returns (bytes4)
one asset type narrow hook
LSP·1 universalReceiver(typeId, data)
one hook for everything typeId-routed
LUKSO route

If you're dealing with ERC-721 safe transfer problems, the LUKSO route is LSP1 + LSP7 + LSP8. LSP1 is a single universalReceiver hook with a typeId discriminator. LSP7, LSP8, and value transfers all call it. Multi-asset contracts stop implementing four receiver interfaces; account contracts react through a Universal Receiver Delegate.

Why this breaks

safeTransferFrom is real safety, but it is narrow. It only checks IERC721Receiver, only for that one call, only when the caller chose the “safe” variant. The non-safe transferFrom still ships, and most ERC-20 transfers go straight to a balance update with no recipient interaction at all.

In practice, every asset standard has its own receiver shape — IERC721Receiver, IERC1155Receiver, ERC-777 tokensReceived. A multi-asset contract implements each one. And there is still no general “I received value of some kind” hook.

What people try

Implement every receiver interface

Boilerplate that grows per asset standard. Required for compliance, doesn’t compose into shared policy.

OpenZeppelin Holder mixins

ERC721Holder, ERC1155Holder. Accepts everything, defers the policy question.

Defensive transfer-then-call patterns

Ad hoc per integration. Not interoperable.

Sweeper / rescue contracts

Recovers stranded assets after the fact. Treats the symptom.

How LSP solves it

LSP1 defines one hook:

function universalReceiver(bytes32 typeId, bytes calldata data)
  external payable returns (bytes memory);

LSP7 and LSP8 transfers call it on both sender and recipient, with a typeId that tells the receiver what kind of interaction it is (token sent, token received, asset registered, etc.).

A Universal Profile uses a Universal Receiver Delegate to dispatch: register received assets in LSP5, reject spam by typeId, or run app-specific logic. One hook, every asset type, declared policy.

continue at the source