mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27:26 +08:00
add alias commands
This commit is contained in:
parent
c28a430e8c
commit
165d4440ce
126
client/cmd/config/alias.go
Normal file
126
client/cmd/config/alias.go
Normal file
@ -0,0 +1,126 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var ClientConfigAliasCmd = &cobra.Command{
|
||||
Use: "alias",
|
||||
Short: "Manage address aliases",
|
||||
Long: `Manage the list of address aliases in the configuration.
|
||||
|
||||
For more information on how to use aliases, see the https://docs.quilibrium.com/docs/run-node/qclient/commands/alias.
|
||||
|
||||
Examples:
|
||||
# Add a new address to the configuration
|
||||
qclient config alias add my-friend 0x1234567890abcdef
|
||||
|
||||
# List all saved addresses
|
||||
qclient config alias list
|
||||
|
||||
# Update an existing address
|
||||
qclient config alias update my-friend 0xabcdef1234567890
|
||||
|
||||
# Remove an address from the configuration
|
||||
qclient config alias remove my-friend`,
|
||||
}
|
||||
|
||||
var addAddressCmd = &cobra.Command{
|
||||
Use: "add [name] [address]",
|
||||
Short: "Add a new address alias",
|
||||
Long: `Add a new address alias to the configuration with a given name.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
address := args[1]
|
||||
|
||||
if ClientConfig.AddressList == nil {
|
||||
ClientConfig.AddressList = make(map[string]string)
|
||||
}
|
||||
|
||||
if _, exists := ClientConfig.AddressList[name]; exists {
|
||||
fmt.Printf("Alias for %s already exists. Use 'update' to modify it.\n", name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ClientConfig.AddressList[name] = address
|
||||
err := utils.SaveClientConfig(ClientConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Added alias %s for %s\n", address, name)
|
||||
},
|
||||
}
|
||||
|
||||
var listAddressesCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all aliases",
|
||||
Long: `List all aliases in the configuration.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(ClientConfig.AddressList) == 0 {
|
||||
fmt.Println("No aliases found in configuration.")
|
||||
return
|
||||
}
|
||||
fmt.Println("Address Aliases:")
|
||||
for name, address := range ClientConfig.AddressList {
|
||||
fmt.Printf(" %s -> %s\n", name, address)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var updateAddressCmd = &cobra.Command{
|
||||
Use: "update [name] [address]",
|
||||
Short: "Update an existing alias",
|
||||
Long: `Update an existing alias in the configuration for a given name.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
address := args[1]
|
||||
if _, exists := ClientConfig.AddressList[name]; !exists {
|
||||
fmt.Printf("Alias for %s does not exist.\n", name)
|
||||
os.Exit(1)
|
||||
}
|
||||
ClientConfig.AddressList[name] = address
|
||||
err := utils.SaveClientConfig(ClientConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Updated address for %s to %s\n", name, address)
|
||||
},
|
||||
}
|
||||
|
||||
var deleteAddressCmd = &cobra.Command{
|
||||
Use: "delete [name]",
|
||||
Short: "Delete an alias",
|
||||
Long: `Delete an alias from the configuration by name.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
name := args[0]
|
||||
|
||||
if _, exists := ClientConfig.AddressList[name]; !exists {
|
||||
fmt.Printf("Alias for %s does not exist.\n", name)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
delete(ClientConfig.AddressList, name)
|
||||
err := utils.SaveClientConfig(ClientConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Deleted alias for %s\n", name)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ClientConfigAliasCmd.AddCommand(addAddressCmd)
|
||||
ClientConfigAliasCmd.AddCommand(listAddressesCmd)
|
||||
ClientConfigAliasCmd.AddCommand(updateAddressCmd)
|
||||
ClientConfigAliasCmd.AddCommand(deleteAddressCmd)
|
||||
}
|
||||
@ -1,12 +1,31 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
var ClientConfig *utils.ClientConfig
|
||||
|
||||
var ConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Performs a QClient configuration operation",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
parent := cmd.Parent()
|
||||
if parent != nil && parent.PersistentPreRun != nil {
|
||||
parent.PersistentPreRun(parent, args)
|
||||
}
|
||||
|
||||
var err error
|
||||
ClientConfig, err = utils.LoadClientConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -15,4 +34,5 @@ func init() {
|
||||
ConfigCmd.AddCommand(ClientConfigPublicRpcCmd)
|
||||
ConfigCmd.AddCommand(ClientConfigSetCustomRpcCmd)
|
||||
ConfigCmd.AddCommand(ClientConfigSignatureCheckCmd)
|
||||
ConfigCmd.AddCommand(ClientConfigAliasCmd)
|
||||
}
|
||||
|
||||
@ -218,5 +218,5 @@ func decodeHexString(hexStr string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
CrossMintCmd.PersistentFlags().StringVar(&ConfigDirectory, "config", "default", "config directory")
|
||||
CrossMintCmd.PersistentFlags().StringVar(&ConfigDirectory, "config", utils.ReservedDefaultConfigName, "config directory")
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ Examples:
|
||||
if len(args) > 0 {
|
||||
NodeGetInfo(args[0])
|
||||
} else {
|
||||
NodeGetInfo("default")
|
||||
NodeGetInfo(utils.ReservedDefaultConfigName)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ The third example will create a new configuration at %s/myconfig and symlink it
|
||||
}
|
||||
|
||||
// Check if trying to use "default" which is reserved for the symlink
|
||||
if configName == "default" {
|
||||
if configName == utils.ReservedDefaultConfigName {
|
||||
fmt.Println("Error: 'default' is reserved for the symlink. Please use a different name.")
|
||||
os.Exit(1)
|
||||
}
|
||||
@ -72,5 +72,5 @@ The third example will create a new configuration at %s/myconfig and symlink it
|
||||
}
|
||||
|
||||
func init() {
|
||||
NodeConfigCreateCmd.Flags().BoolVarP(&SetDefault, "default", "d", false, "Select this config as the default")
|
||||
NodeConfigCreateCmd.Flags().BoolVarP(&SetDefault, utils.ReservedDefaultConfigName, "d", false, "Select this config as the default")
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ This will symlink %s/mynode to %s`, ConfigDirs, NodeConfigToRun),
|
||||
name = configs[choice-1]
|
||||
}
|
||||
|
||||
if name == "default" {
|
||||
if name == utils.ReservedDefaultConfigName {
|
||||
fmt.Println("Invalid configuration name. The 'default' is reserved for the default configuration.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ package nodeconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
|
||||
func ListConfigurations() ([]string, error) {
|
||||
@ -12,7 +14,7 @@ func ListConfigurations() ([]string, error) {
|
||||
|
||||
configs := make([]string, 0)
|
||||
for _, file := range files {
|
||||
if file.IsDir() && file.Name() != "default" {
|
||||
if file.IsDir() && file.Name() != utils.ReservedDefaultConfigName {
|
||||
configs = append(configs, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package token
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/spf13/cobra"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
)
|
||||
@ -12,13 +11,10 @@ var AccountCmd = &cobra.Command{
|
||||
Use: "account",
|
||||
Short: "Shows the account address of the managing account",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
addr, err := poseidon.HashBytes([]byte(peerId))
|
||||
account, err := utils.GetAccountFromNodeConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addrBytes := addr.FillBytes(make([]byte, 32))
|
||||
fmt.Printf("Account: 0x%x\n", addrBytes)
|
||||
fmt.Printf("Account: 0x%x\n", account)
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@ -9,6 +17,10 @@ import (
|
||||
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
mn "github.com/multiformats/go-multiaddr/net"
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"source.quilibrium.com/quilibrium/monorepo/client/utils"
|
||||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||||
)
|
||||
|
||||
func GetGRPCClient() (*grpc.ClientConn, error) {
|
||||
@ -46,3 +58,163 @@ func GetGRPCClient() (*grpc.ClientConn, error) {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// CoinData represents a combined structure for coin information,
|
||||
// including frame and address data.
|
||||
type CoinData struct {
|
||||
Amount string
|
||||
Coin *protobufs.Coin
|
||||
FrameNumber uint64
|
||||
Address []byte
|
||||
Timestamp string
|
||||
}
|
||||
|
||||
func GetAccountCoins(includeMetadata bool) ([]CoinData, error) {
|
||||
conn, err := GetGRPCClient()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := protobufs.NewNodeServiceClient(conn)
|
||||
peerId := utils.GetPeerIDFromConfig(NodeConfig)
|
||||
privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addr, err := poseidon.HashBytes([]byte(peerId))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addrBytes := addr.FillBytes(make([]byte, 32))
|
||||
|
||||
resp, err := client.GetTokensByAccount(
|
||||
context.Background(),
|
||||
&protobufs.GetTokensByAccountRequest{
|
||||
Address: addrBytes,
|
||||
IncludeMetadata: includeMetadata,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp.Coins) != len(resp.FrameNumbers) {
|
||||
return nil, errors.New("invalid response from RPC")
|
||||
}
|
||||
|
||||
pub, err := privKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
altAddr, err := poseidon.HashBytes([]byte(pub))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
altAddrBytes := altAddr.FillBytes(make([]byte, 32))
|
||||
resp2, err := client.GetTokensByAccount(
|
||||
context.Background(),
|
||||
&protobufs.GetTokensByAccountRequest{
|
||||
Address: altAddrBytes,
|
||||
IncludeMetadata: includeMetadata,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(resp2.Coins) != len(resp2.FrameNumbers) {
|
||||
return nil, errors.New("invalid response from RPC")
|
||||
}
|
||||
|
||||
// Merge coin data from both responses into a unified list
|
||||
mergedData := make([]CoinData, 0, len(resp.Coins)+len(resp2.Coins))
|
||||
|
||||
// Add data from first response (resp)
|
||||
for i := 0; i < len(resp.Coins); i++ {
|
||||
|
||||
coin := resp.Coins[i]
|
||||
amount := new(big.Int).SetBytes(coin.Amount)
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
r := new(big.Rat).SetFrac(amount, conversionFactor)
|
||||
|
||||
data := CoinData{
|
||||
Amount: r.FloatString(12),
|
||||
Coin: resp.Coins[i],
|
||||
FrameNumber: resp.FrameNumbers[i],
|
||||
Address: resp.Addresses[i],
|
||||
}
|
||||
if len(resp.Timestamps) > i {
|
||||
t := time.UnixMilli(resp.Timestamps[i])
|
||||
data.Timestamp = t.Format(time.RFC3339)
|
||||
}
|
||||
mergedData = append(mergedData, data)
|
||||
}
|
||||
|
||||
// Add data from second response (resp2)
|
||||
for i := 0; i < len(resp2.Coins); i++ {
|
||||
coin := resp2.Coins[i]
|
||||
amount := new(big.Int).SetBytes(coin.Amount)
|
||||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||||
r := new(big.Rat).SetFrac(amount, conversionFactor)
|
||||
|
||||
data := CoinData{
|
||||
Amount: r.FloatString(12),
|
||||
Coin: resp2.Coins[i],
|
||||
FrameNumber: resp2.FrameNumbers[i],
|
||||
Address: resp2.Addresses[i],
|
||||
}
|
||||
if len(resp2.Timestamps) > i {
|
||||
t := time.UnixMilli(resp2.Timestamps[i])
|
||||
data.Timestamp = t.Format(time.RFC3339)
|
||||
}
|
||||
mergedData = append(mergedData, data)
|
||||
}
|
||||
|
||||
return mergedData, nil
|
||||
}
|
||||
|
||||
// IsAccountCoin checks if the given coin address is owned by the account.
|
||||
func IsAccountCoin(address []byte, coins []CoinData) bool {
|
||||
if len(coins) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, coin := range coins {
|
||||
if bytes.Equal(coin.Address, address) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func PromptTokenForTransfer(coins []CoinData) (string, error) {
|
||||
fmt.Println("Please select a coin to transfer:")
|
||||
for i, coin := range coins {
|
||||
fmt.Printf("%d. %s\n", i+1, coin.Amount)
|
||||
}
|
||||
|
||||
var selectedCoinIndex int
|
||||
fmt.Scanln(&selectedCoinIndex)
|
||||
|
||||
if selectedCoinIndex < 1 || selectedCoinIndex > len(coins) {
|
||||
return "", errors.New("invalid coin index")
|
||||
}
|
||||
|
||||
return hex.EncodeToString(coins[selectedCoinIndex-1].Address), nil
|
||||
}
|
||||
|
||||
func CleanAddress(address string) ([]byte, error) {
|
||||
address = strings.ReplaceAll(address, "0x", "")
|
||||
address = strings.TrimSpace(address)
|
||||
addressBytes, err := hex.DecodeString(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addressBytes, nil
|
||||
}
|
||||
|
||||
@ -11,4 +11,5 @@ var (
|
||||
ClientConfigFile = string(ReleaseTypeQClient) + "-config.yaml"
|
||||
ClientConfigPath = filepath.Join(ClientConfigDir, ClientConfigFile)
|
||||
DefaultQClientSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeQClient))
|
||||
ReservedDefaultConfigName = "default"
|
||||
)
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -18,6 +19,7 @@ func CreateDefaultConfig() {
|
||||
SignatureCheck: true,
|
||||
PublicRpc: false,
|
||||
CustomRpc: "",
|
||||
AddressList: make(map[string]string),
|
||||
})
|
||||
|
||||
sudoUser, err := GetCurrentSudoUser()
|
||||
@ -40,6 +42,7 @@ func LoadClientConfig() (*ClientConfig, error) {
|
||||
SignatureCheck: true,
|
||||
PublicRpc: false,
|
||||
CustomRpc: "",
|
||||
AddressList: make(map[string]string),
|
||||
}
|
||||
if err := SaveClientConfig(config); err != nil {
|
||||
return nil, err
|
||||
@ -89,3 +92,49 @@ func GetConfigDir() string {
|
||||
func IsClientConfigured() bool {
|
||||
return FileExists(ClientConfigPath)
|
||||
}
|
||||
|
||||
func GetAddressList() (map[string]string, error) {
|
||||
config, err := LoadClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if AddressList is nil, and initialize it if necessary
|
||||
if config.AddressList == nil {
|
||||
config.AddressList = make(map[string]string)
|
||||
}
|
||||
|
||||
// Get list of configs in ConfigDir (excluding default)
|
||||
configDir := GetConfigDir()
|
||||
if configDir == "" {
|
||||
configDir = filepath.Join(GetUserQuilibriumDir(), "configs")
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(configDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config directory: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() || file.Name() == ReservedDefaultConfigName {
|
||||
continue
|
||||
}
|
||||
|
||||
tempConfig, err := LoadNodeConfig(file.Name())
|
||||
if err != nil {
|
||||
continue // Skip files that can't be parsed
|
||||
}
|
||||
|
||||
address, err := GetAccountFromNodeConfig(tempConfig)
|
||||
if err != nil {
|
||||
continue // Skip files that can't be parsed
|
||||
}
|
||||
|
||||
// Extract address from filename or content if available
|
||||
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
|
||||
if _, ok := config.AddressList[name]; ok {
|
||||
config.AddressList[name] = string(address)
|
||||
}
|
||||
}
|
||||
return config.AddressList, nil
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/iden3/go-iden3-crypto/poseidon"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
@ -116,7 +117,7 @@ func LoadDefaultNodeConfig() (*config.Config, error) {
|
||||
|
||||
func LoadNodeConfig(configDirectory string) (*config.Config, error) {
|
||||
// if the provided config directory is "default", load the default config
|
||||
if configDirectory == "default" {
|
||||
if configDirectory == ReservedDefaultConfigName {
|
||||
return LoadDefaultNodeConfig()
|
||||
}
|
||||
|
||||
@ -187,7 +188,7 @@ func SetDefaultNodeConfig(configName string) error {
|
||||
}
|
||||
|
||||
// Construct the default directory path
|
||||
defaultDir := filepath.Join(NodeConfigDir, "default")
|
||||
defaultDir := filepath.Join(NodeConfigDir, ReservedDefaultConfigName)
|
||||
|
||||
// Create the symlink
|
||||
if err := CreateSymlink(sourceDir, defaultDir); err != nil {
|
||||
@ -224,3 +225,13 @@ func CreateDefaultNodeConfig(name string) (*config.Config, error) {
|
||||
|
||||
return nodeConfig, nil
|
||||
}
|
||||
|
||||
func GetAccountFromNodeConfig(config *config.Config) ([]byte, error) {
|
||||
peerId := GetPeerIDFromConfig(config)
|
||||
addr, err := poseidon.HashBytes([]byte(peerId))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return addr.FillBytes(make([]byte, 32)), nil
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
package utils
|
||||
|
||||
type ClientConfig struct {
|
||||
DataDir string `yaml:"dataDir"`
|
||||
SymlinkPath string `yaml:"symlinkPath"`
|
||||
SignatureCheck bool `yaml:"signatureCheck"`
|
||||
PublicRpc bool `yaml:"publicRpc"`
|
||||
CustomRpc string `yaml:"customRpc"`
|
||||
NodeSymlinkName string `yaml:"nodeSymlinkName"`
|
||||
DataDir string `yaml:"dataDir"`
|
||||
SymlinkPath string `yaml:"symlinkPath"`
|
||||
SignatureCheck bool `yaml:"signatureCheck"`
|
||||
PublicRpc bool `yaml:"publicRpc"`
|
||||
CustomRpc string `yaml:"customRpc"`
|
||||
NodeSymlinkName string `yaml:"nodeSymlinkName"`
|
||||
AddressList map[string]string `yaml:"addresses"`
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user