Background
To support single-signature bridging between AtomOne and EVM, the Gno chain acts as a protocol relay between ICS20 (spoken by AtomOne) and ZKGM/UCS03 (spoken by Union). The user signs once on either end; everything on Gno is automatic.
The two directions are symmetric in design — both use an adapter realm on Gno that translates one protocol's trigger into the other's outbound call.
Architecture
Forward: AtomOne → EVM
AtomOne ──[ICS20 + memo]──▶ Gno ──[ZKGM OP_TOKEN_ORDER]──▶ Union ──▶ EVM
│
OnRecvPacket
mint token
parse memo → ForwardMetadata
zkgm.Send(OP_TOKEN_ORDER)
Reverse: EVM → AtomOne
EVM ──[ZKGM OP_BATCH]──▶ Union ──▶ Gno ──[ICS20]──▶ AtomOne
│
OP_TOKEN_ORDER → mint to adapter realm
OP_CALL → ZkgmIcs20Adapter.OnZkgm()
parse call data → ICS20ForwardInfo
ics20.Send()
Both directions reduce to the same pattern: an adapter realm on Gno parses a routing payload and fires an outbound packet on the other protocol.
| Direction |
Trigger |
Payload |
| AtomOne → EVM |
ICS20 memo field |
{"forward": {"receiver": "0x...", "port": "transfer", "channel": "channel-1", "timeout": "10m", "retries": 2}} |
| EVM → AtomOne |
ZKGM OP_CALL data |
{"ics20": {"channel_id": X, "receiver": "cosmos1...", "timeout_timestamp": ...}} |
Tasks
gno-realms
gno-ibc — forward: AtomOne → EVM
gno-ibc — reverse: EVM → AtomOne
Out of Scope
- Multi-hop ZKGM forwarding via
OP_FORWARD
- ICS20 v2 (
ics20-v2) multi-token transfer support (AtomOne currently supports v1 only)
Background
To support single-signature bridging between AtomOne and EVM, the Gno chain acts as a protocol relay between ICS20 (spoken by AtomOne) and ZKGM/UCS03 (spoken by Union). The user signs once on either end; everything on Gno is automatic.
The two directions are symmetric in design — both use an adapter realm on Gno that translates one protocol's trigger into the other's outbound call.
Architecture
Forward: AtomOne → EVM
Reverse: EVM → AtomOne
Both directions reduce to the same pattern: an adapter realm on Gno parses a routing payload and fires an outbound packet on the other protocol.
memofield{"forward": {"receiver": "0x...", "port": "transfer", "channel": "channel-1", "timeout": "10m", "retries": 2}}OP_CALLdata{"ics20": {"channel_id": X, "receiver": "cosmos1...", "timeout_timestamp": ...}}Tasks
gno-realmsr/aib/ibc/apps/transfer) withIBCAppinterfaceOnRecvPacket(mint/unescrow)OnAcknowledgementPacketandOnTimeoutPacket(refund)Send()function for ICS20 (required for reverse direction)TransferHookinterface andSetHook()registration; hook is called after minting and its error rolls back the entire recv atomicallyFungibleTokenPacketDatatype withdenom,amount,sender,receiver,memofields and JSON encodinggno-ibc— forward: AtomOne → EVMp/core/ibc/ics20hook/types.gno) following PFM standard{ "forward": { "receiver": "0x...", "port": "transfer", "channel": "channel-1", "timeout": "10m", "retries": 2 } }ZkgmMemoHook(r/core/ibc/v1/apps/ics20hook/hook.gno): parse memo → look up denom registry → buildTokenOrderV2instruction → callzkgm.Send()r/core/ibc/v1/apps/ics20hook/registry.gno): admin-controlledRegisterDenom/UnregisterDenom, maps ICS20 voucher denom to ZKGMbaseTokenics20transfer.SetHook(zkgmMemoHook)gno-ibc— reverse: EVM → AtomOne{"ics20": {"channel_id": X, "receiver": "cosmos1...", "timeout_timestamp": ...}}ZkgmIcs20Adapterrealm implementingOnZkgm(callEnv): parsecallEnv.Operand→ look up denom registry → callics20transfer.Send()OP_BATCH:[OP_TOKEN_ORDER (receiver = adapter realm), OP_CALL (receiver = adapter realm, data = ICS20ForwardInfo)]— both steps execute atomically in a single Gno txOut of Scope
OP_FORWARDics20-v2) multi-token transfer support (AtomOne currently supports v1 only)