From 5b137825d0f1d0aa333455c774d2290a6a03b438 Mon Sep 17 00:00:00 2001 From: Alexey Ostrovsky Date: Wed, 15 Oct 2025 04:02:50 +0300 Subject: [PATCH 1/3] feat: enchance upgrade process to use only one gov transaction --- app/upgrades.go | 1 + app/upgrades/types.go | 2 + app/upgrades/v1.0.2/upgrades.go | 62 ++++++++++++++++++- proposals/liquidstake_upgrade/1_admin_tx.json | 10 +++ proposals/liquidstake_upgrade/2_admin_tx.json | 6 ++ .../liquidstake_upgrade/example_step_1.json | 19 ------ ...ample_step_2.json => gov_transaction.json} | 39 +++--------- 7 files changed, 90 insertions(+), 49 deletions(-) create mode 100644 proposals/liquidstake_upgrade/1_admin_tx.json create mode 100644 proposals/liquidstake_upgrade/2_admin_tx.json delete mode 100644 proposals/liquidstake_upgrade/example_step_1.json rename proposals/liquidstake_upgrade/{example_step_2.json => gov_transaction.json} (65%) diff --git a/app/upgrades.go b/app/upgrades.go index 54a4c99..b5eb39a 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -35,6 +35,7 @@ func (app *TacChainApp) RegisterUpgradeHandlers() { LiquidStakeKeeper: &app.LiquidStakeKeeper, BankKeeper: app.BankKeeper, Erc20Keeper: &app.Erc20Keeper, + StakingKeeper: app.StakingKeeper, } app.GetStoreKeys() // register all upgrade handlers diff --git a/app/upgrades/types.go b/app/upgrades/types.go index b40b013..a170249 100644 --- a/app/upgrades/types.go +++ b/app/upgrades/types.go @@ -14,6 +14,7 @@ import ( authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" evmerc20keeper "github.com/cosmos/evm/x/erc20/keeper" @@ -31,6 +32,7 @@ type AppKeepers struct { LiquidStakeKeeper *liquidstakekeeper.Keeper BankKeeper bankkeeper.Keeper Erc20Keeper *evmerc20keeper.Keeper + StakingKeeper *stakingkeeper.Keeper } type ModuleManager interface { diff --git a/app/upgrades/v1.0.2/upgrades.go b/app/upgrades/v1.0.2/upgrades.go index 2e1df16..f734f12 100644 --- a/app/upgrades/v1.0.2/upgrades.go +++ b/app/upgrades/v1.0.2/upgrades.go @@ -5,22 +5,27 @@ package v102 import ( "context" + "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/Asphere-xyz/tacchain/app/upgrades" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" evmerc20types "github.com/cosmos/evm/x/erc20/types" "github.com/ethereum/go-ethereum/common" "golang.org/x/crypto/sha3" - ) // UpgradeName defines the on-chain upgrade name const UpgradeName = "v1.0.2" +// AdminAddress stands for the authority account that can unilaterally change any of the liquidstake modules params +// note: testnet +const AdminAddress = "tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr" + var Upgrade = upgrades.Upgrade{ UpgradeName: UpgradeName, CreateUpgradeHandler: CreateUpgradeHandler, @@ -38,6 +43,43 @@ func generateAddressFromDenom(denom string) (common.Address, error) { return common.BytesToAddress(hash.Sum(nil)), nil } +func initializeNewValidatorFields(ctx context.Context, ak *upgrades.AppKeepers) error { + params, err := ak.StakingKeeper.GetParams(ctx) + if err != nil { + return err + } + + validators, err := ak.StakingKeeper.GetValidators(ctx, params.MaxValidators) + if err != nil { + return err + } + + for _, validator := range validators { + newValidator := validator + newValidator.ValidatorBondShares = math.ZeroInt().ToLegacyDec() + newValidator.LiquidShares = math.ZeroInt().ToLegacyDec() + + err = ak.StakingKeeper.RemoveValidator(ctx, sdk.ValAddress(validator.OperatorAddress)) + if err != nil { + return err + } + err = ak.StakingKeeper.SetValidator(ctx, newValidator) + if err != nil { + return err + } + err = ak.StakingKeeper.SetValidatorByConsAddr(ctx, newValidator) + if err != nil { + return err + } + err = ak.StakingKeeper.SetValidatorByPowerIndex(ctx, newValidator) + if err != nil { + return err + } + } + + return nil +} + func CreateUpgradeHandler( mm upgrades.ModuleManager, configurator module.Configurator, @@ -69,6 +111,24 @@ func CreateUpgradeHandler( ak.Erc20Keeper.SetToken(sdkCtx, lsmTokenPair) + params := ak.LiquidStakeKeeper.GetParams(sdkCtx) + params.WhitelistAdminAddress = AdminAddress + ak.LiquidStakeKeeper.SetParams(sdkCtx, params) + + stakingParams, err := ak.StakingKeeper.GetParams(ctx) + if err != nil { + return newVM, err + } + + stakingParams.ValidatorBondFactor = stakingtypes.DefaultValidatorBondFactor + stakingParams.GlobalLiquidStakingCap = stakingtypes.DefaultGlobalLiquidStakingCap + stakingParams.ValidatorLiquidStakingCap = stakingtypes.DefaultValidatorLiquidStakingCap + + ak.StakingKeeper.SetParams(ctx, stakingParams) + + initializeNewValidatorFields(ctx, ak) + return newVM, nil } } + diff --git a/proposals/liquidstake_upgrade/1_admin_tx.json b/proposals/liquidstake_upgrade/1_admin_tx.json new file mode 100644 index 0000000..272760e --- /dev/null +++ b/proposals/liquidstake_upgrade/1_admin_tx.json @@ -0,0 +1,10 @@ +{ + "liquid_bond_denom": "stk/utac", + "unstake_fee_rate": "0.000000000000000000", + "lsm_disabled": true, + "min_liquid_stake_amount": "1000", + "cw_locked_pool_address": "", + "fee_account_address": "tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr", + "autocompound_fee_rate": "0.050000000000000000", + "whitelist_admin_address": "tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr" +} diff --git a/proposals/liquidstake_upgrade/2_admin_tx.json b/proposals/liquidstake_upgrade/2_admin_tx.json new file mode 100644 index 0000000..097cf73 --- /dev/null +++ b/proposals/liquidstake_upgrade/2_admin_tx.json @@ -0,0 +1,6 @@ +[ + { + "validator_address": "tacvaloper15lvhklny0khnwy7hgrxsxut6t6ku2cgkwu9tyt", + "target_weight": "10000" + } +] diff --git a/proposals/liquidstake_upgrade/example_step_1.json b/proposals/liquidstake_upgrade/example_step_1.json deleted file mode 100644 index b4bb053..0000000 --- a/proposals/liquidstake_upgrade/example_step_1.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "messages": [ - { - "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", - "authority": "tac10d07y265gmmuvt4z0w9aw880jnsr700jlgpywe", - "plan": { - "name": "v1.0.2", - "height": "50", - "info": "Liquidstake module implementation", - "upgraded_client_state": null - } - } - ], - "metadata": "", - "deposit": "1000000000000000000utac", - "title": "Liquidstake upgrade", - "summary": "Liquidstake implementation", - "expedited": false -} diff --git a/proposals/liquidstake_upgrade/example_step_2.json b/proposals/liquidstake_upgrade/gov_transaction.json similarity index 65% rename from proposals/liquidstake_upgrade/example_step_2.json rename to proposals/liquidstake_upgrade/gov_transaction.json index fa9dffd..b33da79 100644 --- a/proposals/liquidstake_upgrade/example_step_2.json +++ b/proposals/liquidstake_upgrade/gov_transaction.json @@ -1,34 +1,15 @@ { "messages": [ { - "@type": "/tac.liquidstake.v1beta1.MsgUpdateParams", + "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", "authority": "tac10d07y265gmmuvt4z0w9aw880jnsr700jlgpywe", - "params": { - "liquid_bond_denom": "stk/utac", - "unstake_fee_rate": "0.000000000000000000", - "lsm_disabled": true, - "min_liquid_stake_amount": "1000", - "cw_locked_pool_address": "", - "fee_account_address": "tac1w2q3mashs2k4wcpqzs5q5xewnhnnr7wslr34safzvwqzvuqh3gjqn6xzrj", - "autocompound_fee_rate": "0.050000000000000000", - "whitelist_admin_address": "tac1w2q3mashs2k4wcpqzs5q5xewnhnnr7wslr34safzvwqzvuqh3gjqn6xzrj" + "plan": { + "name": "v1.0.2", + "height": "1000", + "info": "This proposal aims to add liquid stake possibility for the TAC network", + "upgraded_client_state": null } }, - { - "@type": "/tac.liquidstake.v1beta1.MsgUpdateWhitelistedValidators", - "authority": "tac10d07y265gmmuvt4z0w9aw880jnsr700jlgpywe", - "whitelisted_validators": [ - { - "validator_address": "tacvaloper15lvhklny0khnwy7hgrxsxut6t6ku2cgkwu9tyt", - "target_weight": "10000" - } - ] - }, - { - "@type": "/tac.liquidstake.v1beta1.MsgSetModulePaused", - "authority": "tac10d07y265gmmuvt4z0w9aw880jnsr700jlgpywe", - "is_paused": false - }, { "@type": "/cosmos.evm.vm.v1.MsgUpdateParams", "authority": "tac10d07y265gmmuvt4z0w9aw880jnsr700jlgpywe", @@ -88,11 +69,11 @@ "0x0000000000000000000000000000000000001600" ] } - } + } ], - "metadata": "", + "metadata": "https://github.com/TacBuild/tacchain/pull/61", "deposit": "1000000000000000000utac", - "title": "Update Liquidstake params", - "summary": "Liquidstake params", + "title": "Add liquid staking functionality to TAC network", + "summary": "The main liquid stake module is compatible with the existing staking and ERC-20 modules of the TAC node, introducing liquid staking tokens (gTAC) that are automatically represented as ERC-20 assets. The second module expands the capabilities of liquid staking and allows users to transfer staked funds to LST bypassing an unbound staking period", "expedited": false } From ec6a5d3e873309694cd3d16b00a124e928f47a24 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenya Date: Wed, 15 Oct 2025 17:14:26 +0300 Subject: [PATCH 2/3] feat: upgrade handler with whitelist admin address --- app/upgrades/v1.0.2/upgrades.go | 137 +++++++++++++++--------- proposals/liquidstake_upgrade/README.md | 12 ++- 2 files changed, 91 insertions(+), 58 deletions(-) diff --git a/app/upgrades/v1.0.2/upgrades.go b/app/upgrades/v1.0.2/upgrades.go index f734f12..417776d 100644 --- a/app/upgrades/v1.0.2/upgrades.go +++ b/app/upgrades/v1.0.2/upgrades.go @@ -4,6 +4,9 @@ package v102 import ( "context" + "errors" + "fmt" + "regexp" "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" @@ -19,13 +22,11 @@ import ( "golang.org/x/crypto/sha3" ) +var WhitelistAdminAddressNotFound = errors.New("failed to find whitelist admin address") + // UpgradeName defines the on-chain upgrade name const UpgradeName = "v1.0.2" -// AdminAddress stands for the authority account that can unilaterally change any of the liquidstake modules params -// note: testnet -const AdminAddress = "tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr" - var Upgrade = upgrades.Upgrade{ UpgradeName: UpgradeName, CreateUpgradeHandler: CreateUpgradeHandler, @@ -35,51 +36,6 @@ var Upgrade = upgrades.Upgrade{ }, } -func generateAddressFromDenom(denom string) (common.Address, error) { - hash := sha3.NewLegacyKeccak256() - if _, err := hash.Write([]byte(denom)); err != nil { - return common.Address{}, err - } - return common.BytesToAddress(hash.Sum(nil)), nil -} - -func initializeNewValidatorFields(ctx context.Context, ak *upgrades.AppKeepers) error { - params, err := ak.StakingKeeper.GetParams(ctx) - if err != nil { - return err - } - - validators, err := ak.StakingKeeper.GetValidators(ctx, params.MaxValidators) - if err != nil { - return err - } - - for _, validator := range validators { - newValidator := validator - newValidator.ValidatorBondShares = math.ZeroInt().ToLegacyDec() - newValidator.LiquidShares = math.ZeroInt().ToLegacyDec() - - err = ak.StakingKeeper.RemoveValidator(ctx, sdk.ValAddress(validator.OperatorAddress)) - if err != nil { - return err - } - err = ak.StakingKeeper.SetValidator(ctx, newValidator) - if err != nil { - return err - } - err = ak.StakingKeeper.SetValidatorByConsAddr(ctx, newValidator) - if err != nil { - return err - } - err = ak.StakingKeeper.SetValidatorByPowerIndex(ctx, newValidator) - if err != nil { - return err - } - } - - return nil -} - func CreateUpgradeHandler( mm upgrades.ModuleManager, configurator module.Configurator, @@ -111,9 +67,18 @@ func CreateUpgradeHandler( ak.Erc20Keeper.SetToken(sdkCtx, lsmTokenPair) + logger := sdkCtx.Logger() params := ak.LiquidStakeKeeper.GetParams(sdkCtx) - params.WhitelistAdminAddress = AdminAddress - ak.LiquidStakeKeeper.SetParams(sdkCtx, params) + adminAddress, err := getAdminAddressFromPlanInfo(plan.Info) + switch err { + case WhitelistAdminAddressNotFound, nil: + default: + logger.Error("invalid whitelist admin address in plan info", "error", err) + } + params.WhitelistAdminAddress = adminAddress + if err := ak.LiquidStakeKeeper.SetParams(sdkCtx, params); err != nil { + return newVM, fmt.Errorf("failed to set params for liquidstake module: %w", err) + } stakingParams, err := ak.StakingKeeper.GetParams(ctx) if err != nil { @@ -124,11 +89,77 @@ func CreateUpgradeHandler( stakingParams.GlobalLiquidStakingCap = stakingtypes.DefaultGlobalLiquidStakingCap stakingParams.ValidatorLiquidStakingCap = stakingtypes.DefaultValidatorLiquidStakingCap - ak.StakingKeeper.SetParams(ctx, stakingParams) + if err := ak.StakingKeeper.SetParams(ctx, stakingParams); err != nil { + return newVM, fmt.Errorf("failed to set params for staking module: %w", err) + } - initializeNewValidatorFields(ctx, ak) + if err := initializeNewValidatorFields(ctx, ak); err != nil { + return newVM, fmt.Errorf("failed to initialize new validtor fields: %w", err) + } return newVM, nil } } +func generateAddressFromDenom(denom string) (common.Address, error) { + hash := sha3.NewLegacyKeccak256() + if _, err := hash.Write([]byte(denom)); err != nil { + return common.Address{}, err + } + return common.BytesToAddress(hash.Sum(nil)), nil +} + +func initializeNewValidatorFields(ctx context.Context, ak *upgrades.AppKeepers) error { + params, err := ak.StakingKeeper.GetParams(ctx) + if err != nil { + return fmt.Errorf("failed to get staking params: %w", err) + } + + validators, err := ak.StakingKeeper.GetValidators(ctx, params.MaxValidators) + if err != nil { + return fmt.Errorf("failed to get validators: %w", err) + } + + for _, validator := range validators { + newValidator := validator + newValidator.ValidatorBondShares = math.LegacyZeroDec() + newValidator.LiquidShares = math.LegacyZeroDec() + + err = ak.StakingKeeper.RemoveValidator(ctx, sdk.ValAddress(validator.OperatorAddress)) + if err != nil { + return fmt.Errorf("failed to remove validator: %w", err) + } + err = ak.StakingKeeper.SetValidator(ctx, newValidator) + if err != nil { + return fmt.Errorf("failed to set validator: %w", err) + } + err = ak.StakingKeeper.SetValidatorByConsAddr(ctx, newValidator) + if err != nil { + return fmt.Errorf("failed to set validator by consensus address: %w", err) + } + err = ak.StakingKeeper.SetValidatorByPowerIndex(ctx, newValidator) + if err != nil { + return fmt.Errorf("failed to set validator by power index: %w", err) + } + } + + return nil +} + +func getAdminAddressFromPlanInfo(info string) (string, error) { + key := "whitelist_admin_address" + addressPrefix := sdk.Bech32MainPrefix + re := regexp.MustCompile(key + `:\s*(` + addressPrefix + `[a-zA-Z0-9]{42})`) + matches := re.FindStringSubmatch(info) + + var addr string + if len(matches) > 1 { + addr = matches[1] + } else { + return "", WhitelistAdminAddressNotFound + } + if _, err := sdk.AccAddressFromBech32(addr); err != nil { + return "", fmt.Errorf("failed to validate whitelist admin address %s: %w", addr, err) + } + return addr, nil +} diff --git a/proposals/liquidstake_upgrade/README.md b/proposals/liquidstake_upgrade/README.md index a04da9c..af1ecfd 100644 --- a/proposals/liquidstake_upgrade/README.md +++ b/proposals/liquidstake_upgrade/README.md @@ -1,9 +1,11 @@ ## Liquidstake upgrade **Steps:** -1. **Send gov tx to upgrade binary** - - [Localnet example](./example_step_1.json) -2. **Send gov tx to update module params** +1. Prepare proposal. - [Localnet example](./example_step_2.json) + **Important note**: If you want to set the `WhitelistAdminAddress`, you need to add the line `whitelist_admin_address: ` (e.g., tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr) to the `plan.info` variable in the governance transaction message. The address must be valid; otherwise, an error log will appear during the update, and you will have to submit the governance transaction again. +2. Send gov tx to upgrade binary. (e.g., [gov_transaction.json](./gov_transaction.json)) +3. Send admin tx to update module state. + + - Update params. (e.g., [1_admin_tx.json](./1_admin_tx.json)) + - Update validators. (e.g., [2_admin_tx.json](./2_admin_tx.json)) From 97067a2512ab958772a143a5f7b8d0ee651b9e48 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenya Date: Wed, 15 Oct 2025 22:32:01 +0300 Subject: [PATCH 3/3] fix: fixed bug with whitelist admin address in upgrade handler chore: updated proposal docs --- app/upgrades/v1.0.2/upgrades.go | 4 +- app/upgrades/v1.0.2/upgrades_test.go | 37 +++++++++++++++ proposals/liquidstake_upgrade/README.md | 45 +++++++++++++++++++ .../liquidstake_upgrade/gov_transaction.json | 6 +-- 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 app/upgrades/v1.0.2/upgrades_test.go diff --git a/app/upgrades/v1.0.2/upgrades.go b/app/upgrades/v1.0.2/upgrades.go index 417776d..9556af4 100644 --- a/app/upgrades/v1.0.2/upgrades.go +++ b/app/upgrades/v1.0.2/upgrades.go @@ -148,8 +148,8 @@ func initializeNewValidatorFields(ctx context.Context, ak *upgrades.AppKeepers) func getAdminAddressFromPlanInfo(info string) (string, error) { key := "whitelist_admin_address" - addressPrefix := sdk.Bech32MainPrefix - re := regexp.MustCompile(key + `:\s*(` + addressPrefix + `[a-zA-Z0-9]{42})`) + addressPrefix := sdk.GetConfig().GetBech32AccountAddrPrefix() + re := regexp.MustCompile(key + `:\s*(` + addressPrefix + `[a-zA-Z0-9]+)`) matches := re.FindStringSubmatch(info) var addr string diff --git a/app/upgrades/v1.0.2/upgrades_test.go b/app/upgrades/v1.0.2/upgrades_test.go new file mode 100644 index 0000000..57abdd5 --- /dev/null +++ b/app/upgrades/v1.0.2/upgrades_test.go @@ -0,0 +1,37 @@ +package v102 + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestGetAdminAddressFromPlanInfo(t *testing.T) { + testCases := []struct { + info string + expected string + expectedError bool + }{ + { + info: "", + expected: "", + expectedError: true, + }, + { + info: "This proposal aims to add liquid stake possibility for the TAC network. whitelist_admin_address: tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr", + expected: "tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr", + expectedError: false, + }, + } + + sdk.GetConfig().SetBech32PrefixForAccount("tac", "tacpub") + + for _, tc := range testCases { + addr, err := getAdminAddressFromPlanInfo(tc.info) + if !tc.expectedError && err != nil { + t.Error(err) + } + require.Equal(t, tc.expected, addr) + } +} diff --git a/proposals/liquidstake_upgrade/README.md b/proposals/liquidstake_upgrade/README.md index af1ecfd..571a4fc 100644 --- a/proposals/liquidstake_upgrade/README.md +++ b/proposals/liquidstake_upgrade/README.md @@ -9,3 +9,48 @@ - Update params. (e.g., [1_admin_tx.json](./1_admin_tx.json)) - Update validators. (e.g., [2_admin_tx.json](./2_admin_tx.json)) + +**Localnet Node Upgrade Checklist (v1.0.1 to v1.0.2)** + +1. Initial Setup and Governance Proposal + 1. Checkout to the old node version. + 2. Build the old version. + ```shell + make build + ``` + 3. Initialize and start the local network (with a short voting period). + ```shell + make localnet-init GOV_TIME_SECONDS=60 TACCHAIND=./build/tacchaind + make localnet-start TACCHAIND=./build/tacchaind + ``` + 4. (New Session) Submit the upgrade governance proposal (targeting height 400). + ```shell + ./build/tacchaind tx gov submit-proposal ./proposals/liquidstake_upgrade/gov_transaction.json --from validator --fees 200000000000000000utac --gas-adjustment 2 --gas 500000 + ``` + 5. Vote 'Yes' on the proposal. + ```shell + ./build/tacchaind tx gov vote 1 yes --from validator --fees 80000000000000000utac + ``` + 6. Wait for the node to stop automatically at upgrade height (400). +2. Upgrade the Node Software + 1. Checkout to the new node version. + 2. Build the new version. + ```shell + make build + ``` + 3. Restart the node using the new binary. + ```shell + make localnet-start TACCHAIND=./build/tacchaind + ``` + 4. Verification: Node starts successfully at height 400. +3. Post-Upgrade Liquidstake Module Configuration + 1. Update Liquidstake module parameters (e.g., setting the admin address). + ```shell + tacchaind tx liquidstake update-params ./proposals/liquidstake_upgrade/1_admin_tx.json --from validator --fees 80000000000000000utac + ``` + 2. Unpause/enable the Liquidstake module. + ```shell + tacchaind tx liquidstake pause-module false --from validator --fees 80000000000000000utac + ``` + +The upgraded node is successfully running, and the module is active. diff --git a/proposals/liquidstake_upgrade/gov_transaction.json b/proposals/liquidstake_upgrade/gov_transaction.json index b33da79..97cef14 100644 --- a/proposals/liquidstake_upgrade/gov_transaction.json +++ b/proposals/liquidstake_upgrade/gov_transaction.json @@ -5,8 +5,8 @@ "authority": "tac10d07y265gmmuvt4z0w9aw880jnsr700jlgpywe", "plan": { "name": "v1.0.2", - "height": "1000", - "info": "This proposal aims to add liquid stake possibility for the TAC network", + "height": "400", + "info": "This proposal aims to add liquid stake possibility for the TAC network. whitelist_admin_address: tac15lvhklny0khnwy7hgrxsxut6t6ku2cgknw79fr", "upgraded_client_state": null } }, @@ -69,7 +69,7 @@ "0x0000000000000000000000000000000000001600" ] } - } + } ], "metadata": "https://github.com/TacBuild/tacchain/pull/61", "deposit": "1000000000000000000utac",