The Freeze Button Stops Working: ERC-3643 Compliance Across a VM Boundary
KEY TAKEAWAYS
- The serious failure is not the check that does not run. It is the remedy that stops existing: once the supply is escrowed at the module account, the ERC-20 sees one holder, and freeze and forced transfer have nothing left to reach.
- My own diagram in episode 1 compressed a step. MsgConvertERC20 calls the token transfer function, so the ERC-3643 checks do fire, once, at the door. The issuer opens that door deliberately. The corridor behind it is the problem.
- ERC-7943 (uRWA) went Final on 5 May 2026. Its one genuinely new idea is splitting account eligibility from transfer authorization. It is an interface, not a system.
- No Final ERC covers compliance state crossing a chain or a VM boundary. Not ERC-7943, and not ERC-3643, which I helped write.
- Verified against cosmos/evm at commit 0ae5e223 and against all 611 files in the ERC repository. No incident, no war story. I read the module.
A compliance agent on an ERC-3643 token has three powers that matter on the day something goes wrong. Freeze part of a balance. Freeze an address. Force a transfer out of a wallet.
All three operate on ERC-20 balances.
I spent a week reading the Cosmos EVM x/erc20 module, because episode 1 of this series made a claim I had never traced line by line myself. What I found was worse than the claim, and worse in a direction I did not expect.
When a token is converted into a Cosmos bank denomination, the converted supply is escrowed at one address: the module account. From that moment the ERC-20 contract has exactly one holder.
The agent still has all three powers. They have nothing left to point at. Freeze which balance? Force a transfer from whom? The real holders moved to a ledger the token cannot see.
That is not a check that failed to run. That is a remedy that stopped existing. If you are a regulated issuer, the remedy is the half a regulator asks about, and it is the half that is gone.
Two things before we go further. The first: episode 1 of this series drew this escape path as four steps, and one of those steps was compressed. I fix my own diagram below.
The second: I have no incident to show you. I went looking for one and what comes back is bridge hacks, which are message-verification and key-compromise failures wearing similar clothes. My evidence here is the module source at a pinned commit and the text of the standards. That is weaker than a war story and a great deal stronger than a hunch.
The remedy, not the check
Almost everyone who looks at this problem looks at the check. Did the transfer restriction run, or did it not. That is the interesting question if you are an engineer and the boring one if you are an issuer, whose question is the next one along: a regulator or a court wants an address frozen, and can you still do it.
ERC-3643 was written so the answer is yes. It gives an agent role setAddressFrozen, freezePartialTokens and forcedTransfer, and its specification requirements say in as many words that the token MUST be able to force transfers from an agent wallet. That is not a convenience bolted on the side. It is a stated requirement of the standard, because forced transfer is what regulated issuance actually needs.
Every one of those functions takes an address and locks or moves an ERC-20 balance.
Now escrow the supply at a module account. The balances the agent can reach are one entry. The balances that matter are somewhere else entirely.
What the agent can do on the EVM
What the agent can reach after conversion
I am one of five named authors of ERC-3643 (T-REX), so read this as someone describing the limits of his own work rather than scoring a point off somebody else.
Where my own diagram was wrong
Episode 1 drew the leak as four nodes: a compliant token, registered in x/erc20 as a bank denomination, moved with bank.MsgSend where the EVM checks never run, then bridged out over IBC.
The shape is right. The step count is not.
Between registered and moved there is a conversion, and the conversion is not a bookkeeping entry. MsgConvertERC20 calls the token own transfer function, inside the EVM, with the module account as recipient.
// cosmos/evm, x/erc20/keeper/convert.go @ 0ae5e223
// Escrow tokens on module account
transferData, err := erc20.Pack("transfer", types.ModuleAddress, amount.BigInt())
if err != nil {
return nil, err
}
res, err := k.evmKeeper.CallEVMWithData(
ctx, stateDB, sender, &erc20Contract, transferData,
commit, callFromPrecompile, nil,
)// ERC-3643: the checks that run on that very call
require(
_tokenIdentityRegistry.isVerified(to),
"ERC-3643: Invalid identity"
);
require(
_tokenCompliance.canTransfer(from, to, amount),
"ERC-3643: Compliance failure"
);For an ERC-3643 token that means the pre-transfer checks run. isVerified fires against the module account. canTransfer fires against the offering rules. If the module account is not a verified identity in the token identity registry, the conversion reverts and nothing moves.
So compliance is not bypassed. Compliance gets exactly one shot, at the door, and it works. Which sounds like it kills the argument, and does the opposite.
Whitelisting the module account is the only way to make the token usable with Cosmos-native tooling, and making the token usable with Cosmos-native tooling is the entire reason anyone registers it. Nobody is tricked here. The issuer opens that door on purpose, for a good reason, after a meeting.
What the issuer does not see is that the door opens onto a corridor with no doors in it.
The corrected path, all five steps
Here is the whole path with the missing step restored, and with one more finding at the end that episode 1 never reached.
Step 2 deserves a note of its own. The registration handler comment is blunt: any account can permissionlessly register a native ERC20 contract to map to a Cosmos Coin. When the chain runs with permissionless registration enabled, the issuer is never consulted. Turn it off and registration takes a governance proposal instead, which is slower and public, and still not the issuer decision.
I read every file in x/erc20/keeper looking for the hook that would make step 5 wrong. There is not one. The only two places the module invokes an ERC-20 transfer are the two conversion paths.
That is an argument from absence, so let me bound it honestly. One module, one commit, 0ae5e223, current cosmos/evm. Chains running older Evmos forks may differ, and I have not read those. The safe generalisation is the one episode 1 already made: compliance that lives in a contract is only enforced on the paths that run that contract.
The exit is unguarded. The return trip is not.
There is a detail in the module that reads like a joke once you have been staring at it long enough.
Converting back does re-run compliance. The reverse path calls transfer from the module address to the receiver, so the token checks fire on the way home, exactly as designed.
The token guards the return trip and not the exit. And a token that left over IBC does not take the return trip. It exists as a voucher on a foreign chain, and it never needs to come home.
As for what the bridge carries, the ICS-20 packet payload is five fields, complete:
interface FungibleTokenPacketData {
denom: string
amount: uint256
sender: string
receiver: string
memo: string
}Denomination, amount, sender, receiver, memo. No identity. No claim. No jurisdiction. No restriction flag.
That is not an oversight either. The ICS-20 specification lists permissionless token transfers with no need to whitelist connections, modules or denominations among its desired properties, and says the protocol requires no additional permissioning. It is doing precisely what it says on the label.
The memo field is the only extension point, and it is a free-form string intended for middleware. No standard assigns it compliance meaning.
Everything above assumes there is a Solidity contract somewhere that could hold the compliance logic. For a token that starts life as a native Cosmos coin, there is not. Under Single Token Representation v2, the ERC-20 face of a native coin is a stateful precompile backed directly by x/bank, not a deployed contract. There is no bytecode in which an identity registry could live, and conversion for those pairs is disabled outright.
Denominations arriving over IBC get a token pair created automatically, with no governance step and no issuer involvement. Episode 1 said whatever VM the chain adds next year. This is that sentence with a file name attached.
Where can the check actually run?
Step back from Cosmos for a second, because the specific module is not the point. The point is altitude.
Draw the bands a value transfer can pass through, and mark the one band where an ERC-3643 check exists. Every band above that mark is a path the check does not cover. New paths get added by whoever maintains the chain, on their schedule, not the issuer.
This is the same discipline I apply to agents that move money: put the rule at the resource rather than at the caller, because a caller can be routed around and a resource cannot.
What ERC-7943 actually changed
Episode 1 described ERC-7943 as an active thread on Ethereum Magicians. That was accurate when I wrote it and stopped being accurate on 5 May 2026, when the repository moved it to Final. There was a press cycle later that month, which is why you will see two dates. The one that matters is the repository one.
It is not my standard. Dario Lo Buglio, Tino Martinez Molina and Mihai Colceriu wrote it, and they took it from first draft to Final in under a year.
June 2025: added
uRWA lands in the ERC repository as a draft: one interface for real-world asset compliance across several token types. It moves to Review the following month.
September 2025: aligned with ERC-3643
A commit titled "Align with ERC-3643 naming conventions" lands, followed by a split into separate interfaces. The compatibility is deliberate and dated.
January 2026: Last Call
The final comment window opens.
5 May 2026: Final
Commit 46745bdc flips the frontmatter from Last Call to Final and deletes the last-call deadline with it. That commit is the date to quote, not the press release three weeks later. Worth knowing why: an EIP carries no finalisation field at all, only a created date, so the repository history is the only place the day is recorded.
What it standardises: one shared interface for RWA compliance across ERC-20, ERC-721, ERC-1155 and ERC-6909. Six functions, forcedTransfer, setFrozenTokens, getFrozenTokens, canSend, canReceive and canTransfer, with ERC-165 introspection and fixed interface identifiers.
The genuinely new idea is one line of that list. ERC-7943 separates whether an account may participate at all from whether a particular transfer is allowed. canSend and canReceive evaluate an account independently of any transfer parameters, which lets you express a one-way restriction: an address blocked from receiving but still permitted to send. That situation is ordinary in regulated markets and ERC-3643 has no way to say it.
I would use that split. It is the right decomposition and I wish we had made it in 2021.
One trap, because it will catch somebody. canTransfer exists in both standards and means different things. In ERC-3643 it lives on the ICompliance contract and looks at global offering rules only, in deliberate opposition to isVerified, which checks one investor eligibility. In ERC-7943 it lives on the token and must cover eligibility and frozen balance together. Same name, different contract, different scope.
If you are choosing between compliance models rather than reading about one, the comparison of ERC-3643, ERC-1400 and CMTAT is the piece that lays out the trade, and it places ERC-7943 in the same frame.
What it does not fix, including in my own standard
I grepped the whole ERC-7943 specification, 446 lines, for anything about chains. Cross-chain, cross-VM, multi-chain, bridge, interoperability, IBC, Cosmos, non-EVM, rollup. Three hits.
One is bridge traditional finance and decentralized finance, a metaphor. One is interoperability between RWA token standards. That is the lot. The Security Considerations cover access control, front-running and ordinary contract security, and say nothing about chain boundaries.
That is not a criticism. ERC-7943 is scoped as a single-contract interface and never claims to be more, and taking a swing at its authors for not solving a problem they did not scope would be cheap.
Here is the part that costs me something. I ran the same grep over ERC-3643. All 414 lines. Zero hits. Neither of the two Final RWA standards says one word about what happens when a token crosses a chain or a VM boundary, and I helped write one of them.
So I checked the rest of the corpus. I cloned the ERC repository, 611 files, highest number 8330, and selected every file mentioning both a compliance term and a chain term. Twenty-five matched. I read the plausible ones.
No Final ERC covers compliance state crossing a chain or a VM boundary. The gap episode 1 named is still open, and it has now been open long enough that two separate Final standards have shipped around it.
Is your token exposed to this?
You can work out where you stand in about a minute, and the answer is usually one of four.
None of those four outcomes is fixed by a better contract. Three of them are decided by where the rule lives.
That is the argument episode 1 made and this is the version with the source lines in it. A compliance rule has to sit under every path that can move value, not on one of them. On the chain I am building, that means transfer restrictions understood at the protocol level rather than deployed on top of it, so the bank layer and the bridge inherit the same rule the token does. Testnet is running now. Mainnet is on track for 2026.
And if you want the honest version of my own position: I helped write a standard that solves this beautifully inside one execution environment, and says nothing at all about the second one. That is the gap. I would rather name it than have somebody else find it in production.
The full thesis, including why a purpose-built chain and not an L2, is in the first episode of this series.
FAQ
No, and that detail matters. MsgConvertERC20 calls the token own transfer function with the module account as recipient, so isVerified and canTransfer run and the conversion reverts unless that account is a verified identity. The problem is what happens next: the escrowed supply sits at a single address, bank sends carry no compliance, and the enforcement powers have nothing left to reach.
No. setAddressFrozen, freezePartialTokens and forcedTransfer all operate on ERC-20 balances, and after conversion the ERC-20 has one holder, the x/erc20 module account. The bank-layer holders are invisible to the token contract. That is a lost remedy rather than a missed check, and it is the half a regulator asks about after the fact.
No, and it does not claim to. ERC-7943 reached Final on 5 May 2026 and standardises a compliance interface for a single token contract: canSend, canReceive, canTransfer, forcedTransfer, setFrozenTokens and getFrozenTokens. Its text contains nothing about chains, bridges or virtual machines. Neither does ERC-3643, and I am one of that standard five named authors.
Not a Final one. I checked all 611 ERC files in the repository. ERC-7802 is crosschain and deliberately neutral on access control. ERC-8121 can resolve a credential held on another chain. ERC-8262 runs the other way and binds attestations to one deployment so they cannot travel. All three are Draft. ERC-7518 sounds like the answer and is not: its Interop means same-chain token wrapping, and it is Review, not Final.
Not that I can find, and I looked. The bridge incidents people reach for are message-verification and key-compromise failures, which is a different problem wearing similar clothes. What I have is the module source at a pinned commit and the standards text. My read is that permissioned RWA volume on Cosmos EVM chains is still small enough that nobody has had to learn this the expensive way.
Building the RWA Chain
Episode 2 · 2 published
Enjoyed this post?
Get more like it in your inbox every Tuesday.
