> ## Documentation Index
> Fetch the complete documentation index at: https://cosmos-docs-sync-security-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Step 3: Create Attestation Light Clients

> Create attestation light clients on each chain and register their counterparties

This step creates an attestation light client on each chain and registers each client's counterparty. Each client is initialized with the attestor address and an initial trusted height and timestamp from the counterparty chain. Once both clients exist and their counterparties are registered, the two chains can verify each other's packets.

Run the following:

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
./setup.sh create-clients
```

The logic for this command is in [`lib/ibc.sh`](https://github.com/cosmos/ibc-e2e-docs-example/blob/main/demo/cosmos-evm/lib/ibc.sh).

## Attestation light client

An attestation light client verifies IBC packets using ECDSA signatures from a registered set of off-chain attestors. There are two implementations: [Go (cosmos/ibc-go)](https://github.com/cosmos/ibc-go/tree/main/modules/light-clients/attestations) for the Cosmos side and [Solidity (cosmos/solidity-ibc-eureka)](https://github.com/cosmos/solidity-ibc-eureka/blob/main/contracts/light-clients/attestation/AttestationLightClient.sol) for the EVM side.

Each client is initialized with a list of attestor Ethereum addresses and a quorum threshold. When a packet arrives with an attestation proof, the light client:

1. Validates that the proof value is non-empty and the path has exactly one element.
2. Looks up the trusted consensus timestamp stored at `proofHeight`.
3. Decodes the proof into `attestationData` and `signatures`.
4. Recomputes the expected digest: `sha256(0x02 || sha256(attested_data))`.
5. For each signature: recovers the signer address via ECDSA, checks it against the registered attestor set, and rejects duplicates. Verifies that `signatures.length >= minRequiredSigs`.
6. Decodes `attestationData` as a `PacketAttestation` and verifies the attested height matches `proofHeight`.
7. Checks that the packet commitment is present in the attested packets array, matching on both the keccak256 hash of the path and the commitment value.

## What the script does

### 1. Generate or read the attestor keystore

Both light clients are initialized with the attestor's Ethereum address. The script first ensures the keystore exists, generating a new one if needed, then reads the address from it:

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
ibc_attestor key show
```

The keystore is written to `ibc/local/.ibc-attestor/ibc-attestor-keystore` and reused by the attestor services started in a later step.

<Note>
  Ethereum addresses configured in the light client must be in EIP-55 checksummed format.
</Note>

### 2. Create the Cosmos-side client

The Cosmos-side client verifies EVM packets on the Cosmos chain. It is initialized with:

* The attestor's Ethereum address
* The current EVM block height and timestamp as the initial trusted state

The script renders two JSON files from templates and submits them:

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
sandboxd tx ibc client create client-state.json consensus-state.json
```

The client state holds the client's configuration: which attestor addresses are trusted, the quorum threshold, the latest known height, and whether the client is frozen:

* `client-state.json` ([template](https://github.com/cosmos/ibc-e2e-docs-example/blob/main/demo/cosmos-evm/ibc/client-state.json.tmpl)):

```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
{
  "@type": "/ibc.lightclients.attestations.v1.ClientState",
  "attestor_addresses": ["<ATTESTOR_ETH_ADDR>"],
  "min_required_sigs": 1,
  "latest_height": <EVM_BLOCK_HEIGHT>,
  "is_frozen": false
}
```

The consensus state records the block timestamp at the initial trusted height, which anchors proof verification:

* `consensus-state.json` ([template](https://github.com/cosmos/ibc-e2e-docs-example/blob/main/demo/cosmos-evm/ibc/consensus-state.json.tmpl)):

```json theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
{
  "@type": "/ibc.lightclients.attestations.v1.ConsensusState",
  "timestamp": "<EVM_BLOCK_TIMESTAMP_NANOSECONDS>"
}
```

Output: `COSMOS_CLIENT_ID` in the format `attestations-N`.

### 3. Create and register the EVM-side client

The EVM-side client verifies Cosmos packets on the EVM chain. It is a Solidity contract ([`AttestationLightClient`](https://github.com/cosmos/solidity-ibc-eureka/blob/main/contracts/light-clients/attestation/AttestationLightClient.sol)) deployed from prebuilt bytecode and registered with the `ICS26Router`.

The script initializes it with:

* The attestor's Ethereum address
* The current Cosmos block height and timestamp as the initial trusted state

After deployment, the contract is registered with the `ICS26Router` on the EVM chain. The `CounterpartyInfo` passed to `addClient` includes the Cosmos client ID, so the EVM client knows its counterparty at registration time:

```
ICS26Router.addClient((COSMOS_CLIENT_ID, [0x]), lcAddress)
```

The EVM client ID (`EVM_CLIENT_ID`, in the format `client-N`) is assigned by the router on registration.

Output: `EVM_CLIENT_ID` in the format `client-N`.

### 4. Register the Cosmos-side counterparty

With both client IDs now known, the script registers the Cosmos-side counterparty. This is the on-chain record that maps the Cosmos attestation client to its EVM peer:

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
sandboxd tx ibc client add-counterparty $COSMOS_CLIENT_ID $EVM_CLIENT_ID ""
```

The EVM client registers its counterparty at `addClient` time (the Cosmos client ID is passed in the `CounterpartyInfo`). The Cosmos client registers its counterparty here after the EVM client ID is known.

## Configuration reference

### Cosmos client state

* ([Client state template](https://github.com/cosmos/ibc-e2e-docs-example/blob/main/demo/cosmos-evm/ibc/client-state.json.tmpl))

| Field                | Description                                                          |
| -------------------- | -------------------------------------------------------------------- |
| `attestor_addresses` | List of registered attestor Ethereum addresses                       |
| `min_required_sigs`  | Quorum threshold: minimum signatures required to accept a proof      |
| `latest_height`      | EVM block height at time of client creation (initial trusted state)  |
| `is_frozen`          | If `true`, the client rejects all proofs — used as an emergency stop |

### Cosmos consensus state

* ([Consensus state template](https://github.com/cosmos/ibc-e2e-docs-example/blob/main/demo/cosmos-evm/ibc/consensus-state.json.tmpl))

| Field       | Description                                            |
| ----------- | ------------------------------------------------------ |
| `timestamp` | EVM block timestamp at `latest_height`, in nanoseconds |

### EVM client constructor

* ([AttestationLightClient.sol](https://github.com/cosmos/solidity-ibc-eureka/blob/main/contracts/light-clients/attestation/AttestationLightClient.sol))

| Argument                  | Description                                                                                                                                                                                                                |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attestorAddresses`       | List of registered attestor Ethereum addresses                                                                                                                                                                             |
| `minRequiredSigs`         | Minimum signatures required to accept a proof                                                                                                                                                                              |
| `initialHeight`           | Cosmos block height at time of deployment (initial trusted state)                                                                                                                                                          |
| `initialTimestampSeconds` | Cosmos block timestamp at `initialHeight`, in Unix seconds                                                                                                                                                                 |
| `roleManager`             | Address granted `DEFAULT_ADMIN_ROLE` (full role administration) and `PROOF_SUBMITTER_ROLE` on the contract. Use `address(0)` to grant `PROOF_SUBMITTER_ROLE` to everyone, allowing any caller to submit proofs (demo only) |

## Applying this to your own setup

### Initial trusted state

The initial height and timestamp anchor the light client to a specific point in the counterparty chain's history.

### Quorum threshold

The demo uses `min_required_sigs: 1` because there is a single attestor. For production, set this to the threshold of your attestor set. It is recommended to use a threshold of greater than 1.

### `roleManager` in production

The demo passes `address(0)` as the `roleManager`, which allows anyone to submit proofs. For production, pass the `ICS26Router` proxy address so only the router can submit proofs to the light client.

## Next steps

With both light clients created, counterparties registered, and their IDs written to state, the next step is to [register the IFT bridges](/ibc/next/cosmos-evm/tutorial/walkthrough/04-wire).
