Add ERC: Sovereign Multi-Issuer Cross-Chain Transfer

Hi everyone — we’d like to open a discussion on a draft ERC: Sovereign Multi-Issuer Cross-Chain Transfer, a burn-and-mint standard for moving ERC-20 tokens between EVM chains.

Pull Request:

The core idea is simple: tokens are destroyed on the source chain and minted on the destination chain, so nothing is ever held in custody and aggregate supply is preserved across zones. The protocol is split into three layers — a transport contract that carries fixed-size messages and verifies threshold EIP-712 signatures scoped to the deployment domain, both chains, both relays, and the verifier set’s version and config; a transfer contract that owns the transfer lifecycle and per-token mappings; and an execution contract that is the only address allowed to mint or burn.

The heart of the proposal is the multi-issuer model. One deployment can host several issuers, but each issuer binds its tokens to its own verifier set — its own keys, its own threshold (say, 7-of-10 for one issuer, 3-of-5 for another), its own rotation policy — and the destination side enforces that binding at delivery. The result: a compromise of one issuer’s signing keys cannot mint another issuer’s tokens. We also state the boundary plainly — this isolates verifier-key risk only. The relay, router, executor, and their governance/upgrade administration remain shared trust roots, and the draft says so rather than pretending otherwise.

Two bridge failure modes drove the design: lock-and-mint bridges that concentrate everything in a single vault, and shared bridges where one validator set covers every asset. Neighboring work exists — ERC-7802 leaves callers and verification unspecified, ERC-7281 handles per-bridge mint limits, ERC-7786 and ERC-5164 are generic messaging — but none of it combines multi-issuer sovereignty with shared infrastructure.

This is an early-stage proposal and we’d welcome any comments or feedback. Thanks for reading, and looking forward to the discussion.

Acknowledgments

We thank the SNZ and BlockUnion teams for their technical review and valuable feedback during the preparation of this proposal. Their comments on the message wire format, epoch and verifier-set transitions, and replay-protection edge cases at the deduplication boundary pushed us to sharpen assumptions we had initially taken for granted.

Nice to see the shared trust roots written down instead of left implicit.

One gap in the replay key, in the direction the re-verification section doesn’t cover. The relay consumes (verifierSetName, globalMessageId), and verifierSetName in the delivered header is whatever set the destination token is bound to when the verifiers sign. If an issuer rebinds a token from set A to set B, every message already delivered under A has an unused slot under B. The refresh endpoint only checks consumption under the descriptor it resolves, which is now B, it is open to anyone holding the Dispatched log, and receiving check 6 passes because B is the current binding. So as written, a transfer that went out with authorizedRelayer set to zero, or to the sender’s own address, can be signed again and minted a second time. The section is right that the stale A binding gets rejected; the case that gets through is delivered under A, then signed under B.

Your reason for keying by set at the relay still holds (another issuer’s threshold shouldn’t be able to burn your slot through a dummy receiver), so one fix is to keep that and add a record in the router keyed by (destinationToken, globalMessageId). A rebinding doesn’t change the token, and an issuer can only get signatures accepted for its own tokens, so that key doesn’t bring the griefing case back.

Smaller one: mapToken overwrites, and receiving check 7 compares against the current mapping. Remapping a destination token to a new source token strands burns of the old one that are still in flight, and the refresh path can’t help because the signatures aren’t what fails. The in flight section lists threshold, membership and rebinding changes; mapping changes probably belong there too, with the same Dispatched vs Delivered diff before a remap.