Follow-up on ERC-1643 — optional multi-token document management extension (fully backward compatible)
Following up on ERC-1643, I’d like to float a small, optional addition and get feedback before it lands: a companion interface, IERC1643MultiDocument, for the case where document management is delegated to a separate contract that serves more than one token contract.
this change is backward compatible. Nothing in the base ERC-1643 interface changes, the base ERC-165 interface id stays the same, and any existing implementation remains compliant with no edits. This is purely an opt-in extension on top of what the standard already defines — not a re-spec.
Why bother
ERC-1643 is a per-contract interface. setDocument MUST emit DocumentUpdated, removeDocument MUST emit DocumentRemoved, and those events carry only (name, uri, documentHash) — no contract address. Consumers subscribe to the address of the contract that exposes the interface and attribute every event to that contract.
That is unambiguous when a single contract both exposes ERC-1643 and stores its own documents. But a potential gas/operational optimization is to delegate document management to another smart contract: the token contract exposes ERC-1643 to the world, while a separate document-management contract actually stores and mutates the documents. Now two contracts are involved, and the question becomes: which one emits the events, and is the result still compliant?
For the shared* manager:
- If the management contract is dedicated to a single token contract, there’s no ambiguity: it effectively is that token’s ERC-1643 implementation, emits the standard events, and is fully compliant on its own. Nothing new is needed.
- If the management contract is shared by many token contracts, it cannot be a per-contract ERC-1643 emitter, because the standard event has no address to distinguish which token a change belongs to. A consumer watching the manager’s address can’t tell whose document just changed.
So the shared case needs an address somewhere in the event. That’s the gap this extension closes.
What I’m proposing to add
An optional interface for shared managers. subject is the address of the contract the documents belong to (typically a token contract, but the same reasoning applies to any ERC-721/ERC-1155 token, vault, or other on-chain product):
/// @title IERC1643MultiDocument Multi-Token Document Management (optional extension)
interface IERC1643MultiDocument is IERC1643 {
function getDocument(address subject, bytes32 name) external view returns (string memory uri, bytes32 documentHash, uint256 lastModified);
function setDocument(address subject, bytes32 name, string calldata uri, bytes32 documentHash) external;
function removeDocument(address subject, bytes32 name) external;
function getAllDocuments(address subject) external view returns (bytes32[] memory documentNames);
event DocumentUpdatedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash);
event DocumentRemovedForSubject(address indexed subject, bytes32 indexed name, string uri, bytes32 documentHash);
}
The address-scoped functions and address-carrying events are what make a shared manager observable and operable per token contract.
Emission responsibility (the rule that keeps it compliant)
The base events must be emitted by the contract that exposes ERC-1643 to consumers — the address consumers are told to subscribe to. Under delegation:
- Dedicated manager → it emits the base
DocumentUpdated / DocumentRemoved events (unambiguous; only one subject).
- Shared manager → each token contract emits the base events for its own documents, and the shared manager emits only the extension events (
Document…ForSubject), which carry the subject.
- Don’t emit the base events from both the token contract and the manager — it’s redundant and wastes gas.
In the shared case, “the token contract emits the base events” only holds if writes are routed through (or reflected back to) the token contract. If callers hit the shared manager directly, the token contract has no execution point to emit from. I think the cleanest framing is that a shared manager exposes the address-scoped writes to trusted token contracts, which emit the base event and let the manager emit the extension event.
ERC-165
Events aren’t part of an ERC-165 interface id, so there’s no on-chain way to discover whether a contract emits the extension events. But the extension’s functions do yield an id: a consumer can detect a multi-token manager via supportsInterface(type(IERC1643MultiDocument).interfaceId) and infer the address-carrying events. Whether a token contract emits the base events stays non-discoverable on-chain — as it already is for base ERC-1643, since events never are.
Why it’s backward compatible
- Distinct selectors. The address-scoped
getDocument/setDocument/removeDocument/getAllDocuments have different signatures than their base counterparts, so they’re overloads that sit alongside the base functions — the base functions keep their exact signatures and behaviour.
- New event topics.
DocumentUpdatedForSubject / DocumentRemovedForSubject are new topics; the base DocumentUpdated / DocumentRemoved are untouched.
- Monotonic ERC-165. Advertising the extension id doesn’t disturb
supportsInterface(type(IERC1643).interfaceId).
- A consumer that only knows base ERC-1643 is unaffected: it keeps calling the base functions and watching the base events on the address it was directed to.
Let me know if you have any feedback on it