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:
-
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.
-
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
- The next packet’s salt is derived deterministically:
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 uniquebytes32
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
- Magic value:
- 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
- For regular packets:
-
path
: Auint256
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
- Composed of compressed
-
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 asuint8
- 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 asuint8
- 0x00: Forward
- 0x01: Multiplex
- 0x02: Batch
- 0x03: FungibleAssetOrder
- 0x04: Stake
- 0x05: Unstake
- 0x06: WithdrawStake
- 0x07: WithdrawRewards
-
operand
: Instruction-specific data asbytes
- 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
: Auint256
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 asuint64
- 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 asuint64
- 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 asbytes
- Must match transaction sender (msg.sender)
- Prevents address impersonation
- Used for callback routing in eureka mode
-
eureka
: Callback mode flag asbool
- false: Standard fire-and-forget mode
- true: IBC-style callback mode
- Determines target contract interface
- Controls acknowledgement handling
-
contractAddress
: Target contract address asbytes
- Must be valid contract on destination chain
- Must implement required interface based on eureka flag
- Where message will be delivered
-
contractCalldata
: Contract call data asbytes
- 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:
-
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
-
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
- Calls
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:
- 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}
- 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 identificationFUNGIBLE_ASSET_METADATA_TYPE_PREIMAGE
(0x01): Provides full metadata implementation and initializer for custom token deploymentFUNGIBLE_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:
-
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.
-
Upgradeability Management: Tokens can be deployed with specific upgradeability patterns chosen by the implementing protocol rather than being fixed to a single implementation pattern.
-
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 asbytes
- Must be valid address on source chain
- Used for refunds on failure/timeout
-
receiver
: Destination chain receiver address asbytes
- Must be valid address on destination chain
- Where quote tokens will be sent on success
- Must be specified by sender
-
baseToken
: Token being sent asbytes
- Token identifier on source chain
- Used to identify/create wrapped token
- Must exist on source chain
-
baseAmount
: Amount being sent asuint256
- Must be available from sender
- Maximum amount to exchange
-
quoteToken
: Requested token asbytes
- Token identifier on destination chain
- What sender wants in exchange
- Must exist on destination chain
-
quoteAmount
: Minimum amount requested asuint256
- Minimum acceptable exchange amount
- Difference (if less than
baseAmount
) is taken as fee by the relayer
The order can be filled in two ways:
-
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
-
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:
- Tokens are locked on the source chain
- Staking is initiated on the destination chain
- A staking position NFT is minted to the beneficiary
- 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:
- The staking position enters the UNSTAKING state
- The unstakingCompletion time is set
- The NFT is returned to the sender
- 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:
- The staking position enters the UNSTAKED state
- The staked tokens are transferred to the beneficiary
- Any rewards are also transferred
- 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:
- The staking position remains in the STAKED state
- Any accumulated rewards are transferred to the beneficiary
- The NFT is returned to the sender