Skip to content

ICS20 ↔ ZKGM adapter for single-sign bridging (AtomOne ↔ Gno ↔ Union ↔ EVM) #134

Description

@aronpark1007

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

  • Implement ICS20 transfer realm (r/aib/ibc/apps/transfer) with IBCApp interface
    • OnRecvPacket (mint/unescrow)
    • OnAcknowledgementPacket and OnTimeoutPacket (refund)
    • denom trace path construction
  • Add outbound Send() function for ICS20 (required for reverse direction)
  • Add TransferHook interface and SetHook() registration; hook is called after minting and its error rolls back the entire recv atomically
  • Add FungibleTokenPacketData type with denom, amount, sender, receiver, memo fields and JSON encoding

gno-ibc — forward: AtomOne → EVM

  • Define forward memo JSON schema (p/core/ibc/ics20hook/types.gno) following PFM standard
    {
      "forward": {
        "receiver": "0x...",
        "port": "transfer",
        "channel": "channel-1",
        "timeout": "10m",
        "retries": 2
      }
    }
  • Implement ZkgmMemoHook (r/core/ibc/v1/apps/ics20hook/hook.gno): parse memo → look up denom registry → build TokenOrderV2 instruction → call zkgm.Send()
  • Implement denom mapping registry (r/core/ibc/v1/apps/ics20hook/registry.gno): admin-controlled RegisterDenom / UnregisterDenom, maps ICS20 voucher denom to ZKGM baseToken
  • Wire hook at chain init via ics20transfer.SetHook(zkgmMemoHook)

gno-ibc — reverse: EVM → AtomOne

  • Define reverse call data JSON schema
    {"ics20": {"channel_id": X, "receiver": "cosmos1...", "timeout_timestamp": ...}}
  • Implement ZkgmIcs20Adapter realm implementing OnZkgm(callEnv): parse callEnv.Operand → look up denom registry → call ics20transfer.Send()
  • EVM-side packet must be OP_BATCH: [OP_TOKEN_ORDER (receiver = adapter realm), OP_CALL (receiver = adapter realm, data = ICS20ForwardInfo)] — both steps execute atomically in a single Gno tx

Out of Scope

  • Multi-hop ZKGM forwarding via OP_FORWARD
  • ICS20 v2 (ics20-v2) multi-token transfer support (AtomOne currently supports v1 only)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-encodingWire encoding: abi, rlp, packet/state schemasA-ibcIBC core, light client, packet/channel/connectionA-specProtocol spec & design decisions: schemas, conventions, ICS conformanceA-zkgmZKGM realm, opcodes, forward, integration

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions