π Migrate from Safe to Universal Profile
Moving custody from a Safe (Gnosis Safe) multisig to a Universal Profile maps the multisig-signer model onto LSP6 Key Manager controllers. Each Safe owner becomes an LSP6 controller address with its own permission bitfield (CALL, SETDATA, TRANSFERVALUE, ADDCONTROLLER, SIGN, and more) β narrower and more expressive than a flat multisig owner list.
Roughly a day for a small Safe. Longer if the Safe custodies many distinct asset types, since each needs its own transfer step.
When to migrateβ
Migrate when per-controller permission scoping in a standard vocabulary β the LSP6 bitfield plus AllowedCalls and AllowedERC725YDataKeys β profile-native metadata storage, or LSP25 relay execution through the Key Manager (no bundler or paymaster contract needed) is what you need. Stay on Safe when an m-of-n multisig threshold is the exact primitive your product encodes β that pattern doesn't map one-to-one onto LSP6 and needs an explicit recovery-contract layer to express instead.
Step 1 β model the Safe as permissionsβ
Safe's "3 of 5" threshold doesn't exist directly in LSP6 β controllers are individual, not aggregated by a vote. Two patterns work:
- Recovery contract β deploy a contract that enforces the threshold itself, and register that contract as a controller holding both
ADDCONTROLLERandEDITPERMISSIONSon the profile. Both are required:ADDCONTROLLERlets it install a brand-new replacement controller (the one that's never held permissions before β the whole point of recovery), whileEDITPERMISSIONSalone only lets it edit or remove a controller that already has some permission entry. Day-to-day controllers handle daily operations; the recovery contract handles ownership-level changes. - Single day-to-day controller + cold multisig β flatten daily operations to one controller, and keep the Safe (or a new threshold contract) as the cold recovery layer behind it.
Step 2 β deploy the Universal Profileβ
Deploy with the standard lsp-factory.js (or equivalent) deployment script. Set LSP3 profile metadata, then add controllers per the design from Step 1.
Step 3 β transfer assetsβ
For each asset class, the Safe is always the one initiating the transfer via Safe.execTransaction:
- Native LYX β
Safe.execTransaction(profileAddress, value, "0x", ...), sending value directly to the profile's address. There's no separate call needed on the profile side to receive a plain LYX transfer. - ERC20 tokens moving to LSP7 β the Safe calls
token.transfer(profile, balance)on the old ERC20 contract (unchanged ERC20 syntax) if you're sweeping the legacy token, or the LSP7 contract's owntransfer(from, to, amount, force, data)if you're moving an already-migrated LSP7 balance β LSP7'stransferis a 5-argument function, not ERC20's 2-argument one. - NFTs β
token.safeTransferFrom(safe, profile, id)for existing ERC721 assets works as-is between EOAs and contracts implementingonERC721Received, but a default Universal Profile does not implementonERC721Receivednatively. Before sending an ERC721 NFT withsafeTransferFrom, register LSP17'sOnERC721ReceivedExtensionon the profile for that selector, or usetransferFrom(the non-safe variant) instead. NFTs already migrated to LSP8 move withtransferBatch(...)or the LSP8transfer(from, to, tokenId, force, data)call, which works against a Universal Profile with no extension needed.
If the Safe holds many distinct assets, write a sweep contract that batches the outbound transfers instead of sending them one by one.
Step 4 β update integrationsβ
Identify every protocol that references the Safe's address directly: vesting contracts, DAO memberships, subscriptions, allowance grants. Addresses don't migrate on their own β every external reference needs to be re-pointed to the new profile address.
Step 5 β sunset the Safeβ
Once nothing material remains in the Safe, sweep out any remaining gas dust and treat it as historical. Leave the contract deployed β destroying multisig contracts is unsupported and risky.
Gotchasβ
- Multisig threshold semantics don't map one-to-one to LSP6 β model it as a recovery-controller contract that enforces the threshold, then register that contract as an LSP6 controller with both
ADDCONTROLLERandEDITPERMISSIONS(installing a never-before-permissioned replacement controller needsADDCONTROLLER;EDITPERMISSIONSalone isn't enough). - Asset transfer is many separate transactions unless the Safe owns assets through a sweep contract that batches outbound moves.
- Anything connected to the Safe's address β vesting schedules, allowance grants, on-chain memberships β needs to be re-pointed to the new profile address; addresses don't migrate, only the references you update do.
- Safe modules have their own permission shape; LSP6 controllers plus LSP17 extensions are the closest LSP-side equivalents. Map each module to its closest LSP primitive deliberately rather than assuming a 1:1 translation.
Verify the migrationβ
- All assets transferred to the Universal Profile.
- Old Safe emptied of material assets β Safe has no built-in pause function, so "sunset" means the Safe holds nothing worth protecting anymore, not that it's disabled on-chain.
- Controllers and permissions configured on the new profile.
- Recovery policy in place, with at least one cold controller holding both
ADDCONTROLLERandEDITPERMISSIONS. - Off-chain integrations updated to the new address.
Related reading: EOA vs Universal Profile Β· Wallet permission scoping