feat: CLI tooling to generate proposal JSONs (#13304)
This commit is contained in:
parent
73debf46d9
commit
7252f4a758
@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### Features
|
||||
|
||||
* (cli) [#13304](https://github.com/cosmos/cosmos-sdk/pull/13304) Add `tx gov draft-proposal` command for generating proposal JSONs.
|
||||
* (cli) [#13207](https://github.com/cosmos/cosmos-sdk/pull/13207) Reduce user's password prompts when calling keyring `List()` function
|
||||
* (x/authz) [#12648](https://github.com/cosmos/cosmos-sdk/pull/12648) Add an allow list, an optional list of addresses allowed to receive bank assets via authz MsgSend grant.
|
||||
* (sdk.Coins) [#12627](https://github.com/cosmos/cosmos-sdk/pull/12627) Make a Denoms method on sdk.Coins.
|
||||
|
||||
@ -4,6 +4,7 @@ package nftv1beta1
|
||||
import (
|
||||
_ "cosmossdk.io/api/cosmos/msg/v1"
|
||||
fmt "fmt"
|
||||
_ "github.com/cosmos/cosmos-proto"
|
||||
runtime "github.com/cosmos/cosmos-proto/runtime"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoiface "google.golang.org/protobuf/runtime/protoiface"
|
||||
@ -1091,14 +1092,19 @@ var file_cosmos_nft_v1beta1_tx_proto_rawDesc = []byte{
|
||||
0x0a, 0x1b, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6e, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x62,
|
||||
0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x63,
|
||||
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x6e, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61,
|
||||
0x31, 0x1a, 0x17, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x73, 0x67, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x75, 0x0a, 0x07, 0x4d, 0x73,
|
||||
0x67, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x49, 0x64,
|
||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65,
|
||||
0x69, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65,
|
||||
0x31, 0x1a, 0x19, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
|
||||
0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x63, 0x6f,
|
||||
0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x6d, 0x73, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x73, 0x67, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x07, 0x4d, 0x73, 0x67, 0x53, 0x65, 0x6e,
|
||||
0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02,
|
||||
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x06,
|
||||
0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4,
|
||||
0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
|
||||
0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x34,
|
||||
0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
||||
0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65,
|
||||
0x69, 0x76, 0x65, 0x72, 0x3a, 0x0b, 0x82, 0xe7, 0xb0, 0x2a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65,
|
||||
0x72, 0x22, 0x11, 0x0a, 0x0f, 0x4d, 0x73, 0x67, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x32, 0x56, 0x0a, 0x03, 0x4d, 0x73, 0x67, 0x12, 0x48, 0x0a, 0x04, 0x53,
|
||||
|
||||
57
client/prompts.go
Normal file
57
client/prompts.go
Normal file
@ -0,0 +1,57 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"unicode"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// ValidatePromptNotEmpty validates that the input is not empty.
|
||||
func ValidatePromptNotEmpty(input string) error {
|
||||
if input == "" {
|
||||
return fmt.Errorf("input cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePromptURL validates that the input is a valid URL.
|
||||
func ValidatePromptURL(input string) error {
|
||||
_, err := url.ParseRequestURI(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePromptAddress validates that the input is a valid Bech32 address.
|
||||
func ValidatePromptAddress(input string) error {
|
||||
if _, err := sdk.AccAddressFromBech32(input); err != nil {
|
||||
return fmt.Errorf("invalid address: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePromptYesNo validates that the input is valid sdk.COins
|
||||
func ValidatePromptCoins(input string) error {
|
||||
if _, err := sdk.ParseCoinsNormalized(input); err != nil {
|
||||
return fmt.Errorf("invalid coins: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CamelCaseToString converts a camel case string to a string with spaces.
|
||||
func CamelCaseToString(str string) string {
|
||||
w := []rune(str)
|
||||
for i := len(w) - 1; i > 1; i-- {
|
||||
if unicode.IsUpper(w[i]) {
|
||||
w = append(w[:i], append([]rune{' '}, w[i:]...)...)
|
||||
}
|
||||
}
|
||||
return string(w)
|
||||
}
|
||||
2
go.mod
2
go.mod
@ -37,6 +37,7 @@ require (
|
||||
github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b
|
||||
github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554
|
||||
github.com/magiconair/properties v1.8.6
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
@ -75,6 +76,7 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cosmos/gorocksdb v1.2.0 // indirect
|
||||
github.com/cosmos/ledger-go v0.9.2 // indirect
|
||||
github.com/creachadair/taskgroup v0.3.2 // indirect
|
||||
|
||||
5
go.sum
5
go.sum
@ -147,8 +147,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
@ -560,6 +563,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
||||
@ -3,6 +3,7 @@ package cosmos.nft.v1beta1;
|
||||
|
||||
option go_package = "github.com/cosmos/cosmos-sdk/x/nft";
|
||||
|
||||
import "cosmos_proto/cosmos.proto";
|
||||
import "cosmos/msg/v1/msg.proto";
|
||||
|
||||
// Msg defines the nft Msg service.
|
||||
@ -24,10 +25,10 @@ message MsgSend {
|
||||
string id = 2;
|
||||
|
||||
// sender is the address of the owner of nft
|
||||
string sender = 3;
|
||||
string sender = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
|
||||
// receiver is the receiver address of nft
|
||||
string receiver = 4;
|
||||
string receiver = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
}
|
||||
// MsgSendResponse defines the Msg/Send response type.
|
||||
message MsgSendResponse {}
|
||||
@ -37,6 +37,7 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
|
||||
github.com/coinbase/rosetta-sdk-go v0.8.0 // indirect
|
||||
github.com/confio/ics23/go v0.7.0 // indirect
|
||||
@ -97,6 +98,7 @@ require (
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
|
||||
|
||||
@ -147,8 +147,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
@ -548,6 +551,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
||||
@ -36,6 +36,7 @@ require (
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
|
||||
github.com/coinbase/rosetta-sdk-go v0.8.0 // indirect
|
||||
github.com/confio/ics23/go v0.7.0 // indirect
|
||||
@ -96,6 +97,7 @@ require (
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
|
||||
|
||||
@ -147,8 +147,11 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
@ -550,6 +553,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
||||
@ -35,11 +35,15 @@ The following specification uses *ATOM* as the native staking token. The module
|
||||
can be adapted to any Proof-Of-Stake blockchain by replacing *ATOM* with the native
|
||||
staking token of the chain.
|
||||
|
||||
* [`x/gov`](#xgov)
|
||||
* [Abstract](#abstract)
|
||||
* [Contents](#contents)
|
||||
* [Concepts](#concepts)
|
||||
* [Proposal submission](#proposal-submission)
|
||||
* [Right to submit a proposal](#right-to-submit-a-proposal)
|
||||
* [Proposal Messages](#proposal-messages)
|
||||
* [Deposit](#deposit)
|
||||
* [Deposit refund and burn](#deposit-refund-and-burn)
|
||||
* [Vote](#vote)
|
||||
* [Participants](#participants)
|
||||
* [Voting period](#voting-period)
|
||||
@ -51,8 +55,11 @@ staking token of the chain.
|
||||
* [Validator’s punishment for non-voting](#validators-punishment-for-non-voting)
|
||||
* [Governance address](#governance-address)
|
||||
* [Software Upgrade](#software-upgrade)
|
||||
* [Signal](#signal)
|
||||
* [Switch](#switch)
|
||||
* [State](#state)
|
||||
* [Proposals](#proposals)
|
||||
* [Writing a module that uses governance](#writing-a-module-that-uses-governance)
|
||||
* [Parameters and base types](#parameters-and-base-types)
|
||||
* [DepositParams](#depositparams)
|
||||
* [VotingParams](#votingparams)
|
||||
@ -75,11 +82,48 @@ staking token of the chain.
|
||||
* [MsgDeposit](#msgdeposit)
|
||||
* [Future Improvements](#future-improvements)
|
||||
* [Parameters](#parameters)
|
||||
* [SubKeys](#subkeys)
|
||||
* [Client](#client)
|
||||
* [CLI](#cli)
|
||||
* [Query](#query)
|
||||
* [deposit](#deposit-3)
|
||||
* [deposits](#deposits)
|
||||
* [param](#param)
|
||||
* [params](#params)
|
||||
* [proposal](#proposal)
|
||||
* [proposals](#proposals-1)
|
||||
* [proposer](#proposer)
|
||||
* [tally](#tally)
|
||||
* [vote](#vote-2)
|
||||
* [votes](#votes)
|
||||
* [Transactions](#transactions)
|
||||
* [deposit](#deposit-4)
|
||||
* [draft-proposal](#draft-proposal)
|
||||
* [submit-proposal](#submit-proposal)
|
||||
* [submit-legacy-proposal](#submit-legacy-proposal)
|
||||
* [vote](#vote-3)
|
||||
* [weighted-vote](#weighted-vote)
|
||||
* [gRPC](#grpc)
|
||||
* [Proposal](#proposal-1)
|
||||
* [Proposals](#proposals-2)
|
||||
* [Vote](#vote-4)
|
||||
* [Votes](#votes-1)
|
||||
* [Params](#params-1)
|
||||
* [Deposit](#deposit-5)
|
||||
* [deposits](#deposits-1)
|
||||
* [TallyResult](#tallyresult)
|
||||
* [REST](#rest)
|
||||
* [proposal](#proposal-2)
|
||||
* [proposals](#proposals-3)
|
||||
* [voter vote](#voter-vote)
|
||||
* [votes](#votes-2)
|
||||
* [params](#params-2)
|
||||
* [deposits](#deposits-2)
|
||||
* [proposal deposits](#proposal-deposits)
|
||||
* [tally](#tally-1)
|
||||
* [Metadata](#metadata)
|
||||
* [Proposal](#proposal-3)
|
||||
* [Vote](#vote-5)
|
||||
|
||||
<!-- order: 1 -->
|
||||
|
||||
@ -1149,6 +1193,16 @@ Example:
|
||||
simd tx gov deposit 1 10000000stake --from cosmos1..
|
||||
```
|
||||
|
||||
#### draft-proposal
|
||||
|
||||
The `draft-proposal` command allows users to draft any type of proposal.
|
||||
The command returns a `draft_proposal.json`, to be used by `submit-proposal` after being completed.
|
||||
The `draft_metadata.json` is meant to be uploaded to [IPFS](#metadata).
|
||||
|
||||
```bash
|
||||
simd tx gov draft-proposal
|
||||
```
|
||||
|
||||
#### submit-proposal
|
||||
|
||||
The `submit-proposal` command allows users to submit a governance proposal along with some messages and metadata.
|
||||
@ -2612,8 +2666,8 @@ Location: off-chain as json object stored on IPFS (mirrors [group proposal](../.
|
||||
"authors": "",
|
||||
"summary": "",
|
||||
"details": "",
|
||||
"proposalForumURL": "",
|
||||
"voteOptionContext": "",
|
||||
"proposal_forum_url": "",
|
||||
"vote_option_context": "",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
297
x/gov/client/cli/prompt.go
Normal file
297
x/gov/client/cli/prompt.go
Normal file
@ -0,0 +1,297 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
)
|
||||
|
||||
const (
|
||||
proposalText = "text"
|
||||
proposalOther = "other"
|
||||
draftProposalFileName = "draft_proposal.json"
|
||||
draftMetadataFileName = "draft_metadata.json"
|
||||
)
|
||||
|
||||
// ProposalMetadata is the metadata of a proposal
|
||||
// This metadata is supposed to live off-chain when submitted in a proposal
|
||||
type ProposalMetadata struct {
|
||||
Title string `json:"title"`
|
||||
Authors string `json:"authors"`
|
||||
Summary string `json:"summary"`
|
||||
Details string `json:"details"`
|
||||
ProposalForumUrl string `json:"proposal_forum_url"` // named 'Url' instead of 'URL' for avoiding the camel case split
|
||||
VoteOptionContext string `json:"vote_option_context"`
|
||||
}
|
||||
|
||||
// Prompt prompts the user for all values of the given type.
|
||||
// data is the struct to be filled
|
||||
// namePrefix is the name to be display as "Enter <namePrefix> <field>"
|
||||
func Prompt[T any](data T, namePrefix string) (T, error) {
|
||||
v := reflect.ValueOf(&data).Elem()
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = reflect.ValueOf(data)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if v.Field(i).Kind() == reflect.Struct || v.Field(i).Kind() == reflect.Slice {
|
||||
// if the field is a struct skip
|
||||
// in a future we can add a recursive call to Prompt
|
||||
continue
|
||||
}
|
||||
|
||||
// create prompts
|
||||
prompt := promptui.Prompt{
|
||||
Label: fmt.Sprintf("Enter %s %s", namePrefix, strings.ToLower(client.CamelCaseToString(v.Type().Field(i).Name))),
|
||||
Validate: client.ValidatePromptNotEmpty,
|
||||
}
|
||||
|
||||
fieldName := strings.ToLower(v.Type().Field(i).Name)
|
||||
// validation per field name
|
||||
if strings.Contains(fieldName, "url") {
|
||||
prompt.Validate = client.ValidatePromptURL
|
||||
}
|
||||
|
||||
if strings.EqualFold(fieldName, "authority") {
|
||||
// pre-fill with gov address
|
||||
prompt.Default = authtypes.NewModuleAddress(types.ModuleName).String()
|
||||
prompt.Validate = client.ValidatePromptAddress
|
||||
}
|
||||
|
||||
if strings.Contains(fieldName, "addr") ||
|
||||
strings.Contains(fieldName, "sender") ||
|
||||
strings.Contains(fieldName, "voter") ||
|
||||
strings.Contains(fieldName, "depositor") ||
|
||||
strings.Contains(fieldName, "granter") ||
|
||||
strings.Contains(fieldName, "grantee") ||
|
||||
strings.Contains(fieldName, "recipient") {
|
||||
prompt.Validate = client.ValidatePromptAddress
|
||||
}
|
||||
|
||||
result, err := prompt.Run()
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("failed to prompt for %s: %w", fieldName, err)
|
||||
}
|
||||
|
||||
switch v.Field(i).Kind() {
|
||||
case reflect.String:
|
||||
v.Field(i).SetString(result)
|
||||
case reflect.Int:
|
||||
resultInt, _ := strconv.Atoi(result)
|
||||
v.Field(i).SetInt(int64(resultInt))
|
||||
default:
|
||||
// skip other types
|
||||
// possibly in the future we can add more types (like slices)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type proposalTypes struct {
|
||||
Type string
|
||||
MsgType string
|
||||
Msg sdk.Msg
|
||||
}
|
||||
|
||||
// Prompt the proposal type values and return the proposal and its metadata
|
||||
func (p *proposalTypes) Prompt(cdc codec.Codec) (*proposal, ProposalMetadata, error) {
|
||||
proposal := &proposal{}
|
||||
|
||||
// set metadata
|
||||
metadata, err := Prompt(ProposalMetadata{}, "proposal")
|
||||
if err != nil {
|
||||
return nil, metadata, fmt.Errorf("failed to set proposal metadata: %w", err)
|
||||
}
|
||||
proposal.Metadata = "ipfs://CID"
|
||||
|
||||
// set deposit
|
||||
depositPrompt := promptui.Prompt{
|
||||
Label: "Enter proposal deposit",
|
||||
Validate: client.ValidatePromptCoins,
|
||||
}
|
||||
proposal.Deposit, err = depositPrompt.Run()
|
||||
if err != nil {
|
||||
return nil, metadata, fmt.Errorf("failed to set proposal deposit: %w", err)
|
||||
}
|
||||
|
||||
if p.Msg == nil {
|
||||
return proposal, metadata, nil
|
||||
}
|
||||
|
||||
// set messages field
|
||||
result, err := Prompt(p.Msg, "msg")
|
||||
if err != nil {
|
||||
return nil, metadata, fmt.Errorf("failed to set proposal message: %w", err)
|
||||
}
|
||||
|
||||
message, err := cdc.MarshalInterfaceJSON(result)
|
||||
if err != nil {
|
||||
return nil, metadata, fmt.Errorf("failed to marshal proposal message: %w", err)
|
||||
}
|
||||
proposal.Messages = append(proposal.Messages, message)
|
||||
return proposal, metadata, nil
|
||||
}
|
||||
|
||||
var supportedProposalTypes = []proposalTypes{
|
||||
{
|
||||
Type: proposalText,
|
||||
MsgType: "", // no message for text proposal
|
||||
},
|
||||
{
|
||||
Type: "community-pool-spend",
|
||||
MsgType: "/cosmos.distribution.v1beta1.MsgCommunityPoolSpend",
|
||||
},
|
||||
{
|
||||
Type: "software-upgrade",
|
||||
MsgType: "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade",
|
||||
},
|
||||
{
|
||||
Type: "cancel-software-upgrade",
|
||||
MsgType: "/cosmos.upgrade.v1beta1.MsgCancelUpgrade",
|
||||
},
|
||||
{
|
||||
Type: proposalOther,
|
||||
MsgType: "", // user will input the message type
|
||||
},
|
||||
}
|
||||
|
||||
func getProposalTypes() []string {
|
||||
types := make([]string, len(supportedProposalTypes))
|
||||
for i, p := range supportedProposalTypes {
|
||||
types[i] = p.Type
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func getProposalMsg(cdc codec.Codec, input string) (sdk.Msg, error) {
|
||||
var msg sdk.Msg
|
||||
bz, err := json.Marshal(struct {
|
||||
Type string `json:"@type"`
|
||||
}{
|
||||
Type: input,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cdc.UnmarshalInterfaceJSON(bz, &msg); err != nil {
|
||||
return nil, fmt.Errorf("failed to determined sdk.Msg from %s proposal type : %w", input, err)
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// NewCmdDraftProposal let a user generate a draft proposal.
|
||||
func NewCmdDraftProposal() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "draft-proposal",
|
||||
Short: "Generate a draft proposal json file. The generated proposal json contains only one message.",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prompt proposal type
|
||||
proposalTypesPrompt := promptui.Select{
|
||||
Label: "Select proposal type",
|
||||
Items: getProposalTypes(),
|
||||
}
|
||||
|
||||
_, proposalType, err := proposalTypesPrompt.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prompt proposal types: %w", err)
|
||||
}
|
||||
|
||||
var proposal proposalTypes
|
||||
for _, p := range supportedProposalTypes {
|
||||
if strings.EqualFold(p.Type, proposalType) {
|
||||
proposal = p
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// create any proposal type
|
||||
if proposal.Type == proposalOther {
|
||||
// prompt proposal type
|
||||
msgPrompt := promptui.Select{
|
||||
Label: "Select proposal message type:",
|
||||
Items: func() []string {
|
||||
msgs := clientCtx.InterfaceRegistry.ListImplementations(sdk.MsgInterfaceProtoName)
|
||||
sort.Strings(msgs)
|
||||
return msgs
|
||||
}(),
|
||||
}
|
||||
|
||||
_, result, err := msgPrompt.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prompt proposal types: %w", err)
|
||||
}
|
||||
|
||||
proposal.MsgType = result
|
||||
}
|
||||
|
||||
if proposal.MsgType != "" {
|
||||
proposal.Msg, err = getProposalMsg(clientCtx.Codec, proposal.MsgType)
|
||||
if err != nil {
|
||||
// should never happen
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
prop, metadata, err := proposal.Prompt(clientCtx.Codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeFile(draftMetadataFileName, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := writeFile(draftProposalFileName, prop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Your draft proposal has successfully been generated.\nProposals should contain off-chain metadata, please upload the metadata JSON to IPFS.\nThen, replace the generated metadata field with the IPFS CID.\n")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func writeFile(fileName string, input any) error {
|
||||
raw, err := json.MarshalIndent(input, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal proposal: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(fileName, raw, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -70,6 +70,7 @@ func NewTxCmd(legacyPropCmds []*cobra.Command) *cobra.Command {
|
||||
NewCmdVote(),
|
||||
NewCmdWeightedVote(),
|
||||
NewCmdSubmitProposal(),
|
||||
NewCmdDraftProposal(),
|
||||
|
||||
// Deprecated
|
||||
cmdSubmitLegacyProp,
|
||||
|
||||
@ -77,9 +77,9 @@ func parseSubmitLegacyProposalFlags(fs *pflag.FlagSet) (*legacyProposal, error)
|
||||
// proposal defines the new Msg-based proposal.
|
||||
type proposal struct {
|
||||
// Msgs defines an array of sdk.Msgs proto-JSON-encoded as Anys.
|
||||
Messages []json.RawMessage
|
||||
Metadata string
|
||||
Deposit string
|
||||
Messages []json.RawMessage `json:"messages,omitempty"`
|
||||
Metadata string `json:"metadata"`
|
||||
Deposit string `json:"deposit"`
|
||||
}
|
||||
|
||||
func parseSubmitProposal(cdc codec.Codec, path string) ([]sdk.Msg, string, sdk.Coins, error) {
|
||||
@ -2073,8 +2073,8 @@ Location: off-chain as json object stored on IPFS (mirrors [gov proposal](../../
|
||||
"authors": "",
|
||||
"summary": "",
|
||||
"details": "",
|
||||
"proposalForumURL": "",
|
||||
"voteOptionContext": "",
|
||||
"proposal_forum_url": "",
|
||||
"vote_option_context": "",
|
||||
}
|
||||
```
|
||||
|
||||
@ -2096,8 +2096,8 @@ Location: off-chain as json object stored on IPFS
|
||||
{
|
||||
"name": "",
|
||||
"description": "",
|
||||
"groupWebsiteURL": "",
|
||||
"groupForumURL": "",
|
||||
"group_website_url": "",
|
||||
"group_forum_url": "",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user