ERC-5023: Shareable NFT
ERC-5023 generalises ERC-721 to multi-holder ownership: a single tokenId can have N holders simultaneously, each with proportional control over transfers / burns / metadata updates. Used for:
- Co-owned art — collectives that jointly hold a high-value NFT.
- Syndicate ownership — investment clubs holding tokenised RWAs.
- Family / household NFTs — game guilds, club memberships.
- Public-good NFTs — DAO treasuries that fractionalise control without selling shares.
Distinct from fractionalisation (which mints fungible shares of an NFT into a separate ERC-20): ERC-5023 keeps the single tokenId but gives multiple addresses joint ownership. Transfers require unanimity (or a configurable threshold).
Required Interface (delta from ERC-721)
interface IERC5023 {
event Share(uint256 indexed tokenId, address indexed sharer, address indexed sharee);
/// Add `sharee` as a co-holder of `tokenId`. Must be called by an existing holder.
function share(uint256 tokenId, address sharee) external;
/// Returns the list of co-holders of `tokenId`.
function holders(uint256 tokenId) external view returns (address[] memory);
}Transfer semantics: by spec, any holder can call transfer (with unanimous-consent variants relegated to extensions). This makes the basic shape closer to "co-keys to the same wallet" than "joint ownership requiring agreement". The Neo port matches this default shape and offers a strict-mode variant.
Neo Equivalent: NEP-11 + Holders Map
The Neo port adds a per-tokenId holders set. OwnerOf returns the primary holder (typically the first sharer); HoldersOf returns the full list:
public static UInt160[] HoldersOf(ByteString tokenId)
{
var ctx = Storage.CurrentContext;
var iter = Storage.Find(ctx, HoldersPrefix(tokenId), FindOptions.KeysOnly | FindOptions.RemovePrefix);
var list = new System.Collections.Generic.List<UInt160>();
while (iter.Next()) list.Add((UInt160)(byte[])iter.Value);
return list.ToArray();
}
public static void Share(ByteString tokenId, UInt160 sharee)
{
var sharer = Runtime.CallingScriptHash;
if (!IsHolder(tokenId, sharer)) throw new Exception("NEP11:NotHolder");
if (!Runtime.CheckWitness(sharer)) throw new Exception("NEP11:NoAuth");
Storage.Put(Storage.CurrentContext, HolderKey(tokenId, sharee), 1);
OnShare(tokenId, sharer, sharee);
}| ERC-5023 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
share(tokenId, sharee) | Share(tokenId, sharee) requires existing-holder witness | Direct port |
holders(tokenId) | HoldersOf(tokenId) enumerates the set | Iterator over storage prefix |
Share event | OnShare notification | |
| Any-holder transfers (default) | Override Transfer to allow any holder's witness | Configurable per collection |
| Unanimous-consent variant | Override Transfer to require all holders' witnesses | Production safety mode |
Composition
- ERC-2981 — NFT royalty. Royalty payments split among all holders proportionally.
- ERC-6147 — NFT guard. Adding a guard to a shared NFT requires unanimous holder consent.
- ERC-6066 — per-NFT signature validation. Returns valid iff the signing party is one of the current holders.
Migration Notes
For Solidity collections using ERC-5023:
- Add the
holdersstorage prefix. - Override
OwnerOfto return the primary holder (or update the primary on each share / unshare). - Choose your transfer authorization model — any-holder (cheaper, less safe) or unanimous (safer, more expensive).
For Neo specifically, use Runtime.CheckWitness for each holder in the unanimous variant — the transaction must be signed by every holder. This is naturally supported by Neo's multi-signer transactions.
