# Igra Exit Bridge Message Format

Last updated on: 2026-04-17

## Document Version History

| Version | Date       | Author    | Description                                                                                           |
| ------- | ---------- | --------- | ----------------------------------------------------------------------------------------------------- |
| v1.0    | 2026-04-16 | IGRA Team | Initial release: Body Format Version 1 wire layout, field encoding rules, and Kaspa recipient default |
| v1.1    | 2026-04-17 | IGRA Team | Align terminology with accepted implementation                                                        |

***

## Introduction

Igra Exit Bridge allows users to burn iKAS on the Igra chain and receive native KAS on Kaspa. The bridge smart contract (`KasExitBridge`) on Igra processes exit requests (transactions) submitted (sent) by users. On a successful exit request, `KasExitBridge` calls `dispatch(...)` on the Hyperlane `Mailbox` contract deployed on Igra, producing EVM events (logs) containing the "exit message" - a Hyperlane outbound message targeted at the endpoint on Kaspa of Igra Exit Bridge. Kaspa-side release actors observe these events (logs) directly on Igra and validate exit messages the events contain — the actors fulfill a role analogous to Hyperlane validators and relayers for standard EVM-to-EVM routes. After exit message validation and finality policy is satisfied, Kaspa-side actors unlock the KAS on Kaspa.

This spec defines the format of the exit message used for this exit flow: the body - a payload specific to Igra Exit Bridge - is included into the Hyperlane standard outer envelope. This specification defines the version 1 of the body format; other format versions may be defined in future revisions.

**Terminology note.** "Exit message" refers specifically to a Hyperlane outbound message carrying the body format defined in this specification and produced by `KasExitBridge`. "Dispatched message" refers to any Hyperlane outbound message after it has been processed by `Mailbox` — i.e., `Mailbox.dispatch(...)` was called and the corresponding events were emitted. Every exit message becomes a dispatched message upon successful dispatch.

## Constants

* Origin chain: `IGRA` (EVM-compatible)
* Origin Hyperlane domain: `38833`
* Origin Hyperlane domain hex: `0x97b1`
* Destination chain / Hyperlane domain: `KASPA`
* `destinationDomain`: `1262570320`
* `destinationDomain` hex: `0x4B415350`
* `destinationDomain` mnemonic: `KASP`
* Kaspa bridge-lock address: `kaspa:ppvnxxzm0rr37zpnwux2f2ntvfpr4uqdpm7zsvsztg3en92r7gs0wkmr72q9n`
* Outer recipient preimage: `igra:v1:bridge-lock:kaspa:ppvnxxzm0rr37zpnwux2f2ntvfpr4uqdpm7zsvsztg3en92r7gs0wkmr72q9n`
* Outer recipient hash: `0x12f926e6d45ae9ace4f1fae2b542f7391bb6af8a321ee48dc60aa57507a56cf3`
* Outer sender: `bytes32(address(KasExitBridge))`
* Body version namespace:
  * `0`: reserved / invalid
  * `1`: body format version 1
  * other values: reserved for future body versions
* Message type namespace:
  * `0`: reserved / invalid
  * `1`: `burn_unlock`

## Hyperlane Outer Envelope

Igra Exit Bridge uses non-modified Hyperlane outer envelope.

### Layout

```
version(1)
nonce(4)
originDomain(4)
sender(32)
destinationDomain(4)
recipient(32)
body(variable)
```

### Field Values For Igra Exit Bridge

| Field               | Value                                                                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `version`           | `0x03` - Hyperlane Mailbox version                                                                                                               |
| `nonce`             | Mailbox outbound nonce (assigned by Mailbox)                                                                                                     |
| `originDomain`      | `0x97b1` - Igra                                                                                                                                  |
| `sender`            | `bytes32(address(KasExitBridge))`                                                                                                                |
| `destinationDomain` | `0x4B415350` - Kaspa                                                                                                                             |
| `recipient`         | effective `kaspaBridgeEndpoint` as configured in `KasExitBridge` (default: `0x12f926e6d45ae9ace4f1fae2b542f7391bb6af8a321ee48dc60aa57507a56cf3`) |
| `body`              | bridge payload, encoded according to Body Format (Version 1)                                                                                     |

The outer `sender` identifies the contract that called `Mailbox.dispatch` and carries protocol authority. The outer `recipient` identifies the Kaspa bridge endpoint; the final user payout address is encoded inside the body. The `recipient` value is the `kaspaBridgeEndpoint` storage parameter of `KasExitBridge` (refer to [IGRA Tech Specs - KasExitBridge](https://igra-labs.gitbook.io/igralabs-docs/for-developers/architecture/specifications/kasexitbridge)) — configurable by the bridge owner — which defaults to the hash of the preimage `igra:v1:bridge-lock:kaspa:ppvnxxzm0rr37zpnwux2f2ntvfpr4uqdpm7zsvsztg3en92r7gs0wkmr72q9n`.

## Body Format

The Version 1 of the body format follows. Other format versions may be defined in future revisions of this specification.

### Layout

```
byte 0     : header = (body_version << 4) | message_type
bytes 1-4  : burn_id
bytes 5-12 : amount_u64_be
bytes 13-32: origin_burner_evm_address_20
byte 33    : final_recipient_len_u8
bytes 34.. : final_recipient_utf8
```

### Field Values

* `body_version`: `1`
* `message_type`: `1`
* `header`: `0x11`

### Field Semantics

* `burn_id`: exit request identifier; equals `uint32(requestId)` as assigned by `KasExitBridge`. Unique per successful exit.
* `amount_u64_be`: amount of native KAS to unlock, in the KAS smallest unit (SOMPI), encoded big-endian.
* `origin_burner_evm_address_20`: 20-byte EVM address of the Igra account whose iKAS was burned.
* `final_recipient_len_u8`: byte length of `final_recipient_utf8`.
* `final_recipient_utf8`: Kaspa payout address as a UTF-8 string (e.g., `kaspa:...`).

## Dispatch Call

`KasExitBridge` produces the exit message by calling:

```solidity
mailbox.dispatch(
    0x4B415350,   // destinationDomain: Kaspa
    kaspaBridgeEndpoint, // effective recipient: configurable in KasExitBridge; default 0x12f926e6d45ae9ace4f1fae2b542f7391bb6af8a321ee48dc60aa57507a56cf3
    body
);
```

## Hyperlane Smart Contract Events

Hyperlane smart contracts on Igra emit EVM events (logs) when an exit message is dispatched.

Kaspa-side release actors observe `Dispatch` events for the Kaspa destination in order to validate and process exit messages. The actors may also observe `DispatchId` and `InsertedIntoTree` events to validate Igra attesters' attestations to checkpoints agreed between attesters and Kaspa-side release actors.

Igra attesters may observe `InsertedIntoTree` events in order to attest to the state (root) of the Merkle tree of dispatched message IDs (maintained by `MerkleTreeHook`) at checkpoints; this provides Kaspa-side release actors with additional confidence in authenticity of exit messages.

## Annex A: Relevant Hyperlane Smart Contract References

### `Mailbox` smart contract

On `Mailbox.dispatch(...)` call it formats the standard Hyperlane message and emits `Dispatch` and `DispatchId` events.

Deployed on Igra at address `0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7`.

> The `sender` field of the Hyperlane message outer envelope equals the immediate `msg.sender` of the `Mailbox.dispatch` call. `KasExitBridge`, not the user EOA, must call `Mailbox.dispatch(...)` directly.

#### `dispatch` function

```solidity
/**
 * @notice Dispatches a message to the destination domain & recipient
 * using the default hook and empty metadata.
 * @param _destinationDomain Domain of destination chain
 * @param _recipientAddress Address of recipient on destination chain as bytes32
 * @param _messageBody Raw bytes content of message body
 * @return The message ID inserted into the Mailbox's merkle tree
 */
function dispatch(
    uint32 _destinationDomain,
    bytes32 _recipientAddress,
    bytes calldata _messageBody
) external payable override returns (bytes32
```

Expected values for messages on Igra Exit Bridge:

* `_destinationDomain`: `0x4B415350`
* `_recipientAddress`: the effective `kaspaBridgeEndpoint` configured in `KasExitBridge`; the default value when unset is `0x12f926e6d45ae9ace4f1fae2b542f7391bb6af8a321ee48dc60aa57507a56cf3`
* `message`: body in Version 1 format

#### `Dispatch` event

Emitted by

```solidity
event Dispatch(
    address indexed sender,
    uint32 indexed destination,
    bytes32 indexed recipient,
    bytes message
);
```

Expected values for messages on Igra Exit Bridge:

* `sender`: `KasExitBridge` contract address
* `destination`: `0x4B415350`
* `recipient`: effective `kaspaBridgeEndpoint` configured in `KasExitBridge` (default: `0x12f926e6d45ae9ace4f1fae2b542f7391bb6af8a321ee48dc60aa57507a56cf3`)
* `message`: full exit message bytes (outer envelope + body)

Use: Kaspa-side release actors and indexers use this event to obtain the full exit message bytes.

#### `DispatchId` event

```solidity
event DispatchId(bytes32 indexed messageId);
```

Expected value:

* `messageId = keccak256(message)`

Use: stable message identity for observability and correlation.

### `MerkleTreeHook` smart contract

It inserts the message id into the Merkle tree of dispatched message IDs and emits `InsertedIntoTree` event.

Deployed on Igra at address `0x75719C858e0c73e07128F95B2C466d142490e933`.

#### `InsertedIntoTree` event

```solidity
event InsertedIntoTree(bytes32 messageId, uint32 index);
```

Expected values:

* `messageId`: same message id as above
* `index`: leaf index in the Merkle tree of dispatched message IDs

> Kaspa-side release actors and Igra attesters may use the leaf index and `messageId` to prove inclusion of exit messages in the Merkle tree of dispatched message IDs.

## Related Documents

* [IGRA Tech Specs - KasExitBridge](https://igra-labs.gitbook.io/igralabs-docs/for-developers/architecture/specifications/kasexitbridge)

## Open Questions

None.
