> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Grant, change, and revoke a role

> Configure permissioned relaying, rotate a relayer key, and transfer access manager admin authority.

This guide demonstrates access manager role changes by restricting relaying, rotating a relayer key, and transferring admin authority.

## Before you begin

Complete [Deploy IBC and send a token](/docs/ibc/05-ibc-cli/02-tutorial/01-deploy-ibc-and-send-a-token.md).
Keep the tutorial's relayer running.
Both connection ends must use the tutorial's `relayer` key alias to sign transactions.
Run commands from `cli`, using the tutorial's home and configuration.

Install [Foundry](https://getfoundry.sh/introduction/installation) for the `cast` commands.
The tutorial deploys one access manager on 41001 and another on 41002.
The `deployer` key administers both.
This guide uses `RELAYER_ROLE` and `ADMIN_ROLE`; see [Permissions and upgrades](/docs/ibc/06-ibc-solidity-contracts/06-permissions-and-upgrades.md#the-access-manager-and-its-roles) for the other available roles.

## Restrict relaying

### 1. Grant the relayer role

Read the values again if you opened a fresh terminal:

```bash theme={null}
AM_A=$(./bin/ibc deploy show 41001 | jq -r '.targetData.accessManager')
AM_B=$(./bin/ibc deploy show 41002 | jq -r '.targetData.accessManager')
ROUTER_A=$(./bin/ibc deploy show 41001 | jq -r '.core.router')
ROUTER_B=$(./bin/ibc deploy show 41002 | jq -r '.core.router')
MULTICALL_41001=$(./bin/ibc deploy show 41001 | jq -er '.core.multicall | strings | select(length > 0)')
MULTICALL_41002=$(./bin/ibc deploy show 41002 | jq -er '.core.multicall | strings | select(length > 0)')
OLD_RELAYER=$(./bin/ibc keys show relayer | jq -r '.evmAddress')
DEPLOYER_KEY=$(./bin/ibc keys show deployer --private | jq -r '.privateKey')
```

The last command reads the admin's raw private key.
Role `1` is `RELAYER_ROLE`.
Use zero execution delay because the relayer cannot schedule delayed calls.

The relayer calls `IBCMulticall`, which calls the router.
The access manager checks the relayer's signing address for multicall calls.
It checks the multicall contract's address for the resulting router calls.
Grant the role to the current key and multicall contract on each chain:

```bash theme={null}
cast send "$AM_A" "grantRole(uint64,address,uint32)" 1 "$OLD_RELAYER" 0 --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "grantRole(uint64,address,uint32)" 1 "$OLD_RELAYER" 0 --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_A" "grantRole(uint64,address,uint32)" 1 "$MULTICALL_41001" 0 --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "grantRole(uint64,address,uint32)" 1 "$MULTICALL_41002" 0 --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Verify the grants before restricting relaying:

```bash theme={null}
for ACCOUNT in "$OLD_RELAYER" "$MULTICALL_41001"; do
  cast call "$AM_A" "hasRole(uint64,address)(bool,uint32)" 1 "$ACCOUNT" --rpc-url http://localhost:8545
done
```

```bash theme={null}
for ACCOUNT in "$OLD_RELAYER" "$MULTICALL_41002"; do
  cast call "$AM_B" "hasRole(uint64,address)(bool,uint32)" 1 "$ACCOUNT" --rpc-url http://localhost:8745
done
```

Every query must return `true`, then `0`.
Continue only after all checks pass, so relaying stays authorized.

### 2. Require the role for relaying

Compute the selectors that identify the four router functions and `multicall`:

```bash theme={null}
RECV=$(cast sig 'recvPacket(((uint64,string,string,uint64,(string,string,string,string,bytes)[]),bytes,(uint64,uint64)))')
ACK=$(cast sig 'ackPacket(((uint64,string,string,uint64,(string,string,string,string,bytes)[]),bytes,bytes,(uint64,uint64)))')
TIMEOUT=$(cast sig 'timeoutPacket(((uint64,string,string,uint64,(string,string,string,string,bytes)[]),bytes,(uint64,uint64)))')
UPDATE=$(cast sig 'updateClient(string,bytes)')
RELAY_FNS="[$RECV,$ACK,$TIMEOUT,$UPDATE]"
MULTICALL_FN=$(cast sig 'multicall((address,bool,bytes)[])')
```

Restrict multicall first, so unauthorized keys cannot use it to relay:

```bash theme={null}
cast send "$AM_A" "setTargetFunctionRole(address,bytes4[],uint64)" "$MULTICALL_41001" "[$MULTICALL_FN]" 1 --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "setTargetFunctionRole(address,bytes4[],uint64)" "$MULTICALL_41002" "[$MULTICALL_FN]" 1 --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Then change the router functions' required role from `PUBLIC_ROLE` to `1`:

```bash theme={null}
cast send "$AM_A" "setTargetFunctionRole(address,bytes4[],uint64)" "$ROUTER_A" "$RELAY_FNS" 1 --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "setTargetFunctionRole(address,bytes4[],uint64)" "$ROUTER_B" "$RELAY_FNS" 1 --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Check the router assignments:

```bash theme={null}
for SELECTOR in "$RECV" "$ACK" "$TIMEOUT" "$UPDATE"; do
  cast call "$AM_A" "getTargetFunctionRole(address,bytes4)(uint64)" "$ROUTER_A" "$SELECTOR" --rpc-url http://localhost:8545
done
```

```bash theme={null}
for SELECTOR in "$RECV" "$ACK" "$TIMEOUT" "$UPDATE"; do
  cast call "$AM_B" "getTargetFunctionRole(address,bytes4)(uint64)" "$ROUTER_B" "$SELECTOR" --rpc-url http://localhost:8745
done
```

Each loop must print four `1` values.
Check both multicall assignments:

```bash theme={null}
cast call "$AM_A" "getTargetFunctionRole(address,bytes4)(uint64)" "$MULTICALL_41001" "$MULTICALL_FN" --rpc-url http://localhost:8545
```

```bash theme={null}
cast call "$AM_B" "getTargetFunctionRole(address,bytes4)(uint64)" "$MULTICALL_41002" "$MULTICALL_FN" --rpc-url http://localhost:8745
```

Both queries must return `1`.
Stop if any assignment returns another role.

For permissioning only, run `unset DEPLOYER_KEY` and stop here.
Continue below to replace the relayer key.

## Rotate relayer keys

### 1. Authorize the replacement key

Create the replacement signer and read its address:

```bash theme={null}
./bin/ibc keys new ecdsa relayer-next --populate-config
```

```bash theme={null}
NEW_RELAYER=$(./bin/ibc keys show relayer-next | jq -r '.evmAddress') && echo "$NEW_RELAYER"
```

Fund it on both chains for transaction fees:

```bash theme={null}
cast send "$NEW_RELAYER" --value 1ether --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$NEW_RELAYER" --value 1ether --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Check that each funding transaction succeeds.
Grant the replacement key role `1` with zero execution delay:

```bash theme={null}
cast send "$AM_A" "grantRole(uint64,address,uint32)" 1 "$NEW_RELAYER" 0 --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "grantRole(uint64,address,uint32)" 1 "$NEW_RELAYER" 0 --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Verify both grants:

```bash theme={null}
cast call "$AM_A" "hasRole(uint64,address)(bool,uint32)" 1 "$NEW_RELAYER" --rpc-url http://localhost:8545
```

```bash theme={null}
cast call "$AM_B" "hasRole(uint64,address)(bool,uint32)" 1 "$NEW_RELAYER" --rpc-url http://localhost:8745
```

Both queries must return `true`, then `0`.

### 2. Switch to the replacement key

Update both connection ends in `~/.ibc/ibc.yml`.
If you customized the attestors, edit only the connection's two `signer` fields to `relayer-next` and skip the command below. Otherwise, run:

```bash theme={null}
./bin/ibc deploy render-config 41001 41002 \
  --signer-a relayer-next --signer-b relayer-next \
  --populate-config --yes
```

The `--yes` flag skips confirmation.
The command preserves the database settings.
It renders attestors from deployment records.

Validate before stopping the running relayer:

```bash theme={null}
./bin/ibc config validate relayer --live
```

In its terminal, press `Ctrl-C` and wait for exit.
Restart with the same home, configuration, and database:

```bash theme={null}
./bin/ibc relayer run
```

Wait for the `Readiness` log.

#### Verify a transfer

Back in your working terminal, read the token address:

```bash theme={null}
IFT_A=$(./bin/ibc deploy show 41001 | jq -r '.tokens[0].address')
```

Send a transfer to test the selected signer on both chains:

```bash theme={null}
TX=$(./bin/ibc tx ift send --chain 41001 --ift "$IFT_A" --client-id cli-41001-41002 --to deployer --from deployer --amount 1000000000000000000 | jq -r '.txHash') && echo "$TX"
```

Poll for `PACKET_STATE_SUCCEEDED`:

```bash theme={null}
./bin/ibc relayer packets --chain-id 41001 --tx-hash "$TX" | jq -r '.packets[0].state'
```

### 3. Revoke the old key's role

Revoke role `1` from the old key on both chains.

```bash theme={null}
cast send "$AM_A" "revokeRole(uint64,address)" 1 "$OLD_RELAYER" --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "revokeRole(uint64,address)" 1 "$OLD_RELAYER" --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Check the old membership:

```bash theme={null}
cast call "$AM_A" "hasRole(uint64,address)(bool,uint32)" 1 "$OLD_RELAYER" --rpc-url http://localhost:8545
```

```bash theme={null}
cast call "$AM_B" "hasRole(uint64,address)(bool,uint32)" 1 "$OLD_RELAYER" --rpc-url http://localhost:8745
```

Both queries must return `false`, then `0`.
Check the replacement's permission:

```bash theme={null}
cast call "$AM_A" "canCall(address,address,bytes4)(bool,uint32)" "$NEW_RELAYER" "$MULTICALL_41001" "$MULTICALL_FN" --rpc-url http://localhost:8545
```

```bash theme={null}
cast call "$AM_B" "canCall(address,address,bytes4)(bool,uint32)" "$NEW_RELAYER" "$MULTICALL_41002" "$MULTICALL_FN" --rpc-url http://localhost:8745
```

Both queries must return `true`, then `0`.
Repeat the [transfer checks](#verify-a-transfer) after revocation.
Then clear the admin key:

```bash theme={null}
unset DEPLOYER_KEY
```

## Transfer admin authority

Role `0` is `ADMIN_ROLE`. Multiple addresses can hold it.
Grant it to the replacement before revoking the deployer's admin role, so an admin remains available.

Create a replacement admin key for this local example:

```bash theme={null}
./bin/ibc keys new ecdsa admin-replacement --populate-config
```

```bash theme={null}
AM_A=$(./bin/ibc deploy show 41001 | jq -r '.targetData.accessManager')
AM_B=$(./bin/ibc deploy show 41002 | jq -r '.targetData.accessManager')
OLD_ADMIN=$(./bin/ibc keys show deployer | jq -er '.evmAddress')
NEW_ADMIN=$(./bin/ibc keys show admin-replacement | jq -er '.evmAddress')
DEPLOYER_KEY=$(./bin/ibc keys show deployer --private | jq -er '.privateKey')
NEW_ADMIN_KEY=$(./bin/ibc keys show admin-replacement --private | jq -er '.privateKey')
```

Fund the replacement on both chains:

```bash theme={null}
cast send "$NEW_ADMIN" --value 1ether --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$NEW_ADMIN" --value 1ether --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Grant admin authority with zero execution delay:

```bash theme={null}
cast send "$AM_A" "grantRole(uint64,address,uint32)" 0 "$NEW_ADMIN" 0 --rpc-url http://localhost:8545 --private-key "$DEPLOYER_KEY"
```

```bash theme={null}
cast send "$AM_B" "grantRole(uint64,address,uint32)" 0 "$NEW_ADMIN" 0 --rpc-url http://localhost:8745 --private-key "$DEPLOYER_KEY"
```

Use the replacement key to revoke the deployer's admin role:

```bash theme={null}
cast send "$AM_A" "revokeRole(uint64,address)" 0 "$OLD_ADMIN" --rpc-url http://localhost:8545 --private-key "$NEW_ADMIN_KEY"
```

```bash theme={null}
cast send "$AM_B" "revokeRole(uint64,address)" 0 "$OLD_ADMIN" --rpc-url http://localhost:8745 --private-key "$NEW_ADMIN_KEY"
```

Successful transactions establish that the replacement can administer both access managers.
Check the old admin's membership:

```bash theme={null}
cast call "$AM_A" "hasRole(uint64,address)(bool,uint32)" 0 "$OLD_ADMIN" --rpc-url http://localhost:8545
```

```bash theme={null}
cast call "$AM_B" "hasRole(uint64,address)(bool,uint32)" 0 "$OLD_ADMIN" --rpc-url http://localhost:8745
```

Both queries must return `false`, then `0`.

```bash theme={null}
unset DEPLOYER_KEY NEW_ADMIN_KEY
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.