Skip to content

UCS03 - ZKGM

ucs03-zkgm-0 is the most advanced and recommended protocol to use for

  • message passing
  • transfers (assets and NFTs)
  • intents
  • storage proofs
  • staking and governance

It’s the most gas-efficient version and suitable for almost all use cases.

Protocol

Open Filling

A groundbreaking protocol improvement on IBC and trust-minimized bridging in general, is that ucs03-zkgm-0 allows arbitrary filling of orders by any party.

For packet submissions and transfers, the protocol allows a different filler from the Union IBC contracts. In the case of an alternative filler, the assets are not minted but transferred from the filler’s account. This allows a transfer to be filled before chains have been finalized or client updates processed. Instead, fillers can rely on preconfirmations to reduce the risk of reorgs.

Theoretically, a filler can submit the transfer to the destination side before the transaction is included on the source, given that they protect themselves against double-spend attacks.

The Acknowledgement, which may contain arbitrary payloads, is used to encode information on the filler and repay the filler for the service by unlocking assets from the vault.

Open filling is opt-in for any protocol, allowing for the same optimizations that ucs03-zkgm-0 leverages to increase transfer speeds.

Rate Limiting

ZKGM incorporates rate limiting capabilities through a token bucket mechanism to manage token consumption:

  • Each token has a configurable capacity and refill rate
  • Transactions are rate-limited based on token amounts
  • Rate limiting can be toggled on/off for specific deployment needs
  • Administrators can adjust bucket parameters as needed

Immutable Packet

A fundamental design principle of the ZKGM protocol is that packets are fully constructed off-chain and remain immutable once sent. Unlike traditional cross-chain messaging protocols, ZKGM never injects or modifies fields during on-chain processing—it only verifies the packet’s integrity and processes it as submitted.

This immutability offers several powerful capabilities:

  1. Mempool Inspection and Intent Filling: Market makers can observe pending transactions in the mempool and identify ZKGM packets before they’re included in a block. This enables near-instant filling of orders, reducing latency to the theoretical minimum.

  2. Deterministic Multi-Chain Execution: The packet’s immutability combined with deterministic salt derivation creates a verifiable and predictable execution chain. When a packet contains a Forward instruction:

    • The next packet’s salt is derived deterministically: deriveForwardSalt(salt) = tintForwardSalt(keccak256(salt))
    • This derived salt is used for the next hop’s packet
    • Each subsequent forwarded packet’s salt is similarly derived
    • The entire chain of packet execution becomes deterministic and verifiable

    This determinism allows market makers to:

    • Predict and validate the entire multi-chain execution path
    • Simulate the outcome across all chains before execution
    • Execute packets atomically across chains with confidence
    • Verify the authenticity of packets in a chain without requiring on-chain storage

Use Cases

Flash Loans Across Chains: A trader can construct a sequence of packets representing a complex cross-chain arbitrage:

  • Packet 1: Borrow assets on Chain A
  • Packet 2: Execute swap on Chain B
  • Packet 3: Repay loan on Chain A with profit

A market maker can detect these packets in the mempool, validate the profitability, and execute all steps atomically, effectively enabling cross-chain flash loans.

Cross-Chain Limit Orders: A user can create a packet representing a limit order with specific execution parameters. Market makers can monitor the mempool and execute only when market conditions match the user’s requirements, providing decentralized cross-chain limit order capabilities.

Preemptive Bridging: Services can monitor user interactions with dApps and predict upcoming cross-chain transfers. By the time the user initiates the transfer, the assets can already be pre-positioned on the destination chain through intent filling, creating an instant bridging experience.

Definition

The zkgm protocol abstracts away multiple facets of IBC assets transfer protocol (ics20). We employ versioning in this protocol to ensure backward compatibility with future upgrades (not relying on the IBC channel upgrade feature). Each instruction has a version and opcode to allow for protocol evolution. Its features include:

  • batching
  • forward/callback envelopes
  • channel multiplexing
  • fungible assets transfer
  • non-fungible assets transfer
  • staking and governance

Packet

The zkgm protocol uses two main structures for packet handling:

ZkgmPacket

struct ZkgmPacket {
bytes32 salt; // Unique packet identifier
uint256 path; // Channel routing information
Instruction instruction; // The instruction to execute
}

Fields:

  • salt: A unique bytes32 identifier used for deterministic packet hashing

    • For regular packets: keccak256(abi.encodePacked(sender, user_provided_salt))
    • For forwarded packets: Uses a tinting mechanism to track packet chain
      • Magic value: 0xC0DE00000000000000000000000000000000000000000000000000BABE
      • Tinting applied as: salt | magic_value (bitwise OR)
      • previous_salt is the salt of the packet being forwarded
      • Next hop salt derived as: keccak256(previous_salt) | magic_value
      • This creates a verifiable chain of salts across hops while preventing salt collisions
    • For batch instructions: Each sub-instruction uses keccak256(index, batch_salt)
      • Where index is the instruction’s position in the batch (0-based)
      • And batch_salt is the parent packet’s salt
  • path: A uint256 that tracks packet routing and asset origins

    • Composed of compressed uint32 channel IDs in 32-bit segments
    • Format: prevDstChannel0 | nextSrcChannel0 << 32 | prevDstChannel1 << 64 ...
    • Supports up to 3 hops (256/32/2 - 1), one hop is a placeholder for the final channel ID.
    • Updated during:
      • Packet forwarding (appending channel pairs)
      • Asset origin tracking
      • Return path validation
  • instruction: The Instruction to execute

Instruction

struct Instruction {
uint8 version; // Protocol version
uint8 opcode; // Instruction type
bytes operand; // Instruction-specific data
}

Fields:

  • version: Protocol version number as uint8

    • Required for backward compatibility
    • Allows dispatching between different versions
    • Each instruction type specifies required version
    • Current supported versions:
      • INSTR_VERSION_0 (0x00)
      • INSTR_VERSION_1 (0x01)
      • INSTR_VERSION_2 (0x02)
  • opcode: Instruction type identifier as uint8

  • operand: Instruction-specific data as bytes

    • Forward: Path, timeouts and instruction to forward
    • Multiplex: Sender, callback mode and contract call data
    • Batch: Array of instructions to execute atomically
    • FungibleAssetOrder: Transfer details like tokens, amounts and parties
    • Stake/Unstake/WithdrawStake/WithdrawRewards: Staking operation parameters

Instructions

0x00 - Forward

The forward instruction uses opcode 0x00 and requires version INSTR_VERSION_0.

struct Forward {
uint256 path; // Channel sequence as (prevDst,nextSrc) pairs
uint64 timeoutHeight; // Block height timeout
uint64 timeoutTimestamp; // Unix timestamp timeout
Instruction instruction; // Instruction to forward
}

Fields:

  • path: A uint256 that encodes the forwarding route

    • Composed of (prevDst,nextSrc) channel ID pairs
    • Each pair uses 64 bits (32 bits per channel ID)
    • Must match valid channel connections
    • Used to verify packet routing path
  • timeoutHeight: Block height timeout as uint64

    • After this height, packet cannot be executed
    • Set to 0 to disable height timeout
    • Must be greater than current height when executed
  • timeoutTimestamp: Unix timestamp timeout as uint64

    • After this time, packet cannot be executed
    • Set to 0 to disable timestamp timeout
    • Must be greater than current time when executed
  • instruction: The Instruction to forward

    • Can be Multiplex, FungibleAssetOrder, or Batch
    • Will be executed on final destination chain
    • Cannot be another Forward instruction

0x01 - Multiplex

The multiplex instruction uses opcode 0x01 and requires version INSTR_VERSION_0.

struct Multiplex {
bytes sender; // Source chain sender address (must match msg.sender)
bool eureka; // Whether to use IBC-style callbacks
bytes contractAddress; // Target contract address on destination
bytes contractCalldata; // Call data for the target contract
}

Fields:

  • sender: Source chain sender address as bytes

    • Must match transaction sender (msg.sender)
    • Prevents address impersonation
    • Used for callback routing in eureka mode
  • eureka: Callback mode flag as bool

    • false: Standard fire-and-forget mode
    • true: IBC-style callback mode
    • Determines target contract interface
    • Controls acknowledgement handling
  • contractAddress: Target contract address as bytes

    • Must be valid contract on destination chain
    • Must implement required interface based on eureka flag
    • Where message will be delivered
  • contractCalldata: Contract call data as bytes

    • Arbitrary data passed to target contract
    • Interpreted by target contract’s implementation
    • Available in both standard and eureka modes

The multiplex instruction has two modes:

  1. Standard Mode (eureka = false):

    • Target must implement IZkgmable interface
    • Calls onZkgm(path, sourceChannel, destChannel, sender, calldata) on target
    • Returns success acknowledgement immediately
    • Fire-and-forget style, no callback to sender
  2. IBC Mode (eureka = true):

    • Calls onRecvPacket(packet, relayer, relayerMsg) on target
    • Packet contains path, sender and calldata
    • Target must return non-empty acknowledgement
    • Acknowledgement forwarded back to original sender

If the target contract is invalid or calls fail:

  • Standard mode returns failure acknowledgement
  • IBC mode propagates target’s error response

0x02 - Batch

The batch instruction uses opcode 0x02 and requires version INSTR_VERSION_0.

struct Batch {
Instruction[] instructions; // Array of instructions to execute
}

Fields:

  • instructions: Array of Instructions to execute atomically
    • Only specific instructions allowed (Multiplex, FungibleAssetOrder, Stake, Unstake, WithdrawStake)
    • Executed in sequential order
    • All must succeed or entire batch reverts
    • Individual acknowledgements collected in array
    • Minimum 2 instructions required

This allows atomic composition of transfers, contract calls, and staking operations in a single transaction.

0x03 - Fungible Asset Order

The fungible asset order instruction has two versions:

  1. Version 1 (INSTR_VERSION_1) - DEPRECATED:
struct FungibleAssetOrder {
bytes sender; // Source chain sender address
bytes receiver; // Destination chain receiver address
bytes baseToken; // Token being sent
uint256 baseAmount; // Amount being sent
string baseTokenSymbol; // Token symbol for wrapped asset
string baseTokenName; // Token name for wrapped asset
uint8 baseTokenDecimals; // Token decimals for wrapped asset
uint256 baseTokenPath; // Origin path for unwrapping
bytes quoteToken; // Token requested in return
uint256 quoteAmount; // Minimum amount requested
}
  1. Version 2 (INSTR_VERSION_2):
struct FungibleAssetOrderV2 {
bytes sender; // Source chain sender address
bytes receiver; // Destination chain receiver address
bytes baseToken; // Token being sent
uint256 baseAmount; // Amount being sent
uint8 metadataType; // Type of metadata (image, preimage, image_unwrap)
bytes metadata; // Token metadata based on type
bytes quoteToken; // Token requested in return
uint256 quoteAmount; // Minimum amount requested
}

FungibleAssetOrderV2: Advanced Token Mapping and Customization

The V2 version introduces a significantly more flexible metadata system with three types:

  • FUNGIBLE_ASSET_METADATA_TYPE_IMAGE (0x00): Uses a metadata image hash for existing token identification
  • FUNGIBLE_ASSET_METADATA_TYPE_PREIMAGE (0x01): Provides full metadata implementation and initializer for custom token deployment
  • FUNGIBLE_ASSET_METADATA_TYPE_IMAGE_UNWRAP (0x02): Specifically for unwrapping operations
struct FungibleAssetMetadata {
bytes implementation; // Implementation contract address
bytes initializer; // Initialization data for proxy
}

The key innovation in V2 is the ability to support 1:N token mappings, allowing the same source token to be represented by different implementations on the destination chain. This enables several powerful use cases:

  1. Custom Token Implementations: Projects can map a token to their own implementation with specific features or behaviors. For example, a project could map USDC to a custom token that includes additional functionality like rebasing or built-in protocol-specific mechanics.

  2. Upgradeability Management: Tokens can be deployed with specific upgradeability patterns chosen by the implementing protocol rather than being fixed to a single implementation pattern.

  3. Enhanced Security Controls: Custom implementations can include additional security features like transfer limits, allowlists, or compliance mechanisms tailored to specific regulatory requirements.

This flexibility is achieved by allowing the specification of:

  • A custom implementation contract address
  • Custom initialization data
  • A deterministic salt based on the metadata

With V2, the protocol uses CREATE3 to deploy the token contracts at deterministic addresses, ensuring that the same token metadata always results in the same deployed address.

Common Fields (both versions):

  • sender: Source chain sender address as bytes

    • Must be valid address on source chain
    • Used for refunds on failure/timeout
  • receiver: Destination chain receiver address as bytes

    • Must be valid address on destination chain
    • Where quote tokens will be sent on success
    • Must be specified by sender
  • baseToken: Token being sent as bytes

    • Token identifier on source chain
    • Used to identify/create wrapped token
    • Must exist on source chain
  • baseAmount: Amount being sent as uint256

    • Must be available from sender
    • Maximum amount to exchange
  • quoteToken: Requested token as bytes

    • Token identifier on destination chain
    • What sender wants in exchange
    • Must exist on destination chain
  • quoteAmount: Minimum amount requested as uint256

    • Minimum acceptable exchange amount
    • Difference (if less than baseAmount) is taken as fee by the relayer

The order can be filled in two ways:

  1. Protocol Fill - If the quote token matches the wrapped version of the base token and base amount >= quote amount:

    • For new assets: Deploy wrapped token contract and mint quote amount to receiver
    • For returning assets: Unwrap base token and transfer quote amount to receiver
    • Any difference between baseAmount and quoteAmount is minted/transferred to the relayer as a fee
    • Rate limiting may be applied based on configuration
  2. Market Maker Fill - Any party can fill the order by providing the quote token:

    • Market maker is specified in acknowledgement
    • Base token is transferred/minted to market maker
    • Market maker must handle quote token transfer on behalf of the protocol

The acknowledgement includes:

  • Fill type (Protocol = 0xB0CAD0 or MarketMaker = 0xD1CEC45E)
  • Market maker address for MM fills (empty for protocol fills)

If the order fails or times out:

  • For new assets: Base token is minted back to sender
  • For returning assets: Base token is transferred back to sender
  • Outstanding balances are decreased

If any of the order in the orders list is failing on execution, the whole packet is reverted and a failure acknowledgement will be yield.

0x04 - Stake

The stake instruction uses opcode 0x04 and requires version INSTR_VERSION_0.

struct Stake {
uint256 tokenId; // NFT token ID for the staking position
bytes governanceToken; // Governance token address
bytes32 governanceTokenMetadataImage; // Metadata image hash for token
bytes sender; // Source chain sender address
bytes beneficiary; // Address that will receive the staking NFT
bytes validator; // Validator address to stake with
uint256 amount; // Amount to stake
}

The stake instruction allows cross-chain staking of governance tokens with validators. The process:

  1. Tokens are locked on the source chain
  2. Staking is initiated on the destination chain
  3. A staking position NFT is minted to the beneficiary
  4. The NFT represents ownership of the staked position and can be used to manage it

0x05 - Unstake

The unstake instruction uses opcode 0x05 and requires version INSTR_VERSION_0.

struct Unstake {
uint256 tokenId; // NFT token ID for the staking position
bytes governanceToken; // Governance token address
bytes32 governanceTokenMetadataImage; // Metadata image hash for token
bytes sender; // Source chain sender address
bytes validator; // Validator address to unstake from
uint256 amount; // Amount to unstake
}

The unstake instruction initiates the unbonding process for staked tokens. When successful:

  1. The staking position enters the UNSTAKING state
  2. The unstakingCompletion time is set
  3. The NFT is returned to the sender
  4. After completion time, tokens can be withdrawn

0x06 - Withdraw Stake

The withdraw stake instruction uses opcode 0x06 and requires version INSTR_VERSION_0.

struct WithdrawStake {
uint256 tokenId; // NFT token ID for the staking position
bytes governanceToken; // Governance token address
bytes32 governanceTokenMetadataImage; // Metadata image hash for token
bytes sender; // Source chain sender address
bytes beneficiary; // Address that will receive the tokens
}

The withdraw stake instruction allows a user to claim their tokens after the unbonding period. On success:

  1. The staking position enters the UNSTAKED state
  2. The staked tokens are transferred to the beneficiary
  3. Any rewards are also transferred
  4. If there was slashing, the appropriate amount is burned

0x07 - Withdraw Rewards

The withdraw rewards instruction uses opcode 0x07 and requires version INSTR_VERSION_0.

struct WithdrawRewards {
uint256 tokenId; // NFT token ID for the staking position
bytes governanceToken; // Governance token address
bytes32 governanceTokenMetadataImage; // Metadata image hash for token
bytes validator; // Validator address
bytes sender; // Source chain sender address
bytes beneficiary; // Address that will receive the rewards
}

The withdraw rewards instruction allows claiming rewards without unstaking. On success:

  1. The staking position remains in the STAKED state
  2. Any accumulated rewards are transferred to the beneficiary
  3. The NFT is returned to the sender

Implementations