mirror of
https://github.com/aljazceru/ark.git
synced 2026-02-02 18:14:49 +01:00
Scaffold noah cli (#10)
* CLI skeleton * noah CLI: send flags * add cypher.go file * fix .PHONY * add password_hash in state.json * encode public key using common pkg * use common.DecodeUrl * remove cli.Exit calls * redeem command: make --amount flag optional only if --force is not set * remove validateURL func * chmod +x scripts/build-noah * Update cmd/noah/redeem.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * Update cmd/noah/redeem.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * Update cmd/noah/init.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * Update cmd/noah/main.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * Update cmd/noah/send.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * rework receive and send * Update cmd/noah/send.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * Update cmd/noah/send.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * Update cmd/noah/redeem.go Co-authored-by: João Bordalo <bordalix@users.noreply.github.com> * receive command: return ark address --------- Co-authored-by: bordalix <joao.bordalo@gmail.com> Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
This commit is contained in:
6
Makefile
6
Makefile
@@ -1,10 +1,14 @@
|
||||
.PHONY: build clean cov help intergrationtest lint run test vet proto proto-lint
|
||||
.PHONY: build build-noah clean cov help intergrationtest lint run test vet proto proto-lint
|
||||
|
||||
## build: build for all platforms
|
||||
build:
|
||||
@echo "Building coordinatord binary..."
|
||||
@bash ./scripts/build
|
||||
|
||||
build-noah:
|
||||
@echo "Building noah binary..."
|
||||
@bash ./scripts/build-noah
|
||||
|
||||
## clean: cleans the binary
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
|
||||
18
cmd/noah/balance.go
Normal file
18
cmd/noah/balance.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var balanceCommand = cli.Command{
|
||||
Name: "balance",
|
||||
Usage: "Print balance of the Noah wallet",
|
||||
Action: balanceAction,
|
||||
}
|
||||
|
||||
func balanceAction(ctx *cli.Context) error {
|
||||
fmt.Println("balance is not implemented yet")
|
||||
return nil
|
||||
}
|
||||
114
cmd/noah/common.go
Normal file
114
cmd/noah/common.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func hashPassword(password []byte) []byte {
|
||||
hash := sha256.Sum256(password)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func verifyPassword(password []byte) error {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
passwordHashString, ok := state["password_hash"]
|
||||
if !ok {
|
||||
return fmt.Errorf("password hash not found")
|
||||
}
|
||||
|
||||
passwordHash, err := hex.DecodeString(passwordHashString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentPassHash := hashPassword(password)
|
||||
|
||||
if !bytes.Equal(passwordHash, currentPassHash) {
|
||||
return fmt.Errorf("invalid password")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPassword() ([]byte, error) {
|
||||
fmt.Print("password: ")
|
||||
passwordInput, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println() // new line
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = verifyPassword(passwordInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return passwordInput, nil
|
||||
}
|
||||
|
||||
func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encryptedPrivateKeyString, ok := state["encrypted_private_key"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("encrypted private key not found")
|
||||
}
|
||||
|
||||
encryptedPrivateKey, err := hex.DecodeString(encryptedPrivateKeyString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
password, err := readPassword()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cypher := NewAES128Cypher()
|
||||
privateKeyBytes, err := cypher.Decrypt(encryptedPrivateKey, password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privateKey := secp256k1.PrivKeyFromBytes(privateKeyBytes)
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arkURL, ok := state["ark_url"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("ark url not found")
|
||||
}
|
||||
|
||||
arkPubKey, _, err := common.DecodeUrl(arkURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, publicKey, err := common.DecodePubKey(arkPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return publicKey, nil
|
||||
}
|
||||
54
cmd/noah/config.go
Normal file
54
cmd/noah/config.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var configCommand = cli.Command{
|
||||
Name: "config",
|
||||
Usage: "Print local configuration of the Noah CLI",
|
||||
Action: printConfigAction,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "connect",
|
||||
Usage: "connect <ARK_URL>",
|
||||
Action: connectAction,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func printConfigAction(ctx *cli.Context) error {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, value := range state {
|
||||
fmt.Println(key + ": " + value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func connectAction(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return fmt.Errorf("missing ark URL")
|
||||
}
|
||||
|
||||
url := ctx.Args().Get(0)
|
||||
|
||||
_, _, err := common.DecodeUrl(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setState(map[string]string{"ark_url": url}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Connected to " + url)
|
||||
return nil
|
||||
}
|
||||
106
cmd/noah/cypher.go
Normal file
106
cmd/noah/cypher.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
type Cypher struct{}
|
||||
|
||||
func NewAES128Cypher() *Cypher {
|
||||
return &Cypher{}
|
||||
}
|
||||
|
||||
func (c *Cypher) Encrypt(privateKey, password []byte) ([]byte, error) {
|
||||
// Due to https://github.com/golang/go/issues/7168.
|
||||
// This call makes sure that memory is freed in case the GC doesn't do that
|
||||
// right after the encryption/decryption.
|
||||
defer debug.FreeOSMemory()
|
||||
|
||||
if len(privateKey) == 0 {
|
||||
return nil, fmt.Errorf("missing plaintext private key")
|
||||
}
|
||||
if len(password) == 0 {
|
||||
return nil, fmt.Errorf("missing encryption password")
|
||||
}
|
||||
|
||||
key, salt, err := deriveKey(password, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(blockCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, privateKey, nil)
|
||||
ciphertext = append(ciphertext, salt...)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (c *Cypher) Decrypt(encrypted, password []byte) ([]byte, error) {
|
||||
defer debug.FreeOSMemory()
|
||||
|
||||
if len(encrypted) == 0 {
|
||||
return nil, fmt.Errorf("missing encrypted mnemonic")
|
||||
}
|
||||
if len(password) == 0 {
|
||||
return nil, fmt.Errorf("missing decryption password")
|
||||
}
|
||||
|
||||
salt := encrypted[len(encrypted)-32:]
|
||||
data := encrypted[:len(encrypted)-32]
|
||||
|
||||
key, _, err := deriveKey(password, salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(blockCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce, text := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||
plaintext, err := gcm.Open(nil, nonce, text, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid password")
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// deriveKey derives a 32 byte array key from a custom passhprase
|
||||
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
||||
if salt == nil {
|
||||
salt = make([]byte, 32)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
// 2^20 = 1048576 recommended length for key-stretching
|
||||
// check the doc for other recommended values:
|
||||
// https://godoc.org/golang.org/x/crypto/scrypt
|
||||
key, err := scrypt.Key(password, salt, 1048576, 8, 1, 32)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return key, salt, nil
|
||||
}
|
||||
90
cmd/noah/init.go
Normal file
90
cmd/noah/init.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
passwordFlag = cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "password to encrypt private key",
|
||||
Value: "",
|
||||
Required: true,
|
||||
}
|
||||
|
||||
privateKeyFlag = cli.StringFlag{
|
||||
Name: "prvkey",
|
||||
Usage: "optional, private key to encrypt",
|
||||
Value: "",
|
||||
Required: false,
|
||||
}
|
||||
)
|
||||
|
||||
var initCommand = cli.Command{
|
||||
Name: "init",
|
||||
Usage: "Initialize Noah wallet private key, encrypted with password",
|
||||
Action: initAction,
|
||||
Flags: []cli.Flag{
|
||||
&passwordFlag,
|
||||
&privateKeyFlag,
|
||||
},
|
||||
}
|
||||
|
||||
func initAction(ctx *cli.Context) error {
|
||||
privateKeyString := ctx.String("prvkey")
|
||||
password := ctx.String("password")
|
||||
|
||||
if len(password) <= 0 {
|
||||
return fmt.Errorf("missing password flag (--password)")
|
||||
}
|
||||
|
||||
var privateKey *secp256k1.PrivateKey
|
||||
|
||||
if len(privateKeyString) <= 0 {
|
||||
privKey, err := generateRandomPrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privateKey = privKey
|
||||
} else {
|
||||
privKeyBytes, err := hex.DecodeString(privateKeyString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKey = secp256k1.PrivKeyFromBytes(privKeyBytes)
|
||||
}
|
||||
|
||||
cypher := NewAES128Cypher()
|
||||
|
||||
encryptedPrivateKey, err := cypher.Encrypt(privateKey.Serialize(), []byte(password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
passwordHash := hashPassword([]byte(password))
|
||||
|
||||
state := map[string]string{
|
||||
"encrypted_private_key": hex.EncodeToString(encryptedPrivateKey),
|
||||
"password_hash": hex.EncodeToString(passwordHash),
|
||||
}
|
||||
|
||||
if err := setState(state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateRandomPrivateKey() (*secp256k1.PrivateKey, error) {
|
||||
privKey, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return privKey, nil
|
||||
}
|
||||
158
cmd/noah/main.go
Normal file
158
cmd/noah/main.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
DATADIR_ENVVAR = "NOAH_DATADIR"
|
||||
STATE_FILE = "state.json"
|
||||
defaultArkURL = "ark://apub1qgvdtj5ttpuhkldavhq8thtm5auyk0ec4dcmrfdgu0u5hgp9we22v3hrs4x?relays=arelay1qt6f8p7h5f6tm7fv2z5wg92sz92rn9desfhd5733se4lkrptqtdrq65987l-arelay1qt6f8p7h5f6tm7fv2z5wg92sz92rn9desfhd5733se4lkrptqtdrq65987l"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "alpha"
|
||||
|
||||
noahDataDirectory = common.AppDataDir("noah", false)
|
||||
statePath = filepath.Join(noahDataDirectory, STATE_FILE)
|
||||
|
||||
initialState = map[string]string{
|
||||
"ark_url": defaultArkURL,
|
||||
"encrypted_private_key": "",
|
||||
"password_hash": "",
|
||||
}
|
||||
)
|
||||
|
||||
func initCLIEnv() {
|
||||
dataDir := cleanAndExpandPath(os.Getenv(DATADIR_ENVVAR))
|
||||
if len(dataDir) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
noahDataDirectory = dataDir
|
||||
statePath = filepath.Join(noahDataDirectory, STATE_FILE)
|
||||
}
|
||||
|
||||
func main() {
|
||||
initCLIEnv()
|
||||
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Version = version
|
||||
app.Name = "noah CLI"
|
||||
app.Usage = "Command line interface for Ark wallet"
|
||||
app.Commands = append(
|
||||
app.Commands,
|
||||
&balanceCommand,
|
||||
&configCommand,
|
||||
&initCommand,
|
||||
&receiveCommand,
|
||||
&redeemCommand,
|
||||
&sendCommand,
|
||||
)
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
if _, err := os.Stat(noahDataDirectory); os.IsNotExist(err) {
|
||||
return os.Mkdir(noahDataDirectory, os.ModeDir|0755)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Errorf("error: %v", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanAndExpandPath expands environment variables and leading ~ in the
|
||||
// passed path, cleans the result, and returns it.
|
||||
// This function is taken from https://github.com/btcsuite/btcd
|
||||
func cleanAndExpandPath(path string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Expand initial ~ to OS specific home directory.
|
||||
if strings.HasPrefix(path, "~") {
|
||||
var homeDir string
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
homeDir = u.HomeDir
|
||||
} else {
|
||||
homeDir = os.Getenv("HOME")
|
||||
}
|
||||
|
||||
path = strings.Replace(path, "~", homeDir, 1)
|
||||
}
|
||||
|
||||
// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
|
||||
// but the variables can still be expanded via POSIX-style $VARIABLE.
|
||||
return filepath.Clean(os.ExpandEnv(path))
|
||||
}
|
||||
|
||||
func getState() (map[string]string, error) {
|
||||
file, err := os.ReadFile(statePath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if err := setInitialState(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return initialState, nil
|
||||
}
|
||||
|
||||
data := map[string]string{}
|
||||
if err := json.Unmarshal(file, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func setInitialState() error {
|
||||
jsonString, err := json.Marshal(initialState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(statePath, jsonString, 0755)
|
||||
}
|
||||
|
||||
func setState(data map[string]string) error {
|
||||
currentData, err := getState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mergedData := merge(currentData, data)
|
||||
|
||||
jsonString, err := json.Marshal(mergedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(statePath, jsonString, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing to file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func merge(maps ...map[string]string) map[string]string {
|
||||
merge := make(map[string]string, 0)
|
||||
for _, m := range maps {
|
||||
for k, v := range m {
|
||||
merge[k] = v
|
||||
}
|
||||
}
|
||||
return merge
|
||||
}
|
||||
37
cmd/noah/receive.go
Normal file
37
cmd/noah/receive.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var receiveCommand = cli.Command{
|
||||
Name: "receive",
|
||||
Usage: "Print the Ark address associated with your wallet and the connected Ark",
|
||||
Action: receiveAction,
|
||||
}
|
||||
|
||||
func receiveAction(ctx *cli.Context) error {
|
||||
privateKey, err := privateKeyFromPassword()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKey := privateKey.PubKey()
|
||||
|
||||
aspPublicKey, err := getServiceProviderPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addr, err := common.EncodeAddress(common.MainNet.Addr, publicKey, aspPublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(addr)
|
||||
|
||||
return nil
|
||||
}
|
||||
67
cmd/noah/redeem.go
Normal file
67
cmd/noah/redeem.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
addressFlag = cli.StringFlag{
|
||||
Name: "address",
|
||||
Usage: "main chain address receiving the redeeemed VTXO",
|
||||
Value: "",
|
||||
Required: true,
|
||||
}
|
||||
|
||||
amountToRedeemFlag = cli.Uint64Flag{
|
||||
Name: "amount",
|
||||
Usage: "amount to redeem",
|
||||
Value: 0,
|
||||
Required: false,
|
||||
}
|
||||
|
||||
forceFlag = cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "force redemption without collaborate with the Ark service provider",
|
||||
Value: false,
|
||||
Required: false,
|
||||
}
|
||||
)
|
||||
|
||||
var redeemCommand = cli.Command{
|
||||
Name: "redeem",
|
||||
Usage: "Redeem VTXO(s) to onchain",
|
||||
Flags: []cli.Flag{&addressFlag, &amountToRedeemFlag, &forceFlag},
|
||||
Action: redeemAction,
|
||||
}
|
||||
|
||||
func redeemAction(ctx *cli.Context) error {
|
||||
address := ctx.String("address")
|
||||
amount := ctx.Uint64("amount")
|
||||
force := ctx.Bool("force")
|
||||
|
||||
if len(address) <= 0 {
|
||||
return fmt.Errorf("missing address flag (--address)")
|
||||
}
|
||||
|
||||
if !force && amount <= 0 {
|
||||
return fmt.Errorf("missing amount flag (--amount)")
|
||||
}
|
||||
|
||||
if force {
|
||||
return unilateralRedeem(address)
|
||||
}
|
||||
|
||||
return collaborativeRedeem(address, amount)
|
||||
}
|
||||
|
||||
func collaborativeRedeem(address string, amount uint64) error {
|
||||
fmt.Println("collaborative redeem is not implemented yet")
|
||||
return nil
|
||||
}
|
||||
|
||||
func unilateralRedeem(address string) error {
|
||||
fmt.Println("unilateral redeem is not implemented yet")
|
||||
return nil
|
||||
}
|
||||
60
cmd/noah/send.go
Normal file
60
cmd/noah/send.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
receiversFlag = cli.StringFlag{
|
||||
Name: "receivers",
|
||||
Usage: "receivers of the send transaction, JSON encoded: '[{\"to\": \"<...>\", \"amount\": <...>}, ...]'",
|
||||
Value: "",
|
||||
Required: true,
|
||||
}
|
||||
)
|
||||
|
||||
var sendCommand = cli.Command{
|
||||
Name: "send",
|
||||
Usage: "Send VTXOs to a list of addresses",
|
||||
Action: sendAction,
|
||||
Flags: []cli.Flag{&receiversFlag},
|
||||
}
|
||||
|
||||
func sendAction(ctx *cli.Context) error {
|
||||
receivers := ctx.String("receivers")
|
||||
|
||||
// parse json encoded receivers
|
||||
var receiversJSON []receiverJSON
|
||||
if err := json.Unmarshal([]byte(receivers), &receiversJSON); err != nil {
|
||||
return fmt.Errorf("invalid receivers: %s", err)
|
||||
}
|
||||
|
||||
if len(receiversJSON) <= 0 {
|
||||
return fmt.Errorf("no receivers specified")
|
||||
}
|
||||
|
||||
for _, receiver := range receiversJSON {
|
||||
// TODO: check if receiver asp public key is valid
|
||||
_, _, _, err := common.DecodeAddress(receiver.To)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid receiver address: %s", err)
|
||||
}
|
||||
|
||||
if receiver.Amount <= 0 {
|
||||
return fmt.Errorf("invalid amount: %d", receiver.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("send command is not implemented yet")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type receiverJSON struct {
|
||||
To string `json:"to"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
9
go.mod
9
go.mod
@@ -6,23 +6,26 @@ replace github.com/ark-network/ark/common => ./pkg/common
|
||||
|
||||
require (
|
||||
github.com/ark-network/ark/common v0.0.0-00010101000000-000000000000
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/viper v1.17.0
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
github.com/vulpemventures/go-elements v0.4.7
|
||||
golang.org/x/term v0.13.0
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.23.1 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||
github.com/btcsuite/btcd/btcutil/psbt v1.1.4 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
@@ -30,6 +33,7 @@ require (
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
@@ -38,6 +42,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
|
||||
10
go.sum
10
go.sum
@@ -75,6 +75,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -212,6 +214,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
|
||||
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
@@ -242,12 +246,16 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 h1:CTcw80hz/Sw8hqlKX5ZYvBUF5gAHSHwdjXxRf/cjDcI=
|
||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:GXBJykxW2kUcktGdsgyay7uwwWvkljASfljNcT0mbh8=
|
||||
github.com/vulpemventures/go-elements v0.4.7 h1:M5dtBHwRXqct75DJeEv5b0PUFS93t0gh2naJaGlvp60=
|
||||
github.com/vulpemventures/go-elements v0.4.7/go.mod h1:aBGuWXHaiAIUIcwqCdtEh2iQ3kJjKwHU9ywvhlcRSeU=
|
||||
github.com/vulpemventures/go-secp256k1-zkp v1.1.6 h1:BmsrmXRLUibwa75Qkk8yELjpzCzlAjYFGLiLiOdq7Xo=
|
||||
github.com/vulpemventures/go-secp256k1-zkp v1.1.6/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -410,6 +418,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
16
scripts/build-noah
Executable file
16
scripts/build-noah
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
PARENT_PATH=$(dirname $(
|
||||
cd $(dirname $0)
|
||||
pwd -P
|
||||
))
|
||||
|
||||
OS=$(eval "go env GOOS")
|
||||
ARCH=$(eval "go env GOARCH")
|
||||
|
||||
pushd $PARENT_PATH
|
||||
mkdir -p build
|
||||
GO111MODULE=on go build -ldflags="-s -w" -o build/noah-$OS-$ARCH ./cmd/noah
|
||||
popd
|
||||
Reference in New Issue
Block a user