Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Waggle sign API server #11

Merged
merged 10 commits into from
Aug 24, 2023
176 changes: 100 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@
2. Manage signing accounts
3. Send drop claims to the Moonstream Engine API

### Installation
## Installation

```
```bash
go install github.com/moonstream-to/waggle@latest
```

### Usage
## Usage as CLI

```
```bash
waggle -h
```

#### Manage accounts

You can import a signing account from an external wallet using its private key:

```
```bash
waggle accounts import -k <path at which to save keystore file>
```

Expand Down Expand Up @@ -63,34 +63,34 @@ claims (`batch.json` below):

```json
[
{
"dropId": "2",
"requestID": "5",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
},
{
"dropId": "2",
"requestID": "6",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
},
{
"dropId": "2",
"requestID": "7",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
},
{
"dropId": "2",
"requestID": "8",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
}
{
"dropId": "2",
"requestID": "5",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
},
{
"dropId": "2",
"requestID": "6",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
},
{
"dropId": "2",
"requestID": "7",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
},
{
"dropId": "2",
"requestID": "8",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000"
}
]
```

Expand All @@ -108,42 +108,42 @@ This results in a file that looks like this:

```json
[
{
"dropId": "2",
"requestID": "5",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "408...",
"signer": "<redacted Ethereum address>"
},
{
"dropId": "2",
"requestID": "6",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "667...",
"signer": "<redacted Ethereum address>"
},
{
"dropId": "2",
"requestID": "7",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "5c6...",
"signer": "<redacted Ethereum address>"
},
{
"dropId": "2",
"requestID": "8",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "85f...",
"signer": "<redacted Ethereum address>"
}
{
"dropId": "2",
"requestID": "5",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "408...",
"signer": "<redacted Ethereum address>"
},
{
"dropId": "2",
"requestID": "6",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "667...",
"signer": "<redacted Ethereum address>"
},
{
"dropId": "2",
"requestID": "7",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "5c6...",
"signer": "<redacted Ethereum address>"
},
{
"dropId": "2",
"requestID": "8",
"claimant": "0x000000000000000000000000000000000000dEaD",
"blockDeadline": "40000000",
"amount": "3000000000000000000",
"signature": "85f...",
"signer": "<redacted Ethereum address>"
}
]
```

Expand All @@ -160,7 +160,7 @@ You can generate an access token at https://moonstream.to/app

Then:

```
```bash
waggle moonstream drop \
--contract-id a035f3f8-7301-45b7-940a-109585419774 \
--infile signed_batch.json
Expand All @@ -183,10 +183,9 @@ https://engineapi.moonstream.to/contracts/requests?contract_id=$CONTRACT_ID&call
Here, `$CONTRACT_ID` should be the same contract ID you used in `waggle moonstream drop`. `$USER_ADDRESS`
is the user's Ethereum account address.


#### Get claim requests from Bugout journal

```
```bash
waggle sign dropper pull \
--cursor <cursor name> \
-j <Bugout Journal ID> \
Expand All @@ -196,7 +195,7 @@ waggle sign dropper pull \
This expects a `BUGOUT_ACCESS_TOKEN` environment variable to be set. You can generate an access token
at https://bugout.dev/account/tokens. Once you have generated a token, set it in your shell session using:

```
```bash
export BUGOUT_ACCESS_TOKEN=<token>
```

Expand All @@ -215,7 +214,7 @@ dropId,requestID,claimant,blockDeadline,amount,signer,signature
You can sign this using the `waggle sign dropper batch command` by passing the CSV file as the `--infile`
argument and setting the `--csv` flag:

```
```bash
waggle sign dropper batch -k signer.json \
--chain-id 80001 \
--dropper 0x4ec36E288E1b5d6914851a141cb041152Cf95328 \
Expand All @@ -227,13 +226,38 @@ waggle sign dropper batch -k signer.json \

### Build

```
```bash
go build ./...
./waggle -h
```

### Test

```
```bash
go test ./... -v
```

## Usage as API server

Example of server configuration file `config.json`:

```json
[
{
"keyfile_path": "dev.json",
"keyfile_password_path": "password.txt"
}
]
```

Config also could be generated with command:

```bash
waggle accounts config --keyfile dev.json --outfile config.json
```

Run server:

```bash
waggle server run --host 0.0.0.0 --config config.json
```
114 changes: 112 additions & 2 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import (
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"

bugout "github.com/bugout-dev/bugout-go/pkg"
"github.com/spf13/cobra"
"golang.org/x/term"
)

func CreateRootCommand() *cobra.Command {
Expand All @@ -33,7 +37,8 @@ func CreateRootCommand() *cobra.Command {
signCmd := CreateSignCommand()
accountsCmd := CreateAccountsCommand()
moonstreamCommand := CreateMoonstreamCommand()
rootCmd.AddCommand(versionCmd, signCmd, accountsCmd, moonstreamCommand)
serverCommand := CreateServerCommand()
rootCmd.AddCommand(versionCmd, signCmd, accountsCmd, moonstreamCommand, serverCommand)

completionCmd := CreateCompletionCommand(rootCmd)
rootCmd.AddCommand(completionCmd)
Expand Down Expand Up @@ -126,7 +131,63 @@ func CreateAccountsCommand() *cobra.Command {
},
}

accountsCommand.AddCommand(importCommand)
var password, outfile string

configCommand := &cobra.Command{
kompotkot marked this conversation as resolved.
Show resolved Hide resolved
Use: "config",
Short: "Prepare configuration for waggle API server.",
RunE: func(cmd *cobra.Command, args []string) error {
serverSignerConfigs := []ServerSignerConfig{}
var passwordRaw []byte
var err error
if password == "" {
fmt.Print("Enter password for keyfile (it will not be displayed on screen): ")
passwordRaw, err = term.ReadPassword(int(os.Stdin.Fd()))
fmt.Print("\n")
if err != nil {
return fmt.Errorf("error reading password from input: %s", err.Error())
}
} else {
passwordRaw = []byte(password)
}

keyfilePath := strings.TrimSuffix(keyfile, "/")
_, err = os.Stat(keyfilePath)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("file %s not found, err: %v", keyfilePath, err)
}
return fmt.Errorf("error due checking keyfile path %s, err: %v", keyfilePath, err)
}
dir, file := filepath.Split(keyfilePath)
passwordFilePath := fmt.Sprintf("%spassword-%s", dir, file)
os.WriteFile(passwordFilePath, passwordRaw, 0640)

// TODO(kompotkot): Provide functionality to generate config with multiple keyfiles
serverSignerConfigs = append(serverSignerConfigs, ServerSignerConfig{
KeyfilePath: keyfile,
KeyfilePasswordPath: passwordFilePath,
})
resultJSON, err := json.Marshal(serverSignerConfigs)
if err != nil {
return err
}

if outfile != "" {
os.WriteFile(outfile, resultJSON, 0644)
} else {
os.Stdout.Write(resultJSON)
}

return nil
},
}

configCommand.PersistentFlags().StringVarP(&keyfile, "keystore", "k", "", "Path to keystore file (this should be a JSON file)")
configCommand.PersistentFlags().StringVarP(&password, "password", "p", "", "Password for keystore file. If not provided, you will be prompted for it when you sign with the key")
configCommand.PersistentFlags().StringVarP(&outfile, "outfile", "o", "config.json", "Config file output path")

accountsCommand.AddCommand(importCommand, configCommand)

return accountsCommand
}
Expand Down Expand Up @@ -504,3 +565,52 @@ func CreateMoonstreamCommand() *cobra.Command {

return moonstreamCommand
}

func CreateServerCommand() *cobra.Command {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to have:

  • waggle server configure (replacing waggle accounts config)
  • waggle server run

serverCommand := &cobra.Command{
Use: "server",
Short: "API of signing and registration of call requests",
}

var host, config string
var port int
runSubcommand := &cobra.Command{
Use: "run",
Short: "Run API server.",
RunE: func(cmd *cobra.Command, args []string) error {
configs, err := ReadServerConfig(config)
if err != nil {
return err
}
if len(*configs) == 0 {
return fmt.Errorf("no signers available")
}

availableSigners = make(map[string]AvailableSigner)
for _, c := range *configs {
passwordRaw, err := os.ReadFile(c.KeyfilePasswordPath)
if err != nil {
return err
}
key, keyErr := KeyFromFile(c.KeyfilePath, string(passwordRaw))
if keyErr != nil {
return keyErr
}
availableSigners[key.Address.String()] = AvailableSigner{
key: key,
}
log.Printf("Loaded signer %s", key.Address.String())
}

err = ServerRun(host, port)
return err
},
}
runSubcommand.Flags().StringVar(&host, "host", "127.0.0.1", "Server listening address")
runSubcommand.Flags().IntVar(&port, "port", 7379, "Server listening port")
runSubcommand.Flags().StringVar(&config, "config", "./config.json", "Path to server configuration file")

serverCommand.AddCommand(runSubcommand)

return serverCommand
}
Loading
Loading