Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
369 lines
8.7 KiB
Go
369 lines
8.7 KiB
Go
package cosmosaccount
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
|
|
dkeyring "github.com/99designs/keyring"
|
|
"github.com/cosmos/go-bip39"
|
|
|
|
addresscodec "cosmossdk.io/core/address"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/codec/address"
|
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
|
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
|
|
"github.com/cosmos/cosmos-sdk/crypto/hd"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
sdktypes "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
"github.com/ignite/cli/v29/ignite/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
// KeyringServiceName used for the name of keyring in OS backend.
|
|
KeyringServiceName = "ignite"
|
|
|
|
// DefaultAccount is the name of the default account.
|
|
DefaultAccount = "default"
|
|
)
|
|
|
|
// KeyringHome used to store account related data.
|
|
var KeyringHome = os.ExpandEnv("$HOME/.ignite/accounts")
|
|
|
|
var ErrAccountExists = errors.New("account already exists")
|
|
|
|
const (
|
|
CoinTypeCosmos = sdktypes.CoinType
|
|
AccountPrefixCosmos = "cosmos"
|
|
)
|
|
|
|
// KeyringBackend is the backend for where keys are stored.
|
|
type KeyringBackend string
|
|
|
|
const (
|
|
// KeyringTest is the test keyring backend. With this backend, your keys will be
|
|
// stored under your app's data dir.
|
|
KeyringTest KeyringBackend = "test"
|
|
|
|
// KeyringOS is the OS keyring backend. With this backend, your keys will be
|
|
// stored in your operating system's secured keyring.
|
|
KeyringOS KeyringBackend = "os"
|
|
|
|
// KeyringMemory is in memory keyring backend, your keys will be stored in application memory.
|
|
KeyringMemory KeyringBackend = "memory"
|
|
)
|
|
|
|
// Registry for accounts.
|
|
type Registry struct {
|
|
homePath string
|
|
keyringServiceName string
|
|
keyringBackend KeyringBackend
|
|
addressCodec addresscodec.Codec
|
|
coinType uint32
|
|
|
|
Keyring keyring.Keyring
|
|
}
|
|
|
|
// Option configures your registry.
|
|
type Option func(*Registry)
|
|
|
|
func WithHome(path string) Option {
|
|
return func(c *Registry) {
|
|
c.homePath = path
|
|
}
|
|
}
|
|
|
|
func WithKeyringServiceName(name string) Option {
|
|
return func(c *Registry) {
|
|
c.keyringServiceName = name
|
|
}
|
|
}
|
|
|
|
func WithKeyringBackend(backend KeyringBackend) Option {
|
|
return func(c *Registry) {
|
|
c.keyringBackend = backend
|
|
}
|
|
}
|
|
|
|
func WithBech32Prefix(prefix string) Option {
|
|
return func(c *Registry) {
|
|
c.addressCodec = address.NewBech32Codec(prefix)
|
|
}
|
|
}
|
|
|
|
func WithCoinType(coinType uint32) Option {
|
|
return func(c *Registry) {
|
|
c.coinType = coinType
|
|
}
|
|
}
|
|
|
|
// New creates a new registry to manage accounts.
|
|
func New(options ...Option) (Registry, error) {
|
|
r := Registry{
|
|
keyringServiceName: sdktypes.KeyringServiceName(),
|
|
keyringBackend: KeyringTest,
|
|
homePath: KeyringHome,
|
|
addressCodec: address.NewBech32Codec(AccountPrefixCosmos),
|
|
coinType: CoinTypeCosmos,
|
|
}
|
|
|
|
for _, apply := range options {
|
|
apply(&r)
|
|
}
|
|
|
|
var err error
|
|
inBuf := bufio.NewReader(os.Stdin)
|
|
interfaceRegistry := types.NewInterfaceRegistry()
|
|
cryptocodec.RegisterInterfaces(interfaceRegistry)
|
|
cdc := codec.NewProtoCodec(interfaceRegistry)
|
|
r.Keyring, err = keyring.New(r.keyringServiceName, string(r.keyringBackend), r.homePath, inBuf, cdc)
|
|
if err != nil {
|
|
return Registry{}, err
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func NewStandalone(options ...Option) (Registry, error) {
|
|
return New(
|
|
append([]Option{
|
|
WithKeyringServiceName(KeyringServiceName),
|
|
WithHome(KeyringHome),
|
|
}, options...)...,
|
|
)
|
|
}
|
|
|
|
func NewInMemory(options ...Option) (Registry, error) {
|
|
return New(
|
|
append([]Option{
|
|
WithKeyringBackend(KeyringMemory),
|
|
}, options...)...,
|
|
)
|
|
}
|
|
|
|
// Account represents a Cosmos SDK account.
|
|
type Account struct {
|
|
// Name of the account.
|
|
Name string
|
|
|
|
// Record holds additional info about the account.
|
|
Record *keyring.Record
|
|
}
|
|
|
|
// Address returns the address of the account from given prefix.
|
|
func (a Account) Address(accPrefix string) (string, error) {
|
|
if accPrefix == "" {
|
|
accPrefix = AccountPrefixCosmos
|
|
}
|
|
|
|
pk, err := a.Record.GetPubKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
addressCodec := address.NewBech32Codec(accPrefix)
|
|
addr, err := addressCodec.BytesToString(pk.Address())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return addr, nil
|
|
}
|
|
|
|
// PubKey returns a public key for account.
|
|
func (a Account) PubKey() (string, error) {
|
|
pk, err := a.Record.GetPubKey()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return pk.String(), nil
|
|
}
|
|
|
|
// EnsureDefaultAccount ensures that default account exists.
|
|
func (r Registry) EnsureDefaultAccount() error {
|
|
_, err := r.GetByName(DefaultAccount)
|
|
|
|
var accErr *AccountDoesNotExistError
|
|
if errors.As(err, &accErr) {
|
|
_, _, err = r.Create(DefaultAccount)
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Create creates a new account with name.
|
|
func (r Registry) Create(name string) (acc Account, mnemonic string, err error) {
|
|
if _, err = r.GetByName(name); err == nil {
|
|
return Account{}, "", ErrAccountExists
|
|
}
|
|
var accErr *AccountDoesNotExistError
|
|
if !errors.As(err, &accErr) {
|
|
return Account{}, "", err
|
|
}
|
|
entropySeed, err := bip39.NewEntropy(256)
|
|
if err != nil {
|
|
return Account{}, "", err
|
|
}
|
|
mnemonic, err = bip39.NewMnemonic(entropySeed)
|
|
if err != nil {
|
|
return Account{}, "", err
|
|
}
|
|
algo, err := r.algo()
|
|
if err != nil {
|
|
return Account{}, "", err
|
|
}
|
|
record, err := r.Keyring.NewAccount(name, mnemonic, "", r.hdPath(), algo)
|
|
if err != nil {
|
|
return Account{}, "", err
|
|
}
|
|
|
|
acc = Account{
|
|
Name: name,
|
|
Record: record,
|
|
}
|
|
|
|
return acc, mnemonic, nil
|
|
}
|
|
|
|
// Import imports an existing account with name and passphrase and secret where secret can be a
|
|
// mnemonic or a private key.
|
|
func (r Registry) Import(name, secret, passphrase string) (Account, error) {
|
|
_, err := r.GetByName(name)
|
|
if err == nil {
|
|
return Account{}, ErrAccountExists
|
|
}
|
|
var accErr *AccountDoesNotExistError
|
|
if !errors.As(err, &accErr) {
|
|
return Account{}, err
|
|
}
|
|
|
|
if bip39.IsMnemonicValid(secret) {
|
|
algo, err := r.algo()
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
_, err = r.Keyring.NewAccount(name, secret, passphrase, r.hdPath(), algo)
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
} else if err := r.Keyring.ImportPrivKey(name, secret, passphrase); err != nil {
|
|
return Account{}, err
|
|
}
|
|
|
|
return r.GetByName(name)
|
|
}
|
|
|
|
// Export exports an account as a private key.
|
|
func (r Registry) Export(name, passphrase string) (key string, err error) {
|
|
if _, err = r.GetByName(name); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return r.Keyring.ExportPrivKeyArmor(name, passphrase)
|
|
}
|
|
|
|
// ExportHex exports an account as a private key in hex.
|
|
func (r Registry) ExportHex(name, passphrase string) (hex string, err error) {
|
|
if _, err = r.GetByName(name); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return unsafeExportPrivKeyHex(r.Keyring, name, passphrase)
|
|
}
|
|
|
|
func unsafeExportPrivKeyHex(kr keyring.Keyring, uid, passphrase string) (privKey string, err error) {
|
|
priv, err := kr.ExportPrivKeyArmor(uid, passphrase)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString([]byte(priv)), nil
|
|
}
|
|
|
|
// GetByName returns an account by its name.
|
|
func (r Registry) GetByName(name string) (Account, error) {
|
|
record, err := r.Keyring.Key(name)
|
|
if errors.Is(err, dkeyring.ErrKeyNotFound) || errors.Is(err, sdkerrors.ErrKeyNotFound) {
|
|
return Account{}, &AccountDoesNotExistError{name}
|
|
}
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
|
|
acc := Account{
|
|
Name: name,
|
|
Record: record,
|
|
}
|
|
|
|
return acc, nil
|
|
}
|
|
|
|
// GetByAddress returns an account by its address.
|
|
func (r Registry) GetByAddress(address string) (Account, error) {
|
|
sdkAddr, err := r.addressCodec.StringToBytes(address)
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
record, err := r.Keyring.KeyByAddress(sdktypes.AccAddress(sdkAddr))
|
|
if errors.Is(err, dkeyring.ErrKeyNotFound) || errors.Is(err, sdkerrors.ErrKeyNotFound) {
|
|
return Account{}, &AccountDoesNotExistError{address}
|
|
}
|
|
if err != nil {
|
|
return Account{}, err
|
|
}
|
|
return Account{
|
|
Name: record.Name,
|
|
Record: record,
|
|
}, nil
|
|
}
|
|
|
|
// List lists all accounts.
|
|
func (r Registry) List() ([]Account, error) {
|
|
records, err := r.Keyring.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var accounts []Account
|
|
|
|
for _, record := range records {
|
|
accounts = append(accounts, Account{
|
|
Name: record.Name,
|
|
Record: record,
|
|
})
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
// DeleteByName deletes an account by name.
|
|
func (r Registry) DeleteByName(name string) error {
|
|
err := r.Keyring.Delete(name)
|
|
if errors.Is(err, dkeyring.ErrKeyNotFound) {
|
|
return &AccountDoesNotExistError{name}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r Registry) hdPath() string {
|
|
return hd.CreateHDPath(r.coinType, 0, 0).String()
|
|
}
|
|
|
|
func (r Registry) algo() (keyring.SignatureAlgo, error) {
|
|
algos, _ := r.Keyring.SupportedAlgorithms()
|
|
return keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), algos)
|
|
}
|
|
|
|
type AccountDoesNotExistError struct {
|
|
Name string
|
|
}
|
|
|
|
func (e *AccountDoesNotExistError) Error() string {
|
|
return fmt.Sprintf("account %q does not exist", e.Name)
|
|
}
|