diff --git a/docs/tutorials/02-channel-upgrades/01-intro.md b/docs/tutorials/02-channel-upgrades/01-intro.md new file mode 100644 index 00000000000..83adb299946 --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/01-intro.md @@ -0,0 +1,38 @@ +--- +title: Introduction +sidebar_label: Introduction +sidebar_position: 1 +slug: /channel-upgrades/intro +--- + +import HighlightTag from '@site/src/components/HighlightTag'; +import HighlightBox from '@site/src/components/HighlightBox'; + +# Introduction + + + +This is a tutorial for upgrading an existing ICS 20 transfer channel to wrap it with the ICS 29 Fee Middleware. + + + +- Basic Knowledge of Cosmos SDK. + - If you are new to Cosmos SDK, we recommend you to go through the first two categories of the [Developer Portal](https://tutorials.cosmos.network/academy/1-what-is-cosmos/). +- Basic Knowledge of [the Fee Middleware module](https://ibc.cosmos.network/main/middleware/ics29-fee/overview). +- Basic knowledge of [channel upgrades](https://ibc.cosmos.network/main/ibc/channel-upgrades). + + + +## Scope + +This tutorial will cover the process of upgrading an existing ICS 20 transfer channel to add packet incentivization using the Fee Middleware. + + + +In this tutorial, you will: + +- Run two IBC-enabled blockchains locally. +- Open an ICS 20 transfer channel using the Hermes relayer. +- Upgrade the ICS 20 transfer channel to add ICS-29 Fee Middleware. + + diff --git a/docs/tutorials/02-channel-upgrades/02-setup-env.md b/docs/tutorials/02-channel-upgrades/02-setup-env.md new file mode 100644 index 00000000000..ad4c4cd2b20 --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/02-setup-env.md @@ -0,0 +1,75 @@ +--- +title: Set Up Your Work Environment +sidebar_label: Set Up Your Work Environment +sidebar_position: 2 +slug: /channel-upgrades/setup-env +--- + +import HighlightBox from '@site/src/components/HighlightBox'; + +# Set up your work environment + +On this page, you can find helpful links to set up your work environment. + + + +In this section, you can find all you need to install: + +- [jq](https://jqlang.github.io/jq/) +- [gm](https://github.com/informalsystems/gm/) +- [ibc-go simd](https://github.com/cosmos/ibc-go/) +- [Hermes v1.9.0](https://hermes.informal.systems/) + + + +## jq + +Install `jq` following the instructions on [its website](https://jqlang.github.io/jq/download/). Test if it is installed by running the following command: + +```bash +jq --version +``` + +At the moment of writing this tutorial, the version of `jq` used was 1.6. + +## gm + +The [gaiad manager](https://github.com/informalsystems/gm) (`gm`) is a configurable command-line tool (CLI) that helps manage local gaiad networks. It can be used to easily and quickly run a local setup of multiple blockchains. Follow the installation steps [here](https://github.com/informalsystems/gm#how-to-run). + +## ibc-go simd + +Download the simd binary from the [v8.1.0 release](https://github.com/cosmos/ibc-go/releases/tag/v8.1.0). This chain binary has the Fee Middleware already wired up and wrapping the ICS 20 transfer application. If you want to know how to wire up the Fee Middleware, please read [this section](../01-fee/04-wire-feeibc-mod.md) from the Fee Middleware tutorial. + +## Hermes + +Install Hermes relayer version `v1.9.0` via cargo following the instructions on the [Hermes website](https://hermes.informal.systems/quick-start/installation.html#install-via-cargo) or by using the command below. + +```bash +cargo install ibc-relayer-cli --version 1.9.0 --bin hermes --locked +``` + +Test if Hermes is installed by running the following command: + +```bash +hermes version +``` + +# Folder structure + +This tutorial assumes the following folder structure: + +```text +testing +├── bin +│ ├── chain1 +│ │ ├── simd +│ │ └── proposal.json +│ └── chain2 +│ └── simd +├── gm +└── hermes + ├── hermes + └── config.toml +``` + +`simd` if the chain binary that will be used to run 2 blockchains (`chain1` and `chain2`). THe folder `gm` will contain the data folders for both blockchains. diff --git a/docs/tutorials/02-channel-upgrades/03-run-chains.md b/docs/tutorials/02-channel-upgrades/03-run-chains.md new file mode 100644 index 00000000000..7f4c2b09e6d --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/03-run-chains.md @@ -0,0 +1,110 @@ +--- +title: Run 2 Cosmos SDK Blockchains Locally +sidebar_label: Run 2 Cosmos SDK Blockchains Locally +sidebar_position: 3 +slug: /channel-upgrades/run-chains +--- + +# Run 2 Cosmos SDK blockchains locally + +The gm tool uses a [configuration file](https://github.com/informalsystems/gm/blob/master/gm.toml). This tutorial uses the following configuration file for gm: + +```yaml title="gm.toml" +[global] +add_to_hermes=true +home_dir="~/testing/gm" + +[global.hermes] +binary="~/testing/hermes/hermes" +config="~/testing/hermes/config.toml" + +[chain1] + gaiad_binary="~/testing/bin/chain1/simd" + ports_start_at=27000 + +[chain2] + gaiad_binary="~/testing/bin/chain2/simd" + ports_start_at=27010 +``` + +The configuration file needs to be placed in `$HOME/.gm`. This configuration file sets up 2 blockchains (`chain1` and `chain2`), each with 2 accounts (1 validator, 1 wallet). The ports where the CometBFT RPC interface for each chain is 27000 for `chain1` and 27010 for `chain2`. + +In order to shorten the voting period of governance proposal, we are going to change some of the `x/gov` module parameters in the `genesis.json` file, so that we can complete the upgrade faster. These are the changes needed in the `genesis.json` of `chain1`: + +```json title="genesis.json" +"gov": { + "starting_proposal_id": "1", + "deposits": [], + "votes": [], + "proposals": [], + "deposit_params": null, + "voting_params": null, + "tally_params": null, + "params": { + "min_deposit": [ + { + "denom": "stake", +// minus-diff-line +- "amount": "10000000" +// plus-diff-line ++ "amount": "100" + } + ], + "max_deposit_period": "172800s", +// minus-diff-line +- "voting_period": "172800s", +// plus-diff-line ++ "voting_period": "180s", + "quorum": "0.334000000000000000", +// minus-diff-line +- "threshold": "0.500000000000000000", +// plus-diff-line ++ "threshold": "0.300000000000000000", + "veto_threshold": "0.334000000000000000", + "min_initial_deposit_ratio": "0.000000000000000000", + "proposal_cancel_ratio": "0.500000000000000000", + "proposal_cancel_dest": "", + "expedited_voting_period": "86400s", + "expedited_threshold": "0.667000000000000000", + "expedited_min_deposit": [ + { + "denom": "stake", + "amount": "50000000" + } + ], + "burn_vote_quorum": false, + "burn_proposal_deposit_prevote": false, + "burn_vote_veto": true, + "min_deposit_ratio": "0.010000000000000000" + }, + "constitution": "" +} +``` + +We start both blockchains by running the following command: + +```bash +gm start +``` + +For convenience, we are going to store a few account addresses as variables in the current shell environment. Execute the following commands to store the relayer addresses on chains `chain1` and `chain2`, respectively: + +```bash +export RLY_CHAIN1=$(simd keys show wallet -a \ +--keyring-backend test \ +--home ../../gm/chain1) && echo $RLY_CHAIN1; +export RLY_CHAIN2=$(simd keys show wallet -a \ +--keyring-backend test \ +--home ../../gm/chain2) && echo $RLY_CHAIN2; +``` + +And execute also the following commands to store the validator account addresses on chains `chain1` and `chain2` that we will use throughout this tutorial: + +```bash +export VALIDATOR_CHAIN1=$(simd keys show validator -a \ +--keyring-backend test \ +--home ../../gm/chain1) && echo $VALIDATOR_CHAIN1; +export VALIDATOR_CHAIN2=$(simd keys show validator -a \ +--keyring-backend test \ +--home ../../gm/chain2) && echo $VALIDATOR_CHAIN2; +``` diff --git a/docs/tutorials/02-channel-upgrades/04-open-transfer-channel.md b/docs/tutorials/02-channel-upgrades/04-open-transfer-channel.md new file mode 100644 index 00000000000..77636dcbd71 --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/04-open-transfer-channel.md @@ -0,0 +1,167 @@ +--- +title: Open transfer channel +sidebar_label: Open transfer channel +sidebar_position: 4 +slug: /channel-upgrades/open-channel +--- + +# Open an ICS 20 transfer channel + +The relayer needs to submit transactions on both blockchains, so we run the following command to add the keys for the accounts on `chain1` and `chain2` that the relayer can use to submit transactions: + +```bash +gm hermes keys +``` + +The relayer also needs a [configuration file](https://github.com/informalsystems/hermes/blob/master/config.toml). In this tutorial we will have the configuration file in the same folder as the relayer binary and specify it using the `--config` flag in each command. + +You can generate a default configuration by running: + +```bash +gm hermes config +``` + +This tutorial has been completed with the following configuration file: + +```yaml +[global] +log_level = 'trace' + +[telemetry] +enabled = true +host = '127.0.0.1' +port = 3001 + +# Specify the mode to be used by the relayer. [Required] +[mode] + +# Specify the client mode. +[mode.clients] + +# Whether or not to enable the client workers. [Required] +enabled = true + +# Whether or not to enable periodic refresh of clients. [Default: true] +# Note: Even if this is disabled, clients will be refreshed automatically if +# there is activity on a connection or channel they are involved with. +refresh = true + +# Whether or not to enable misbehaviour detection for clients. [Default: false] +misbehaviour = true + +# Specify the connections mode. +[mode.connections] + +# Whether or not to enable the connection workers for handshake completion. [Required] +enabled = true + +[mode.channels] +enabled = true + +# Specify the packets mode. +[mode.packets] + +# Whether or not to enable the packet workers. [Required] +enabled = true + +clear_interval = 1 + +[[chains]] +id = 'chain1' +type = 'CosmosSdk' +rpc_addr = 'http://localhost:27000' +grpc_addr = 'http://localhost:27002' +event_source = { mode = 'push', url = 'ws://127.0.0.1:27000/websocket', batch_delay = '500ms' } +rpc_timeout = '15s' +account_prefix = 'cosmos' +key_name = 'wallet' +store_prefix = 'ibc' +gas_price = { price = 0.001, denom = 'stake' } +max_gas = 1000000 +clock_drift = '5s' +trusting_period = '14days' +trust_threshold = { numerator = '1', denominator = '3' } + +[[chains]] +id = 'chain2' +type = 'CosmosSdk' +rpc_addr = 'http://localhost:27010' +grpc_addr = 'http://localhost:27012' +event_source = { mode = 'push', url = 'ws://127.0.0.1:27010/websocket', batch_delay = '500ms' } +rpc_timeout = '15s' +account_prefix = 'cosmos' +key_name = 'wallet' +store_prefix = 'ibc' +gas_price = { price = 0.001, denom = 'stake' } +max_gas = 1000000 +clock_drift = '5s' +trusting_period = '14days' +trust_threshold = { numerator = '1', denominator = '3' } +``` + +With both blockchains running, we can run the following command in hermes to establish a connection and an ICS 20 transfer channel: + +```bash +hermes --config config.toml create channel --a-chain chain1 \ +--b-chain chain2 \ +--a-port transfer \ +--b-port transfer \ +--new-client-connection +``` + +When both the connection and channel handshakes complete, the output on the console looks like this: + +```bash +SUCCESS Channel { + ordering: Unordered, + a_side: ChannelSide { + chain: BaseChainHandle { + chain_id: ChainId { + id: "chain1", + version: 0, + }, + runtime_sender: Sender { .. }, + }, + client_id: ClientId( + "07-tendermint-0", + ), + connection_id: ConnectionId( + "connection-0", + ), + port_id: PortId( + "transfer", + ), + channel_id: Some( + ChannelId( + "channel-0", + ), + ), + version: None, + }, + b_side: ChannelSide { + chain: BaseChainHandle { + chain_id: ChainId { + id: "chain2", + version: 0, + }, + runtime_sender: Sender { .. }, + }, + client_id: ClientId( + "07-tendermint-0", + ), + connection_id: ConnectionId( + "connection-0", + ), + port_id: PortId( + "transfer", + ), + channel_id: Some( + ChannelId( + "channel-0", + ), + ), + version: None, + }, + connection_delay: 0ns, +} +``` diff --git a/docs/tutorials/02-channel-upgrades/05-upgrade-channel.md b/docs/tutorials/02-channel-upgrades/05-upgrade-channel.md new file mode 100644 index 00000000000..421c9fafb6e --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/05-upgrade-channel.md @@ -0,0 +1,213 @@ +--- +title: Upgrade channel +sidebar_label: Upgrade channel +sidebar_position: 5 +slug: /channel-upgrades/upgrade-channel +--- + +# Upgrade the ICS 20 transfer channel + +## Start the relayer + +We start the relayer: + +```bash +hermes --config config.toml start +``` + +## Initiate the upgrade + +The [initiation of the upgrade process is authority-gated](https://ibc.cosmos.network/main/ibc/channel-upgrades#governance-gating-on-chanupgradeinit), and thus the upgrade will begin when a governance proposal passes. There a couple of ways in which the initiation can be triggered. + +### With `upgrade-channels` CLI + +We have a [CLI command](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/core/04-channel/client/cli/tx.go#L62) that streamlines the submission of the governance proposal to initiate the upgrade. By default the CLI will upgrade all channels on `transfer` port with the provided channel version string. Both the port and the channels to be upgraded can be customized with CLI flags. To keep it simple, and since we only have one ICS20 channel, for this example the CLI command would look like the following: + +```bash +simd tx ibc channel upgrade-channels "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" --deposit 10stake +``` + +After the governance proposal is submitted, the user can vote for it and wait that it passes, but these steps are shown in more detail in the next section. + +### With `submit-proposal` CLI + +And alternative way of submitting the proposal is using `x/gov` module's CLI command. The contents of the governance proposal are: + +```json title=proposal.json +{ + "title": "Channel upgrade init", + "summary": "Channel upgrade init", + "messages": [ + { + "@type": "/ibc.core.channel.v1.MsgChannelUpgradeInit", + "signer": "cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn", + "port_id": "transfer", + "channel_id": "channel-0", + "fields": { + "ordering": "ORDER_UNORDERED", + "connection_hops": ["connection-0"], + "version": "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" + } + } + ], + "metadata": "AQ==", + "deposit": "100005stake" +} +``` + +where `cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn` is the address of the governance module on `chain1`. You can retrieve the account of the governance module running the following command: + +```bash +simd query auth module-account gov --node http://localhost:27000 +``` + +The upgrade will modify the channel version to include the fee version. We submit the proposal: + +```bash +simd tx gov submit-proposal ./proposal_upgrade_channel.json \ +--from $VALIDATOR_CHAIN1 \ +--chain-id chain1 \ +--keyring-backend test \ +--home ../../gm/chain1 \ +--node http://localhost:27000 +``` + +Now we vote for the proposal: + +```bash +simd tx gov vote 1 yes \ +--from $VALIDATOR_CHAIN1 \ +--chain-id chain1 \ +--keyring-backend test \ +--home ../../gm/chain1 \ +--node http://localhost:27000 +``` + +And we wait for the voting period to end (approximately 3 minutes). Once it ends we can check that the proposal has passed (i.e. the status has changed from `PROPOSAL_STATUS_VOTING_PERIOD` to `PROPOSAL_STATUS_PASSED`): + +```bash +simd q gov proposals --node http://localhost:27000 +``` + +```yaml +pagination: + total: "1" +proposals: +- deposit_end_time: "2024-01-27T21:29:52.430508Z" + final_tally_result: + abstain_count: "0" + no_count: "0" + no_with_veto_count: "0" + yes_count: "1000000" + id: "1" + messages: + - type: /ibc.core.channel.v1.MsgChannelUpgradeInit + value: + channel_id: channel-0 + fields: {} + port_id: transfer + signer: cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn + metadata: AQ== + proposer: cosmos1vdy5fp0jy2l2ees870a7mls357v7uad6ufzcyz + status: 3 + submit_time: "2024-01-25T21:29:52.430508Z" + summary: Channel upgrade init + title: Channel upgrade init + total_deposit: + - amount: "100005" + denom: stake + voting_end_time: "2024-01-25T21:32:52.430508Z" + voting_start_time: "2024-01-25T21:29:52.430508Z" +``` + +Now we wait for the relayer to complete the upgrade handshake. + +## Check ugprade completed + +Once the handshake has completed we verify that the channel has successfully upgraded: + +```bash +simd q ibc channel channels --node http://localhost:27000 +``` + +```yaml +channels: +- channel_id: channel-0 + connection_hops: + - connection-0 + counterparty: + channel_id: channel-0 + port_id: transfer + ordering: ORDER_UNORDERED + port_id: transfer + state: STATE_OPEN + upgrade_sequence: "1" + version: '{"fee_version":"ics29-1","app_version":"ics20-1"}' +height: + revision_height: "135" + revision_number: "0" +pagination: + next_key: null + total: "0" +``` + +The channel version on `chain1` is what we expect. + +```bash +simd q ibc-fee channels --node http://localhost:27000 +``` + +```yaml +fee_enabled_channels: +- channel_id: channel-0 + port_id: transfer +pagination: + next_key: null + total: "0" +``` + +As we expect there is one incentivized channel. + +```bash +simd q ibc channel channels --node http://localhost:27010 +``` + +```yaml +channels: +- channel_id: channel-0 + connection_hops: + - connection-0 + counterparty: + channel_id: channel-0 + port_id: transfer + ordering: ORDER_UNORDERED + port_id: transfer + state: STATE_OPEN + upgrade_sequence: "1" + version: '{"fee_version":"ics29-1","app_version":"ics20-1"}' +height: + revision_height: "138" + revision_number: "0" +pagination: + next_key: null + total: "0" +``` + +The channel version on `chain2` is also what we expect. + +```bash +simd q ibc-fee channels --node http://localhost:27010 +``` + +```yaml +fee_enabled_channels: +- channel_id: channel-0 + port_id: transfer +pagination: + next_key: null + total: "0" +``` + +As we expect there is one incentivized channel as well on `chain2`. + +From now ICS 20 packets sent on this channel can be incentivized. diff --git a/docs/tutorials/02-channel-upgrades/06-incentivize-packet.md b/docs/tutorials/02-channel-upgrades/06-incentivize-packet.md new file mode 100644 index 00000000000..f6b14a5b87b --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/06-incentivize-packet.md @@ -0,0 +1,105 @@ +--- +title: Incentivize packet +sidebar_label: Incentivize packet +sidebar_position: 6 +slug: /channel-upgrades/incentivize-packet +--- + +# Incentivize an ICS 20 transfer packet + +## Register the counterparty payee + +All incentivization fees are paid to accounts on the chain from where the IBC packets originate. To ensure that the relayer that delivers the `MsgRecvPacket` on the destination chain is correctly compensated, the counterparty payee address (i.e. the account address of the relayer on the source chain) needs to be registered on the destination chain. Throughout this tutorial the source chain is `chain1` and the destination chain is `chain2`, therefore we need to register the account address of the relayer on chain `chain1` (`RLY_CHAIN1`) on chain `chain2` (where the relayer has the account address `RLY_CHAIN2`): + +```bash +simd tx ibc-fee register-counterparty-payee transfer channel-0 $RLY_CHAIN2 $RLY_CHAIN1 \ +--from $RLY_CHAIN2 \ +--chain-id chain2 \ +--keyring-backend test \ +--home ../../gm/chain2 \ +--node http://localhost:27010 +``` + +Once the above command succeeds, then we can verify which counterparty payee is registered on chain `chain2` for account `RLY_CHAIN2`: + +```bash +simd q ibc-fee counterparty-payee channel-0 $RLY_CHAIN2 --node http://localhost:27010 +``` + +```yaml +counterparty_payee: cosmos1vdy5fp0jy2l2ees870a7mls357v7uad6ufzcyz +``` + +We see that the counterparty payee address matches what we expected (i.e. the `RLY_CHAIN1` address). In this tutorial we are going to send only one packet from chain `chain1` to chain `chain2`, so we only need to register the counterparty payee on chain `chain2`. In real life circumstances relayers relay packets on both directions (i.e. from chain `chain1` to `chain2` and also viceversa), and thus relayers should register as well on chain `chain1` the counterparty payee address to be compensated for delivering the `MsgRecvPacket` on chain `chain1`. + +## Multi-message transaction with single `MsgPayPacketFee` message + +We first generate (not execute) an IBC transfer transaction (again `1000samoleans` from `VALIDATOR_CHAIN1` to `VALIDATOR_CHAIN2`): + +```bash +simd tx ibc-transfer transfer transfer channel-0 $VALIDATOR_CHAIN2 1000samoleans \ +--from $VALIDATOR_CHAIN1 \ +--chain-id chain1 \ +--keyring-backend test \ +--home ../../gm/chain1 \ +--node tcp://localhost:27000 \ +--generate-only > transfer.json +``` + +Then we prepend a `MsgPayPacketFee`, sign the transaction and broadcast it. Please note that `jq` is used to manipulate the transaction JSON file, making it a multi-message transaction. In practice, this multi-message transaction would be built using a gRPC or web client, for example, a web-based wallet application could fulfill this role. Note also that the `signer` field uses the address of `VALIDATOR_CHAIN1`. + +` +jq '.body.messages |= [{"@type":"/ibc.applications.fee.v1.MsgPayPacketFee","fee": {"recv_fee": [{"denom": "samoleans", "amount": "50"}], "ack_fee": [{"denom": "samoleans", "amount": "25"}], "timeout_fee": [{"denom": "samoleans", "amount": "10"}]}, "source_port_id": "transfer", "source_channel_id": "channel-0", "signer": "cosmos18phmkrpnn6gmpzscf6hnf5zpv06sygxc6f2v92" }] + .' transfer.json > incentivized_transfer.json +` + +```bash +simd tx sign incentivized_transfer.json \ +--from $VALIDATOR_CHAIN1 \ +--chain-id chain1 \ +--keyring-backend test \ +--home ../../gm/chain1 \ +--node tcp://localhost:27000 > signed.json +``` + +```bash +simd tx broadcast signed.json \ +--home ../../gm/chain1 \ +--node tcp://localhost:27000 +``` + +We wait for the relayer to relay the packet, and then we query the balance of account `VALIDATOR_CHAIN2` on chain `chain2` and see that it has indeed received an equivalent amount of vouchers for the `1000samoleans` sent by `VALIDATOR_CHAIN1`: + +```yaml +simd q bank balances $VALIDATOR_CHAIN2 --node http://localhost:27010 +``` + +```bash +balances: +- amount: "1000" + denom: ibc/27A6394C3F9FF9C9DCF5DFFADF9BB5FE9A37C7E92B006199894CF1824DF9AC7C +- amount: "100000000" + denom: samoleans +- amount: "99000000" + denom: stake +pagination: + total: "3" +``` + +We check as well the balance for account `VALIDATOR_CHAIN1` on chain `chain1`: + +```bash +simd q bank balances $WALLET_1 --node http://localhost:16657 +``` + +```yaml +./simd q bank balances $VALIDATOR_CHAIN1 --node http://localhost:27000 +balances: +- amount: "99998925" + denom: samoleans +- amount: "99000000" + denom: stake +pagination: + total: "2" +``` + +An amount of `1075samoleans` has been deducted, which is what we expected: `1000samoleans` have been transfered to `VALIDATOR_CHAIN2` and `75stake` have been paid for the receive and acknowledgment fees. The timeout fee has been refunded to `VALIDATOR_CHAIN1` and the relayer address `RLY_CHAIN1` should have gained `75samoleans` for submitting the `MsgRecvPacket` and the `MsgAcknowledgement` messages. diff --git a/docs/tutorials/02-channel-upgrades/_category_.json b/docs/tutorials/02-channel-upgrades/_category_.json new file mode 100644 index 00000000000..a43d5906845 --- /dev/null +++ b/docs/tutorials/02-channel-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Channel Upgrades", + "position": 2, + "link": null +}