From 2e9812519e462f5441bc2d1b2c80a45494917ebf Mon Sep 17 00:00:00 2001 From: Chris Gianelloni Date: Sat, 30 Sep 2023 17:00:19 -0400 Subject: [PATCH] feat: initial cli support Signed-off-by: Chris Gianelloni --- .gitignore | 2 +- bursa.go | 49 ++++---------------- cmd/bursa/cli.go | 23 ++++++++++ cmd/bursa/main.go | 21 ++++++++- go.mod | 2 + go.sum | 9 +++- internal/cli/cli.go | 91 +++++++++++++++++++++++++++++++++++++ internal/config/config.go | 12 ++++- internal/logging/logging.go | 54 ++++++++++++++++++++++ 9 files changed, 215 insertions(+), 48 deletions(-) create mode 100644 cmd/bursa/cli.go create mode 100644 internal/cli/cli.go create mode 100644 internal/logging/logging.go diff --git a/.gitignore b/.gitignore index 09bcfd4..7feb49e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ go.work # binary -bursa +/bursa diff --git a/bursa.go b/bursa.go index 042c0ef..5b82453 100644 --- a/bursa.go +++ b/bursa.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" - "github.com/blinklabs-io/bursa/internal/config" // TODO: replace these w/ gOuroboros (blinklabs-io/gouroboros#364) "github.com/fivebinaries/go-cardano-serialization/address" "github.com/fivebinaries/go-cardano-serialization/bip32" @@ -86,9 +85,9 @@ func GetPaymentVKey(paymentKey bip32.XPrv) KeyFile { panic(err) } return KeyFile{ - Type: "PaymentVerificationKeyShelley_ed25519", + Type: "PaymentVerificationKeyShelley_ed25519", Description: "Payment Verification Key", - CborHex: fmt.Sprintf("%x", keyCbor), + CborHex: fmt.Sprintf("%x", keyCbor), } } @@ -98,9 +97,9 @@ func GetPaymentSKey(paymentKey bip32.XPrv) KeyFile { panic(err) } return KeyFile{ - Type: "PaymentExtendedSigningKeyShelley_ed25519_bip32", + Type: "PaymentExtendedSigningKeyShelley_ed25519_bip32", Description: "Payment Signing Key", - CborHex: fmt.Sprintf("%x", keyCbor), + CborHex: fmt.Sprintf("%x", keyCbor), } } @@ -114,9 +113,9 @@ func GetStakeVKey(stakeKey bip32.XPrv) KeyFile { panic(err) } return KeyFile{ - Type: "StakeVerificationKeyShelley_ed25519", + Type: "StakeVerificationKeyShelley_ed25519", Description: "Stake Verification Key", - CborHex: fmt.Sprintf("%x", keyCbor), + CborHex: fmt.Sprintf("%x", keyCbor), } } @@ -126,9 +125,9 @@ func GetStakeSKey(stakeKey bip32.XPrv) KeyFile { panic(err) } return KeyFile{ - Type: "StakeExtendedSigningKeyShelley_ed25519_bip32", + Type: "StakeExtendedSigningKeyShelley_ed25519_bip32", Description: "Stake Signing Key", - CborHex: fmt.Sprintf("%x", keyCbor), + CborHex: fmt.Sprintf("%x", keyCbor), } } @@ -170,35 +169,3 @@ func GetKeyFile(keyFile KeyFile) string { // Append newline return fmt.Sprintf("%s\n", ret) } - -func Run() { - // Load Config - cfg, err := config.LoadConfig() - if err != nil { - panic(err) - } - - mnemonic := cfg.Mnemonic - if mnemonic == "" { - mnemonic, err = NewMnemonic() - if err != nil { - panic(err) - } - } - rootKey, err := GetRootKeyFromMnemonic(mnemonic) - if err != nil { - panic(err) - } - accountKey := GetAccountKey(rootKey, 0) // TODO: more accounts - addr := GetAddress(accountKey, cfg.Network, 0) // TODO: more addresses - - fmt.Println("Loaded mnemonic and generated address...") - fmt.Printf("MNEMONIC=%s\n", mnemonic) - fmt.Printf("PAYMENT_ADDRESS=%s\n", addr.String()) - fmt.Printf("STAKE_ADDRESS=%s\n", addr.ToReward().String()) - - fmt.Printf("payment.vkey=%s", GetKeyFile(GetPaymentVKey(GetPaymentKey(accountKey, 0)))) - fmt.Printf("payment.skey=%s", GetKeyFile(GetPaymentSKey(GetPaymentKey(accountKey, 0)))) - fmt.Printf("stake.vkey=%s", GetKeyFile(GetStakeVKey(GetStakeKey(accountKey, 0)))) - fmt.Printf("stake.vkey=%s", GetKeyFile(GetStakeSKey(GetStakeKey(accountKey, 0)))) -} diff --git a/cmd/bursa/cli.go b/cmd/bursa/cli.go new file mode 100644 index 0000000..884a001 --- /dev/null +++ b/cmd/bursa/cli.go @@ -0,0 +1,23 @@ +// Copyright 2023 Blink Labs, LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "github.com/blinklabs-io/bursa/internal/cli" +) + +func cliRun() { + cli.Run() +} diff --git a/cmd/bursa/main.go b/cmd/bursa/main.go index 631cfdf..1b7539c 100644 --- a/cmd/bursa/main.go +++ b/cmd/bursa/main.go @@ -14,8 +14,25 @@ package main -import "github.com/blinklabs-io/bursa" +import ( + "fmt" + "os" +) func main() { - bursa.Run() + var subCommand string + // Parse subcommand (default: "cli") + if len(os.Args) < 2 { + subCommand = "cli" + } else { + subCommand = os.Args[1] + } + + switch subCommand { + case "cli": + cliRun() + default: + fmt.Printf("Unknown subcommand: %s\n", subCommand) + os.Exit(1) + } } diff --git a/go.mod b/go.mod index 3aada66..894e742 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,13 @@ require ( github.com/fxamacker/cbor/v2 v2.5.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/tyler-smith/go-bip39 v1.1.0 + go.uber.org/zap v1.26.0 ) require ( github.com/btcsuite/btcutil v1.0.2 // indirect github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/sys v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index f759aca..0ed8e29 100644 --- a/go.sum +++ b/go.sum @@ -27,11 +27,16 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -51,4 +56,4 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..780b8f0 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,91 @@ +// Copyright 2023 Blink Labs, LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "fmt" + "os" + + "github.com/blinklabs-io/bursa" + "github.com/blinklabs-io/bursa/internal/config" + "github.com/blinklabs-io/bursa/internal/logging" +) + +func NewDefaultWallet(mnemonic string) (*bursa.Wallet, error) { + cfg := config.GetConfig() + logger := logging.GetLogger() + + rootKey, err := bursa.GetRootKeyFromMnemonic(mnemonic) + if err != nil { + logger.Errorf("failed to get root key from mnemonic") + return nil, fmt.Errorf("failed to get root key from mnemonic: %s", err) + } + accountKey := bursa.GetAccountKey(rootKey, 0) + paymentKey := bursa.GetPaymentKey(accountKey, 0) + stakeKey := bursa.GetStakeKey(accountKey, 0) + addr := bursa.GetAddress(accountKey, cfg.Network, 0) + w := &bursa.Wallet{ + Mnemonic: mnemonic, + PaymentAddress: addr.String(), + StakeAddress: addr.ToReward().String(), + PaymentVKey: bursa.GetPaymentVKey(paymentKey), + PaymentSKey: bursa.GetPaymentSKey(paymentKey), + StakeVKey: bursa.GetStakeVKey(stakeKey), + StakeSKey: bursa.GetStakeSKey(stakeKey), + } + return w, nil +} + +func Run() { + // Load Config + cfg, err := config.LoadConfig() + if err != nil { + fmt.Printf("Failed to load config: %s\n", err) + os.Exit(1) + } + // Configure logging + logging.Setup() + logger := logging.GetLogger() + // Sync logger on exit + defer func() { + if err := logger.Sync(); err != nil { + // ignore error + return + } + }() + + // Load mnemonic + mnemonic := cfg.Mnemonic + if mnemonic == "" { + mnemonic, err = bursa.NewMnemonic() + if err != nil { + logger.Fatalf("failed to load mnemonic: %s", err) + } + } + w, err := NewDefaultWallet(mnemonic) + if err != nil { + logger.Fatalf("failed to initialize wallet: %s", err) + } + + logger.Infof("Loaded mnemonic and generated address...") + fmt.Printf("MNEMONIC=%s\n", w.Mnemonic) + fmt.Printf("PAYMENT_ADDRESS=%s\n", w.PaymentAddress) + fmt.Printf("STAKE_ADDRESS=%s\n", w.StakeAddress) + + fmt.Printf("payment.vkey=%s\n", w.PaymentVKey) + fmt.Printf("payment.skey=%s\n", w.PaymentSKey) + fmt.Printf("stake.vkey=%s\n", w.StakeVKey) + fmt.Printf("stake.skey=%s\n", w.StakeSKey) +} diff --git a/internal/config/config.go b/internal/config/config.go index f3727a9..fa71c5a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,12 +21,20 @@ import ( ) type Config struct { - Mnemonic string `envconfig:"MNEMONIC"` - Network string `envconfig:"NETWORK"` + Logging LoggingConfig `yaml:"logging"` + Mnemonic string `envconfig:"MNEMONIC"` + Network string `envconfig:"NETWORK"` +} + +type LoggingConfig struct { + Level string `yaml:"level" envconfig:"LOGGING_LEVEL"` } // We use a singleton for the config for convenience var globalConfig = Config{ + Logging: LoggingConfig{ + Level: "info", + }, Mnemonic: "", Network: "mainnet", } diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..75e07f1 --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,54 @@ +package logging + +import ( + "log" + "time" + + "github.com/blinklabs-io/bursa/internal/config" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Logger = zap.SugaredLogger + +var globalLogger *Logger + +func Setup() { + cfg := config.GetConfig() + // Build our custom logging config + loggerConfig := zap.NewProductionConfig() + // Change timestamp key name + loggerConfig.EncoderConfig.TimeKey = "timestamp" + // Use a human readable time format + loggerConfig.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout(time.RFC3339) + + // Set level + if cfg.Logging.Level != "" { + level, err := zapcore.ParseLevel(cfg.Logging.Level) + if err != nil { + log.Fatalf("error configuring logger: %s", err) + } + loggerConfig.Level.SetLevel(level) + } + + // Create the logger + l, err := loggerConfig.Build() + if err != nil { + log.Fatal(err) + } + + // Store the "sugared" version of the logger + globalLogger = l.Sugar() +} + +func GetLogger() *Logger { + return globalLogger +} + +func GetDesugaredLogger() *zap.Logger { + return globalLogger.Desugar() +} + +func GetAccessLogger() *zap.Logger { + return globalLogger.Desugar().With(zap.String("type", "access")).WithOptions(zap.WithCaller(false)) +}