diff --git a/.gitignore b/.gitignore
index 99960265f..e0e647300 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ index.html
# Ignore flow json config
flow.json
flow*.json
+!tests/flow.json
# IDE related files
.idea
diff --git a/.golangci.yml b/.golangci.yml
index 743e832fd..b25be26be 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -10,3 +10,9 @@ linters:
- ineffassign
- typecheck
- misspell
+ - goimports
+ - unused
+linters-settings:
+ goimports:
+ # put imports beginning with prefix after 3rd-party package
+ local-prefixes: github.com/onflow/flow-cli
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d718f71db..332dee36c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -127,6 +127,249 @@ We try to follow the coding guidelines from the Go community.
- Code should be commented
- Code should pass all tests: `make test`
+# CLI Guidelines
+This is a design guideline used for the development of the Flow CLI. The purpose of this guideline is to achieve consistency across new features and allow composability of commands that build the fundamentals of great CLI design.
+
+> Whatever software you’re building, you can be absolutely certain that people will
+> use it in ways you didn’t anticipate. Your software will become a part in a larger system
+> — your only choice is over whether it will be a well-behaved part. - [Clig](https://clig.dev/)
+
+
+# Human Interaction
+
+Our interaction with the CLI is done by executing CLI commands and passing arguments
+and flags which need to be consistent. Consistency will allow interactions to
+be predictable and will with time feel natural even without resorting to reading instructions.
+
+## Commands
+Be consistent across commands and subcommands. In case we define a subcommand we
+should use `noun verb` pattern. **Don’t have ambiguous or similarly-named commands.**
+Naming and language should be the same as used in the rest of Flow's documentation.
+Use only lowercase letters, and dashes if you really need to. Keep it short.
+Users will be typing it all the time.
+
+```
+flow accounts
+```
+
+## Flags
+Flags are **named parameters**, denoted with either a hyphen, and a
+single-letter name (`-r`) or a double hyphen and a multiple-letter
+name (`--recursive`). The longer version is preferred for better readability
+and simpler learning experience in the beginning but after a while users will
+probably start using the shorter version.
+
+Every flag should have a shorter version and they should be both presented in the help.
+Every flag that can have a default value should have a default value
+so users don’t have to specify all of them when not needed.
+
+Support flags following a value delimited by a space or
+equal (=) sign (ex: `--username test` or `--username=test`)
+
+```
+flow accounts get --filter "address"
+```
+
+Use common naming for default falgs such as:
+
+`--log`: Logging level.
+
+`--output`: Output format (JSON, inline etc...).
+
+`-h, --help`: Help. This should only mean help. See the help section.
+
+`--version`: Version.
+
+
+## Arguments
+Arguments, or args, are positional parameters to a command.
+There should be as few arguments as possible, because it is hard for users to remember the sequence of arguments.
+
+Because they are relying on position, flags should be used where more than one argument would be required.
+
+Arguments should be one word verbs following general naming guideline.
+**Prefer flags to arguments**.
+
+Use an argument for the value that command requires in order to run.
+
+```
+flow accounts get
+```
+
+## Robustness
+CLI should feel robust. Long operation should also provide output as they are processing.
+**Responsivness is more important than speed.**
+
+Warn before you make a non-additive change. Eventually, you’ll find that you can’t
+avoid breaking an interface. Before you do, forewarn your users in the program itself:
+when they pass the flag you’re looking to deprecate, tell them it’s going to change soon.
+Make sure there’s a way they can modify their usage today to make it
+future-proof, and tell them how to do it.
+
+# Computer Interaction
+
+The CLI will respond to execution of commands by providing some output.
+Output should with the same reasons as input be consistent and predictable,
+even further it should follow some industry standards and thus allow great strength
+of CLI use: composability. Output should be served in the right dosage. We shouldn't
+output lines and lines of data because we will shadow the important part the same
+way we shouldn't let the CLI command run in background not providing any output for
+minutes as it is working because the user will start to doubt if the command is broken.
+It's important to get this balance right.
+
+## Interaction
+Commands must be stateless and idempotent, meaning they can be run without relying on external state.
+If we have some external state, like the configuration, each command must include an option to
+define it on the fly, but it must also work without it by first relying on the externally
+saved state in the configuration, and if not found, relying on a default value.
+When relying on a default value results in an error, there should be an explanation for the user that a configuration should be created.
+
+We try to use default values first to get that “works like magic” feeling.
+
+Never require a prompt for user input. Always offer a flag to provide that input.
+However, if a user doesn't provide required input we can offer a prompt as an alternative.
+
+
+## Output
+
+“Expect the output of every program to become the input to another, as yet unknown, program.” — Doug McIlroy
+
+Output should use **stdout**. Output of commands should be presented in a clear formatted way for
+users to easily scan it, but it must also be compatible with `grep` command often used in
+command chaining. Output should also be possible in json by using `--output json` flag.
+
+Default command response should be to the stdout and not saved to a file. Anytime we want
+the output to be saved to a file we should explicitly specify so by using `--save filename.txt`
+flag and providing the path.
+
+
+```
+Address 179b6b1cb6755e31
+Balance 0
+Keys 2
+
+Key 0 Public Key c8a2a318b9099cc6c872a0ec3dcd9f59d17837e4ffd6cd8a1f913ddfa769559605e1ad6ad603ebb511f5a6c8125f863abc2e9c600216edaa07104a0fe320dba7
+ Weight 1000
+ Signature Algorithm ECDSA_P256
+ Hash Algorithm SHA3_256
+
+Code
+ pub contract Foo {
+ pub var bar: String
+
+ init() {
+ self.bar = "Hello, World!"
+ }
+ }
+```
+
+```
+{"Address":"179b6b1cb6755e31","Balance":0,"Code":"CnB1YiBjb250cmFjdCBGb28gewoJcHViIHZhciBiYXI6IFN0cmluZwoKCWluaXQoKSB7CgkJc2VsZi5iYXIgPSAiSGVsbG8sIFdvcmxkISIKCX0KfQo=","Keys":[{"Index":0,"PublicKey":{},"SigAlgo":2,"HashAlgo":3,"Weight":1000,"SequenceNumber":0,"Revoked":false}],"Contracts":null}
+```
+
+## Error
+Error should be human-readable, avoid printing out stack trace. It should give some
+pointers as to what could have gone wrong and how to fix it.
+Maybe try involving an example. However allow `--log debug` flag for complete error info
+with stack. Error messages should be sent to `stderr`
+
+```
+❌ Error while dialing dial tcp 127.0.0.1:3569: connect: connection refused"
+🙏 Make sure your emulator is running or connection address is correct.
+```
+
+## Saving
+Saving output to files should be optional and never a default action unless it is
+clear from the command name the output will be saved and that is
+required for later use (example: `flow init`). Output can be piped in files, and
+our CLI should resort to that for default saving. If there is a specific format
+that needs to be saved or converted to we should be able to display that format as
+well hence again allowing us to pipe it to files.
+
+## Help
+Main help screen must contain a list of all commands with their own description,
+it must also include examples for commands. Help screen should include link to the documentation website.
+
+Commands should have a description that is not too long (less than 100 characters).
+Help should be outputed if command is ran without any arguments. Help outline should be:
+
+```Description:
+
+
+Usage:
+
+
+Examples:
+ An optional section of example(s) on how to run the command.
+
+Commands:
+
+
+
+
+ …
+
+Flags:
+ --
+
+ --
+
+ …
+```
+
+## Progress
+Show progress for longer running commands visually. Visual feedback on longer
+running commands is important so users don’t get confused if command is running
+or the CLI is hang up resulting in user quitting.
+
+```
+Loading 0x1fd892083b3e2a4c...⠼
+```
+
+## Feedback
+Commands should provide feedback if there is no result to be presented.
+If a command completes an operation that has no result to be shown it
+should write out that command was executed. This is meant to assure the user
+of the completion of the command.
+If user is executing a destructive command they should be asked for approval
+but this command should allow passing confirmation with a flag `--yes`.
+
+```
+💾 result saved to: account.txt
+```
+
+## Exit
+CLI should return zero status unless it is shut down because of an error.
+This will allow chaining of commands and interfacing with other cli. If a
+command is long running then provide description how to exit it (e.g. "Press Ctrl+C to stop").
+```
+exit status 1
+```
+
+## Colors
+Base color should be white with yellow reserved for warnings and red for errors.
+Blue color can be used to accent some commands but it shouldn’t be
+used too much as it will confuse the user.
+
+
+## Inspiration and Reference
+https://clig.dev/
+
+https://blog.developer.atlassian.com/10-design-principles-for-delightful-clis/
+
+https://devcenter.heroku.com/articles/cli-style-guide
+
+https://eng.localytics.com/exploring-cli-best-practices/
+
+
+
+
+
+
+
+
+
+
## Additional Notes
Thank you for your interest in contributing to the Flow CLI!
diff --git a/Makefile b/Makefile
index f471bd6c9..99e8900d7 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,14 @@ install-tools:
test:
GO111MODULE=on go test -coverprofile=$(COVER_PROFILE) $(if $(JSON_OUTPUT),-json,) ./...
+.PHONY: test-e2e
+test-e2e:
+ GO111MODULE=on E2E=1 go test tests/e2e_test.go
+
+.PHONY: test-e2e-emulator
+test-e2e-emulator:
+ flow -f tests/flow.json emulator start
+
.PHONY: coverage
coverage:
ifeq ($(COVER), true)
diff --git a/cmd/flow/main.go b/cmd/flow/main.go
index f2ed32596..93cbf7f6c 100644
--- a/cmd/flow/main.go
+++ b/cmd/flow/main.go
@@ -22,44 +22,47 @@ package main
import (
"github.com/spf13/cobra"
- cli "github.com/onflow/flow-cli/flow"
- "github.com/onflow/flow-cli/flow/accounts"
- "github.com/onflow/flow-cli/flow/blocks"
- "github.com/onflow/flow-cli/flow/cadence"
- "github.com/onflow/flow-cli/flow/collections"
- "github.com/onflow/flow-cli/flow/emulator"
- "github.com/onflow/flow-cli/flow/events"
- "github.com/onflow/flow-cli/flow/initialize"
- "github.com/onflow/flow-cli/flow/keys"
- "github.com/onflow/flow-cli/flow/project"
- "github.com/onflow/flow-cli/flow/scripts"
- "github.com/onflow/flow-cli/flow/transactions"
- "github.com/onflow/flow-cli/flow/version"
+ "github.com/onflow/flow-cli/internal/accounts"
+ "github.com/onflow/flow-cli/internal/blocks"
+ "github.com/onflow/flow-cli/internal/cadence"
+ "github.com/onflow/flow-cli/internal/collections"
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/internal/config"
+ "github.com/onflow/flow-cli/internal/emulator"
+ "github.com/onflow/flow-cli/internal/events"
+ "github.com/onflow/flow-cli/internal/keys"
+ "github.com/onflow/flow-cli/internal/project"
+ "github.com/onflow/flow-cli/internal/scripts"
+ "github.com/onflow/flow-cli/internal/transactions"
+ "github.com/onflow/flow-cli/internal/version"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
)
-var cmd = &cobra.Command{
- Use: "flow",
- TraverseChildren: true,
-}
+func main() {
+ var cmd = &cobra.Command{
+ Use: "flow",
+ TraverseChildren: true,
+ }
-func init() {
- cmd.AddCommand(project.Cmd)
- cmd.AddCommand(initialize.Cmd)
- cmd.AddCommand(accounts.Cmd)
- cmd.AddCommand(blocks.Cmd)
- cmd.AddCommand(collections.Cmd)
- cmd.AddCommand(keys.Cmd)
- cmd.AddCommand(emulator.Cmd)
- cmd.AddCommand(events.Cmd)
+ // hot commands
+ config.InitCommand.AddToParent(cmd)
+
+ // structured commands
cmd.AddCommand(cadence.Cmd)
+ cmd.AddCommand(version.Cmd)
+ cmd.AddCommand(emulator.Cmd)
+ cmd.AddCommand(accounts.Cmd)
cmd.AddCommand(scripts.Cmd)
cmd.AddCommand(transactions.Cmd)
- cmd.AddCommand(version.Cmd)
- cmd.PersistentFlags().StringSliceVarP(&cli.ConfigPath, "config-path", "f", cli.ConfigPath, "Path to flow configuration file")
-}
+ cmd.AddCommand(keys.Cmd)
+ cmd.AddCommand(events.Cmd)
+ cmd.AddCommand(blocks.Cmd)
+ cmd.AddCommand(collections.Cmd)
+ cmd.AddCommand(project.Cmd)
+
+ command.InitFlags(cmd)
-func main() {
if err := cmd.Execute(); err != nil {
- cli.Exit(1, err.Error())
+ util.Exit(1, err.Error())
}
}
diff --git a/docs/account-add-contract.md b/docs/account-add-contract.md
new file mode 100644
index 000000000..ccdfaa8ce
--- /dev/null
+++ b/docs/account-add-contract.md
@@ -0,0 +1,117 @@
+---
+title: Deploy a Contract with the Fow CLI
+sidebar_title: Deploy a Contract
+---
+
+Deploy a new contract to a Flow account using the Flow CLI.
+
+```shell
+flow accounts add-contract
+````
+
+## Example Usage
+
+```shell
+> flow accounts add-contract FungibleToken ./FungibleToken.cdc
+
+Contract 'FungibleToken' deployed to the account 0xf8d6e0586b0a20c7
+
+Address 0xf8d6e0586b0a20c7
+Balance 9999999999970000000
+Keys 1
+
+Key 0 Public Key 640a5a359bf3536d15192f18d872d57c98a96cb871b92b70cecb0739c2d5c37b4be12548d3526933c2cda9b0b9c69412f45ffb6b85b6840d8569d969fe84e5b7
+ Weight 1000
+ Signature Algorithm ECDSA_P256
+ Hash Algorithm SHA3_256
+
+Contracts Deployed: 1
+Contract: 'FungibleToken'
+```
+
+## Arguments
+
+### Name
+
+- Name: `name`
+- Valid inputs: any string value.
+
+Name of the contract as it is defined in the contract source code.
+
+### Filename
+
+- Name: `filename`
+- Valid inputs: a path in the current filesystem.
+
+Path to the file containing the contract source code.
+
+## Flags
+
+### Signer
+
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in the configuration (`flow.json`)
+
+Specify the name of the account that will be used to sign the transaction.
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`).
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/account-remove-contract.md b/docs/account-remove-contract.md
new file mode 100644
index 000000000..0d3b68fb7
--- /dev/null
+++ b/docs/account-remove-contract.md
@@ -0,0 +1,109 @@
+---
+title: Remove a Contract with the Fow CLI
+sidebar_title: Remove a Contract
+---
+
+Remove an existing contract deployed to a Flow account using the Flow CLI.
+
+```shell
+flow accounts remove-contract
+```
+
+## Example Usage
+
+```shell
+> flow accounts remove-contract FungibleToken ./FungibleToken.cdc
+
+Contract 'FungibleToken' removed from account '0xf8d6e0586b0a20c7'
+
+Address 0xf8d6e0586b0a20c7
+Balance 0
+Keys 1
+
+Key 0 Public Key 640a5a359bf3536d15192f18d872d57c98a96cb871b92b70cecb0739c2d5c37b4be12548d3526933c2cda9b0b9c69412f45ffb6b85b6840d8569d969fe84e5b7
+Weight 1000
+Signature Algorithm ECDSA_P256
+Hash Algorithm SHA3_256
+
+Contracts Deployed: 0
+```
+
+## Arguments
+
+### Name
+
+- Name: `name`
+- Valid inputs: any string value.
+
+Name of the contract as it is defined in the contract source code.
+
+## Flags
+
+### Signer
+
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in the configuration (`flow.json`).
+
+Specify the name of the account that will be used to sign the transaction.
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/account-staking-info.md b/docs/account-staking-info.md
new file mode 100644
index 000000000..b544fd917
--- /dev/null
+++ b/docs/account-staking-info.md
@@ -0,0 +1,114 @@
+---
+title: Get Account Staking Info with the Fow CLI
+sidebar_title: Staking Info
+description: How to get staking info
+---
+
+Retrieve staking information for the account on the Flow network using Flow CLI.
+
+`flow accounts staking-info `
+
+## Example Usage
+
+```shell
+> accounts staking-info 535b975637fb6bee --host access.testnet.nodes.onflow.org:9000
+
+Account Staking Info:
+ID: "ca00101101010100001011010101010101010101010101011010101010101010"
+Initial Weight: 100
+Networking Address: "ca00101101010100001011010101010101010101010101011010101010101010"
+Networking Key: "ca00101101010100001011010101010101010101010101011010101010101010ca00101101010100001011010101010101010101010101011010101010101010"
+Role: 1
+Staking Key: "ca00101101010100001011010101010101010101010101011010101010101010ca00101101010100001011010101010101010101010101011010101010101010ca00101101010100001011010101010101010101010101011010101010101010"
+Tokens Committed: 0.00000000
+Tokens To Unstake: 0.00000000
+Tokens Rewarded: 82627.77000000
+Tokens Staked: 250000.00000000
+Tokens Unstaked: 0.00000000
+Tokens Unstaking: 0.00000000
+Total Tokens Staked: 250000.00000000
+
+
+Account Delegation Info:
+ID: 7
+Tokens Committed: 0.00000000
+Tokens To Unstake: 0.00000000
+Tokens Rewarded: 30397.81936000
+Tokens Staked: 100000.00000000
+Tokens Unstaked: 0.00000000
+Tokens Unstaking: 0.00000000
+
+```
+
+## Arguments
+
+### Address
+
+- Name: `address`
+- Valid Input: Flow account address
+
+Flow [account address](https://docs.onflow.org/concepts/accounts-and-keys/) (prefixed with `0x` or not).
+
+## Flags
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/account-update-contract.md b/docs/account-update-contract.md
new file mode 100644
index 000000000..4b7d780b4
--- /dev/null
+++ b/docs/account-update-contract.md
@@ -0,0 +1,115 @@
+---
+title: Update a Contract with the Fow CLI
+sidebar_title: Update a Contract
+---
+
+Update an existing contract deployed to a Flow account using the Flow CLI.
+
+```shell
+flow accounts update-contract
+```
+
+## Example Usage
+
+```shell
+> flow accounts update-contract FungibleToken ./FungibleToken.cdc
+
+Contract 'FungibleToken' updated on account '0xf8d6e0586b0a20c7'
+
+Address 0xf8d6e0586b0a20c7
+Balance 9999999999970000000
+Keys 1
+
+Key 0 Public Key 640a5a359bf3536d15192f18d872d57c98a96cb871b92b70cecb0739c2d5c37b4be12548d3526933c2cda9b0b9c69412f45ffb6b85b6840d8569d969fe84e5b7
+ Weight 1000
+ Signature Algorithm ECDSA_P256
+ Hash Algorithm SHA3_256
+
+Contracts Deployed: 1
+Contract: 'FungibleToken'
+```
+
+## Arguments
+
+### Name
+- Name: `name`
+- Valid inputs: Any string value
+
+Name of the contract as it is defined in the contract source code.
+
+### Filename
+- Name: `filename`
+- Valid inputs: Any filename and path valid on the system.
+
+Filename of the file containing contract source code.
+
+## Flags
+
+### Signer
+
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in the configuration (`flow.json`)
+
+Specify the name of the account that will be used to sign the transaction.
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/initialize-project.md b/docs/configuration.md
similarity index 83%
rename from docs/initialize-project.md
rename to docs/configuration.md
index 3a0577e38..3352b4b53 100644
--- a/docs/initialize-project.md
+++ b/docs/configuration.md
@@ -1,20 +1,14 @@
---
-title: Initialize a Flow Project
-sidebar_title: Initialize a Project
-description: How to initialize a new Flow project with the CLI
+title: Flow CLI Configuration
+sidebar_title: Configuration
+description: What is Flow CLI Configuration
---
-⚠️ _Warning: CLI projects are an experimental feature. Functionality is subject to change._
+Flow CLI uses a state called configuration which is stored in a file (usually `flow.json`).
-```shell
-flow project init
-```
-
-This command initializes an empty Flow project by creating a `flow.json` file
-in the current directory.
+Flow configuration (`flow.json`) file will contain the following properties:
-The new `flow.json` file will contain the following:
-- A `networks` list pre-populated with the Flow Emulator connection configuration.
+- A `networks` list pre-populated with the Flow emulator, testnet and mainnet connection configuration.
- An `accounts` list pre-populated with the Flow Emulator service account.
- An `emulators` list pre-populated with Flow Emulator configuration.
- A `deployments` empty object where all [deployment targets](https://docs.onflow.org/flow-cli/project-contracts/) can be defined.
@@ -34,6 +28,14 @@ The new `flow.json` file will contain the following:
"emulator": {
"host": "127.0.0.1:3569",
"chain": "flow-emulator"
+ },
+ "mainnet": {
+ "host": "access.mainnet.nodes.onflow.org:9000",
+ "chain": "flow-mainnet"
+ },
+ "testnet": {
+ "host": "access.devnet.nodes.onflow.org:9000",
+ "chain": "flow-testnet"
}
},
"accounts": {
@@ -103,9 +105,23 @@ We'll walk through each property one by one.
"networks": {
"emulator": {
"host": "127.0.0.1:3569",
- "serviceAccount": "emulator-service"
+ "chain": "flow-emulator"
+ },
+ "mainnet": {
+ "host": "access.mainnet.nodes.onflow.org:9000",
+ "chain": "flow-mainnet"
},
- "testnet": "access.testnet.nodes.onflow.org:9000"
+ "testnet": {
+ "host": "access.devnet.nodes.onflow.org:9000",
+ "chain": "flow-testnet"
+ }
+ },
+
+ "emulators": {
+ "default": {
+ "port": 3569,
+ "serviceAccount": "emulator-account"
+ }
}
}
```
@@ -162,7 +178,7 @@ Each account must include a name, which is then referenced throughout the config
#### Simple Format
-When using the simple format, simply specify the address for the account and a single hex-encoded
+When using the simple format, simply specify the address for the account, and a single hex-encoded
private key.
```json
@@ -262,9 +278,16 @@ Use this section to define networks and connection parameters for that specific
"networks": {
"emulator": {
"host": "127.0.0.1:3569",
- "serviceAccount": "emulator-service"
+ "chain": "flow-emulator"
+ },
+ "mainnet": {
+ "host": "access.mainnet.nodes.onflow.org:9000",
+ "chain": "flow-mainnet"
},
- "testnet": "access.testnet.nodes.onflow.org:9000"
+ "testnet": {
+ "host": "access.devnet.nodes.onflow.org:9000",
+ "chain": "flow-testnet"
+ }
}
...
diff --git a/docs/create-accounts.md b/docs/create-accounts.md
index a741b0a76..71b618175 100644
--- a/docs/create-accounts.md
+++ b/docs/create-accounts.md
@@ -7,7 +7,9 @@ description: How to create a Flow account from the command line
The Flow CLI provides a command to submit an account creation
transaction to any Flow Access API.
-`flow accounts create`
+```shell
+flow accounts create
+```
⚠️ _This command requires an existing Testnet or Mainnet account._
@@ -16,12 +18,20 @@ transaction to any Flow Access API.
```shell
# Create an account on Flow Testnet
> flow accounts create \
- --key a69c6986e69fa1eadcd3bcb4aa51ee8aed74fc9430004af6b96f9e7d0e4891e84cfb99171846ba6d0354d195571397f5904cd319c3e01e96375d5777f1a47010 \
- --sig-algo ECDSA_secp256k1 \
- --hash-algo SHA3_256 \
+ --key a69c6986e846ba6d0....1397f5904cd319c3e01e96375d5777f1a47010 \
--host access.testnet.nodes.onflow.org:9000 \
- --signer my-testnet-account \
- --results
+ --signer my-testnet-account
+
+Address 0x01cf0e2f2f715450
+Balance 10000000
+Keys 1
+
+Key 0 Public Key a69c6986e846ba6d0....1397f5904cd319c3e01e96375d5777f1a47010
+ Weight 1000
+ Signature Algorithm ECDSA_P256
+ Hash Algorithm SHA3_256
+
+Contracts Deployed: 0
```
In the above example, the `flow.json` file would look something like this:
@@ -30,10 +40,8 @@ In the above example, the `flow.json` file would look something like this:
{
"accounts": {
"my-testnet-account": {
- "address": "f8d6e0586b0a20c7",
- "privateKey": "xxxxxxxx",
- "sigAlgorithm": "ECDSA_P256",
- "hashAlgorithm": "SHA3_256"
+ "address": "a2c4941b5f3c7151",
+ "keys": "12c5dfde...bb2e542f1af710bd1d40b2"
}
}
}
@@ -43,7 +51,7 @@ In the above example, the `flow.json` file would look something like this:
### Public Key
-- Flag: `--key,-k`
+- Flag: `--key`
- Valid inputs: a hex-encoded public key in raw form.
Specify the public key that will be added to the new account
@@ -66,45 +74,84 @@ Flow supports the secp256k1 and P-256 curves.
- Valid inputs: `"SHA2_256", "SHA3_256"`
- Default: `"SHA3_256"`
-Specify the hashing algorithm that will be paired with the public key
+Specify the hash algorithm that will be paired with the public key
upon account creation.
-### Wait for Seal
-
-- Flag: `--sealed`
-- Valid inputs: `true`, `false`
-- Default: `false`
-
-Indicate whether to wait for the transaction to be sealed.
-If true, the CLI will block until the transaction has been sealed, or
-a timeout is reached.
-
### Signer
-- Flag: `--signer,s`
-- Valid inputs: the name of an account defined in `flow.json`
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in `flow.json`.
Specify the name of the account that will be used to sign the transaction
and pay the account creation fee.
+### Contract
+
+- Flag: `--contract`
+- Valid inputs: String with format `name:filename`, where `name` is
+ name of the contract as it is defined in the contract source code
+ and `filename` is the filename of the contract source code.
+
+Specify one or more contracts to be deployed during account creation.
+
### Host
- Flag: `--host`
- Valid inputs: an IP address or hostname.
-- Default: `localhost:3569` (Flow Emulator)
+- Default: `127.0.0.1:3569` (Flow Emulator)
Specify the hostname of the Access API that will be
-used to submit the transaction.
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
-### Results
+Specify the log level. Control how much output you want to see during command execution.
-- Flag: `--results`
-- Valid inputs: `true`, `false`
-- Default: `false`
+### Configuration
-Indicate whether to wait for the transaction to be sealed
-and display the result, including the new account address.
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
-If false, the command returns immediately after sending the transaction
-to the Access API. You can later use the `transactions status` command
-to fetch the result.
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/deploy-project-contracts.md b/docs/deploy-project-contracts.md
index 84edb9248..497c26153 100644
--- a/docs/deploy-project-contracts.md
+++ b/docs/deploy-project-contracts.md
@@ -4,8 +4,6 @@ sidebar_title: Deploy a Project
description: How to deploy Flow project contracts with the CLI
---
-⚠️ _Warning: CLI projects are an experimental feature. Functionality is subject to change._
-
```shell
flow project deploy
```
@@ -19,19 +17,21 @@ Before using this command, read about how to
## Example Usage
```shell
-# Deploy project contracts to all Testnet targets
> flow project deploy --network=testnet
-NonFungibleToken -> 0xf8d6e0586b0a20c7
-KittyItems -> 0xf8d6e0586b0a20c7
+Deploying 2 contracts for accounts: my-testnet-account
+
+NonFungibleToken -> 0x8910590293346ec4
+KittyItems -> 0x8910590293346ec4
-✅ All contracts deployed successfully
+✨ All contracts deployed successfully
```
In the example above, your `flow.json` file might look something like this:
```json
{
+ ...
"contracts": {
"NonFungibleToken": "./cadence/contracts/NonFungibleToken.cdc",
"KittyItems": "./cadence/contracts/KittyItems.cdc"
@@ -40,7 +40,8 @@ In the example above, your `flow.json` file might look something like this:
"testnet": {
"my-testnet-account": ["KittyItems", "NonFungibleToken"]
}
- }
+ },
+ ...
}
```
@@ -60,72 +61,9 @@ pub contract KittyItems {
}
```
-## Security
-
-⚠️ Warning: Please be careful when using private keys in configuration files. We suggest you
-to separate private keys in another configuration file, put that file in `.gitignore` and then
-reference that account in configuration with `fromeFile` property.
-
-### Private Account Configuration File
-`flow.json` Main configuration file example:
-```json
-{
- "contracts": {
- "NonFungibleToken": "./cadence/contracts/NonFungibleToken.cdc",
- "KittyItems": "./cadence/contracts/KittyItems.cdc"
- },
- "deployments": {
- "testnet": {
- "my-testnet-account": ["KittyItems", "NonFungibleToken"]
- }
- },
- "accounts": {
- "my-testnet-account": { "fromFile": "./flow.testnet.json" }
- }
-}
-```
-
-`flow.testnet.json` Private configuration file. **Put this file in `.gitignore`**
-```json
-{
- "accounts": {
- "my-testnet-account": {
- "address": "3ae53cb6e3f42a79",
- "keys": "334232967f52bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad1111"
- }
- }
-}
-```
-
-### Private Account Configuration Environment Variable
-
-Use environment variable for any values that should be kept private (private keys, addresses...).
-See example bellow:
-
-`flow.json` Main configuration file. Set environment variable when running flow cli like so:
-```shell
-PRIVATE_KEY=key flow project deploy
-```
-```json
-{
- ...
- "accounts": {
- "my-testnet-account": {
- "address": "3ae53cb6e3f42a79",
- "keys": "$PRIVATE_KEY"
- }
- }
- ...
-}
-```
-
-### Composing Multiple Configuration Files
-You can use composition of configuration files like so:
-```shell
-flow project deploy -f main.json -f private.json
-```
-
-This way you can keep your private accounts in the `private.json` file and add that file to `.gitignore`.
+⚠️ Warning: before proceeding,
+we recommend reading the [Flow CLI security guidelines](https://docs.onflow.org/flow-cli/security/)
+to learn about the best practices for private key storage.
## Dependency Resolution
@@ -156,15 +94,7 @@ pub contract Bar {
}
```
-## Options
-
-### Network
-
-- Flag: `--network`
-- Valid inputs: the name of a network configured in `flow.json`
-- Default: `emulator`
-
-Specify which network you want to deploy the project contracts to.
+## Flags
### Allow Updates
@@ -175,3 +105,65 @@ Specify which network you want to deploy the project contracts to.
Indicate whether to overwrite and upgrade existing contracts.
⚠️ _Warning: contract upgrades are a dangerous experimental feature._
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/developer-updates/release-notes-v17.md b/docs/developer-updates/release-notes-v17.md
new file mode 100644
index 000000000..5a157a46d
--- /dev/null
+++ b/docs/developer-updates/release-notes-v17.md
@@ -0,0 +1,202 @@
+## ⬆️ Install or Upgrade
+
+Follow the [Flow CLI installation guide](https://docs.onflow.org/flow-cli/install/) for instructions on how to install or upgrade the CLI.
+
+## 💥 Breaking Changes
+
+### Configuration Format
+
+The default configuration format (i.e. the contents of `flow.json`) has been updated.
+It is now unified to work with all CLI commands.
+The new format is not backwards compatible with the old format.
+
+If needed, you can generate a new configuration file with the `flow init` command.
+
+Read more about the new configuration format in the [documentation](https://docs.onflow.org/flow-cli/configuration).
+
+### Updated: `flow blocks get`
+
+The `--latest`, `--id` and `--height` have been removed.
+
+Instead, use the new argument syntax:
+
+```sh
+# get latest block
+flow blocks get latest
+
+# get a block by ID
+flow blocks get 6bb0e0fceef9225a3cf9ceb6df9a31bd0063e6ee8e8dd7fdd93b831783243cd3
+
+# get a block by height
+flow blocks get 28329914
+```
+
+Read more about this change in the [documentation](https://docs.onflow.org/flow-cli/get-blocks).
+
+### Removed: `flow keys decode`
+
+The `flow keys decode` command has been temporarily removed due to a bug that requires further investigation.
+
+### Removed: `flow keys save`
+
+The `flow keys save` command has been removed in favour of an upcoming `flow accounts add` command.
+
+## ⚠️ Deprecation Warnings
+
+The following functionality has been deprecated and will be removed in an upcoming release.
+
+**`flow accounts create`, `flow accounts add-contract`, `flow accounts remove-contract`, `flow accounts update-contract`**
+
+- Flag `--results` is deprecated, results are displayed by default.
+
+**`flow accounts get`**
+
+- Flag `--code` is deprecated, use `--contracts` flag instead.
+
+**`flow events get`**
+
+- Flag `--verbose` is deprecated.
+
+**`flow keys generate`**
+
+- Flag `--algo` is deprecated, use flag `--sig-algo`.
+
+**`flow transactions send`**
+
+- Flag `--code` is deprecated, use filename argument instead.
+- Flag `--args` is deprecated, use `--arg` or `--args-json` instead.
+- Flag `--results` is deprecated, results are displayed by default.
+
+**`flow scripts execute`**
+
+- Flag `--code` is deprecated, use filename argument instead.
+- Flag `--args` is deprecated, use `--arg` or `--args-json` instead.
+
+**`flow transactions status`**
+
+- This command has been deprecated in favour of `flow transactions get`.
+
+**`flow project init`**
+
+- This command has been deprecated in favour of `flow init`.
+
+**`flow project start-emulator`**
+
+- This command has been deprecated in favour of `flow emulator`.
+
+**`flow emulator start`**
+
+- This command has been deprecated in favour of `flow emulator`.
+
+## ⭐ Features
+
+### Output
+
+Output format was changed, so it stays consistent between commands. New flags were introduced
+that control the output. Let's take a quick look at the new flags, but make sure to read
+more about them in the documentation on each command:
+
+- Output: `--output` specify the format of the command results (JSON, inline...),
+- Save: `--save` specify the filename where you want the result to be saved,
+- Log: `--log` control how much output you want to see during command execution,
+- Filter: `--filter` Specify any property name from the result you want to return as the only value.
+
+All the flags and their allowed values are specified
+for each command in the [documentation](https://docs.onflow.org/flow-cli/).
+
+Changed output for fetching account.
+```
+Address 179b6b1cb6755e31
+Balance 0
+Keys 2
+
+Key 0 Public Key c8a2a318b9099cc6...a0fe320dba7
+ Weight 1000
+ Signature Algorithm ECDSA_P256
+ Hash Algorithm SHA3_256
+
+Code
+ pub contract Foo {
+ pub var bar: String
+
+ init() {
+ self.bar = "Hello, World!"
+ }
+ }
+```
+
+Output account result as JSON.
+
+```
+{"address":"179b6b1cb6755e31","balance":0,"code":"CnB1YiBj...SIKCX0KfQo=","keys":[{"index":0,"publicKey":{},"sigAlgo":2,"hashAlgo":3,"weight":1000,"sequenceNumber":0,"revoked":false}],"Contracts":null}
+```
+
+Improved progress feedback with loaders.
+```
+Loading 0x1fd892083b3e2a4c...⠼
+```
+
+### Shared Library
+
+You can import Flow CLI shared library from the `flowcli` package and use the functionality
+from the service layer in your own software. Codebase was divided into two components, first
+is the CLI interaction layer, and the second is the shared library component which is meant
+to be reused.
+
+### Account Staking Info Command
+
+New command to fetch staking info from the account was added. Read more about it in the
+[documentation](https://docs.onflow.org/flow-cli/staking-info).
+
+```shell
+> accounts staking-info 535b975637fb6bee --host access.testnet.nodes.onflow.org:9000
+
+Account Staking Info:
+ID: "ca00101101010100001011010101010101010101010101011010101010101010"
+Initial Weight: 100
+Networking Address: "ca00101101010100001011010101010101010101010101011010101010101010"
+Networking Key: "ca00101101010100001011010101010101010101010101011010101010101010ca00101101010100001011010101010101010101010101011010101010101010"
+Role: 1
+Staking Key: "ca00101101010100001011010101010101010101010101011010101010101010ca00101101010100001011010101010101010101010101011010101010101010ca00101101010100001011010101010101010101010101011010101010101010"
+Tokens Committed: 0.00000000
+Tokens To Unstake: 0.00000000
+Tokens Rewarded: 82627.77000000
+Tokens Staked: 250000.00000000
+Tokens Unstaked: 0.00000000
+Tokens Unstaking: 0.00000000
+Total Tokens Staked: 250000.00000000
+
+
+Account Delegation Info:
+ID: 7
+Tokens Committed: 0.00000000
+Tokens To Unstake: 0.00000000
+Tokens Rewarded: 30397.81936000
+Tokens Staked: 100000.00000000
+Tokens Unstaked: 0.00000000
+Tokens Unstaking: 0.00000000
+
+```
+
+## 🐞 Bug Fixes
+
+### Address 0x prefix
+
+Addresses are not required to be prefixed with `0x` anymore. You can use either format, but
+due to consistency we advise using `0x` prefix with addresses represented in `hex` format.
+
+### Project deploy error
+
+Deploying contract provides improved error handling in case something goes wrong you
+can now read what the error was right from the commandline.
+
+Example of error output:
+```
+Deploying 2 contracts for accounts: emulator-account
+
+❌ contract Kibble is already deployed to this account. Use the --update flag to force update
+❌ contract KittyItemsMarket is already deployed to this account. Use the --update flag to force update
+❌ failed to deploy contracts
+
+❌ Command Error: failed to deploy contracts
+```
diff --git a/docs/developer-updates/v17.md b/docs/developer-updates/v17.md
new file mode 100644
index 000000000..537878300
--- /dev/null
+++ b/docs/developer-updates/v17.md
@@ -0,0 +1,30 @@
+# CLI Developer Update v17.0
+
+The new CLI version introduces big improvements to the codebase and allows
+other developers to import the `flowcli` package directly in their Go projects.
+
+The configuration was unified and you can now use the new configuration format for all the commands.
+
+The commands and flags were adapted for more consistency.
+The new [documentation](https://docs.onflow.org/flow-cli/) covers all the commands you can use.
+
+This release also includes CLI design guidelines that you
+can read the [CONTRIBUTING.md](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md) document.
+
+We've also deprecated some features and improved deprecation warnings. Commands now include deprecated flags which instruct you
+on how to use the command with new flags. We're aiming to wait 2 weeks before removing deprecated functionality.
+
+For example:
+
+```
+❌ Command Error: ⚠️ No longer supported: use command argument.
+```
+
+Improved error reporting with suggestions on how to fix the error:
+
+```
+❌ Error while dialing dial tcp 127.0.0.1:3569: connect: connection refused"
+🙏 Make sure your emulator is running or connection address is correct.
+```
+
+You can find the full release notes here: {{RELEASE_NOTES}}
diff --git a/docs/execute-scripts.md b/docs/execute-scripts.md
index 758b5a1eb..7f7e08bda 100644
--- a/docs/execute-scripts.md
+++ b/docs/execute-scripts.md
@@ -7,31 +7,112 @@ description: How to execute a Cadence script on Flow from the command line
The Flow CLI provides a command to execute a Cadence script on
the Flow execution state with any Flow Access API.
-`flow scripts execute`
+```shell
+flow scripts execute
+```
## Example Usage
```shell
-# Submit a transaction to Flow Testnet
-> flow scripts execute \
- MyScript.cdc \
- --host access.testnet.nodes.onflow.org:9000
+# Execute a script on Flow Testnet
+> flow scripts execute script.cdc --arg String:"Hello" --arg String:"World"
+
+"Hello World"
```
## Arguments
-### Script Code
+### Filename
+
+- Name: `filename`
+- Valid inputs: a path in the current filesystem.
The first argument is a path to a Cadence file containing the
script to be executed.
-## Options
+## Flags
+
+### Arguments
+
+- Flag: `--arg`
+- Valid inputs: argument in `Type:Value` format.
+
+Arguments passed to the Cadence script in `Type:Value` format.
+The `Type` must be the same as type in the script source code for that argument.
+
+### Arguments JSON
+
+- Flag: `--argsJSON`
+- Valid inputs: arguments in JSON-Cadence form.
+
+Arguments passed to the Cadence script in `Type:Value` format.
+The `Type` must be the same type as the corresponding parameter
+in the Cadence source code.
+
+### Code
+
+- Flag: `--code`
+
+⚠️ No longer supported: use filename argument.
### Host
- Flag: `--host`
- Valid inputs: an IP address or hostname.
-- Default: `localhost:3569` (Flow Emulator)
+- Default: `127.0.0.1:3569` (Flow Emulator)
Specify the hostname of the Access API that will be
-used to execute the script.
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/generate-keys.md b/docs/generate-keys.md
index e102bd284..5c21daa97 100644
--- a/docs/generate-keys.md
+++ b/docs/generate-keys.md
@@ -1,7 +1,7 @@
---
-title: Generate Keys with the Flow CLI
-sidebar_title: Generate a Key
-description: How to generate a Flow account key-pair from the command line
+title: Generate Key Pair with the Flow CLI
+sidebar_title: Generate Keys
+description: How to generate key pair from the command line
---
The Flow CLI provides a command to generate ECDSA key pairs
@@ -9,32 +9,29 @@ that can be [attached to new or existing Flow accounts](https://docs.onflow.org/
`flow keys generate`
+⚠️ Store private key safely and don't share with anyone!
+
## Example Usage
```shell
-> flow keys generate
-
-Generating key pair with signature algorithm: ECDSA_P256
-...
-🔐 Private key (do not share with anyone): xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-🕊 Encoded public key (share freely): a69c6986e69fa1eadcd3bcb4aa51ee8aed74fc9430004af6b96f9e7d0e4891e84cfb99171846ba6d0354d195571397f5904cd319c3e01e96375d5777f1a47010
+flow keys generate
```
-## Options
-
-### Signature Algorithm
+### Example response
-- Flag: `--algo,-a`
-- Valid inputs: `"ECDSA_P256", "ECDSA_secp256k1"`
-- Default: `"ECDSA_P256"`
+```shell
+> flow keys generate
-Specify the ECDSA signature algorithm for the key pair.
+🔴️ Store Private Key safely and don't share with anyone!
+Private Key c778170793026a9a7a3815dabed68ded445bde7f40a8c66889908197412be89f
+Public Key 584245c57e5316d6606c53b1ce46dae29f5c9bd26e9e8...aaa5091b2eebcb2ac71c75cf70842878878a2d650f7
+```
-Flow supports the secp256k1 and P-256 curves.
+## Flags
### Seed
-- Flag: `--seed,s`
+- Flag: `--seed`
- Valid inputs: any string with length >= 32
Specify a UTF-8 seed string that will be used to generate the key pair.
@@ -43,3 +40,59 @@ result in the same key.
If no seed is specified, the key pair will be generated using
a random 32 byte seed.
+
+⚠️ Using seed with production keys can be dangerous if seed was not generated
+by using safe random generators.
+
+### Signature Algorithm
+
+- Flag: `--sig-algo`
+- Valid inputs: `"ECDSA_P256", "ECDSA_secp256k1"`
+
+Specify the ECDSA signature algorithm for the key pair.
+
+Flow supports the secp256k1 and P-256 curves.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/get-accounts.md b/docs/get-accounts.md
new file mode 100644
index 000000000..88aa14432
--- /dev/null
+++ b/docs/get-accounts.md
@@ -0,0 +1,108 @@
+---
+title: Get an Account with the Flow CLI
+sidebar_title: Get an Account
+description: How to get a Flow account from the command line
+---
+
+The Flow CLI provides a command to fetch any account by its address from the Flow network.
+
+`flow accounts get `
+
+
+## Example Usage
+
+```shell
+flow accounts get 0xf8d6e0586b0a20c7
+```
+
+### Example response
+```shell
+Address 0xf8d6e0586b0a20c7
+Balance 9999999999970000000
+Keys 1
+
+Key 0 Public Key 858a7d978b25d61f348841a343f79131f4b9fab341dd8a476a6f4367c25510570bf69b795fc9c3d2b7191327d869bcf848508526a3c1cafd1af34f71c7765117
+ Weight 1000
+ Signature Algorithm ECDSA_P256
+ Hash Algorithm SHA3_256
+
+Contracts Deployed: 2
+Contract: 'FlowServiceAccount'
+Contract: 'FlowStorageFees'
+
+
+```
+
+## Flags
+
+### Contracts
+
+- Flag: `--contracts`
+
+Display contracts deployed to the account.
+
+### Code
+⚠️ No longer supported: use contracts flag instead.
+
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/get-blocks.md b/docs/get-blocks.md
new file mode 100644
index 000000000..3b14c558b
--- /dev/null
+++ b/docs/get-blocks.md
@@ -0,0 +1,149 @@
+---
+title: Get Block with the Flow CLI
+sidebar_title: Get Block
+description: How to get a block from the command line
+---
+
+The Flow CLI provides a command to fetch any block from the Flow network.
+
+`flow blocks get `
+
+## Example Usage
+
+```shell
+flow blocks get 12884163 --host access.mainnet.nodes.onflow.org:9000 --verbose
+```
+
+### Example response
+
+```shell
+Block ID 2fb7571a6ccf02f3ac42f27c14ce0a4cb119060e4fbd7af36fd51894465e7002
+Prent ID 1c5a6267ba9512e141e4e90630cb326cecfbf6113818487449efeb37fc98ca18
+Timestamp 2021-03-19 17:46:15.973305066 +0000 UTC
+Height 12884163
+Total Seals 2
+Total Collections 8
+ Collection 0: 3e694588e789a72489667a36dd73104dea4579bcd400959d47aedccd7f930eeb
+ Transaction 0: acc2ae1ff6deb2f4d7663d24af6ab1baf797ec264fd76a745a30792f6882093b
+ Transaction 1: ae8bfbc85ce994899a3f942072bfd3455823b1f7652106ac102d161c17fcb55c
+ Transaction 2: 70c4d39d34e654173c5c2746e7bb3a6cdf1f5e6963538d62bad2156fc02ea1b2
+ Transaction 3: 2466237b5eafb469c01e2e5f929a05866de459df3bd768cde748e068c81c57bf
+ Collection 1: e93f2bd988d66288c7e1ad991dec227c6c74b8039a430e43896ad94cf8feccce
+ Transaction 0: 4d790300722b646e7ed3e2c52675430d7ccf2efd1d93f106b53bc348df601af6
+ Collection 2: c7d93b80ae55809b1328c686f6a8332e8e15083ab32f8b3105c4d910646f54bf
+ Transaction 0: 95c4efbb30f86029574d6acd7df04afe6108f6fd610d823dfd398c80cfa5e842
+ Collection 3: 1a4f563b48aaa38f3a7e867c89422e0bd84887de125e8f48ba147f4ee58ddf0d
+ Transaction 0: fbcc99326336d4dbb4cbc01a3b9b85cfcdcdc071b3d0e01ee88ecd144444600b
+ Collection 4: 01000c7773cc3c22cba6d8917a2486dc7a1a1842dd7fb7c0e87e63c22bb14abe
+ Transaction 0: a75097639b434044de0122d3a28620e093f277fa715001e80a035568e118c59f
+ Collection 5: 6f2b08f9673545a2e61e954feb8d55d2a3ef2b3cef7a8d2f8de527bc42d92c28
+ Transaction 0: 8ea63d397bd07a25db3f06fb9785dbf09bc652159f68a84c55ea2be606ada1e9
+ Collection 6: 13b5c48252930824a8c6e846470763582cacdacb772c1e9c584adefced6724b2
+ Transaction 0: 8ba57a92311367189a89a59bcb3c32192387fefca9bde493e087bc0d479186a8
+ Transaction 1: 8ab1d99702ccf31b6f4b3acd2580dddd440f08bc07acab4884337c0c593a8f69
+ Collection 7: bf90fdd2761b8f37565af60fc38165dd09edf0671fdd35b37f718a7eb45e804f
+ Transaction 0: b92a14c0802183719efed00363d31076d7e50f41a6207781cf34d39c822bbacb
+
+
+```
+
+## Arguments
+
+### Query
+- Name: ``
+- Valid Input: Block ID, `latest` or block height
+
+Specify the block to retrieve by block ID or block height.
+
+## Arguments
+
+### Address
+- Name: `address`
+- Valid Input: Flow account address
+
+Flow [account address](https://docs.onflow.org/concepts/accounts-and-keys/) (prefixed with `0x` or not).
+
+
+## Flags
+
+### Events
+
+- Flag: `--events`
+- Valid inputs: Valid event name
+
+List events of this type for the block.
+
+### Verbose
+
+- Flag: `--verbose`
+
+Display transactions hashes in block.
+
+### Signer
+
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in the configuration (`flow.json`)
+
+Specify the name of the account that will be used to sign the transaction.
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/get-collections.md b/docs/get-collections.md
new file mode 100644
index 000000000..a21a94362
--- /dev/null
+++ b/docs/get-collections.md
@@ -0,0 +1,101 @@
+---
+title: Get Block with the Flow CLI
+sidebar_title: Get Collection
+description: How to get a collection from the command line
+---
+
+The Flow CLI provides a command to fetch any collection from the Flow network.
+
+`flow collections get `
+
+## Example Usage
+
+```shell
+flow collections get 3e694588e789a72489667a36dd73104dea4579bcd400959d47aedccd7f930eeb \
+--host access.mainnet.nodes.onflow.org:9000
+```
+
+### Example response
+
+```shell
+Collection ID 3e694588e789a72489667a36dd73104dea4579bcd400959d47aedccd7f930eeb:
+acc2ae1ff6deb2f4d7663d24af6ab1baf797ec264fd76a745a30792f6882093b
+ae8bfbc85ce994899a3f942072bfd3455823b1f7652106ac102d161c17fcb55c
+70c4d39d34e654173c5c2746e7bb3a6cdf1f5e6963538d62bad2156fc02ea1b2
+2466237b5eafb469c01e2e5f929a05866de459df3bd768cde748e068c81c57bf
+
+```
+
+## Arguments
+
+### Collection ID
+- Name: `collection_id`
+- Valid Input: SHA3-256 hash of the collection contents
+
+{argument general description}
+
+## Arguments
+
+## Flags
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/get-events.md b/docs/get-events.md
new file mode 100644
index 000000000..6471210c9
--- /dev/null
+++ b/docs/get-events.md
@@ -0,0 +1,136 @@
+---
+title: Get Events with the Flow CLI
+sidebar_title: Get Event
+description: How to get an event from the command line
+---
+
+The Flow CLI provides a command to fetch any block from the Flow network.
+
+Events can be requested for a specific sealed block range via the
+start and end block height fields and further filtered by event name.
+
+```shell
+flow events get `
+```
+
+## Example Usage
+
+```shell
+flow events get A.0b2a3299cc857e29.TopShot.Deposit 12913388 12913389 \
+ --host access.mainnet.nodes.onflow.org:9000
+```
+
+### Example response
+
+```shell
+Events Block #12913388:
+ Index 2
+ Type A.0b2a3299cc857e29.TopShot.Deposit
+ Tx ID 0a1e6cdc4eeda0e23402193d7ad5ba01a175df4c08f48fa7ac8d53e811c5357c
+ Values
+ id (UInt64) 3102159
+ to ({}?) 24214cf0faa7844d
+
+ Index 2
+ Type A.0b2a3299cc857e29.TopShot.Deposit
+ Tx ID 1fa5e64dcdc8ed5dad87ba58207ee4c058feb38fa271fff659ab992dc2ec2645
+ Values
+ id (UInt64) 5178448
+ to ({}?) 26c96b6c2c31e419
+
+ Index 9
+ Type A.0b2a3299cc857e29.TopShot.Deposit
+ Tx ID 262ab3996bdf98f5f15804c12b4e5d4e89c0fa9b71d57be4d7c6e8288c507c4a
+ Values
+ id (UInt64) 1530408
+ to ({}?) 2da5c6d1a541971b
+
+...
+```
+
+## Arguments
+
+### Event Name
+
+- Name: `event_name`
+- Valid Input: String
+
+Fully-qualified identifier for the events.
+
+### Block Height Range Start
+
+- Name: `block_height_range_start`
+- Valid Input: Number (lower than `block_height_range_end` value)
+
+Height of the block in the chain.
+
+### Block Height Range End (optional)
+
+- Name: `block_height_range_end`
+- Valid Input: Number (higher than `block_height_range_end` value) or value `latest`
+
+Height of the block in the chain. Use `latest` for latest block.
+
+## Flags
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/get-transactions.md b/docs/get-transactions.md
new file mode 100644
index 000000000..f9f953dc9
--- /dev/null
+++ b/docs/get-transactions.md
@@ -0,0 +1,121 @@
+---
+title: Get a Transaction with the Flow CLI
+sidebar_title: Get a Transaction
+description: How to get a Flow transaction from the command line
+---
+
+The Flow CLI provides a command to fetch a transaction
+that was previously submitted to an Access API.
+
+`flow transactions get `
+
+## Example Usage
+
+```shell
+> flow transactions get ff35821007322405608c0d3da79312617f8d16e118afe63e764b5e68edc96dd5 --host access.mainnet.nodes.onflow.org:9000
+
+ID ff35821007322405608c0d3da79312617f8d16e118afe63e764b5e68edc96dd5
+Status SEALED
+Payer 12e354a23e4f791d
+Events
+ Index 0
+ Type flow.AccountCreated
+ Tx ID ff35821007322405608c0d3da79312617f8d16e118afe63e764b5e68edc96dd5
+ Values
+ address (Address) 18c4931b5f3c7151
+
+ Index 1
+ Type flow.AccountKeyAdded
+ Tx ID ff35821007322405608c0d3da79312617f8d16e118afe63e764b5e68edc96dd5
+ Values
+ address (Address) 18c4931b5f3c7151
+ publicKey (Unknown) f847b8404c296679364d2...7b168678cc762bc08f342d8d92e0a36e6ecfdcf15850721821823e8
+```
+
+## Arguments
+
+### Transaction ID
+
+- Name: ``
+- Valid Input: transaction ID.
+
+The first argument is the ID (hash) of the transaction.
+
+## Flags
+
+### Display Transaction Code
+
+- Flag: `--code`
+- Default: `false`
+
+Indicate whether to print the transaction Cadence code.
+
+### Wait for Seal
+
+- Flag: `--sealed`
+- Default: `false`
+
+Indicate whether to wait for the transaction to be sealed
+before displaying the result.
+
+### Host
+
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
+
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/initialize-configuration.md b/docs/initialize-configuration.md
new file mode 100644
index 000000000..bca85847e
--- /dev/null
+++ b/docs/initialize-configuration.md
@@ -0,0 +1,86 @@
+---
+title: Initialize Flow Configuration
+sidebar_title: Initialize Configuration
+description: How to initialize Flow configuration using CLI
+---
+
+Flow CLI uses a state to operate which is called configuration (usually `flow.json` file).
+Before using commands that require this configuration we must initialize the project by
+using the init command. Read more about [state configuration here](https://docs.onflow.org/flow-cli/configuration/).
+
+```shell
+flow init
+```
+
+## Example Usage
+
+```shell
+> flow init
+
+Configuration initialized
+Service account: 0xf8d6e0586b0a20c7
+
+Start emulator by running: 'flow emulator'
+Reset configuration using: 'flow init --reset'
+
+```
+
+### Error Handling
+
+Existing configuration will cause the error bellow.
+You should initialize in an empty folder or reset configuration using `--reset` flag
+or by removing the configuration file first.
+```shell
+❌ Command Error: configuration already exists at: flow.json, if you want to reset configuration use the reset flag
+```
+
+
+## Flags
+
+### Reset
+
+- Flag: `reset`
+
+Using this flag will reset the existing configuration and create a new one.
+
+### Service Private Key
+
+- Flag: `service-private-key`
+- Valid inputs: a hex-encoded private key in raw form.
+
+Private key used on the default service account.
+
+
+### Service Key Signature Algorithm
+
+- Flag: `--sig-algo`
+- Valid inputs: `"ECDSA_P256", "ECDSA_secp256k1"`
+- Default: `"ECDSA_P256"`
+
+Specify the ECDSA signature algorithm for the provided public key.
+
+Flow supports the secp256k1 and P-256 curves.
+
+### Service Key Hash Algorithm
+
+- Flag: `--hash-algo`
+- Valid inputs: `"SHA2_256", "SHA3_256"`
+- Default: `"SHA3_256"`
+
+Specify the hashing algorithm that will be paired with the public key
+upon account creation.
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see while command execution.
+
+
+
+
+
+
diff --git a/docs/project-contracts.md b/docs/project-contracts.md
index dd1b5e126..3bf0cc958 100644
--- a/docs/project-contracts.md
+++ b/docs/project-contracts.md
@@ -4,8 +4,6 @@ sidebar_title: Add Project Contracts
description: How to define the Cadence contracts for Flow project
---
-⚠️ _Warning: CLI projects are an experimental feature. Functionality is subject to change._
-
## Add a Contract
To add a contract to your project, update the `"contracts"` section of your `flow.json` file.
@@ -33,7 +31,7 @@ In a typical project, a contract has one deployment target per network (e.g. Emu
Deployment targets are defined in the `"deployments"` section of your `flow.json` file.
-Targets are grouped by network, where each network is a mapping from target account to contract list.
+Targets are grouped by their network, where each network is a mapping from target account to contract list.
Multiple contracts can be deployed to the same target account.
For example, here's how we'd deploy contracts `Foo` and `Bar` to the account `my-testnet-account`:
diff --git a/docs/query-transactions.md b/docs/query-transactions.md
deleted file mode 100644
index c4ea7736f..000000000
--- a/docs/query-transactions.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: Query a Transaction Result with the Flow CLI
-sidebar_title: Query a Transaction Result
-description: How to get the result of a Flow transaction from the command line
----
-
-The Flow CLI provides a command to query the result of a transaction
-that was previously submitted to an Access API.
-
-`flow transactions status`
-
-## Example Usage
-
-```shell
-# Query a transaction on Flow Testnet
-> flow transactions status \
- --host access.testnet.nodes.onflow.org:9000 \
- 74c02a67297458dbed26273d3b407eedcb42957bdc8d2deb3c6939145bf2b240
-```
-
-## Arguments
-
-### Transaction ID
-
-The first argument is the ID (hash) of the transaction.
-
-## Options
-
-### Display Transaction Code
-
-- Flag: `--code`
-- Valid inputs: `true`, `false`
-- Default: `false`
-
-Indicate whether to print the transaction Cadence code.
-
-### Wait for Seal
-
-- Flag: `--sealed`
-- Valid inputs: `true`, `false`
-- Default: `false`
-
-Indicate whether to wait for the transaction to be sealed
-before displaying the result.
-
-### Host
-
-- Flag: `--host`
-- Valid inputs: an IP address or hostname.
-- Default: `localhost:3569` (Flow Emulator)
-
-Specify the hostname of the Access API that will be
-used to submit the transaction query.
diff --git a/docs/security.md b/docs/security.md
new file mode 100644
index 000000000..b1c52a72d
--- /dev/null
+++ b/docs/security.md
@@ -0,0 +1,84 @@
+---
+title: Flow CLI security
+sidebar_title: Security
+description: How to securely use CLI
+---
+
+The managing of accounts and private keys is intrinsically dangerous.
+We must take extra precautions to not expose private key data when using
+the CLI.
+
+The Flow CLI provides several options to secure private account data.
+
+⚠️ Warning: please be careful when using private keys in configuration files.
+Never commit private key data to source control.
+If private key data must be kept in text, we suggest using a separate file
+that is not checked into source control (e.g. excluded with `.gitignore`).
+
+### Private Account Configuration File
+
+#### Main configuration file
+```json
+// flow.json
+{
+ "contracts": {
+ "NonFungibleToken": "./cadence/contracts/NonFungibleToken.cdc",
+ "KittyItems": "./cadence/contracts/KittyItems.cdc"
+ },
+ "deployments": {
+ "testnet": {
+ "my-testnet-account": ["KittyItems", "NonFungibleToken"]
+ }
+ },
+ "accounts": {
+ "my-testnet-account": { "fromFile": "./flow.testnet.json" }
+ }
+}
+```
+
+#### Private configuration file
+
+**Put this file in `.gitignore`:**
+```json
+// flow.testnet.json
+{
+ "accounts": {
+ "my-testnet-account": {
+ "address": "3ae53cb6e3f42a79",
+ "keys": "334232967f52bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad1111"
+ }
+ }
+}
+```
+
+### Store Configuration in Environment Variables
+
+You can use environment variables for values that should be kept private (e.g. private keys, addresses).
+
+See example below:
+
+```shell
+PRIVATE_KEY=key flow project deploy
+```
+
+```json
+// flow.json
+{
+ ...
+ "accounts": {
+ "my-testnet-account": {
+ "address": "3ae53cb6e3f42a79",
+ "keys": "$PRIVATE_KEY"
+ }
+ }
+ ...
+}
+```
+
+### Composing Multiple Configuration Files
+
+You can merge multiple configuration files like so:
+
+```shell
+flow project deploy -f main.json -f private.json
+```
diff --git a/docs/send-transactions.md b/docs/send-transactions.md
index ec745bec3..f7f3f8f6b 100644
--- a/docs/send-transactions.md
+++ b/docs/send-transactions.md
@@ -13,10 +13,14 @@ any Flow Access API.
```shell
# Submit a transaction to Flow Testnet
-> flow transactions send \
- --code MyTransaction.cdc \
+> flow transactions send
--signer my-testnet-account \
--host access.testnet.nodes.onflow.org:9000
+
+ID f23582ba17322405608c0d3da79312617f8d16e118afe63e764b5e68edc5f211
+Status SEALED
+Payer a2c4941b5f3c7151
+Events
```
In the above example, the `flow.json` file would look something like this:
@@ -25,27 +29,40 @@ In the above example, the `flow.json` file would look something like this:
{
"accounts": {
"my-testnet-account": {
- "address": "f8d6e0586b0a20c7",
- "privateKey": "xxxxxxxx",
- "sigAlgorithm": "ECDSA_P256",
- "hashAlgorithm": "SHA3_256"
+ "address": "a2c4941b5f3c7151",
+ "keys": "12c5dfde...bb2e542f1af710bd1d40b2"
}
}
}
```
-## Options
-
-### Transaction Code
+## Arguments
+
+### Filename
+- Name: `filename`
+- Valid inputs: Any filename and path valid on the system.
+
+The first argument is a path to a Cadence file containing the
+transaction to be executed.
+
+## Flags
+
+### Code
+
+- Flag: `--code`
-- Flag: `--code,-c`
+⚠️ No longer supported: use filename argument.
-Specify a path to a Cadence file containing the transaction script.
+### Results
+
+- Flag: `--results`
+
+⚠️ No longer supported: all transactions will provide result.
### Signer
-- Flag: `--signer,s`
-- Valid inputs: the name of an account defined in `flow.json`
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in the configuration (`flow.json`)
Specify the name of the account that will be used to sign the transaction.
@@ -53,20 +70,60 @@ Specify the name of the account that will be used to sign the transaction.
- Flag: `--host`
- Valid inputs: an IP address or hostname.
-- Default: `localhost:3569` (Flow Emulator)
+- Default: `127.0.0.1:3569` (Flow Emulator)
Specify the hostname of the Access API that will be
-used to submit the transaction.
+used to execute the command. This flag overrides
+any host defined by the `--network` flag.
-### Results
+### Network
-- Flag: `--results`
-- Valid inputs: `true`, `false`
-- Default: `false`
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: a case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify the format of the command results.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: a path in the current filesystem.
+
+Specify the filename where you want the result to be saved
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see during command execution.
+
+### Configuration
-Indicate whether to wait for the transaction to be sealed
-and display the result.
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: a path in the current filesystem.
+- Default: `flow.json`
-If false, the command returns immediately after sending the transaction
-to the Access API. You can later use the `transactions status` command
-to fetch the result.
+Specify the path to the `flow.json` configuration file.
+You can use the `-f` flag multiple times to merge
+several configuration files.
diff --git a/docs/start-emulator.md b/docs/start-emulator.md
new file mode 100644
index 000000000..053a4d0b4
--- /dev/null
+++ b/docs/start-emulator.md
@@ -0,0 +1,43 @@
+---
+title: Start Emulator with the Flow CLI
+sidebar_title: Start Emulator
+description: How to start Flow emulator from the command line
+---
+
+The Flow CLI provides a command to start an emulator.
+The Flow Emulator is a lightweight tool that emulates the behaviour of the real Flow network.
+
+`flow emulator`
+
+⚠️ The emulator command expects configuration to be initialized. See [flow init](https://docs.onflow.org/flow-cli/initialize-configuration/) command.
+
+
+## Example Usage
+
+```shell
+> flow emulator start
+
+INFO[0000] ⚙️ Using service account 0xf8d6e0586b0a20c7 serviceAddress=f8d6e0586b0a20c7 ...
+...
+```
+
+To learn more about using the Emulator, have a look at the [README of the repository](https://github.com/onflow/flow-emulator).
+
+## Flags
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: valid filename
+
+Specify a filename for the configuration files, you can provide multiple configuration
+files by using `-f` flag multiple times.
+
+### Initialize
+
+- Flag: `--init`
+
+⚠️ Deprecated: use `flow init` instead.
+
+Initialize configuration during the command execution.
\ No newline at end of file
diff --git a/docs/template.md b/docs/template.md
new file mode 100644
index 000000000..a2e6c374b
--- /dev/null
+++ b/docs/template.md
@@ -0,0 +1,115 @@
+---
+title:
+sidebar_title:
+description:
+---
+
+{short description}
+
+`{command}`
+
+{optional warning}
+
+## Example Usage
+
+```shell
+{usage example}
+```
+
+### Example response
+
+## Arguments
+
+### {Argument 1}
+- Name: `{argument}`
+- Valid Input: `{input}`
+
+{argument general description}
+
+## Arguments
+
+### Address
+- Name: `address`
+- Valid Input: Flow account address
+
+Flow [account address](https://docs.onflow.org/concepts/accounts-and-keys/) (prefixed with `0x` or not).
+
+
+## Flags
+
+### {Option 1}
+
+- Flag: `{flag value}`
+- Valid inputs: {input description}
+
+{flag general description}
+
+### Signer
+
+- Flag: `--signer`
+- Valid inputs: the name of an account defined in the configuration (`flow.json`)
+
+Specify the name of the account that will be used to sign the transaction.
+
+### Host
+- Flag: `--host`
+- Valid inputs: an IP address or hostname.
+- Default: `127.0.0.1:3569` (Flow Emulator)
+
+Specify the hostname of the Access API that will be
+used to execute the commands.
+
+### Network
+
+- Flag: `--network`
+- Short Flag: `-n`
+- Valid inputs: the name of a network defined in the configuration (`flow.json`)
+
+Specify which network you want the command to use for execution.
+
+### Filter
+
+- Flag: `--filter`
+- Short Flag: `-x`
+- Valid inputs: case-sensitive name of the result property.
+
+Specify any property name from the result you want to return as the only value.
+
+### Output
+
+- Flag: `--output`
+- Short Flag: `-o`
+- Valid inputs: `json`, `inline`
+
+Specify in which format you want to display the result.
+
+### Save
+
+- Flag: `--save`
+- Short Flag: `-s`
+- Valid inputs: valid filename
+
+Specify the filename where you want the result to be saved.
+
+### Log
+
+- Flag: `--log`
+- Short Flag: `-l`
+- Valid inputs: `none`, `error`, `debug`
+- Default: `info`
+
+Specify the log level. Control how much output you want to see while command execution.
+
+### Configuration
+
+- Flag: `--config-path`
+- Short Flag: `-f`
+- Valid inputs: valid filename
+
+Specify a filename for the configuration files, you can provide multiple configuration
+files by using `-f` flag multiple times.
+
+
+
+
+
diff --git a/flow/account.go b/flow/account.go
deleted file mode 100644
index 1c3d8be4e..000000000
--- a/flow/account.go
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "context"
- "fmt"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
- "google.golang.org/grpc"
-)
-
-func GetAccount(host string, address flow.Address) *flow.Account {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- account, err := flowClient.GetAccount(ctx, address)
- if err != nil {
- Exitf(1, "Failed to get account with address %s: %s", address, err)
- }
- return account
-}
-
-func GetAddressNetwork(address flow.Address) (flow.ChainID, error) {
- networks := []flow.ChainID{
- flow.Mainnet,
- flow.Testnet,
- flow.Emulator,
- }
- for _, net := range networks {
- if address.IsValid(net) {
- return net, nil
- }
- }
- return flow.ChainID(""), fmt.Errorf("Unrecognized address not valid for any known chain: %s", address)
-}
diff --git a/flow/accounts/accounts.go b/flow/accounts/accounts.go
deleted file mode 100644
index 505abebb4..000000000
--- a/flow/accounts/accounts.go
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 accounts
-
-import (
- "github.com/spf13/cobra"
-
- add_contract "github.com/onflow/flow-cli/flow/accounts/add-contract"
- "github.com/onflow/flow-cli/flow/accounts/create"
- "github.com/onflow/flow-cli/flow/accounts/get"
- remove_contract "github.com/onflow/flow-cli/flow/accounts/remove-contract"
- staking_info "github.com/onflow/flow-cli/flow/accounts/staking-info"
- update_contract "github.com/onflow/flow-cli/flow/accounts/update-contract"
-)
-
-var Cmd = &cobra.Command{
- Use: "accounts",
- Short: "Utilities to manage accounts",
- TraverseChildren: true,
-}
-
-func init() {
- Cmd.AddCommand(create.Cmd)
- Cmd.AddCommand(get.Cmd)
- Cmd.AddCommand(staking_info.Cmd)
- Cmd.AddCommand(add_contract.Cmd)
- Cmd.AddCommand(update_contract.Cmd)
- Cmd.AddCommand(remove_contract.Cmd)
-}
diff --git a/flow/accounts/add-contract/add.go b/flow/accounts/add-contract/add.go
deleted file mode 100644
index 96b9885e4..000000000
--- a/flow/accounts/add-contract/add.go
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 add_contract
-
-import (
- "io/ioutil"
- "log"
-
- "github.com/onflow/flow-go-sdk/templates"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Signer string `default:"service" flag:"signer,s"`
- Host string `flag:"host" info:"Flow Access API host address"`
- Results bool `default:"false" flag:"results" info:"Display the results of the transaction"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "add-contract ",
- Short: "Deploy a new contract to an account",
- Args: cobra.ExactArgs(2),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- contractName := args[0]
- contractFilename := args[1]
-
- contractSource, err := ioutil.ReadFile(contractFilename)
- if err != nil {
- cli.Exitf(1, "Failed to read contract from source file %s", contractFilename)
- }
-
- signerAccount := projectConf.Accounts[conf.Signer]
- // TODO: Remove once new configuration is migrated
- if signerAccount == nil && conf.Signer == "service" {
- signerAccount = projectConf.Accounts["emulator-account"]
- }
-
- tx := templates.AddAccountContract(
- signerAccount.Address,
- templates.Contract{
- Name: contractName,
- Source: string(contractSource),
- },
- )
-
- cli.SendTransaction(
- projectConf.HostWithOverride(conf.Host),
- signerAccount,
- tx,
- conf.Results,
- )
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/accounts/create/create.go b/flow/accounts/create/create.go
deleted file mode 100644
index 63a4b95f8..000000000
--- a/flow/accounts/create/create.go
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 create
-
-import (
- "io/ioutil"
- "log"
- "strings"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/onflow/flow-go-sdk/templates"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Signer string `default:"service" flag:"signer,s"`
- Keys []string `flag:"key,k" info:"Public keys to attach to account"`
- SigAlgo string `default:"ECDSA_P256" flag:"sig-algo" info:"Signature algorithm used to generate the keys"`
- HashAlgo string `default:"SHA3_256" flag:"hash-algo" info:"Hash used for the digest"`
- Host string `flag:"host" info:"Flow Access API host address"`
- Results bool `default:"false" flag:"results" info:"Display the results of the transaction"`
- Contracts []string `flag:"contract,c" info:"Contract to be deployed during account creation. "`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "create",
- Short: "Create a new account",
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- signerAccount := projectConf.Accounts[conf.Signer]
- // TODO: Remove once new configuration is migrated
- if signerAccount == nil && conf.Signer == "service" {
- signerAccount = projectConf.Accounts["emulator-account"]
- }
-
- accountKeys := make([]*flow.AccountKey, len(conf.Keys))
-
- sigAlgo := crypto.StringToSignatureAlgorithm(conf.SigAlgo)
- if sigAlgo == crypto.UnknownSignatureAlgorithm {
- cli.Exitf(1, "Failed to determine signature algorithm from %s", conf.SigAlgo)
- }
- hashAlgo := crypto.StringToHashAlgorithm(conf.HashAlgo)
- if hashAlgo == crypto.UnknownHashAlgorithm {
- cli.Exitf(1, "Failed to determine hash algorithm from %s", conf.HashAlgo)
- }
-
- for i, publicKeyHex := range conf.Keys {
- publicKey := cli.MustDecodePublicKeyHex(sigAlgo, publicKeyHex)
- accountKeys[i] = &flow.AccountKey{
- PublicKey: publicKey,
- SigAlgo: sigAlgo,
- HashAlgo: hashAlgo,
- Weight: flow.AccountKeyWeightThreshold,
- }
- }
-
- contracts := []templates.Contract{}
-
- for _, contract := range conf.Contracts {
- contractFlagContent := strings.SplitN(contract, ":", 2)
- if len(contractFlagContent) != 2 {
- cli.Exitf(1, "Failed to read contract name and path from flag. Ensure you're providing a contract name and a file path. %s", contract)
- }
- contractName := contractFlagContent[0]
- contractPath := contractFlagContent[1]
- contractSource, err := ioutil.ReadFile(contractPath)
- if err != nil {
- cli.Exitf(1, "Failed to read contract from source file %s", contractPath)
- }
- contracts = append(contracts,
- templates.Contract{
- Name: contractName,
- Source: string(contractSource),
- },
- )
- }
-
- tx := templates.CreateAccount(accountKeys, contracts, signerAccount.Address)
-
- cli.SendTransaction(projectConf.HostWithOverride(conf.Host), signerAccount, tx, conf.Results)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/accounts/get/get.go b/flow/accounts/get/get.go
deleted file mode 100644
index a89f1ba08..000000000
--- a/flow/accounts/get/get.go
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 get
-
-import (
- "fmt"
- "log"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- "github.com/onflow/cadence"
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Host string `flag:"host" info:"Flow Access API host address"`
- Code bool `default:"false" flag:"code" info:"Display code deployed to the account"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "get ",
- Short: "Get account info",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
-
- address := flow.HexToAddress(args[0])
-
- account := cli.GetAccount(
- projectConf.HostWithOverride(conf.Host),
- address,
- )
-
- printAccount(account, conf.Code)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func printAccount(account *flow.Account, printCode bool) {
- fmt.Println()
- fmt.Println("Address: " + account.Address.Hex())
- fmt.Println("Balance: ", cadence.UFix64(account.Balance))
- fmt.Println("Total Keys: ", len(account.Keys))
-
- for _, key := range account.Keys {
- fmt.Println(" ---")
- fmt.Println(" Key Index: ", key.Index)
- fmt.Printf(" PublicKey: %x\n", key.PublicKey.Encode())
- fmt.Println(" SigAlgo: ", key.SigAlgo)
- fmt.Println(" HashAlgo: ", key.HashAlgo)
- fmt.Println(" Weight: ", key.Weight)
- fmt.Println(" SequenceNumber: ", key.SequenceNumber)
- fmt.Println(" Revoked: ", key.Revoked)
- }
-
- fmt.Println(" ---")
-
- if printCode {
- for name, code := range account.Contracts {
- fmt.Printf("Code '%s':\n", name)
- fmt.Println(string(code))
- }
- }
-
- fmt.Println()
-}
diff --git a/flow/accounts/remove-contract/remove.go b/flow/accounts/remove-contract/remove.go
deleted file mode 100644
index 7cfbe2c48..000000000
--- a/flow/accounts/remove-contract/remove.go
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 remove_contract
-
-import (
- "log"
-
- "github.com/onflow/flow-go-sdk/templates"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Signer string `default:"service" flag:"signer,s"`
- Host string `flag:"host" info:"Flow Access API host address"`
- Results bool `default:"false" flag:"results" info:"Display the results of the transaction"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "remove-contract ",
- Short: "Remove a contract deployed to an account",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- contractName := args[0]
-
- signerAccount := projectConf.Accounts[conf.Signer]
- // TODO: Remove once new configuration is migrated
- if signerAccount == nil && conf.Signer == "service" {
- signerAccount = projectConf.Accounts["emulator-account"]
- }
-
- tx := templates.RemoveAccountContract(signerAccount.Address, contractName)
-
- cli.SendTransaction(
- projectConf.HostWithOverride(conf.Host),
- signerAccount,
- tx,
- conf.Results,
- )
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/accounts/staking-info/get.go b/flow/accounts/staking-info/get.go
deleted file mode 100644
index f506a2140..000000000
--- a/flow/accounts/staking-info/get.go
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 staking_info
-
-import (
- "fmt"
- "log"
-
- "github.com/onflow/cadence"
-
- "github.com/onflow/flow-core-contracts/lib/go/templates"
- "github.com/onflow/flow-go-sdk"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Host string `flag:"host" info:"Flow Access API host address"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "staking-info ",
- Short: "Get account staking info",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
-
- address := flow.HexToAddress(args[0])
-
- cadenceAddress := cadence.NewAddress(address)
-
- chain, err := cli.GetAddressNetwork(address)
-
- if err != nil {
- cli.Exitf(1, "Failed to determine network from input address ")
- }
-
- env := cli.EnvFromNetwork(chain)
-
- stakingInfoScript := templates.GenerateGetLockedStakerInfoScript(env)
-
- fmt.Println("Account Staking Info:")
- cli.ExecuteScript(projectConf.HostWithOverride(conf.Host), []byte(stakingInfoScript), cadenceAddress)
-
- delegationInfoScript := templates.GenerateGetLockedDelegatorInfoScript(env)
-
- fmt.Println("Account Delegation Info:")
- cli.ExecuteScript(projectConf.HostWithOverride(conf.Host), []byte(delegationInfoScript), cadenceAddress)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/accounts/update-contract/update.go b/flow/accounts/update-contract/update.go
deleted file mode 100644
index 789e004cf..000000000
--- a/flow/accounts/update-contract/update.go
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 update_contract
-
-import (
- "io/ioutil"
- "log"
-
- "github.com/onflow/flow-go-sdk/templates"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Signer string `default:"service" flag:"signer,s"`
- Host string `flag:"host" info:"Flow Access API host address"`
- Results bool `default:"false" flag:"results" info:"Display the results of the transaction"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "update-contract ",
- Short: "Update a contract deployed to an account",
- Args: cobra.ExactArgs(2),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- contractName := args[0]
- contractFilename := args[1]
-
- contractSource, err := ioutil.ReadFile(contractFilename)
- if err != nil {
- cli.Exitf(1, "Failed to read contract from source file %s", contractFilename)
- }
-
- signerAccount := projectConf.Accounts[conf.Signer]
- // TODO: Remove once new configuration is migrated
- if signerAccount == nil && conf.Signer == "service" {
- signerAccount = projectConf.Accounts["emulator-account"]
- }
-
- tx := templates.UpdateAccountContract(
- signerAccount.Address,
- templates.Contract{
- Name: contractName,
- Source: string(contractSource),
- },
- )
-
- cli.SendTransaction(
- projectConf.HostWithOverride(conf.Host),
- signerAccount,
- tx,
- conf.Results,
- )
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/blocks.go b/flow/blocks.go
deleted file mode 100644
index 5b35c84b6..000000000
--- a/flow/blocks.go
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "context"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
- "google.golang.org/grpc"
-)
-
-func GetBlockByID(host string, blockID flow.Identifier) *flow.Block {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- block, err := flowClient.GetBlockByID(ctx, blockID)
- if err != nil {
- Exitf(1, "Failed to retrieve block by ID %s: %s", blockID, err)
- }
- return block
-}
-
-func GetBlockByHeight(host string, height uint64) *flow.Block {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- block, err := flowClient.GetBlockByHeight(ctx, height)
- if err != nil {
- Exitf(1, "Failed to retrieve block by height %d: %s", height, err)
- }
- return block
-}
-
-func GetLatestBlock(host string) *flow.Block {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- block, err := flowClient.GetLatestBlock(ctx, true)
- if err != nil {
- Exitf(1, "Failed to retrieve latest block: %s", err)
- }
- return block
-}
diff --git a/flow/blocks/get/get.go b/flow/blocks/get/get.go
deleted file mode 100644
index d07ce14f8..000000000
--- a/flow/blocks/get/get.go
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 get
-
-import (
- "fmt"
- "log"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Host string `flag:"host" info:"Flow Access API host address"`
- Latest bool `default:"false" flag:"latest" info:"Display latest block"`
- BlockID string `default:"" flag:"id" info:"Display block by id"`
- BlockHeight uint64 `default:"0" flag:"height" info:"Display block by height"`
- Events string `default:"" flag:"events" info:"List events of this type for the block"`
- Verbose bool `default:"false" flag:"verbose" info:"Display transactions in block"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "get ",
- Short: "Get block info",
- Run: func(cmd *cobra.Command, args []string) {
- var block *flow.Block
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
- host := projectConf.HostWithOverride(conf.Host)
- if conf.Latest {
- block = cli.GetLatestBlock(host)
- } else if len(conf.BlockID) > 0 {
- blockID := flow.HexToID(conf.BlockID)
- block = cli.GetBlockByID(host, blockID)
- } else if len(args) > 0 && len(args[0]) > 0 {
- blockID := flow.HexToID(args[0])
- block = cli.GetBlockByID(host, blockID)
- } else {
- block = cli.GetBlockByHeight(host, conf.BlockHeight)
- }
- printBlock(block, conf.Verbose)
- if conf.Events != "" {
- cli.GetBlockEvents(host, block.Height, block.Height, conf.Events, true)
- }
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func printBlock(block *flow.Block, verbose bool) {
- fmt.Println()
- fmt.Println("Block ID: ", block.ID)
- fmt.Println("Parent ID: ", block.ParentID)
- fmt.Println("Height: ", block.Height)
- fmt.Println("Timestamp: ", block.Timestamp)
- fmt.Println("Total Collections: ", len(block.CollectionGuarantees))
- for i, guarantee := range block.CollectionGuarantees {
- fmt.Printf(" Collection %d: %s\n", i, guarantee.CollectionID)
- if verbose {
- collection := cli.GetCollectionByID(conf.Host, guarantee.CollectionID)
- for i, transaction := range collection.TransactionIDs {
- fmt.Printf(" Transaction %d: %s\n", i, transaction)
- }
- }
- }
- fmt.Println("Total Seals: ", len(block.Seals))
- fmt.Println()
-}
diff --git a/flow/collections/get/get.go b/flow/collections/get/get.go
deleted file mode 100644
index 1ffcdd5f1..000000000
--- a/flow/collections/get/get.go
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 get
-
-import (
- "fmt"
- "log"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Host string `flag:"host" info:"Flow Access API host address"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "get ",
- Short: "Get collection info",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
- collectionID := flow.HexToID(args[0])
- collection := cli.GetCollectionByID(projectConf.HostWithOverride(conf.Host), collectionID)
- printCollection(collection)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func printCollection(collection *flow.Collection) {
- fmt.Println()
- fmt.Println("Collection ID: ", collection.ID())
- for i, transaction := range collection.TransactionIDs {
- fmt.Printf(" Transaction %d: %s\n", i, transaction)
- }
- fmt.Println()
-}
diff --git a/flow/config.go b/flow/config.go
deleted file mode 100644
index 1d2fbca64..000000000
--- a/flow/config.go
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "context"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "regexp"
- "strings"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/onflow/flow-go-sdk/crypto/cloudkms"
-)
-
-type KeyType string
-
-const (
- serviceAccountName = "service"
-
- KeyTypeHex KeyType = "hex" // Hex private key with in memory signer
- KeyTypeKMS KeyType = "kms" // Google KMS signer
- KeyTypeShell KeyType = "shell" // Exec out to a shell script
-
- defaultKeyType = KeyTypeHex
-)
-
-type Account struct {
- KeyType KeyType
- KeyIndex int
- KeyContext map[string]string
- Address flow.Address
- PrivateKey crypto.PrivateKey
- SigAlgo crypto.SignatureAlgorithm
- HashAlgo crypto.HashAlgorithm
- Signer crypto.Signer
-}
-
-// An internal utility struct that defines how Account is converted to JSON.
-type accountJSON struct {
- Address string `json:"address"`
- PrivateKey string `json:"privateKey"`
- SigAlgo string `json:"sigAlgorithm"`
- HashAlgo string `json:"hashAlgorithm"`
- KeyType string `json:"keyType"`
- KeyIndex int `json:"keyIndex"`
- KeyContext map[string]string `json:"keyContext"`
-
- // TODO: Remove once new configuration is migrated
- Keys string `json:"keys"`
-}
-
-func (acct *Account) MarshalJSON() ([]byte, error) {
- prKeyHex := "deprecated"
- keyContext := acct.KeyContext
- if keyContext == nil {
- keyContext = make(map[string]string)
- }
-
- switch acct.KeyType {
- case KeyTypeHex:
- prKeyBytes := acct.PrivateKey.Encode()
- keyContext["privateKey"] = hex.EncodeToString(prKeyBytes)
- // Deprecated, but keep for now
- prKeyHex = keyContext["privateKey"]
- case KeyTypeKMS:
- // Key context should be filled, do nothing
- // TODO: Could validate contents
- default:
- return nil, fmt.Errorf("unknown key type %s", acct.KeyType)
- }
-
- return json.Marshal(accountJSON{
- Address: acct.Address.Hex(),
- PrivateKey: prKeyHex,
- SigAlgo: acct.SigAlgo.String(),
- HashAlgo: acct.HashAlgo.String(),
- KeyType: string(acct.KeyType),
- KeyIndex: acct.KeyIndex,
- KeyContext: keyContext,
- })
-}
-
-func (acct *Account) UnmarshalJSON(data []byte) (err error) {
- var alias accountJSON
- if err = json.Unmarshal(data, &alias); err != nil {
- return
- }
-
- // TODO: Remove once new configuration is migrated
- if alias.Keys != "" {
- return acct.parseProjectConfig(alias)
- }
-
- acct.Address = flow.HexToAddress(alias.Address)
- acct.SigAlgo = crypto.StringToSignatureAlgorithm(alias.SigAlgo)
- acct.HashAlgo = crypto.StringToHashAlgorithm(alias.HashAlgo)
- acct.KeyIndex = alias.KeyIndex
- acct.KeyContext = alias.KeyContext
- if alias.KeyType == "" {
- acct.KeyType = defaultKeyType
- } else {
- acct.KeyType = KeyType(alias.KeyType)
- }
-
- if acct.KeyType == KeyTypeHex {
- var prKeyBytes []byte
- prKeyBytes, err = hex.DecodeString(alias.PrivateKey)
- if err != nil {
- return
- }
-
- acct.PrivateKey, err = crypto.DecodePrivateKey(acct.SigAlgo, prKeyBytes)
- if err != nil {
- return err
- }
- }
-
- return
-}
-
-// TODO: Remove once new configuration is migrated
-func (acct *Account) parseProjectConfig(conf accountJSON) error {
- fmt.Println("⚠️ You are using a new experimental configuration format. Support for this format is not yet available across all CLI commands.")
-
- acct.Address = flow.HexToAddress(conf.Address)
- acct.SigAlgo = crypto.ECDSA_P256
- acct.HashAlgo = crypto.SHA3_256
- acct.KeyIndex = 0
- acct.KeyType = defaultKeyType
-
- var prKeyBytes []byte
- prKeyBytes, err := hex.DecodeString(conf.Keys)
- if err != nil {
- return err
- }
-
- acct.PrivateKey, _ = crypto.DecodePrivateKey(acct.SigAlgo, prKeyBytes)
- acct.KeyContext = map[string]string{"privateKey": conf.Keys}
-
- return nil
-}
-
-func (acct *Account) LoadSigner() error {
- switch acct.KeyType {
- case KeyTypeHex:
- acct.Signer = crypto.NewNaiveSigner(
- acct.PrivateKey,
- acct.HashAlgo,
- )
- case KeyTypeKMS:
- ctx := context.Background()
- accountKMSKey, err := kmsKeyFromKeyContext(acct.KeyContext)
- if err != nil {
- return err
- }
- kmsClient, err := cloudkms.NewClient(ctx)
- if err != nil {
- return err
- }
-
- accountKMSSigner, err := kmsClient.SignerForKey(
- ctx,
- acct.Address,
- accountKMSKey,
- )
- if err != nil {
- return err
- }
- acct.Signer = accountKMSSigner
- default:
- return fmt.Errorf("could not load signer with type %s", acct.KeyType)
- }
- return nil
-}
-
-type Config struct {
- Host string `json:"host"`
- Accounts map[string]*Account `json:"accounts"`
-}
-
-func NewConfig() *Config {
- return &Config{
- Accounts: make(map[string]*Account),
- }
-}
-
-func (c *Config) ServiceAccount() *Account {
- serviceAcct, ok := c.Accounts[serviceAccountName]
- if !ok {
- // TODO: Remove once new configuration is migrated
- return c.getProjectServiceAccount()
- // Exit(1, "Missing service account!")
- }
- return serviceAcct
-}
-
-// TODO: Remove once new configuration is migrated
-func (c *Config) getProjectServiceAccount() *Account {
- serviceAcct, ok := c.Accounts["emulator-account"]
- if !ok {
- Exit(1, "Missing service account")
- }
- return serviceAcct
-}
-
-func (c *Config) SetServiceAccountKey(privateKey crypto.PrivateKey, hashAlgo crypto.HashAlgorithm) {
- c.Accounts[serviceAccountName] = &Account{
- KeyType: KeyTypeHex,
- Address: flow.ServiceAddress(flow.Emulator),
- PrivateKey: privateKey,
- SigAlgo: privateKey.Algorithm(),
- HashAlgo: hashAlgo,
- }
-}
-
-func (c *Config) HostWithOverride(overrideIfNotEmpty string) string {
- if len(strings.TrimSpace(overrideIfNotEmpty)) > 0 {
- return overrideIfNotEmpty
- }
- if len(strings.TrimSpace(c.Host)) > 0 {
- return c.Host
- }
- return DefaultHost
-}
-
-func (c *Config) LoadSigners() error {
- for configName, account := range c.Accounts {
- err := account.LoadSigner()
- if err != nil {
- return fmt.Errorf("Could not load signer for config %s: %w", configName, err)
- }
- }
- return nil
-}
-
-func SaveConfig(conf *Config) error {
- data, err := json.MarshalIndent(conf, "", "\t")
- if err != nil {
- return err
- }
- err = ioutil.WriteFile(ConfigPath[0], data, 0777)
- if err != nil {
- return err
- }
- fmt.Printf("💾 Config file %s saved\n", ConfigPath)
- return nil
-}
-
-func MustSaveConfig(conf *Config) {
- if err := SaveConfig(conf); err != nil {
- Exitf(1, "Failed to save config err: %v", err)
- }
-}
-
-func LoadConfig() *Config {
- f, err := os.Open(ConfigPath[0])
- if err != nil {
- if os.IsNotExist(err) {
- fmt.Printf("Project config file %s does not exist. Please initialize first\n", ConfigPath)
- } else {
- fmt.Printf("Failed to open project configuration in %s\n", ConfigPath)
- }
-
- os.Exit(1)
- }
-
- d := json.NewDecoder(f)
-
- conf := new(Config)
-
- if err := d.Decode(conf); err != nil {
- fmt.Printf("%s contains invalid json: %s\n", ConfigPath, err.Error())
- os.Exit(1)
- }
-
- err = conf.LoadSigners()
- if err != nil {
- fmt.Printf("could not load signers for %s: %s\n", ConfigPath, err.Error())
- os.Exit(1)
- }
- return conf
-}
-
-func ConfigExists() bool {
- info, err := os.Stat(ConfigPath[0])
- if os.IsNotExist(err) {
- return false
- }
- return !info.IsDir()
-}
-
-var kmsKeyContextFields = []string{
- "projectId",
- "locationId",
- "keyRingId",
- "keyId",
- "keyVersion",
-}
-
-func kmsKeyFromKeyContext(keyContext map[string]string) (cloudkms.Key, error) {
-
- for _, field := range kmsKeyContextFields {
- if val, ok := keyContext[field]; !ok || val == "" {
- return cloudkms.Key{}, fmt.Errorf("Could not generate KMS key from Context. Invalid value for %s", field)
- }
- }
-
- return cloudkms.Key{
- ProjectID: keyContext[kmsKeyContextFields[0]],
- LocationID: keyContext[kmsKeyContextFields[1]],
- KeyRingID: keyContext[kmsKeyContextFields[2]],
- KeyID: keyContext[kmsKeyContextFields[3]],
- KeyVersion: keyContext[kmsKeyContextFields[4]],
- }, nil
-}
-
-// Regex that matches the resource name of GCP KMS keys, and parses out the values
-var resourceRegexp = regexp.MustCompile(`projects/(?P[^/]*)/locations/(?P[^/]*)/keyRings/(?P[^/]*)/cryptoKeys/(?P[^/]*)/cryptoKeyVersions/(?P[^/]*)`)
-
-func KeyContextFromKMSResourceID(resourceID string) (map[string]string, error) {
- match := resourceRegexp.FindStringSubmatch(resourceID)
- keyContext := make(map[string]string)
- for i, name := range resourceRegexp.SubexpNames() {
- if i != 0 && name != "" {
- keyContext[name] = match[i]
- }
- }
-
- return keyContext, nil
-}
diff --git a/flow/emulator/emulator.go b/flow/emulator/emulator.go
deleted file mode 100644
index 296d4a344..000000000
--- a/flow/emulator/emulator.go
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 emulator
-
-import (
- "fmt"
-
- emulator "github.com/onflow/flow-emulator"
- "github.com/onflow/flow-emulator/cmd/emulator/start"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
- "github.com/onflow/flow-cli/flow/initialize"
-)
-
-var Cmd = &cobra.Command{
- Use: "emulator",
- Short: "Flow emulator server",
- TraverseChildren: true,
-}
-
-func configuredServiceKey(
- init bool,
- sigAlgo crypto.SignatureAlgorithm,
- hashAlgo crypto.HashAlgorithm,
-) (
- crypto.PrivateKey,
- crypto.SignatureAlgorithm,
- crypto.HashAlgorithm,
-) {
- if sigAlgo == crypto.UnknownSignatureAlgorithm {
- sigAlgo = emulator.DefaultServiceKeySigAlgo
- }
-
- if hashAlgo == crypto.UnknownHashAlgorithm {
- hashAlgo = emulator.DefaultServiceKeyHashAlgo
- }
-
- var serviceAcct *cli.Account
-
- if init {
- pconf := initialize.InitProject(sigAlgo, hashAlgo)
- serviceAcct = pconf.ServiceAccount()
-
- fmt.Printf("⚙️ Flow client initialized with service account:\n\n")
- fmt.Printf("👤 Address: 0x%s\n", serviceAcct.Address)
- } else {
- serviceAcct = cli.LoadConfig().ServiceAccount()
- }
-
- return serviceAcct.PrivateKey, serviceAcct.SigAlgo, serviceAcct.HashAlgo
-}
-
-func init() {
- Cmd.AddCommand(start.Cmd(configuredServiceKey))
-}
diff --git a/flow/events/get/get.go b/flow/events/get/get.go
deleted file mode 100644
index dcd053ae6..000000000
--- a/flow/events/get/get.go
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 get
-
-import (
- "log"
- "strconv"
- "strings"
-
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Host string `flag:"host" info:"Flow Access API host address"`
- Verbose bool `flag:"verbose" info:"Verbose output"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "get ",
- Short: "Get events in a block range",
- Args: cobra.RangeArgs(2, 3),
- Example: "flow events get A.1654653399040a61.FlowToken.TokensDeposited 11559500 11559600",
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
- host := projectConf.HostWithOverride(conf.Host)
-
- eventName, startHeight, endHeight := validateArguments(host, args)
-
- cli.GetBlockEvents(host, startHeight, endHeight, eventName, conf.Verbose)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func validateArguments(host string, args []string) (eventName string, startHeight, endHeight uint64) {
- var err error
- eventName = args[0]
- if len(eventName) == 0 {
- cli.Exitf(1, "Cannot use empty string as event name")
- }
-
- startHeight, err = strconv.ParseUint(args[1], 10, 64)
- if err != nil {
- cli.Exitf(1, "Failed to parse start height of block range: %s", args[1])
- }
- if len(args) == 2 {
- endHeight = startHeight
- return
- }
- if strings.EqualFold(strings.TrimSpace(args[2]), "latest") {
- latestBlock := cli.GetLatestBlock(host)
- endHeight = latestBlock.Height
- return
- }
- endHeight, err = strconv.ParseUint(args[2], 10, 64)
- if err != nil {
- cli.Exitf(1, "Failed to parse end height of block range: %s", args[2])
- }
- if endHeight < startHeight {
- cli.Exitf(1, "Cannot have end height (%d) of block range less that start height (%d)", endHeight, startHeight)
- }
- return
-}
diff --git a/flow/initialize/init.go b/flow/initialize/init.go
deleted file mode 100644
index 28d6dfc7b..000000000
--- a/flow/initialize/init.go
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 initialize
-
-import (
- "fmt"
- "log"
-
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- ServicePrivateKey string `flag:"service-priv-key" info:"Service account private key"`
- ServiceKeySigAlgo string `default:"ECDSA_P256" flag:"service-sig-algo" info:"Service account key signature algorithm"`
- ServiceKeyHashAlgo string `default:"SHA3_256" flag:"service-hash-algo" info:"Service account key hash algorithm"`
- Reset bool `default:"false" flag:"reset" info:"Reset flow.json config file"`
-}
-
-var (
- conf Config
-)
-
-var Cmd = &cobra.Command{
- Use: "init",
- Short: "Initialize a new account profile",
- Run: func(cmd *cobra.Command, args []string) {
- if !cli.ConfigExists() || conf.Reset {
- var pconf *cli.Config
- serviceKeySigAlgo := crypto.StringToSignatureAlgorithm(conf.ServiceKeySigAlgo)
- serviceKeyHashAlgo := crypto.StringToHashAlgorithm(conf.ServiceKeyHashAlgo)
- if len(conf.ServicePrivateKey) > 0 {
- serviceKey := cli.MustDecodePrivateKeyHex(serviceKeySigAlgo, conf.ServicePrivateKey)
- pconf = InitProjectWithServiceKey(serviceKey, serviceKeyHashAlgo)
- } else {
- pconf = InitProject(serviceKeySigAlgo, serviceKeyHashAlgo)
- }
- serviceAcct := pconf.ServiceAccount()
-
- fmt.Printf("⚙️ Flow client initialized with service account:\n\n")
- fmt.Printf("👤 Address: 0x%x\n", serviceAcct.Address.Bytes())
- fmt.Printf("ℹ️ Start the emulator with this service account by running: flow emulator start\n")
- } else {
- fmt.Printf("⚠️ Flow configuration file already exists! Begin by running: flow emulator start\n")
- }
- },
-}
-
-// InitProject generates a new service key and saves project config.
-func InitProject(sigAlgo crypto.SignatureAlgorithm, hashAlgo crypto.HashAlgorithm) *cli.Config {
- seed := cli.RandomSeed(crypto.MinSeedLength)
-
- serviceKey, err := crypto.GeneratePrivateKey(sigAlgo, seed)
- if err != nil {
- cli.Exitf(1, "Failed to generate private key: %v", err)
- }
-
- return InitProjectWithServiceKey(serviceKey, hashAlgo)
-}
-
-// InitProjectWithServiceKey creates and saves a new project config
-// using the specified service key.
-func InitProjectWithServiceKey(privateKey crypto.PrivateKey, hashAlgo crypto.HashAlgorithm) *cli.Config {
- pconf := cli.NewConfig()
- pconf.SetServiceAccountKey(privateKey, hashAlgo)
- cli.MustSaveConfig(pconf)
- return pconf
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/keys/decode/decode.go b/flow/keys/decode/decode.go
deleted file mode 100644
index e62976955..000000000
--- a/flow/keys/decode/decode.go
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 decode
-
-import (
- "encoding/hex"
- "fmt"
-
- flowsdk "github.com/onflow/flow-go-sdk"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-var Cmd = &cobra.Command{
- Use: "decode ",
- Short: "Decode a public account key hex string",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
-
- publicKey := args[0]
-
- publicKeyBytes, err := hex.DecodeString(publicKey)
- if err != nil {
- cli.Exitf(1, "Failed to decode public key: %v", err)
- }
-
- accountKey, err := flowsdk.DecodeAccountKey(publicKeyBytes)
- if err != nil {
- cli.Exitf(1, "Failed to decode private key bytes: %v", err)
- }
-
- fmt.Printf(" Public key: %x\n", accountKey.PublicKey.Encode())
- fmt.Printf(" Signature algorithm: %s\n", accountKey.SigAlgo)
- fmt.Printf(" Hash algorithm: %s\n", accountKey.HashAlgo)
- fmt.Printf(" Weight: %d\n", accountKey.Weight)
- },
-}
diff --git a/flow/keys/generate/generate.go b/flow/keys/generate/generate.go
deleted file mode 100644
index ec26c5b71..000000000
--- a/flow/keys/generate/generate.go
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 generate
-
-import (
- "encoding/hex"
- "fmt"
- "log"
-
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Seed string `flag:"seed,s" info:"Deterministic seed phrase"`
- SigAlgo string `default:"ECDSA_P256" flag:"algo,a" info:"Signature algorithm"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "generate",
- Short: "Generate a new key-pair",
- Run: func(cmd *cobra.Command, args []string) {
- var seed []byte
- if conf.Seed == "" {
- seed = cli.RandomSeed(crypto.MinSeedLength)
- } else {
- seed = []byte(conf.Seed)
- }
-
- sigAlgo := crypto.StringToSignatureAlgorithm(conf.SigAlgo)
- if sigAlgo == crypto.UnknownSignatureAlgorithm {
- cli.Exitf(1, "Invalid signature algorithm: %s", conf.SigAlgo)
- }
-
- fmt.Printf(
- "Generating key pair with signature algorithm: %s\n...\n",
- sigAlgo,
- )
-
- privateKey, err := crypto.GeneratePrivateKey(sigAlgo, seed)
- if err != nil {
- cli.Exitf(1, "Failed to generate private key: %v", err)
- }
-
- fmt.Printf(
- "\U0001F510 Private key (\u26A0\uFE0F\u202F store safely and don't share with anyone): %s\n",
- hex.EncodeToString(privateKey.Encode()),
- )
- fmt.Printf(
- "\U0001F54A️\uFE0F\u202F Encoded public key (share freely): %x\n",
- privateKey.PublicKey().Encode(),
- )
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/keys/keys.go b/flow/keys/keys.go
deleted file mode 100644
index f96a47b79..000000000
--- a/flow/keys/keys.go
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 keys
-
-import (
- "github.com/spf13/cobra"
-
- "github.com/onflow/flow-cli/flow/keys/decode"
- "github.com/onflow/flow-cli/flow/keys/generate"
- "github.com/onflow/flow-cli/flow/keys/save"
-)
-
-var Cmd = &cobra.Command{
- Use: "keys",
- Short: "Utilities to manage cryptographic keys",
- TraverseChildren: true,
-}
-
-func init() {
- Cmd.AddCommand(decode.Cmd)
- Cmd.AddCommand(generate.Cmd)
- Cmd.AddCommand(save.Cmd)
-}
diff --git a/flow/keys/save/hex/hex.go b/flow/keys/save/hex/hex.go
deleted file mode 100644
index a02e8673f..000000000
--- a/flow/keys/save/hex/hex.go
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 hex
-
-import (
- "encoding/hex"
- "log"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Name string `flag:"name" info:"name of the key"`
- Address string `flag:"address" info:"flow address of the account"`
- SigAlgo string `flag:"sigalgo" info:"signature algorithm for the key"`
- HashAlgo string `flag:"hashalgo" info:"hash algorithm for the key"`
- KeyIndex int `flag:"index" info:"index of the key on the account"`
- KeyHex string `flag:"privatekey" info:"private key in hex format"`
- Overwrite bool `flag:"overwrite" info:"bool indicating if we should overwrite an existing config with the same name in the config file"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "hex",
- Short: "Save a hex key to the config file",
- Example: "flow keys save hex --name test --address 8c5303eaa26202d6 --sigalgo ECDSA_secp256k1 --hashalgo SHA2_256 --index 0 --privatekey ",
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- if conf.Name == "" {
- cli.Exitf(1, "missing name")
- }
-
- _, accountExists := projectConf.Accounts[conf.Name]
- if accountExists && !conf.Overwrite {
- cli.Exitf(1, "%s already exists in the config, and overwrite is false", conf.Name)
- }
-
- // Parse address
- decodedAddress, err := hex.DecodeString(conf.Address)
- if err != nil {
- cli.Exitf(1, "invalid address: %s", err.Error())
- }
- address := flow.BytesToAddress(decodedAddress)
-
- // Parse signature algorithm
- if conf.SigAlgo == "" {
- cli.Exitf(1, "missing signature algorithm")
- }
-
- algorithm := crypto.StringToSignatureAlgorithm(conf.SigAlgo)
- if algorithm == crypto.UnknownSignatureAlgorithm {
- cli.Exitf(1, "invalid signature algorithm")
- }
-
- // Parse hash algorithm
-
- if conf.HashAlgo == "" {
- cli.Exitf(1, "missing hash algorithm")
- }
-
- hashAlgorithm := crypto.StringToHashAlgorithm(conf.HashAlgo)
- if hashAlgorithm == crypto.UnknownHashAlgorithm {
- cli.Exitf(1, "invalid hash algorithm")
- }
-
- // Populate account
- account := &cli.Account{
- KeyType: cli.KeyTypeHex,
- Address: address,
- SigAlgo: algorithm,
- HashAlgo: hashAlgorithm,
- KeyIndex: conf.KeyIndex,
- KeyContext: map[string]string{"privateKey": conf.KeyHex},
- }
- privateKey, err := crypto.DecodePrivateKeyHex(account.SigAlgo, conf.KeyHex)
- if err != nil {
- cli.Exitf(1, "key hex could not be parsed")
- }
-
- account.PrivateKey = privateKey
-
- // Validate account
- err = account.LoadSigner()
- if err != nil {
- cli.Exitf(1, "provide key could not be loaded as a valid signer %s", conf.KeyHex)
- }
-
- projectConf.Accounts[conf.Name] = account
-
- err = cli.SaveConfig(projectConf)
- if err != nil {
- cli.Exitf(1, "could not save config file %s", cli.ConfigPath)
- }
-
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/keys/save/kms/kms.go b/flow/keys/save/kms/kms.go
deleted file mode 100644
index 12ee04505..000000000
--- a/flow/keys/save/kms/kms.go
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 kms
-
-import (
- "log"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Name string `flag:"name" info:"name of the key"`
- Address string `flag:"address" info:"flow address of the account"`
- SigAlgo string `flag:"sigalgo" info:"signature algorithm for the key"`
- HashAlgo string `flag:"hashalgo" info:"hash algorithm for the key"`
- KeyIndex int `flag:"index" info:"index of the key on the account"`
- KeyContext string `flag:"context" info:"projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/"`
- Overwrite bool `flag:"overwrite" info:"bool indicating if we should overwrite an existing config with the same name in the config file"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "kms",
- Short: "Save a KMS key to the config file",
- Example: "flow keys save kms --name test --address 8c5303eaa26202d6 --sigalgo ECDSA_secp256k1 --hashalgo SHA2_256 --index 0 --context 'KMS_RESOURCE_ID'",
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- _, accountExists := projectConf.Accounts[conf.Name]
- if accountExists && !conf.Overwrite {
- cli.Exitf(1, "%s already exists in the config, and overwrite is false", conf.Name)
- }
-
- keyContext, err := cli.KeyContextFromKMSResourceID(conf.KeyContext)
- if err != nil {
- cli.Exitf(1, "key context could not be parsed %s", conf.KeyContext)
- }
- // Populate account
- account := &cli.Account{
- KeyType: cli.KeyTypeKMS,
- Address: flow.HexToAddress(conf.Address),
- SigAlgo: crypto.StringToSignatureAlgorithm(conf.SigAlgo),
- HashAlgo: crypto.StringToHashAlgorithm(conf.HashAlgo),
- KeyIndex: conf.KeyIndex,
- KeyContext: keyContext,
- }
-
- // Validate account
- err = account.LoadSigner()
- if err != nil {
- cli.Exitf(1, "provide key context could not be loaded as a valid signer %s", conf.KeyContext)
- }
-
- projectConf.Accounts[conf.Name] = account
- err = cli.SaveConfig(projectConf)
- if err != nil {
- cli.Exitf(1, "could not save config file %s", cli.ConfigPath)
- }
-
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/keys/save/save.go b/flow/keys/save/save.go
deleted file mode 100644
index 9c5a77579..000000000
--- a/flow/keys/save/save.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 save
-
-import (
- "github.com/spf13/cobra"
-
- "github.com/onflow/flow-cli/flow/keys/save/hex"
- "github.com/onflow/flow-cli/flow/keys/save/kms"
-)
-
-var Cmd = &cobra.Command{
- Use: "save",
- Short: "save a key to the config",
- TraverseChildren: true,
-}
-
-func init() {
- Cmd.AddCommand(hex.Cmd)
- Cmd.AddCommand(kms.Cmd)
-}
diff --git a/flow/project/cli/cli.go b/flow/project/cli/cli.go
deleted file mode 100644
index f9c9e8b88..000000000
--- a/flow/project/cli/cli.go
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "crypto/rand"
- "fmt"
- "os"
-
- "github.com/fatih/color"
-)
-
-const EnvPrefix = "FLOW"
-
-func Exit(code int, msg string) {
- fmt.Println(msg)
- os.Exit(code)
-}
-
-func Exitf(code int, msg string, args ...interface{}) {
- fmt.Printf(msg+"\n", args...)
- os.Exit(code)
-}
-
-func RandomSeed(n int) []byte {
- seed := make([]byte, n)
-
- _, err := rand.Read(seed)
- if err != nil {
- Exitf(1, "Failed to generate random seed: %v", err)
- }
-
- return seed
-}
-
-// Colors
-var Yellow = color.New(color.FgYellow, color.Bold).SprintfFunc()
-var Green = color.New(color.FgGreen, color.Bold).SprintfFunc()
-var Red = color.New(color.FgRed, color.Bold).SprintfFunc()
-var Bold = color.New(color.Bold).SprintfFunc()
diff --git a/flow/project/cli/project.go b/flow/project/cli/project.go
deleted file mode 100644
index 780e79f63..000000000
--- a/flow/project/cli/project.go
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "errors"
- "fmt"
- "github.com/onflow/flow-cli/flow/project/cli/config/json"
- "path"
- "strings"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/spf13/afero"
- "github.com/thoas/go-funk"
-
- "github.com/onflow/flow-cli/flow/project/cli/config"
- "github.com/onflow/flow-cli/flow/project/cli/keys"
-)
-
-// Project has all the funcionality to manage project
-type Project struct {
- composer *config.Loader
- conf *config.Config
- accounts []*Account
-}
-
-// LoadProject loads configuration and setup the project
-func LoadProject(configFilePath []string) *Project {
- composer := config.NewLoader(afero.NewOsFs())
-
- // here we add all available parsers (more to add yaml etc...)
- composer.AddConfigParser(json.NewParser())
- conf, err := composer.Load(configFilePath)
-
- if err != nil {
- if errors.Is(err, config.ErrDoesNotExist) {
- Exitf(
- 1,
- "Project config file %s does not exist. Please initialize first\n",
- configFilePath,
- )
- }
-
- Exitf(1, "Failed to open project configuration in %s", configFilePath)
-
- return nil
- }
-
- proj, err := newProject(conf, composer)
- if err != nil {
- // TODO: replace with a more detailed error message
- fmt.Println("⚠️ Make sure you generated configuration by using: flow project init, and not: flow init")
- Exitf(1, "Invalid project configuration: %s", err)
- }
-
- return proj
-}
-
-// ProjectExists checks if project exists
-func ProjectExists(path string) bool {
- return config.Exists(path)
-}
-
-// InitProject initializes the project
-func InitProject() *Project {
- emulatorServiceAccount := generateEmulatorServiceAccount()
-
- composer := config.NewLoader(afero.NewOsFs())
- composer.AddConfigParser(json.NewParser())
-
- return &Project{
- composer: composer,
- conf: defaultConfig(emulatorServiceAccount),
- accounts: []*Account{emulatorServiceAccount},
- }
-}
-
-// REF: move this to config
-const (
- defaultEmulatorNetworkName = "emulator"
- defaultEmulatorServiceAccountName = "emulator-account"
- defaultEmulatorPort = 3569
- defaultEmulatorHost = "127.0.0.1:3569"
-)
-
-// REF: this also might be part of config
-func defaultConfig(defaultEmulatorServiceAccount *Account) *config.Config {
- return &config.Config{
- Emulators: config.Emulators{{
- Name: config.DefaultEmulatorConfigName,
- ServiceAccount: defaultEmulatorServiceAccount.name,
- Port: defaultEmulatorPort,
- }},
- Networks: config.Networks{{
- Name: defaultEmulatorNetworkName,
- Host: defaultEmulatorHost,
- ChainID: flow.Emulator,
- }},
- }
-}
-
-func generateEmulatorServiceAccount() *Account {
- seed := RandomSeed(crypto.MinSeedLength)
-
- privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
- if err != nil {
- Exitf(1, "Failed to generate emulator service key: %v", err)
- }
-
- serviceAccountKey := keys.NewHexAccountKeyFromPrivateKey(0, crypto.SHA3_256, privateKey)
-
- return &Account{
- name: defaultEmulatorServiceAccountName,
- address: flow.ServiceAddress(flow.Emulator),
- chainID: flow.Emulator,
- keys: []keys.AccountKey{
- serviceAccountKey,
- },
- }
-}
-
-func newProject(conf *config.Config, composer *config.Loader) (*Project, error) {
- accounts, err := accountsFromConfig(conf)
- if err != nil {
- return nil, err
- }
-
- return &Project{
- composer: composer,
- conf: conf,
- accounts: accounts,
- }, nil
-}
-
-// CheckContractConflict checks if there is any contract duplication between accounts
-// for now we don't allow two different accounts deploying same contract
-func (p *Project) ContractConflictExists(network string) bool {
- contracts := p.GetContractsByNetwork(network)
-
- uniq := funk.Uniq(
- funk.Map(contracts, func(c Contract) string {
- return c.Name
- }).([]string),
- ).([]string)
-
- all := funk.Map(contracts, func(c Contract) string {
- return c.Name
- }).([]string)
-
- return len(all) != len(uniq)
-}
-
-func (p *Project) Host(network string) string {
- return p.conf.Networks.GetByName(network).Host
-}
-
-func (p *Project) EmulatorServiceAccount() *config.Account {
- emulator := p.conf.Emulators.GetDefault()
- return p.conf.Accounts.GetByName(emulator.ServiceAccount)
-}
-
-func (p *Project) GetContractsByNetwork(network string) []Contract {
- contracts := make([]Contract, 0)
-
- // get deployments for specific network
- for _, deploy := range p.conf.Deployments.GetByNetwork(network) {
- account := p.GetAccountByName(deploy.Account)
-
- // go through each contract for this deploy
- for _, contractName := range deploy.Contracts {
- c := p.conf.Contracts.GetByNameAndNetwork(contractName, network)
-
- contract := Contract{
- Name: c.Name,
- Source: path.Clean(c.Source),
- Target: account.address,
- }
-
- contracts = append(contracts, contract)
- }
- }
-
- return contracts
-}
-
-func (p *Project) GetAllAccountNames() []string {
- return funk.Uniq(
- funk.Map(p.accounts, func(a *Account) string {
- return a.name
- }).([]string),
- ).([]string)
-}
-
-func (p *Project) GetAccountByName(name string) *Account {
- return funk.Filter(p.accounts, func(a *Account) bool {
- return a.name == name
- }).([]*Account)[0]
-}
-
-func (p *Project) GetAccountByAddress(address string) *Account {
- return funk.Filter(p.accounts, func(a *Account) bool {
- return a.address.String() == strings.ReplaceAll(address, "0x", "")
- }).([]*Account)[0]
-}
-
-func (p *Project) GetAliases(network string) map[string]string {
- aliases := make(map[string]string)
-
- // get all contracts for selected network and if any has an address as target make it an alias
- for _, contract := range p.conf.Contracts.GetByNetwork(network) {
- if contract.IsAlias() {
- aliases[path.Clean(contract.Source)] = contract.Alias
- }
- }
-
- return aliases
-}
-
-func (p *Project) Save(path string) {
- p.conf.Accounts = accountsToConfig(p.accounts)
- err := p.composer.Save(p.conf, path)
-
- if err != nil {
- fmt.Println(err)
- Exitf(1, "Failed to save project configuration to \"%s\"", path)
- }
-}
-
-type Contract struct {
- Name string
- Source string
- Target flow.Address
-}
-
-type Account struct {
- name string
- address flow.Address
- chainID flow.ChainID
- keys []keys.AccountKey
-}
-
-func (a *Account) Address() flow.Address {
- return a.address
-}
-
-func (a *Account) DefaultKey() keys.AccountKey {
- return a.keys[0]
-}
-
-func accountsFromConfig(conf *config.Config) ([]*Account, error) {
- accounts := make([]*Account, 0, len(conf.Accounts))
-
- for _, accountConf := range conf.Accounts {
- account, err := accountFromConfig(accountConf)
- if err != nil {
- return nil, err
- }
-
- accounts = append(accounts, account)
- }
-
- return accounts, nil
-}
-
-func accountFromConfig(accountConf config.Account) (*Account, error) {
- accountKeys := make([]keys.AccountKey, 0, len(accountConf.Keys))
-
- for _, key := range accountConf.Keys {
- accountKey, err := keys.NewAccountKey(key)
- if err != nil {
- return nil, err
- }
-
- accountKeys = append(accountKeys, accountKey)
- }
-
- return &Account{
- name: accountConf.Name,
- address: accountConf.Address,
- chainID: accountConf.ChainID,
- keys: accountKeys,
- }, nil
-}
-
-func accountsToConfig(accounts []*Account) config.Accounts {
- accountConfs := make([]config.Account, 0)
-
- for _, account := range accounts {
- accountConfs = append(accountConfs, accountToConfig(account))
- }
-
- return accountConfs
-}
-
-func accountToConfig(account *Account) config.Account {
- keyConfigs := make([]config.AccountKey, 0, len(account.keys))
-
- for _, key := range account.keys {
- keyConfigs = append(keyConfigs, key.ToConfig())
- }
-
- return config.Account{
- Name: account.name,
- Address: account.address,
- ChainID: account.chainID,
- Keys: keyConfigs,
- }
-}
diff --git a/flow/project/cli/txsender/txsender.go b/flow/project/cli/txsender/txsender.go
deleted file mode 100644
index 1a90f7ee6..000000000
--- a/flow/project/cli/txsender/txsender.go
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 txsender
-
-import (
- "context"
- "time"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
-
- "github.com/onflow/flow-cli/flow/project/cli"
-)
-
-const (
- defaultGasLimit = 1000
- defaultResultPollInterval = time.Second
-)
-
-type Sender struct {
- client *client.Client
-}
-
-func NewSender(c *client.Client) *Sender {
- return &Sender{
- client: c,
- }
-}
-
-func (s *Sender) Send(
- ctx context.Context,
- tx *flow.Transaction,
- signer *cli.Account,
-) <-chan Result {
-
- result := make(chan Result)
-
- go func() {
- txResult, err := s.send(ctx, tx, signer)
- result <- Result{
- txResult: txResult,
- err: err,
- }
- }()
-
- return result
-}
-
-func (s *Sender) send(
- ctx context.Context,
- tx *flow.Transaction,
- signer *cli.Account,
-) (*flow.TransactionResult, error) {
-
- latestSealedBlock, err := s.client.GetLatestBlockHeader(ctx, true)
- if err != nil {
- return nil, err
- }
-
- seqNo, err := s.getSequenceNumber(ctx, signer)
- if err != nil {
- return nil, err
- }
-
- tx.SetGasLimit(defaultGasLimit).
- SetPayer(signer.Address()).
- SetProposalKey(signer.Address(), signer.DefaultKey().Index(), seqNo).
- SetReferenceBlockID(latestSealedBlock.ID)
-
- err = tx.SignEnvelope(signer.Address(), signer.DefaultKey().Index(), signer.DefaultKey().Signer())
- if err != nil {
- return nil, err
- }
-
- err = s.client.SendTransaction(ctx, *tx)
- if err != nil {
- return nil, err
- }
-
- result, err := s.waitForSeal(ctx, tx.ID())
- if err != nil {
- return nil, err
- }
-
- return result, nil
-}
-
-func (s *Sender) waitForSeal(
- ctx context.Context,
- id flow.Identifier,
-) (*flow.TransactionResult, error) {
- result, err := s.client.GetTransactionResult(ctx, id)
- if err != nil {
- return nil, err
- }
-
- for result.Status != flow.TransactionStatusSealed {
- time.Sleep(defaultResultPollInterval)
-
- result, err = s.client.GetTransactionResult(ctx, id)
- if err != nil {
- return nil, err
- }
- }
-
- return result, nil
-}
-
-func (s *Sender) getSequenceNumber(ctx context.Context, account *cli.Account) (uint64, error) {
- accountResult, err := s.client.GetAccount(ctx, account.Address())
- if err != nil {
- return 0, err
- }
-
- keyIndex := account.DefaultKey().Index()
-
- accountKey := accountResult.Keys[keyIndex]
-
- return accountKey.SequenceNumber, nil
-}
-
-type Result struct {
- txResult *flow.TransactionResult
- err error
-}
-
-func (r *Result) Error() error {
- if r.err != nil {
- return r.err
- }
-
- if r.txResult.Error != nil {
- return r.txResult.Error
- }
-
- return nil
-}
-
-func (r *Result) TransactionResult() *flow.TransactionResult {
- return r.txResult
-}
diff --git a/flow/project/commands/deploy_contracts/deploy.go b/flow/project/commands/deploy_contracts/deploy.go
deleted file mode 100644
index 959d2e441..000000000
--- a/flow/project/commands/deploy_contracts/deploy.go
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 deploy_contracts
-
-import (
- "context"
- "fmt"
- "log"
- "strings"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
- "github.com/onflow/flow-go-sdk/templates"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
- "google.golang.org/grpc"
-
- c "github.com/onflow/flow-cli/flow"
- "github.com/onflow/flow-cli/flow/project/cli"
- "github.com/onflow/flow-cli/flow/project/cli/txsender"
- "github.com/onflow/flow-cli/flow/project/contracts"
-)
-
-type Config struct {
- Network string `flag:"network" default:"emulator" info:"network configuration to use"`
- Update bool `flag:"update" default:"false" info:"use update flag to update existing contracts"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "deploy",
- Short: "Deploy Cadence contracts",
- Run: func(cmd *cobra.Command, args []string) {
- project := cli.LoadProject(c.ConfigPath)
-
- host := project.Host(conf.Network)
-
- grpcClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- cli.Exit(1, err.Error())
- return
- }
-
- // check there are not multiple accounts with same contract
- if project.ContractConflictExists(conf.Network) {
- fmt.Println("\n❌ Currently it is not possible to deploy same contract with multiple accounts, please check Deployments in config and make sure contract is only present in one account")
- cli.Exit(1, "")
- return
- }
-
- sender := txsender.NewSender(grpcClient)
-
- processor := contracts.NewPreprocessor(
- contracts.FilesystemLoader{},
- project.GetAliases(conf.Network),
- )
-
- for _, contract := range project.GetContractsByNetwork(conf.Network) {
- err = processor.AddContractSource(
- contract.Name,
- contract.Source,
- contract.Target,
- )
- if err != nil {
- cli.Exit(1, err.Error())
- return
- }
- }
-
- err = processor.ResolveImports()
- if err != nil {
- cli.Exit(1, err.Error())
- return
- }
-
- contracts, err := processor.ContractDeploymentOrder()
- if err != nil {
- cli.Exit(1, err.Error())
- return
- }
-
- fmt.Printf(
- "Deploying %v contracts for accounts: %s\n",
- len(contracts),
- strings.Join(project.GetAllAccountNames(), ","),
- )
-
- var errs []error
-
- for _, contract := range contracts {
- targetAccount := project.GetAccountByAddress(contract.Target().String())
-
- ctx := context.Background()
-
- targetAccountInfo, err := grpcClient.GetAccountAtLatestBlock(ctx, targetAccount.Address())
- if err != nil {
- cli.Exitf(1, "Failed to fetch information for account %s", targetAccount.Address())
- return
- }
-
- var tx *flow.Transaction
-
- _, exists := targetAccountInfo.Contracts[contract.Name()]
- if exists {
- if !conf.Update {
- continue
- }
-
- tx = prepareUpdateContractTransaction(targetAccount.Address(), contract)
- } else {
- tx = prepareAddContractTransaction(targetAccount.Address(), contract)
- }
-
- getResult := sender.Send(ctx, tx, targetAccount)
-
- spinner := cli.NewSpinner(
- fmt.Sprintf("%s ", cli.Bold(contract.Name())),
- " deploying...",
- )
- spinner.Start()
-
- result := <-getResult
-
- if result.Error() == nil {
- spinner.Stop(fmt.Sprintf("%s -> 0x%s", cli.Green(contract.Name()), contract.Target()))
- } else {
- spinner.Stop(fmt.Sprintf("%s error", cli.Red(contract.Name())))
- errs = append(errs, result.Error())
- }
- }
-
- if len(errs) == 0 {
- fmt.Println("\n✅ All contracts deployed successfully")
- } else {
- // REF: better output when errors
- fmt.Println("\n❌ Failed to deploy all contracts")
- }
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func prepareUpdateContractTransaction(
- targetAccount flow.Address,
- contract *contracts.Contract,
-) *flow.Transaction {
- return templates.UpdateAccountContract(
- targetAccount,
- templates.Contract{
- Name: contract.Name(),
- Source: contract.TranspiledCode(),
- },
- )
-}
-
-func prepareAddContractTransaction(
- targetAccount flow.Address,
- contract *contracts.Contract,
-) *flow.Transaction {
- return templates.AddAccountContract(
- targetAccount,
- templates.Contract{
- Name: contract.Name(),
- Source: contract.TranspiledCode(),
- },
- )
-}
diff --git a/flow/project/commands/initialize/init.go b/flow/project/commands/initialize/init.go
deleted file mode 100644
index 41103620f..000000000
--- a/flow/project/commands/initialize/init.go
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 initialize
-
-import (
- "fmt"
-
- "github.com/spf13/cobra"
-
- c "github.com/onflow/flow-cli/flow"
- "github.com/onflow/flow-cli/flow/project/cli"
-)
-
-var Cmd = &cobra.Command{
- Use: "init",
- Short: "Initialize a new Flow project",
- Run: func(cmd *cobra.Command, args []string) {
- if !cli.ProjectExists(c.ConfigPath[0]) {
- proj := cli.InitProject()
- proj.Save(c.ConfigPath[0])
-
- fmt.Print(
- cli.Green(fmt.Sprintf("Initialized a new Flow project in %s\n\n", c.ConfigPath)),
- )
- fmt.Printf(
- "Start the Flow Emulator by running: %s\n",
- cli.Bold("flow project start-emulator"),
- )
- } else {
- fmt.Printf(
- "%s\n\nStart the Flow Emulator by running: %s\n",
- cli.Red(fmt.Sprintf("A Flow project already exists in %s", c.ConfigPath)),
- cli.Bold("flow project start-emulator"),
- )
- }
- },
-}
diff --git a/flow/project/commands/start_emulator/emulator.go b/flow/project/commands/start_emulator/emulator.go
deleted file mode 100644
index b97ad71f6..000000000
--- a/flow/project/commands/start_emulator/emulator.go
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 start_emulator
-
-import (
- "github.com/onflow/flow-emulator/cmd/emulator/start"
- "github.com/onflow/flow-go-sdk/crypto"
- "github.com/spf13/cobra"
-
- c "github.com/onflow/flow-cli/flow"
- "github.com/onflow/flow-cli/flow/project/cli"
- "github.com/onflow/flow-cli/flow/project/cli/config"
-)
-
-var Cmd *cobra.Command
-
-func configuredServiceKey(
- _ bool,
- _ crypto.SignatureAlgorithm,
- _ crypto.HashAlgorithm,
-) (
- crypto.PrivateKey,
- crypto.SignatureAlgorithm,
- crypto.HashAlgorithm,
-) {
- proj := cli.LoadProject(c.ConfigPath)
-
- serviceAccount := proj.EmulatorServiceAccount()
- // TODO: this shouldn't be accessed like this, make a getter
- serviceKey := serviceAccount.Keys[0]
-
- privateKey, err := crypto.DecodePrivateKeyHex(serviceKey.SigAlgo, serviceKey.Context["privateKey"])
- if err != nil {
- cli.Exitf(
- 1,
- "Invalid private key in \"%s\" emulator configuration",
- config.DefaultEmulatorConfigName,
- )
- }
-
- return privateKey, serviceKey.SigAlgo, serviceKey.HashAlgo
-}
-
-func init() {
- Cmd = start.Cmd(configuredServiceKey)
- Cmd.Use = "start-emulator"
-}
diff --git a/flow/project/project.go b/flow/project/project.go
deleted file mode 100644
index 0a78bce91..000000000
--- a/flow/project/project.go
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 project
-
-import (
- "github.com/spf13/cobra"
-
- "github.com/onflow/flow-cli/flow/project/commands/deploy_contracts"
- "github.com/onflow/flow-cli/flow/project/commands/initialize"
- "github.com/onflow/flow-cli/flow/project/commands/start_emulator"
-)
-
-var Cmd = &cobra.Command{
- Use: "project",
- Short: "Manage your Cadence project",
- TraverseChildren: true,
-}
-
-func init() {
- Cmd.AddCommand(initialize.Cmd)
- Cmd.AddCommand(start_emulator.Cmd)
- Cmd.AddCommand(deploy_contracts.Cmd)
-}
diff --git a/flow/result.go b/flow/result.go
deleted file mode 100644
index 05b0db417..000000000
--- a/flow/result.go
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "context"
- "fmt"
- "time"
-
- "github.com/onflow/cadence"
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
- "google.golang.org/grpc"
-)
-
-func GetTransactionResult(host string, id string, sealed bool, showTransactionCode bool) {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize)))
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- txID := flow.HexToID(id)
-
- var res *flow.TransactionResult
- if sealed {
- res, err = waitForSeal(ctx, flowClient, txID)
- } else {
- res, err = flowClient.GetTransactionResult(ctx, txID)
- }
- if err != nil {
- Exitf(1, "Failed to get transaction result: %s", err)
- }
-
- tx, err := flowClient.GetTransaction(ctx, txID)
- if err != nil {
- Exitf(1, "Failed to get transaction: %s", err)
- }
-
- // Print out results of the TX to std out
- printTxResult(tx, res, showTransactionCode)
-}
-
-func waitForSeal(ctx context.Context, c *client.Client, id flow.Identifier) (*flow.TransactionResult, error) {
- result, err := c.GetTransactionResult(ctx, id)
- if err != nil {
- return nil, err
- }
-
- fmt.Printf("Waiting for transaction %s to be sealed...\n", id)
- for result.Status != flow.TransactionStatusSealed {
- time.Sleep(time.Second)
- fmt.Print(".")
- result, err = c.GetTransactionResult(ctx, id)
- if err != nil {
- return nil, err
- }
- }
-
- fmt.Println()
- fmt.Printf("Transaction %s sealed\n", id)
-
- return result, nil
-}
-
-func printTxResult(tx *flow.Transaction, res *flow.TransactionResult, showCode bool) {
- fmt.Println()
- fmt.Println("Status: " + res.Status.String())
- if res.Error != nil {
- fmt.Println("Execution Error: " + res.Error.Error())
- }
-
- if showCode {
- fmt.Println("Code: ")
- fmt.Println(string(tx.Script))
- }
-
- fmt.Println("Events:")
- printEvents(res.Events, false)
- fmt.Println()
-}
-
-func GetBlockEvents(host string, startHeight, endHeight uint64, eventType string, printEmpty bool) {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize)))
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- events, err := flowClient.GetEventsForHeightRange(ctx, client.EventRangeQuery{
- Type: eventType,
- StartHeight: startHeight,
- EndHeight: endHeight,
- })
-
- if err != nil {
- Exitf(1, "Failed to query block event by height: %s", err)
- }
-
- for i, blockEvent := range events {
- if !printEmpty && len(blockEvent.Events) == 0 {
- continue
- }
- fmt.Printf("Events for Block %s (%d):\n", blockEvent.BlockID, startHeight+uint64(i))
- printEvents(blockEvent.Events, true)
- }
-}
-
-func printEvents(events []flow.Event, txID bool) {
- if len(events) == 0 {
- PrintIndent(1)
- fmt.Println("None")
- }
- // Basic event info printing
- for _, event := range events {
- PrintIndent(1)
- fmt.Printf("Event %d: %s\n", event.EventIndex, event.String())
- if txID {
- PrintIndent(1)
- fmt.Printf("Tx ID: %s\n", event.TransactionID)
- }
- PrintIndent(2)
- fmt.Println("Fields:")
- for i, field := range event.Value.EventType.Fields {
- value := event.Value.Fields[i]
- printField(field, value)
- }
- }
-}
-
-func isByteSlice(v interface{}) bool {
- slice, isSlice := v.([]interface{})
- if !isSlice {
- return false
- }
- _, isBytes := slice[0].(byte)
- return isBytes
-}
-
-func printField(field cadence.Field, value cadence.Value) {
- v := value.ToGoValue()
- typeInfo := "Unknown"
- if field.Type != nil {
- typeInfo = field.Type.ID()
- } else if _, isAddress := v.([8]byte); isAddress {
- typeInfo = "Address"
- }
- // TODO: consider replacing with a string writer
- PrintIndent(3)
- fmt.Printf("%s (%s): ", field.Identifier, typeInfo)
- // Try the two most obvious cases
- if address, ok := v.([8]byte); ok {
- fmt.Printf("%x", address)
- } else if isByteSlice(v) || field.Identifier == "publicKey" {
- // make exception for public key, since it get's interpreted as
- // []*big.Int
- for _, b := range v.([]interface{}) {
- fmt.Printf("%x", b)
- }
- } else if uintVal, ok := v.(uint64); typeInfo == "UFix64" && ok {
- fmt.Print(cadence.UFix64(uintVal))
- } else {
- fmt.Printf("%v", v)
- }
- fmt.Println()
-}
diff --git a/flow/script.go b/flow/script.go
deleted file mode 100644
index 11521fd7f..000000000
--- a/flow/script.go
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
-
- "github.com/onflow/cadence"
- jsoncdc "github.com/onflow/cadence/encoding/json"
- "github.com/onflow/flow-go-sdk/client"
- "google.golang.org/grpc"
-)
-
-func ExecuteScript(host string, script []byte, arguments ...cadence.Value) {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
- value, err := flowClient.ExecuteScriptAtLatestBlock(ctx, script, arguments)
- if err != nil {
- Exitf(1, "Failed to submit executable script: %s", err)
- }
- b, err := jsoncdc.Encode(value)
- if err != nil {
- Exitf(1, "Failed to decode cadence value: %s", err)
- }
- // TODO: Consider using printField function, once we're adapted the function to print more cadence types
- // For now, just pretty print the JSON return object, at least
- var prettyJSON bytes.Buffer
- err = json.Indent(&prettyJSON, b, Indent, Indent)
- if err != nil {
- Exitf(1, "Failed to print cadence value: %s", err)
- }
- PrintIndent(1)
- fmt.Println(prettyJSON.String())
-}
diff --git a/flow/scripts/execute/execute.go b/flow/scripts/execute/execute.go
deleted file mode 100644
index 7d0b5fd46..000000000
--- a/flow/scripts/execute/execute.go
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 execute
-
-import (
- "github.com/onflow/cadence"
- "io/ioutil"
- "log"
-
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Args string `default:"" flag:"args" info:"arguments in JSON-Cadence format"`
- Code string `flag:"code,c" info:"path to Cadence file"`
- Host string `flag:"host" info:"Flow Access API host address"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "execute",
- Short: "Execute a script",
- Example: `flow scripts execute --code=script.cdc --args="[{\"type\": \"String\", \"value\": \"Hello, Cadence\"}]"`,
- Run: func(cmd *cobra.Command, args []string) {
- code, err := ioutil.ReadFile(conf.Code)
- if err != nil {
- cli.Exitf(1, "Failed to read script from %s", conf.Code)
- }
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
-
- // Arguments
- var scriptArguments []cadence.Value
- if conf.Args != "" {
- scriptArguments, err = cli.ParseArguments(conf.Args)
- if err != nil {
- cli.Exitf(1, "Invalid arguments passed: %s", conf.Args)
- }
- }
-
- cli.ExecuteScript(projectConf.HostWithOverride(conf.Host), code, scriptArguments...)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/send.go b/flow/send.go
deleted file mode 100644
index 3a86601c8..000000000
--- a/flow/send.go
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 (
- "context"
- "fmt"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
- "google.golang.org/grpc"
-)
-
-func SendTransaction(host string, signerAccount *Account, tx *flow.Transaction, withResults bool) {
- ctx := context.Background()
-
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
- }
-
- signerAddress := signerAccount.Address
-
- fmt.Printf("Getting information for account with address 0x%s ...\n", signerAddress.Hex())
-
- account, err := flowClient.GetAccount(ctx, signerAddress)
- if err != nil {
- Exitf(1, "Failed to get account with address %s: 0x%s", signerAddress.Hex(), err)
- }
-
- // Default 0, i.e. first key
- accountKey := account.Keys[signerAccount.KeyIndex]
-
- sealed, err := flowClient.GetLatestBlockHeader(ctx, true)
- if err != nil {
- Exitf(1, "Failed to get latest sealed block: %s", err)
- }
-
- tx.SetReferenceBlockID(sealed.ID).
- SetProposalKey(signerAddress, accountKey.Index, accountKey.SequenceNumber).
- SetPayer(signerAddress)
-
- err = tx.SignEnvelope(signerAddress, accountKey.Index, signerAccount.Signer)
- if err != nil {
- Exitf(1, "Failed to sign transaction: %s", err)
- }
-
- fmt.Printf("Submitting transaction with ID %s ...\n", tx.ID())
-
- err = flowClient.SendTransaction(context.Background(), *tx)
- if err == nil {
- fmt.Printf("Successfully submitted transaction with ID %s\n", tx.ID())
- } else {
- Exitf(1, "Failed to submit transaction: %s", err)
- }
- if withResults {
- res, err := waitForSeal(ctx, flowClient, tx.ID())
- if err != nil {
- Exitf(1, "Failed to seal transaction: %s", err)
- }
- printTxResult(tx, res, true)
- }
-}
diff --git a/flow/transactions/send/send.go b/flow/transactions/send/send.go
deleted file mode 100644
index ec3934736..000000000
--- a/flow/transactions/send/send.go
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 send
-
-import (
- "io/ioutil"
- "log"
- "os"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Args string `default:"" flag:"args" info:"arguments in JSON-Cadence format"`
- Code string `flag:"code,c" info:"path to Cadence file"`
- Host string `flag:"host" info:"Flow Access API host address"`
- Signer string `default:"service" flag:"signer,s"`
- Results bool `default:"false" flag:"results" info:"Display the results of the transaction"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "send",
- Short: "Send a transaction",
- Example: `flow transactions send --code=tx.cdc --args="[{\"type\": \"String\", \"value\": \"Hello, Cadence\"}]"`,
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := cli.LoadConfig()
-
- signerAccount := projectConf.Accounts[conf.Signer]
- // TODO: Remove once new configuration is migrated
- if signerAccount == nil && conf.Signer == "service" {
- signerAccount = projectConf.Accounts["emulator-account"]
- }
-
- validateKeyPreReq(signerAccount)
- var (
- code []byte
- err error
- )
-
- if conf.Code != "" {
- code, err = ioutil.ReadFile(conf.Code)
- if err != nil {
- cli.Exitf(1, "Failed to read transaction script from %s", conf.Code)
- }
- }
-
- tx := flow.
- NewTransaction().
- SetScript(code).
- AddAuthorizer(signerAccount.Address)
-
- // Arguments
- if conf.Args != "" {
- transactionArguments, err := cli.ParseArguments(conf.Args)
- if err != nil {
- cli.Exitf(1, "Invalid arguments passed: %s", conf.Args)
- }
-
- for _, arg := range transactionArguments {
- err := tx.AddArgument(arg)
-
- if err != nil {
- cli.Exitf(1, "Failed to add %s argument to a transaction ", conf.Code)
- }
- }
- }
-
- cli.SendTransaction(projectConf.HostWithOverride(conf.Host), signerAccount, tx, conf.Results)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func validateKeyPreReq(account *cli.Account) {
- if account.KeyType == cli.KeyTypeHex {
- // Always Valid
- return
- } else if account.KeyType == cli.KeyTypeKMS {
- // Check GOOGLE_APPLICATION_CREDENTIALS
- googleAppCreds := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
- if len(googleAppCreds) == 0 {
- if len(account.KeyContext["projectId"]) == 0 {
- cli.Exitf(1, "Could not get GOOGLE_APPLICATION_CREDENTIALS, no google service account json provided but private key type is KMS", account.Address)
- }
- cli.GcloudApplicationSignin(account.KeyContext["projectId"])
- }
- return
- }
- cli.Exitf(1, "Failed to validate %s key for %s", account.KeyType, account.Address)
-
-}
diff --git a/flow/transactions/status/status.go b/flow/transactions/status/status.go
deleted file mode 100644
index 166778d0c..000000000
--- a/flow/transactions/status/status.go
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 status
-
-import (
- "log"
-
- "github.com/psiemens/sconfig"
- "github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
-)
-
-type Config struct {
- Host string `flag:"host" info:"Flow Access API host address"`
- Sealed bool `default:"true" flag:"sealed" info:"Wait for a sealed result"`
- Code bool `default:"false" flag:"code" info:"Display transaction code"`
-}
-
-var conf Config
-
-var Cmd = &cobra.Command{
- Use: "status ",
- Short: "Get the transaction status",
- Args: cobra.ExactArgs(1),
- Run: func(cmd *cobra.Command, args []string) {
- projectConf := new(cli.Config)
- if conf.Host == "" {
- projectConf = cli.LoadConfig()
- }
- cli.GetTransactionResult(projectConf.HostWithOverride(conf.Host), args[0], conf.Sealed, conf.Code)
- },
-}
-
-func init() {
- initConfig()
-}
-
-func initConfig() {
- err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
- BindFlags(Cmd.PersistentFlags()).
- Parse()
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/flow/transactions/transactions.go b/flow/transactions/transactions.go
deleted file mode 100644
index c0ff5c95f..000000000
--- a/flow/transactions/transactions.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Flow CLI
- *
- * Copyright 2019-2021 Dapper Labs, Inc.
- *
- * 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 transactions
-
-import (
- "github.com/spf13/cobra"
-
- "github.com/onflow/flow-cli/flow/transactions/send"
- "github.com/onflow/flow-cli/flow/transactions/status"
-)
-
-var Cmd = &cobra.Command{
- Use: "transactions",
- Short: "Utilities to send transactions",
- TraverseChildren: true,
-}
-
-func init() {
- Cmd.AddCommand(send.Cmd)
- Cmd.AddCommand(status.Cmd)
-}
diff --git a/go.mod b/go.mod
index 6e574cbb6..c59d773ec 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
github.com/onflow/cadence/languageserver v0.14.4
github.com/onflow/flow-core-contracts/lib/go/templates v0.6.0
github.com/onflow/flow-emulator v0.17.0
+ github.com/onflow/flow-go v0.15.3
github.com/onflow/flow-go-sdk v0.17.0
github.com/psiemens/sconfig v0.0.0-20190623041652-6e01eb1354fc
github.com/spf13/afero v1.1.2
diff --git a/internal/accounts/accounts.go b/internal/accounts/accounts.go
new file mode 100644
index 000000000..2ff688c42
--- /dev/null
+++ b/internal/accounts/accounts.go
@@ -0,0 +1,127 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "bytes"
+ "fmt"
+ "text/tabwriter"
+
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/spf13/cobra"
+)
+
+var Cmd = &cobra.Command{
+ Use: "accounts",
+ Short: "Utilities to manage accounts",
+ TraverseChildren: true,
+}
+
+func init() {
+ AddContractCommand.AddToParent(Cmd)
+ RemoveCommand.AddToParent(Cmd)
+ UpdateCommand.AddToParent(Cmd)
+ CreateCommand.AddToParent(Cmd)
+ StakingCommand.AddToParent(Cmd)
+ GetCommand.AddToParent(Cmd)
+}
+
+// AccountResult represent result from all account commands
+type AccountResult struct {
+ *flow.Account
+ showCode bool
+}
+
+// JSON convert result to JSON
+func (r *AccountResult) JSON() interface{} {
+ result := make(map[string]interface{})
+ result["address"] = r.Address
+ result["balance"] = r.Balance
+
+ keys := make([]string, 0)
+ for _, key := range r.Keys {
+ keys = append(keys, fmt.Sprintf("%x", key.PublicKey.Encode()))
+ }
+
+ result["keys"] = keys
+
+ contracts := make([]string, 0)
+ for name := range r.Contracts {
+ contracts = append(contracts, name)
+ }
+
+ result["contracts"] = contracts
+
+ if r.showCode {
+ c := make(map[string]string)
+ for name, code := range r.Contracts {
+ c[name] = string(code)
+ }
+ result["code"] = c
+ }
+
+ return result
+}
+
+// String convert result to string
+func (r *AccountResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ fmt.Fprintf(writer, "Address\t 0x%s\n", r.Address)
+ fmt.Fprintf(writer, "Balance\t %s\n", cadence.UFix64(r.Balance))
+
+ fmt.Fprintf(writer, "Keys\t %d\n", len(r.Keys))
+
+ for i, key := range r.Keys {
+ fmt.Fprintf(writer, "\nKey %d\tPublic Key\t %x\n", i, key.PublicKey.Encode())
+ fmt.Fprintf(writer, "\tWeight\t %d\n", key.Weight)
+ fmt.Fprintf(writer, "\tSignature Algorithm\t %s\n", key.SigAlgo)
+ fmt.Fprintf(writer, "\tHash Algorithm\t %s\n", key.HashAlgo)
+ fmt.Fprintf(writer, "\tRevoked \t %t\n", key.Revoked)
+ fmt.Fprintf(writer, "\n")
+ }
+
+ fmt.Fprintf(writer, "Contracts Deployed: %d\n", len(r.Contracts))
+ for name := range r.Contracts {
+ fmt.Fprintf(writer, "Contract: '%s'\n", name)
+ }
+
+ if r.showCode {
+ for name, code := range r.Contracts {
+ fmt.Fprintf(writer, "Contracts '%s':\n", name)
+ fmt.Fprintln(writer, string(code))
+ }
+ }
+
+ writer.Flush()
+
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *AccountResult) Oneliner() string {
+ keys := make([]string, 0)
+ for _, key := range r.Keys {
+ keys = append(keys, key.PublicKey.String())
+ }
+
+ return fmt.Sprintf("Address: 0x%s, Balance: %v, Public Keys: %s", r.Address, r.Balance, keys)
+}
diff --git a/internal/accounts/contract-add.go b/internal/accounts/contract-add.go
new file mode 100644
index 000000000..19643c5f8
--- /dev/null
+++ b/internal/accounts/contract-add.go
@@ -0,0 +1,71 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsAddContract struct {
+ Signer string `default:"emulator-account" flag:"signer" info:"Account name from configuration used to sign the transaction"`
+ Results bool `default:"false" flag:"results" info:"⚠️ Deprecated: results are provided by default"`
+}
+
+var addContractFlags = flagsAddContract{}
+
+var AddContractCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "add-contract ",
+ Short: "Deploy a new contract to an account",
+ Example: `flow accounts add-contract FungibleToken ./FungibleToken.cdc`,
+ Args: cobra.ExactArgs(2),
+ },
+ Flags: &addContractFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if createFlags.Results {
+ fmt.Println("⚠️ DEPRECATION WARNING: results flag is deprecated, results are by default included in all executions")
+ }
+
+ account, err := services.Accounts.AddContract(
+ addContractFlags.Signer,
+ args[0], // name
+ args[1], // filename
+ false,
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &AccountResult{
+ Account: account,
+ showCode: false,
+ }, nil
+ },
+}
diff --git a/internal/accounts/contract-remove.go b/internal/accounts/contract-remove.go
new file mode 100644
index 000000000..e41787c44
--- /dev/null
+++ b/internal/accounts/contract-remove.go
@@ -0,0 +1,68 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsRemoveContract struct {
+ Signer string `default:"emulator-account" flag:"signer" info:"Account name from configuration used to sign the transaction"`
+ Results bool `default:"false" flag:"results" info:"⚠️ Deprecated: results are provided by default"`
+}
+
+var flagsRemove = flagsRemoveContract{}
+
+var RemoveCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "remove-contract ",
+ Short: "Remove a contract deployed to an account",
+ Example: `flow accounts remove-contract FungibleToken`,
+ Args: cobra.ExactArgs(1),
+ },
+ Flags: &flagsRemove,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if flagsRemove.Results {
+ fmt.Println("⚠️ DEPRECATION WARNING: results flag is deprecated, results are by default included in all executions")
+ }
+
+ account, err := services.Accounts.RemoveContract(
+ args[0], // name
+ flagsRemove.Signer,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &AccountResult{
+ Account: account,
+ showCode: false,
+ }, nil
+ },
+}
diff --git a/internal/accounts/contract-update.go b/internal/accounts/contract-update.go
new file mode 100644
index 000000000..a411ef952
--- /dev/null
+++ b/internal/accounts/contract-update.go
@@ -0,0 +1,70 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsUpdateContract struct {
+ Signer string `default:"emulator-account" flag:"signer" info:"Account name from configuration used to sign the transaction"`
+ Results bool `default:"false" flag:"results" info:"⚠️ Deprecated: results are provided by default"`
+}
+
+var updateFlags = flagsUpdateContract{}
+
+var UpdateCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "update-contract ",
+ Short: "Update a contract deployed to an account",
+ Example: `flow accounts update-contract FungibleToken ./FungibleToken.cdc`,
+ Args: cobra.ExactArgs(2),
+ },
+ Flags: &updateFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if updateFlags.Results {
+ fmt.Println("⚠️ DEPRECATION WARNING: results flag is deprecated, results are by default included in all executions")
+ }
+
+ account, err := services.Accounts.AddContract(
+ updateFlags.Signer,
+ args[0], // name
+ args[1], // filename
+ true,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &AccountResult{
+ Account: account,
+ showCode: false,
+ }, nil
+ },
+}
diff --git a/internal/accounts/create.go b/internal/accounts/create.go
new file mode 100644
index 000000000..54c5ae75f
--- /dev/null
+++ b/internal/accounts/create.go
@@ -0,0 +1,74 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsCreate struct {
+ Signer string `default:"emulator-account" flag:"signer" info:"Account name from configuration used to sign the transaction"`
+ Keys []string `flag:"key" info:"Public keys to attach to account"`
+ SigAlgo string `default:"ECDSA_P256" flag:"sig-algo" info:"Signature algorithm used to generate the keys"`
+ HashAlgo string `default:"SHA3_256" flag:"hash-algo" info:"Hash used for the digest"`
+ Contracts []string `flag:"contract" info:"Contract to be deployed during account creation. "`
+ Results bool `default:"false" flag:"results" info:"⚠️ Deprecated: results are provided by default"`
+}
+
+var createFlags = flagsCreate{}
+
+var CreateCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "create",
+ Short: "Create a new account on network",
+ Example: `flow accounts create --key d651f1931a2...8745`,
+ },
+ Flags: &createFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if createFlags.Results {
+ fmt.Println("⚠️ DEPRECATION WARNING: results flag is deprecated, results are by default included in all executions")
+ }
+
+ account, err := services.Accounts.Create(
+ createFlags.Signer,
+ createFlags.Keys,
+ createFlags.SigAlgo,
+ createFlags.HashAlgo,
+ createFlags.Contracts,
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &AccountResult{
+ Account: account,
+ }, nil
+ },
+}
diff --git a/internal/accounts/get.go b/internal/accounts/get.go
new file mode 100644
index 000000000..7a9309b3f
--- /dev/null
+++ b/internal/accounts/get.go
@@ -0,0 +1,64 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsGet struct {
+ Contracts bool `default:"false" flag:"contracts" info:"Display contracts deployed to the account"`
+ Code bool `default:"false" flag:"code" info:"⚠️ Deprecated: use contracts flag instead"`
+}
+
+var getFlags = flagsGet{}
+
+var GetCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "get ",
+ Short: "Gets an account by address",
+ Args: cobra.ExactArgs(1),
+ },
+ Flags: &getFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if getFlags.Code {
+ fmt.Println("⚠️ DEPRECATION WARNING: use contracts flag instead")
+ }
+
+ account, err := services.Accounts.Get(args[0]) // address
+ if err != nil {
+ return nil, err
+ }
+
+ return &AccountResult{
+ Account: account,
+ showCode: getFlags.Contracts || getFlags.Code,
+ }, nil
+ },
+}
diff --git a/internal/accounts/staking-info.go b/internal/accounts/staking-info.go
new file mode 100644
index 000000000..88e008788
--- /dev/null
+++ b/internal/accounts/staking-info.go
@@ -0,0 +1,116 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 accounts
+
+import (
+ "bytes"
+ "fmt"
+ "text/tabwriter"
+
+ "github.com/onflow/cadence"
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsStakingInfo struct{}
+
+var stakingFlags = flagsStakingInfo{}
+
+var StakingCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "staking-info ",
+ Short: "Get account staking info",
+ Args: cobra.ExactArgs(1),
+ },
+ Flags: &stakingFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ staking, delegation, err := services.Accounts.StakingInfo(args[0]) // address
+ if err != nil {
+ return nil, err
+ }
+
+ return &StakingResult{*staking, *delegation}, nil
+ },
+}
+
+// StakingResult represent result from all account commands
+type StakingResult struct {
+ staking cadence.Value
+ delegation cadence.Value
+}
+
+// JSON convert result to JSON
+func (r *StakingResult) JSON() interface{} {
+ result := make(map[string]interface{})
+ result["staking"] = flowcli.NewStakingInfoFromValue(r.staking)
+ result["delegation"] = flowcli.NewStakingInfoFromValue(r.delegation)
+
+ return result
+}
+
+// String convert result to string
+func (r *StakingResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ fmt.Fprintf(writer, "Account Staking Info:\n")
+
+ stakingInfo := flowcli.NewStakingInfoFromValue(r.staking)
+
+ fmt.Fprintf(writer, "ID: \t %v\n", stakingInfo["id"])
+ fmt.Fprintf(writer, "Initial Weight: \t %v\n", stakingInfo["initialWeight"])
+ fmt.Fprintf(writer, "Networking Address: \t %v\n", stakingInfo["networkingAddress"])
+ fmt.Fprintf(writer, "Networking Key: \t %v\n", stakingInfo["networkingKey"])
+ fmt.Fprintf(writer, "Role: \t %v\n", stakingInfo["role"])
+ fmt.Fprintf(writer, "Staking Key: \t %v\n", stakingInfo["stakingKey"])
+ fmt.Fprintf(writer, "Tokens Committed: \t %v\n", stakingInfo["tokensCommitted"])
+ fmt.Fprintf(writer, "Tokens To Unstake: \t %v\n", stakingInfo["tokensRequestedToUnstake"])
+ fmt.Fprintf(writer, "Tokens Rewarded: \t %v\n", stakingInfo["tokensRewarded"])
+ fmt.Fprintf(writer, "Tokens Staked: \t %v\n", stakingInfo["tokensStaked"])
+ fmt.Fprintf(writer, "Tokens Unstaked: \t %v\n", stakingInfo["tokensUnstaked"])
+ fmt.Fprintf(writer, "Tokens Unstaking: \t %v\n", stakingInfo["tokensUnstaking"])
+ fmt.Fprintf(writer, "Total Tokens Staked: \t %v\n", stakingInfo["totalTokensStaked"])
+
+ delegationStakingInfo := flowcli.NewStakingInfoFromValue(r.delegation)
+
+ fmt.Fprintf(writer, "\n\nAccount Delegation Info:\n")
+ fmt.Fprintf(writer, "ID: \t %v\n", delegationStakingInfo["id"])
+ fmt.Fprintf(writer, "Tokens Committed: \t %v\n", delegationStakingInfo["tokensCommitted"])
+ fmt.Fprintf(writer, "Tokens To Unstake: \t %v\n", delegationStakingInfo["tokensRequestedToUnstake"])
+ fmt.Fprintf(writer, "Tokens Rewarded: \t %v\n", delegationStakingInfo["tokensRewarded"])
+ fmt.Fprintf(writer, "Tokens Staked: \t %v\n", delegationStakingInfo["tokensStaked"])
+ fmt.Fprintf(writer, "Tokens Unstaked: \t %v\n", delegationStakingInfo["tokensUnstaked"])
+ fmt.Fprintf(writer, "Tokens Unstaking: \t %v\n", delegationStakingInfo["tokensUnstaking"])
+
+ writer.Flush()
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *StakingResult) Oneliner() string {
+ return ""
+}
diff --git a/internal/blocks/blocks.go b/internal/blocks/blocks.go
new file mode 100644
index 000000000..9e90aa58c
--- /dev/null
+++ b/internal/blocks/blocks.go
@@ -0,0 +1,118 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 blocks
+
+import (
+ "bytes"
+ "fmt"
+ "text/tabwriter"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/events"
+)
+
+var Cmd = &cobra.Command{
+ Use: "blocks",
+ Short: "Utilities to read blocks",
+ TraverseChildren: true,
+}
+
+func init() {
+ GetCommand.AddToParent(Cmd)
+}
+
+// BlockResult
+type BlockResult struct {
+ block *flow.Block
+ events []client.BlockEvents
+ collections []*flow.Collection
+ verbose bool
+}
+
+// JSON convert result to JSON
+func (r *BlockResult) JSON() interface{} {
+ result := make(map[string]interface{})
+ result["blockId"] = r.block.ID.String()
+ result["parentId"] = r.block.ParentID.String()
+ result["height"] = r.block.Height
+ result["totalSeals"] = len(r.block.Seals)
+ result["totalCollections"] = len(r.block.CollectionGuarantees)
+
+ collections := make([]interface{}, 0)
+ for i, guarantee := range r.block.CollectionGuarantees {
+ collection := make(map[string]interface{})
+ collection["id"] = guarantee.CollectionID.String()
+
+ if r.verbose {
+ txs := make([]string, 0)
+ for _, tx := range r.collections[i].TransactionIDs {
+ txs = append(txs, tx.String())
+ }
+ collection["transactions"] = txs
+ }
+
+ collections = append(collections, collection)
+ }
+
+ result["collection"] = collections
+ return result
+}
+
+// String convert result to string
+func (r *BlockResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ fmt.Fprintf(writer, "Block ID\t%s\n", r.block.ID)
+ fmt.Fprintf(writer, "Parent ID\t%s\n", r.block.ParentID)
+ fmt.Fprintf(writer, "Timestamp\t%s\n", r.block.Timestamp)
+ fmt.Fprintf(writer, "Height\t%v\n", r.block.Height)
+
+ fmt.Fprintf(writer, "Total Seals\t%v\n", len(r.block.Seals))
+
+ fmt.Fprintf(writer, "Total Collections\t%v\n", len(r.block.CollectionGuarantees))
+
+ for i, guarantee := range r.block.CollectionGuarantees {
+ fmt.Fprintf(writer, " Collection %d:\t%s\n", i, guarantee.CollectionID)
+
+ if r.verbose {
+ for x, tx := range r.collections[i].TransactionIDs {
+ fmt.Fprintf(writer, " Transaction %d: %s\n", x, tx)
+ }
+ }
+ }
+
+ if len(r.events) > 0 {
+ fmt.Fprintf(writer, "\n")
+
+ e := events.EventResult{BlockEvents: r.events}
+ fmt.Fprintf(writer, "%s", e.String())
+ }
+
+ writer.Flush()
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *BlockResult) Oneliner() string {
+ return r.block.ID.String()
+}
diff --git a/internal/blocks/get.go b/internal/blocks/get.go
new file mode 100644
index 000000000..ffe3aa869
--- /dev/null
+++ b/internal/blocks/get.go
@@ -0,0 +1,73 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 blocks
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsBlocks struct {
+ Events string `default:"" flag:"events" info:"List events of this type for the block"`
+ Verbose bool `default:"false" flag:"verbose" info:"Display transactions in block"`
+ Latest bool `default:"false" flag:"latest" info:"⚠️ No longer supported: use command argument"`
+ BlockID string `default:"" flag:"id" info:"⚠️ No longer supported: use command argument"`
+ BlockHeight uint64 `default:"0" flag:"height" info:"⚠️ No longer supported: use command argument"`
+}
+
+var blockFlags = flagsBlocks{}
+
+var GetCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "get ",
+ Short: "Get block info",
+ Args: cobra.ExactArgs(1),
+ },
+ Flags: &blockFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if blockFlags.Latest || blockFlags.BlockID != "" || blockFlags.BlockHeight != 0 {
+ return nil, fmt.Errorf("⚠️ No longer supported: use command argument.")
+ }
+
+ block, events, collections, err := services.Blocks.GetBlock(
+ args[0], // block id
+ blockFlags.Events,
+ blockFlags.Verbose,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &BlockResult{
+ block: block,
+ events: events,
+ verbose: blockFlags.Verbose,
+ collections: collections,
+ }, nil
+ },
+}
diff --git a/flow/cadence/cadence.go b/internal/cadence/cadence.go
similarity index 89%
rename from flow/cadence/cadence.go
rename to internal/cadence/cadence.go
index 11a37a97b..e0ded82f5 100644
--- a/flow/cadence/cadence.go
+++ b/internal/cadence/cadence.go
@@ -22,8 +22,8 @@ import (
"github.com/onflow/cadence/runtime/cmd/execute"
"github.com/spf13/cobra"
- "github.com/onflow/flow-cli/flow/cadence/languageserver"
- "github.com/onflow/flow-cli/flow/cadence/vscode"
+ "github.com/onflow/flow-cli/internal/cadence/languageserver"
+ "github.com/onflow/flow-cli/internal/cadence/vscode"
)
var Cmd = &cobra.Command{
diff --git a/flow/cadence/languageserver/languageserver.go b/internal/cadence/languageserver/languageserver.go
similarity index 86%
rename from flow/cadence/languageserver/languageserver.go
rename to internal/cadence/languageserver/languageserver.go
index 14eb84d7a..0a49d22ff 100644
--- a/flow/cadence/languageserver/languageserver.go
+++ b/internal/cadence/languageserver/languageserver.go
@@ -21,15 +21,15 @@ package languageserver
import (
"log"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+
"github.com/onflow/cadence/languageserver"
"github.com/psiemens/sconfig"
"github.com/spf13/cobra"
-
- cli "github.com/onflow/flow-cli/flow"
)
type Config struct {
- EnableFlowClient bool `default:"true" flag:"enableFlowClient" info:"Enable Flow client functionality"`
+ EnableFlowClient bool `default:"true" flag:"enable-flow-client" info:"Enable Flow client functionality"`
}
var conf Config
@@ -48,7 +48,7 @@ func init() {
func initConfig() {
err := sconfig.New(&conf).
- FromEnvironment(cli.EnvPrefix).
+ FromEnvironment(util.EnvPrefix).
BindFlags(Cmd.PersistentFlags()).
Parse()
if err != nil {
diff --git a/flow/cadence/vscode/.gitignore b/internal/cadence/vscode/.gitignore
similarity index 100%
rename from flow/cadence/vscode/.gitignore
rename to internal/cadence/vscode/.gitignore
diff --git a/flow/cadence/vscode/cadence_bin.go b/internal/cadence/vscode/cadence_bin.go
similarity index 100%
rename from flow/cadence/vscode/cadence_bin.go
rename to internal/cadence/vscode/cadence_bin.go
diff --git a/flow/cadence/vscode/gen.go b/internal/cadence/vscode/gen.go
similarity index 100%
rename from flow/cadence/vscode/gen.go
rename to internal/cadence/vscode/gen.go
diff --git a/flow/cadence/vscode/vscode.go b/internal/cadence/vscode/vscode.go
similarity index 91%
rename from flow/cadence/vscode/vscode.go
rename to internal/cadence/vscode/vscode.go
index a87a0c65e..905582960 100644
--- a/flow/cadence/vscode/vscode.go
+++ b/internal/cadence/vscode/vscode.go
@@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"
- cli "github.com/onflow/flow-cli/flow"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
)
const cadenceExt = "cadence.vsix"
@@ -40,7 +40,7 @@ var Cmd = &cobra.Command{
// create temporary directory
dir, err := ioutil.TempDir("", "vscode-cadence")
if err != nil {
- cli.Exit(1, err.Error())
+ util.Exit(1, err.Error())
}
// delete temporary directory
@@ -50,14 +50,14 @@ var Cmd = &cobra.Command{
err = ioutil.WriteFile(tmpCadenceExt, ext, 0644)
if err != nil {
- cli.Exit(1, err.Error())
+ util.Exit(1, err.Error())
}
// run vscode command to install extension from temporary directory
c := exec.Command("code", "--install-extension", tmpCadenceExt)
err = c.Run()
if err != nil {
- cli.Exit(1, err.Error())
+ util.Exit(1, err.Error())
}
fmt.Println("Installed the Cadence Visual Studio Code extension")
diff --git a/internal/collections/collections.go b/internal/collections/collections.go
new file mode 100644
index 000000000..f18b4ec4e
--- /dev/null
+++ b/internal/collections/collections.go
@@ -0,0 +1,76 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 collections
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/spf13/cobra"
+)
+
+var Cmd = &cobra.Command{
+ Use: "collections",
+ Short: "Utilities to read collections",
+ TraverseChildren: true,
+}
+
+func init() {
+ GetCommand.AddToParent(Cmd)
+}
+
+// CollectionResult
+type CollectionResult struct {
+ *flow.Collection
+}
+
+// JSON convert result to JSON
+func (c *CollectionResult) JSON() interface{} {
+ txIDs := make([]string, 0)
+
+ for _, tx := range c.Collection.TransactionIDs {
+ txIDs = append(txIDs, tx.String())
+ }
+
+ return txIDs
+}
+
+// String convert result to string
+func (c *CollectionResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ fmt.Fprintf(writer, "Collection ID %s:\n", c.Collection.ID())
+
+ for _, tx := range c.Collection.TransactionIDs {
+ fmt.Fprintf(writer, "%s\n", tx.String())
+ }
+
+ writer.Flush()
+
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (c *CollectionResult) Oneliner() string {
+ return strings.Join(c.JSON().([]string), ",")
+}
diff --git a/internal/collections/get.go b/internal/collections/get.go
new file mode 100644
index 000000000..1bbbb3922
--- /dev/null
+++ b/internal/collections/get.go
@@ -0,0 +1,52 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 collections
+
+import (
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsCollections struct{}
+
+var collectionFlags = flagsCollections{}
+
+var GetCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "get ",
+ Short: "Get collection info",
+ Args: cobra.ExactArgs(1),
+ },
+ Flags: &collectionFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ collection, err := services.Collections.Get(args[0]) // collection id
+ if err != nil {
+ return nil, err
+ }
+
+ return &CollectionResult{collection}, nil
+ },
+}
diff --git a/internal/command/command.go b/internal/command/command.go
new file mode 100644
index 000000000..2cf4cf64f
--- /dev/null
+++ b/internal/command/command.go
@@ -0,0 +1,352 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 command
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/onflow/flow-go-sdk/client"
+ "github.com/psiemens/sconfig"
+ "github.com/spf13/afero"
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+type RunCommand func(
+ *cobra.Command,
+ []string,
+ GlobalFlags,
+ *services.Services,
+) (Result, error)
+
+type Command struct {
+ Cmd *cobra.Command
+ Flags interface{}
+ Run RunCommand
+}
+
+type GlobalFlags struct {
+ Filter string
+ Format string
+ Save string
+ Host string
+ Log string
+ Network string
+ ConfigPath []string
+}
+
+const (
+ formatText = "text"
+ formatInline = "inline"
+ formatJSON = "json"
+)
+
+const (
+ logLevelDebug = "debug"
+ logLevelInfo = "info"
+ logLevelError = "error"
+ logLevelNone = "none"
+)
+
+var flags = GlobalFlags{
+ Filter: "",
+ Format: formatText,
+ Save: "",
+ Host: "",
+ Log: logLevelInfo,
+ Network: "",
+ ConfigPath: project.DefaultConfigPaths,
+}
+
+// InitFlags init all the global persistent flags
+func InitFlags(cmd *cobra.Command) {
+ cmd.PersistentFlags().StringVarP(
+ &flags.Filter,
+ "filter",
+ "x",
+ flags.Filter,
+ "Filter result values by property name",
+ )
+
+ cmd.PersistentFlags().StringVarP(
+ &flags.Host,
+ "host",
+ "",
+ flags.Host,
+ "Flow Access API host address",
+ )
+
+ cmd.PersistentFlags().StringVarP(
+ &flags.Format,
+ "output",
+ "o",
+ flags.Format,
+ "Output format, options: \"text\", \"json\", \"inline\"",
+ )
+
+ cmd.PersistentFlags().StringVarP(
+ &flags.Save,
+ "save",
+ "s",
+ flags.Save,
+ "Save result to a filename",
+ )
+
+ cmd.PersistentFlags().StringVarP(
+ &flags.Log,
+ "log",
+ "l",
+ flags.Log,
+ "Log level, options: \"debug\", \"info\", \"error\", \"none\"",
+ )
+
+ cmd.PersistentFlags().StringSliceVarP(
+ &flags.ConfigPath,
+ "config-path",
+ "f",
+ flags.ConfigPath,
+ "Path to flow configuration file",
+ )
+
+ cmd.PersistentFlags().StringVarP(
+ &flags.Network,
+ "network",
+ "n",
+ flags.Network,
+ "Network from configuration file",
+ )
+}
+
+// AddToParent add new command to main parent cmd
+// and initializes all necessary things as well as take care of errors and output
+// here we can do all boilerplate code that is else copied in each command and make sure
+// we have one place to handle all errors and ensure commands have consistent results
+func (c Command) AddToParent(parent *cobra.Command) {
+ c.Cmd.Run = func(cmd *cobra.Command, args []string) {
+ // initialize project but ignore error since config can be missing
+ proj, err := project.Load(flags.ConfigPath)
+ // here we ignore if config does not exist as some commands don't require it
+ if !errors.Is(err, config.ErrDoesNotExist) {
+ handleError("Config Error", err)
+ }
+
+ host, err := resolveHost(proj, flags.Host, flags.Network)
+ handleError("Host Error", err)
+
+ clientGateway, err := createGateway(host)
+ handleError("Gateway Error", err)
+
+ logger := createLogger(flags.Log, flags.Format)
+
+ service := services.NewServices(clientGateway, proj, logger)
+
+ // run command
+ result, err := c.Run(cmd, args, flags, service)
+ handleError("Command Error", err)
+
+ // format output result
+ formattedResult, err := formatResult(result, flags.Filter, flags.Format)
+ handleError("Result", err)
+
+ // output result
+ err = outputResult(formattedResult, flags.Save)
+ handleError("Output Error", err)
+ }
+
+ bindFlags(c)
+ parent.AddCommand(c.Cmd)
+}
+
+// createGateway creates a gateway to be used, defaults to grpc but can support others
+func createGateway(host string) (gateway.Gateway, error) {
+ // TODO implement emulator gateway and check emulator flag here
+
+ // create default grpc client
+ return gateway.NewGrpcGateway(host)
+}
+
+// resolveHost from the flags provided
+func resolveHost(proj *project.Project, hostFlag string, networkFlag string) (string, error) {
+ host := hostFlag
+ if networkFlag != "" && proj != nil {
+ check := proj.NetworkByName(networkFlag)
+ if check == nil {
+ return "", fmt.Errorf("provided network with name %s doesn't exists in condiguration", networkFlag)
+ }
+
+ host = proj.NetworkByName(networkFlag).Host
+ } else if host == "" {
+ host = config.DefaultEmulatorNetwork().Host
+ }
+
+ return host, nil
+}
+
+// create logger utility
+func createLogger(logFlag string, formatFlag string) output.Logger {
+
+ // disable logging if we user want a specific format like JSON
+ // (more common they will not want also to have logs)
+ if formatFlag != formatText {
+ logFlag = logLevelNone
+ }
+
+ var logLevel int
+
+ switch logFlag {
+ case logLevelDebug:
+ logLevel = output.DebugLog
+ case logLevelError:
+ logLevel = output.ErrorLog
+ case logLevelNone:
+ logLevel = output.NoneLog
+ default:
+ logLevel = output.InfoLog
+ }
+
+ return output.NewStdoutLogger(logLevel)
+}
+
+// formatResult formats a result for printing.
+func formatResult(result Result, filterFlag string, formatFlag string) (string, error) {
+ if result == nil {
+ return "", fmt.Errorf("missing result")
+ }
+
+ if filterFlag != "" {
+ value, err := filterResultValue(result, filterFlag)
+ if err != nil {
+ return "", err
+ }
+
+ return fmt.Sprintf("%v", value), nil
+ }
+
+ switch formatFlag {
+ case formatJSON:
+ jsonRes, _ := json.Marshal(result.JSON())
+ return string(jsonRes), nil
+ case formatInline:
+ return result.Oneliner(), nil
+ default:
+ return result.String(), nil
+ }
+}
+
+// outputResult to selected media
+func outputResult(result string, saveFlag string) error {
+ if saveFlag != "" {
+ af := afero.Afero{
+ Fs: afero.NewOsFs(),
+ }
+
+ fmt.Printf("💾 result saved to: %s \n", saveFlag)
+ return af.WriteFile(saveFlag, []byte(result), 0644)
+ }
+
+ // default normal output
+ fmt.Fprintf(os.Stdout, "%s\n", result)
+ return nil
+}
+
+// filterResultValue returns a value by its name filtered from other result values
+func filterResultValue(result Result, filter string) (interface{}, error) {
+ var jsonResult map[string]interface{}
+ val, _ := json.Marshal(result.JSON())
+ err := json.Unmarshal(val, &jsonResult)
+ if err != nil {
+ return "", err
+ }
+
+ possibleFilters := make([]string, 0)
+ for key := range jsonResult {
+ possibleFilters = append(possibleFilters, key)
+ }
+
+ value := jsonResult[filter]
+
+ if value == nil {
+ return nil, fmt.Errorf("value for filter: '%s' doesn't exists, possible values to filter by: %s", filter, possibleFilters)
+ }
+
+ return value, nil
+}
+
+// handleError handle errors
+func handleError(description string, err error) {
+ if err == nil {
+ return
+ }
+
+ // TODO: refactor this to better handle errors not by string matching
+ // handle rpc error
+ switch t := err.(type) {
+ case *client.RPCError:
+ fmt.Fprintf(os.Stderr, "❌ Grpc Error: %s \n", t.GRPCStatus().Err().Error())
+ default:
+ if errors.Is(err, config.ErrOutdatedFormat) {
+ fmt.Fprintf(os.Stderr, "❌ Config Error: %s \n", err.Error())
+ fmt.Fprintf(os.Stderr, "🙏 Please reset configuration using: 'flow init --reset'. Read more about new configuration here: https://github.com/onflow/flow-cli/releases/tag/v0.17.0")
+ } else if strings.Contains(err.Error(), "transport:") {
+ fmt.Fprintf(os.Stderr, "❌ %s \n", strings.Split(err.Error(), "transport:")[1])
+ fmt.Fprintf(os.Stderr, "🙏 Make sure your emulator is running or connection address is correct.")
+ } else if strings.Contains(err.Error(), "NotFound desc =") {
+ fmt.Fprintf(os.Stderr, "❌ Not Found:%s \n", strings.Split(err.Error(), "NotFound desc =")[1])
+ } else if strings.Contains(err.Error(), "code = InvalidArgument desc = ") {
+ fmt.Fprintf(os.Stderr, "❌ Invalid argument: %s \n", strings.Split(err.Error(), "code = InvalidArgument desc = ")[1])
+ if strings.Contains(err.Error(), "is invalid for chain") {
+ fmt.Fprintf(os.Stderr, "🙏 Check you are connecting to the correct network or account address you use is correct.")
+ } else {
+ fmt.Fprintf(os.Stderr, "🙏 Check your argument and flags value, you can use --help.")
+ }
+ } else if strings.Contains(err.Error(), "invalid signature:") {
+ fmt.Fprintf(os.Stderr, "❌ Invalid signature: %s \n", strings.Split(err.Error(), "invalid signature:")[1])
+ fmt.Fprintf(os.Stderr, "🙏 Check the signer private key is provided or is in the correct format. If running emulator, make sure it's using the same configuration as this command.")
+ } else if strings.Contains(err.Error(), "signature could not be verified using public key with") {
+ fmt.Fprintf(os.Stderr, "❌ %s: %s \n", description, err)
+ fmt.Fprintf(os.Stderr, "🙏 If you are running emulator locally make sure that the emulator was started with the same config as used in this command. \nTry restarting the emulator.")
+ } else {
+ fmt.Fprintf(os.Stderr, "❌ %s: %s", description, err)
+ }
+ }
+
+ fmt.Println()
+ os.Exit(1)
+}
+
+// bindFlags bind all the flags needed
+func bindFlags(command Command) {
+ err := sconfig.New(command.Flags).
+ FromEnvironment(util.EnvPrefix).
+ BindFlags(command.Cmd.PersistentFlags()).
+ Parse()
+ if err != nil {
+ fmt.Println(err)
+ }
+}
diff --git a/flow/events/events.go b/internal/command/result.go
similarity index 69%
rename from flow/events/events.go
rename to internal/command/result.go
index 156eaf89d..994e7d462 100644
--- a/flow/events/events.go
+++ b/internal/command/result.go
@@ -16,20 +16,10 @@
* limitations under the License.
*/
-package events
+package command
-import (
- "github.com/spf13/cobra"
-
- "github.com/onflow/flow-cli/flow/events/get"
-)
-
-var Cmd = &cobra.Command{
- Use: "events",
- Short: "Utilities to get events",
- TraverseChildren: true,
-}
-
-func init() {
- Cmd.AddCommand(get.Cmd)
+type Result interface {
+ String() string
+ Oneliner() string
+ JSON() interface{}
}
diff --git a/internal/config/init.go b/internal/config/init.go
new file mode 100644
index 000000000..5571ecdf6
--- /dev/null
+++ b/internal/config/init.go
@@ -0,0 +1,100 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 config
+
+import (
+ "bytes"
+ "fmt"
+ "text/tabwriter"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+type FlagsInit struct {
+ ServicePrivateKey string `flag:"service-private-key" info:"Service account private key"`
+ ServiceKeySigAlgo string `default:"ECDSA_P256" flag:"service-sig-algo" info:"Service account key signature algorithm"`
+ ServiceKeyHashAlgo string `default:"SHA3_256" flag:"service-hash-algo" info:"Service account key hash algorithm"`
+ Reset bool `default:"false" flag:"reset" info:"Reset flow.json config file"`
+}
+
+var initFlag = FlagsInit{}
+
+var InitCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "init",
+ Short: "Initialize a new configuration",
+ },
+ Flags: &initFlag,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ proj, err := services.Project.Init(
+ initFlag.Reset,
+ initFlag.ServiceKeySigAlgo,
+ initFlag.ServiceKeyHashAlgo,
+ initFlag.ServicePrivateKey,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &InitResult{proj}, nil
+ },
+}
+
+// InitResult result structure
+type InitResult struct {
+ *project.Project
+}
+
+// JSON convert result to JSON
+func (r *InitResult) JSON() interface{} {
+ return r
+}
+
+// String convert result to string
+func (r *InitResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+ account, _ := r.Project.EmulatorServiceAccount()
+
+ fmt.Fprintf(writer, "Configuration initialized\n")
+ fmt.Fprintf(writer, "Service account: %s\n\n", util.Bold("0x"+account.Address().String()))
+ fmt.Fprintf(writer,
+ "Start emulator by running: %s \nReset configuration using: %s\n",
+ util.Bold("'flow emulator'"),
+ util.Bold("'flow init --reset'"),
+ )
+
+ writer.Flush()
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *InitResult) Oneliner() string {
+ return ""
+}
diff --git a/internal/emulator/start.go b/internal/emulator/start.go
new file mode 100644
index 000000000..86990d524
--- /dev/null
+++ b/internal/emulator/start.go
@@ -0,0 +1,100 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 emulator
+
+import (
+ "errors"
+
+ emulator "github.com/onflow/flow-emulator"
+
+ "github.com/onflow/flow-emulator/cmd/emulator/start"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+var Cmd *cobra.Command
+
+func ConfiguredServiceKey(
+ init bool,
+ sigAlgo crypto.SignatureAlgorithm,
+ hashAlgo crypto.HashAlgorithm,
+) (
+ crypto.PrivateKey,
+ crypto.SignatureAlgorithm,
+ crypto.HashAlgorithm,
+) {
+ var proj *project.Project
+ var err error
+
+ if init {
+ if sigAlgo == crypto.UnknownSignatureAlgorithm {
+ sigAlgo = emulator.DefaultServiceKeySigAlgo
+ }
+
+ if hashAlgo == crypto.UnknownHashAlgorithm {
+ hashAlgo = emulator.DefaultServiceKeyHashAlgo
+ }
+
+ proj, err = project.Init(sigAlgo, hashAlgo)
+ if err != nil {
+ util.Exitf(1, err.Error())
+ } else {
+ err = proj.Save(project.DefaultConfigPath)
+ if err != nil {
+ util.Exitf(1, err.Error())
+ }
+ }
+ } else {
+ proj, err = project.Load(project.DefaultConfigPaths)
+ if err != nil {
+ if errors.Is(err, config.ErrDoesNotExist) {
+ util.Exitf(1, "🙏 Configuration is missing, initialize it with: 'flow project init' and then rerun this command.")
+ } else {
+ util.Exitf(1, err.Error())
+ }
+ }
+ }
+
+ serviceAccount, _ := proj.EmulatorServiceAccount()
+
+ serviceKeyHex, ok := serviceAccount.DefaultKey().(*project.HexAccountKey)
+ if !ok {
+ util.Exit(1, "Only hexadecimal keys can be used as the emulator service account key.")
+ }
+
+ privateKey, err := crypto.DecodePrivateKeyHex(serviceKeyHex.SigAlgo(), serviceKeyHex.PrivateKeyHex())
+ if err != nil {
+ util.Exitf(
+ 1,
+ "Invalid private key in \"%s\" emulator configuration",
+ config.DefaultEmulatorConfigName,
+ )
+ }
+
+ return privateKey, serviceKeyHex.SigAlgo(), serviceKeyHex.HashAlgo()
+}
+
+func init() {
+ Cmd = start.Cmd(ConfiguredServiceKey)
+ Cmd.Use = "emulator"
+}
diff --git a/internal/events/events.go b/internal/events/events.go
new file mode 100644
index 000000000..30373388d
--- /dev/null
+++ b/internal/events/events.go
@@ -0,0 +1,151 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 events
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "text/tabwriter"
+
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+var Cmd = &cobra.Command{
+ Use: "events",
+ Short: "Utilities to read events",
+ TraverseChildren: true,
+}
+
+func init() {
+ GetCommand.AddToParent(Cmd)
+}
+
+// EventResult result structure
+type EventResult struct {
+ BlockEvents []client.BlockEvents
+ Events []flow.Event
+}
+
+// JSON convert result to JSON
+func (k *EventResult) JSON() interface{} {
+ result := make(map[string]map[uint64]map[string]interface{})
+ for _, blockEvent := range k.BlockEvents {
+ if len(blockEvent.Events) > 0 {
+ for _, event := range blockEvent.Events {
+ result["blockId"][blockEvent.Height]["index"] = event.EventIndex
+ result["blockId"][blockEvent.Height]["type"] = event.Type
+ result["blockId"][blockEvent.Height]["transactionId"] = event.TransactionID
+ result["blockId"][blockEvent.Height]["values"] = event.Value
+ }
+ }
+ }
+
+ return result
+}
+
+// String convert result to string
+func (k *EventResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ for _, blockEvent := range k.BlockEvents {
+ if len(blockEvent.Events) > 0 {
+ fmt.Fprintf(writer, "Events Block #%v:", blockEvent.Height)
+ eventsString(writer, blockEvent.Events)
+ fmt.Fprintf(writer, "\n")
+ }
+ }
+
+ // if we have events passed directly and not in relation to block
+ eventsString(writer, k.Events)
+
+ writer.Flush()
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (k *EventResult) Oneliner() string {
+ result := ""
+ for _, blockEvent := range k.BlockEvents {
+ if len(blockEvent.Events) > 0 {
+ result += fmt.Sprintf("Events Block #%v: [", blockEvent.Height)
+ for _, event := range blockEvent.Events {
+ result += fmt.Sprintf(
+ "Index: %v, Type: %v, TxID: %s, Value: %v",
+ event.EventIndex, event.Type, event.TransactionID, event.Value,
+ )
+ }
+ result += "] "
+ }
+ }
+
+ return result
+}
+
+func eventsString(writer io.Writer, events []flow.Event) {
+ for _, event := range events {
+ eventString(writer, event)
+ }
+}
+
+func eventString(writer io.Writer, event flow.Event) {
+ fmt.Fprintf(writer, "\n\t Index\t %v\n", event.EventIndex)
+ fmt.Fprintf(writer, "\t Type\t %s\n", event.Type)
+ fmt.Fprintf(writer, "\t Tx ID\t %s\n", event.TransactionID)
+ fmt.Fprintf(writer, "\t Values\n")
+
+ for i, field := range event.Value.EventType.Fields {
+ value := event.Value.Fields[i]
+ printField(writer, field, value)
+ }
+}
+
+func printField(writer io.Writer, field cadence.Field, value cadence.Value) {
+ v := value.ToGoValue()
+ typeInfo := "Unknown"
+
+ if field.Type != nil {
+ typeInfo = field.Type.ID()
+ } else if _, isAddress := v.([8]byte); isAddress {
+ typeInfo = "Address"
+ }
+
+ fmt.Fprintf(writer, "\t\t")
+ fmt.Fprintf(writer, " %s (%s)\t", field.Identifier, typeInfo)
+ // Try the two most obvious cases
+ if address, ok := v.([8]byte); ok {
+ fmt.Fprintf(writer, "%x", address)
+ } else if util.IsByteSlice(v) || field.Identifier == "publicKey" {
+ // make exception for public key, since it get's interpreted as []*big.Int
+ for _, b := range v.([]interface{}) {
+ fmt.Fprintf(writer, "%x", b)
+ }
+ } else if uintVal, ok := v.(uint64); typeInfo == "UFix64" && ok {
+ fmt.Fprintf(writer, "%v", cadence.UFix64(uintVal))
+ } else {
+ fmt.Fprintf(writer, "%v", v)
+ }
+ fmt.Fprintf(writer, "\n")
+}
diff --git a/internal/events/get.go b/internal/events/get.go
new file mode 100644
index 000000000..90dac40a7
--- /dev/null
+++ b/internal/events/get.go
@@ -0,0 +1,70 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 events
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsGenerate struct {
+ Verbose bool `flag:"verbose" info:"⚠️ Deprecated"`
+}
+
+var generateFlag = flagsGenerate{}
+
+var GetCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "get ",
+ Short: "Get events in a block range",
+ Args: cobra.RangeArgs(2, 3),
+ Example: "flow events get A.1654653399040a61.FlowToken.TokensDeposited 11559500 11559600",
+ },
+ Flags: &generateFlag,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if generateFlag.Verbose {
+ fmt.Println("⚠️ DEPRECATION WARNING: verbose flag is deprecated")
+ }
+
+ end := ""
+ if len(args) == 3 {
+ end = args[2] // block height range end
+ }
+
+ events, err := services.Events.Get(
+ args[0], // event name
+ args[1], // block height range start
+ end,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &EventResult{BlockEvents: events}, nil
+ },
+}
diff --git a/internal/keys/decode.go b/internal/keys/decode.go
new file mode 100644
index 000000000..b651b8f0d
--- /dev/null
+++ b/internal/keys/decode.go
@@ -0,0 +1,56 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 keys
+
+import (
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsDecode struct{}
+
+var decodeFlags = flagsDecode{}
+
+var DecodeCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "decode ",
+ Short: "Decode a public account key hex string",
+ Args: cobra.ExactArgs(1),
+ Example: "flow keys decode 4a22246...31bce1e71a7b6d11",
+ },
+ Flags: &decodeFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ accountKey, err := services.Keys.Decode(
+ args[0], // public key
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ pubKey := accountKey.PublicKey
+ return &KeyResult{publicKey: &pubKey, accountKey: accountKey}, err
+ },
+}
diff --git a/internal/keys/generate.go b/internal/keys/generate.go
new file mode 100644
index 000000000..ebb513aa5
--- /dev/null
+++ b/internal/keys/generate.go
@@ -0,0 +1,64 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 keys
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsGenerate struct {
+ Seed string `flag:"seed" info:"Deterministic seed phrase"`
+ KeySigAlgo string `default:"ECDSA_P256" flag:"sig-algo" info:"Signature algorithm"`
+ Algo string `default:"" flag:"algo" info:"⚠️ Deprecated: use sig-algo argument"`
+}
+
+var generateFlags = flagsGenerate{}
+
+var GenerateCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "generate",
+ Short: "Generate a new key-pair",
+ Example: "flow keys generate",
+ },
+ Flags: &generateFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if generateFlags.Algo != "" {
+ fmt.Println("⚠️ DEPRECATION WARNING: flag no longer supported, use '--sig-algo' instead.")
+ generateFlags.KeySigAlgo = generateFlags.Algo
+ }
+
+ privateKey, err := services.Keys.Generate(generateFlags.Seed, generateFlags.KeySigAlgo)
+ if err != nil {
+ return nil, err
+ }
+
+ pubKey := privateKey.PublicKey()
+ return &KeyResult{privateKey: privateKey, publicKey: &pubKey}, nil
+ },
+}
diff --git a/internal/keys/keys.go b/internal/keys/keys.go
new file mode 100644
index 000000000..8c271934c
--- /dev/null
+++ b/internal/keys/keys.go
@@ -0,0 +1,85 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 keys
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "text/tabwriter"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/spf13/cobra"
+)
+
+var Cmd = &cobra.Command{
+ Use: "keys",
+ Short: "Utilities to manage keys",
+ TraverseChildren: true,
+}
+
+func init() {
+ GenerateCommand.AddToParent(Cmd)
+}
+
+// KeyResult represent result from all account commands
+type KeyResult struct {
+ privateKey *crypto.PrivateKey
+ publicKey *crypto.PublicKey
+ accountKey *flow.AccountKey
+}
+
+// JSON convert result to JSON
+func (k *KeyResult) JSON() interface{} {
+ result := make(map[string]string)
+ result["private"] = hex.EncodeToString(k.privateKey.PublicKey().Encode())
+ result["public"] = hex.EncodeToString(k.privateKey.Encode())
+
+ return result
+}
+
+// String convert result to string
+func (k *KeyResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ if k.privateKey != nil {
+ fmt.Fprintf(writer, "🔴️ Store private key safely and don't share with anyone! \n")
+ fmt.Fprintf(writer, "Private Key \t %x \n", k.privateKey.Encode())
+ }
+
+ fmt.Fprintf(writer, "Public Key \t %x \n", k.publicKey.Encode())
+
+ if k.accountKey != nil {
+ fmt.Fprintf(writer, "Signature algorithm \t %s\n", k.accountKey.SigAlgo)
+ fmt.Fprintf(writer, "Hash algorithm \t %s\n", k.accountKey.HashAlgo)
+ fmt.Fprintf(writer, "Weight \t %d\n", k.accountKey.Weight)
+ fmt.Fprintf(writer, "Revoked \t %t\n", k.accountKey.Revoked)
+ }
+
+ writer.Flush()
+
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (k *KeyResult) Oneliner() string {
+ return fmt.Sprintf("Private Key: %x, Public Key: %x", k.privateKey.Encode(), k.publicKey.Encode())
+}
diff --git a/internal/project/deploy.go b/internal/project/deploy.go
new file mode 100644
index 000000000..87a8034b4
--- /dev/null
+++ b/internal/project/deploy.go
@@ -0,0 +1,80 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 project
+
+import (
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/contracts"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsDeploy struct {
+ Update bool `flag:"update" default:"false" info:"use update flag to update existing contracts"`
+}
+
+var deployFlags = flagsDeploy{}
+
+var DeployCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "deploy",
+ Short: "Deploy Cadence contracts",
+ },
+ Flags: &deployFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ c, err := services.Project.Deploy(globalFlags.Network, deployFlags.Update)
+ if err != nil {
+ return nil, err
+ }
+
+ return &DeployResult{c}, nil
+ },
+}
+
+// DeployResult result structure
+type DeployResult struct {
+ contracts []*contracts.Contract
+}
+
+// JSON convert result to JSON
+func (r *DeployResult) JSON() interface{} {
+ result := make(map[string]string)
+
+ for _, contract := range r.contracts {
+ result[contract.Name()] = contract.Target().String()
+ }
+
+ return result
+}
+
+// String convert result to string
+func (r *DeployResult) String() string {
+ return ""
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *DeployResult) Oneliner() string {
+ return ""
+}
diff --git a/flow/blocks/blocks.go b/internal/project/emulator.go
similarity index 62%
rename from flow/blocks/blocks.go
rename to internal/project/emulator.go
index 5c2ee0ce8..19fea805a 100644
--- a/flow/blocks/blocks.go
+++ b/internal/project/emulator.go
@@ -16,20 +16,23 @@
* limitations under the License.
*/
-package blocks
+package project
import (
+ "fmt"
+
+ "github.com/onflow/flow-emulator/cmd/emulator/start"
"github.com/spf13/cobra"
- "github.com/onflow/flow-cli/flow/blocks/get"
+ "github.com/onflow/flow-cli/internal/emulator"
)
-var Cmd = &cobra.Command{
- Use: "blocks",
- Short: "Utilities to read blocks",
- TraverseChildren: true,
-}
+var EmulatorCommand *cobra.Command
func init() {
- Cmd.AddCommand(get.Cmd)
+ EmulatorCommand = start.Cmd(emulator.ConfiguredServiceKey)
+ EmulatorCommand.PreRun = func(cmd *cobra.Command, args []string) {
+ fmt.Printf("⚠️ DEPRECATION WARNING: use \"flow emulator\" instead\n\n")
+ }
+ EmulatorCommand.Use = "start-emulator"
}
diff --git a/internal/project/init.go b/internal/project/init.go
new file mode 100644
index 000000000..1cf28f441
--- /dev/null
+++ b/internal/project/init.go
@@ -0,0 +1,59 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 project
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/internal/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+var initFlag = config.FlagsInit{}
+
+var InitCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "init",
+ Short: "Initialize a new configuration",
+ },
+ Flags: &initFlag,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ fmt.Println("⚠️ DEPRECATION WARNING: use \"flow init\" instead")
+
+ proj, err := services.Project.Init(
+ initFlag.Reset,
+ initFlag.ServiceKeySigAlgo,
+ initFlag.ServiceKeyHashAlgo,
+ initFlag.ServicePrivateKey,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &config.InitResult{Project: proj}, nil
+ },
+}
diff --git a/flow/collections/collections.go b/internal/project/project.go
similarity index 79%
rename from flow/collections/collections.go
rename to internal/project/project.go
index 8c9cfbdea..1d766474f 100644
--- a/flow/collections/collections.go
+++ b/internal/project/project.go
@@ -16,20 +16,20 @@
* limitations under the License.
*/
-package collections
+package project
import (
"github.com/spf13/cobra"
-
- "github.com/onflow/flow-cli/flow/collections/get"
)
var Cmd = &cobra.Command{
- Use: "collections",
- Short: "Utilities to read collections",
+ Use: "project",
+ Short: "Manage your Cadence project",
TraverseChildren: true,
}
func init() {
- Cmd.AddCommand(get.Cmd)
+ InitCommand.AddToParent(Cmd)
+ DeployCommand.AddToParent(Cmd)
+ Cmd.AddCommand(EmulatorCommand)
}
diff --git a/internal/scripts/execute.go b/internal/scripts/execute.go
new file mode 100644
index 000000000..ecb97fb3b
--- /dev/null
+++ b/internal/scripts/execute.go
@@ -0,0 +1,82 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 scripts
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsScripts struct {
+ ArgsJSON string `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
+ Arg []string `default:"" flag:"arg" info:"argument in Type:Value format"`
+ Code string `default:"" flag:"code" info:"⚠️ Deprecated: use filename argument"`
+ Args string `default:"" flag:"args" info:"⚠️ Deprecated: use arg or args-json flag"`
+}
+
+var scriptFlags = flagsScripts{}
+
+var ExecuteCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "execute ",
+ Short: "Execute a script",
+ Example: `flow scripts execute script.cdc --arg String:"Meow" --arg String:"Woof"`,
+ Args: cobra.MaximumNArgs(1),
+ },
+ Flags: &scriptFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ filename := ""
+ if len(args) == 1 {
+ filename = args[0]
+ } else if scriptFlags.Code != "" {
+ fmt.Println("⚠️ DEPRECATION WARNING: use filename as a command argument ")
+ filename = scriptFlags.Code
+ } else {
+ return nil, fmt.Errorf("provide a valide filename command argument")
+ }
+
+ if scriptFlags.Args != "" {
+ fmt.Println("⚠️ DEPRECATION WARNING: use arg flag in Type:Value format or args-json for JSON format")
+
+ if len(scriptFlags.Arg) == 0 && scriptFlags.ArgsJSON == "" {
+ scriptFlags.ArgsJSON = scriptFlags.Args // backward compatible, args was in json format
+ }
+ }
+
+ value, err := services.Scripts.Execute(
+ filename,
+ scriptFlags.Arg,
+ scriptFlags.ArgsJSON,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &ScriptResult{value}, nil
+ },
+}
diff --git a/flow/scripts/scripts.go b/internal/scripts/scripts.go
similarity index 54%
rename from flow/scripts/scripts.go
rename to internal/scripts/scripts.go
index 83c7d9d4d..a9190d419 100644
--- a/flow/scripts/scripts.go
+++ b/internal/scripts/scripts.go
@@ -19,9 +19,12 @@
package scripts
import (
- "github.com/spf13/cobra"
+ "bytes"
+ "fmt"
+ "text/tabwriter"
- "github.com/onflow/flow-cli/flow/scripts/execute"
+ "github.com/onflow/cadence"
+ "github.com/spf13/cobra"
)
var Cmd = &cobra.Command{
@@ -31,5 +34,33 @@ var Cmd = &cobra.Command{
}
func init() {
- Cmd.AddCommand(execute.Cmd)
+ ExecuteCommand.AddToParent(Cmd)
+}
+
+type ScriptResult struct {
+ cadence.Value
+}
+
+// JSON convert result to JSON
+func (r *ScriptResult) JSON() interface{} {
+ result := make(map[string]interface{})
+ result["result"] = r.Value
+ return result
+}
+
+// String convert result to string
+func (r *ScriptResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ fmt.Fprintf(writer, "Result: %s\n", r.Value)
+
+ writer.Flush()
+
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *ScriptResult) Oneliner() string {
+ return r.Value.String()
}
diff --git a/internal/transactions/get.go b/internal/transactions/get.go
new file mode 100644
index 000000000..4563651e3
--- /dev/null
+++ b/internal/transactions/get.go
@@ -0,0 +1,69 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 transactions
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsGet struct {
+ Sealed bool `default:"true" flag:"sealed" info:"Wait for a sealed result"`
+ Code bool `default:"false" flag:"code" info:"Display transaction code"`
+}
+
+var getFlags = flagsGet{}
+
+var GetCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "get ",
+ Aliases: []string{"status"},
+ Short: "Get the transaction by ID",
+ Args: cobra.ExactArgs(1),
+ },
+ Flags: &getFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if cmd.CalledAs() == "status" {
+ fmt.Println("⚠️ DEPRECATION WARNING: use \"flow transactions get\" instead")
+ }
+
+ tx, result, err := services.Transactions.GetStatus(
+ args[0], // transaction id
+ getFlags.Sealed,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &TransactionResult{
+ result: result,
+ tx: tx,
+ code: getFlags.Code,
+ }, nil
+ },
+}
diff --git a/internal/transactions/send.go b/internal/transactions/send.go
new file mode 100644
index 000000000..71c8f8161
--- /dev/null
+++ b/internal/transactions/send.go
@@ -0,0 +1,92 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 transactions
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/command"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+type flagsSend struct {
+ ArgsJSON string `default:"" flag:"args-json" info:"arguments in JSON-Cadence format"`
+ Arg []string `default:"" flag:"arg" info:"argument in Type:Value format"`
+ Signer string `default:"emulator-account" flag:"signer" info:"Account name from configuration used to sign the transaction"`
+ Code string `default:"" flag:"code" info:"⚠️ Deprecated: use filename argument"`
+ Results bool `default:"" flag:"results" info:"⚠️ Deprecated: all transactions will provide result"`
+ Args string `default:"" flag:"args" info:"⚠️ Deprecated: use arg or args-json flag"`
+}
+
+var sendFlags = flagsSend{}
+
+var SendCommand = &command.Command{
+ Cmd: &cobra.Command{
+ Use: "send ",
+ Short: "Send a transaction",
+ Args: cobra.MaximumNArgs(1),
+ Example: `flow transactions send tx.cdc --arg String:"Hello world"`,
+ },
+ Flags: &sendFlags,
+ Run: func(
+ cmd *cobra.Command,
+ args []string,
+ globalFlags command.GlobalFlags,
+ services *services.Services,
+ ) (command.Result, error) {
+ if sendFlags.Results {
+ fmt.Println("⚠️ DEPRECATION WARNING: all transactions will provide results")
+ }
+
+ if sendFlags.Args != "" {
+ fmt.Println("⚠️ DEPRECATION WARNING: use arg flag in Type:Value format or arg-json for JSON format")
+
+ if len(sendFlags.Arg) == 0 && sendFlags.ArgsJSON == "" {
+ sendFlags.ArgsJSON = sendFlags.Args // backward compatible, args was in json format
+ }
+ }
+
+ filename := ""
+ if len(args) == 1 {
+ filename = args[0]
+ } else if sendFlags.Code != "" {
+ fmt.Println("⚠️ DEPRECATION WARNING: use filename as a command argument ")
+ filename = sendFlags.Code
+ } else {
+ return nil, fmt.Errorf("provide a valide filename command argument")
+ }
+
+ tx, result, err := services.Transactions.Send(
+ filename,
+ sendFlags.Signer,
+ sendFlags.Arg,
+ sendFlags.ArgsJSON,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return &TransactionResult{
+ result: result,
+ tx: tx,
+ }, nil
+ },
+}
diff --git a/internal/transactions/transactions.go b/internal/transactions/transactions.go
new file mode 100644
index 000000000..e2a9ae2bb
--- /dev/null
+++ b/internal/transactions/transactions.go
@@ -0,0 +1,88 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 transactions
+
+import (
+ "bytes"
+ "fmt"
+ "text/tabwriter"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/spf13/cobra"
+
+ "github.com/onflow/flow-cli/internal/events"
+)
+
+var Cmd = &cobra.Command{
+ Use: "transactions",
+ Short: "Utilities to send transactions",
+ TraverseChildren: true,
+}
+
+func init() {
+ GetCommand.AddToParent(Cmd)
+ SendCommand.AddToParent(Cmd)
+}
+
+// TransactionResult represent result from all account commands
+type TransactionResult struct {
+ result *flow.TransactionResult
+ tx *flow.Transaction
+ code bool
+}
+
+// JSON convert result to JSON
+func (r *TransactionResult) JSON() interface{} {
+ result := make(map[string]string)
+ result["id"] = r.tx.ID().String()
+ result["status"] = r.result.Status.String()
+
+ if r.result != nil {
+ result["events"] = fmt.Sprintf("%s", r.result.Events)
+ }
+
+ return result
+}
+
+// String convert result to string
+func (r *TransactionResult) String() string {
+ var b bytes.Buffer
+ writer := tabwriter.NewWriter(&b, 0, 8, 1, '\t', tabwriter.AlignRight)
+
+ fmt.Fprintf(writer, "ID\t %s\n", r.tx.ID())
+ fmt.Fprintf(writer, "Status\t %s\n", r.result.Status)
+ fmt.Fprintf(writer, "Payer\t %s\n", r.tx.Payer.Hex())
+
+ events := events.EventResult{
+ Events: r.result.Events,
+ }
+ fmt.Fprintf(writer, "Events\t %s\n", events.String())
+
+ if r.code {
+ fmt.Fprintf(writer, "Code\n\n%s\n", r.tx.Script)
+ }
+
+ writer.Flush()
+ return b.String()
+}
+
+// Oneliner show result as one liner grep friendly
+func (r *TransactionResult) Oneliner() string {
+ return fmt.Sprintf("ID: %s, Status: %s, Events: %s", r.tx.ID(), r.result.Status, r.result.Events)
+}
diff --git a/flow/version/version.go b/internal/version/version.go
similarity index 100%
rename from flow/version/version.go
rename to internal/version/version.go
diff --git a/pkg/flowcli/README.md b/pkg/flowcli/README.md
new file mode 100644
index 000000000..e867f8823
--- /dev/null
+++ b/pkg/flowcli/README.md
@@ -0,0 +1,24 @@
+## Flow CLI
+
+Flow CLI package contains all the functionality used in CLI.
+This package is meant to be used by third party (langauge server etc...).
+
+### Config
+
+Config package implements parsing and serializing configuration
+and persisting state needed for some commands.
+
+### Gateway
+
+Gateway package contains functions that interact with the Flow blockchain.
+This package offers abstraction over communicating with the Flow network and
+takes care of initializing the network client and handling errors.
+
+Function accept arguments in go-sdk types or lib types and must already be validated.
+Client is already initialized and only referenced inside here.
+
+### Services
+
+Service layer is meant to be used as an api. Service function accepts raw
+arguments, validate them, use gateways to do network interactions and lib to
+build resources needed in gateways.
diff --git a/flow/arguments.go b/pkg/flowcli/arguments.go
similarity index 56%
rename from flow/arguments.go
rename to pkg/flowcli/arguments.go
index cb95e3965..b175a72c9 100644
--- a/flow/arguments.go
+++ b/pkg/flowcli/arguments.go
@@ -16,10 +16,13 @@
* limitations under the License.
*/
-package cli
+package flowcli
import (
"encoding/json"
+ "fmt"
+ "strings"
+
"github.com/onflow/cadence"
jsoncdc "github.com/onflow/cadence/encoding/json"
)
@@ -31,6 +34,7 @@ type CadenceArgument struct {
func (v CadenceArgument) MarshalJSON() ([]byte, error) {
return jsoncdc.Encode(v.Value)
}
+
func (v *CadenceArgument) UnmarshalJSON(b []byte) (err error) {
v.Value, err = jsoncdc.Decode(b)
if err != nil {
@@ -38,7 +42,8 @@ func (v *CadenceArgument) UnmarshalJSON(b []byte) (err error) {
}
return nil
}
-func ParseArguments(input string) ([]cadence.Value, error) {
+
+func ParseArgumentsJSON(input string) ([]cadence.Value, error) {
var args []CadenceArgument
b := []byte(input)
err := json.Unmarshal(b, &args)
@@ -53,3 +58,43 @@ func ParseArguments(input string) ([]cadence.Value, error) {
}
return cadenceArgs, nil
}
+
+func ParseArgumentsCommaSplit(input []string) ([]cadence.Value, error) {
+ args := make([]map[string]string, 0)
+
+ if len(input) == 0 {
+ return make([]cadence.Value, 0), nil
+ }
+
+ for _, in := range input {
+ argInput := strings.Split(in, ":")
+ if len(argInput) != 2 {
+ return nil, fmt.Errorf(
+ "argument not passed in correct format, correct format is: Type:Value, got %s",
+ in,
+ )
+ }
+
+ args = append(args, map[string]string{
+ "value": argInput[1],
+ "type": argInput[0],
+ })
+ }
+ jsonArgs, _ := json.Marshal(args)
+ cadenceArgs, err := ParseArgumentsJSON(string(jsonArgs))
+
+ return cadenceArgs, err
+}
+
+func ParseArguments(args []string, argsJSON string) (scriptArgs []cadence.Value, err error) {
+ if argsJSON != "" {
+ scriptArgs, err = ParseArgumentsJSON(argsJSON)
+ } else {
+ scriptArgs, err = ParseArgumentsCommaSplit(args)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ return
+}
diff --git a/flow/project/cli/config/config.go b/pkg/flowcli/config/config.go
similarity index 65%
rename from flow/project/cli/config/config.go
rename to pkg/flowcli/config/config.go
index a648fc8f5..07ed99a94 100644
--- a/flow/project/cli/config/config.go
+++ b/pkg/flowcli/config/config.go
@@ -19,9 +19,10 @@
package config
import (
+ "errors"
+
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
- "github.com/thoas/go-funk"
)
type Config struct {
@@ -38,21 +39,21 @@ type Accounts []Account
type Deployments []Deploy
type Emulators []Emulator
-// Network config sets host and chain id
+// Network defines the configuration for a Flow network.
type Network struct {
Name string
Host string
ChainID flow.ChainID
}
-// Deploy structure for contract
+// Deploy defines the configuration for a contract deployment.
type Deploy struct {
Network string // network name to deploy to
Account string // account name to which to deploy to
Contracts []string // contracts names to deploy
}
-// Contract is config for contract
+// Contract defines the configuration for a Cadence contract.
type Contract struct {
Name string
Source string
@@ -60,7 +61,7 @@ type Contract struct {
Alias string
}
-// Account is main config for each account
+// Account defines the configuration for a Flow account.
type Account struct {
Name string
Address flow.Address
@@ -68,7 +69,7 @@ type Account struct {
Keys []AccountKey
}
-// AccountKey is config for account key
+// AccountKey defines the configuration for a Flow account key.
type AccountKey struct {
Type KeyType
Index int
@@ -77,7 +78,7 @@ type AccountKey struct {
Context map[string]string
}
-// Emulator is config for emulator
+// Emulator defines the configuration for a Flow Emulator instance.
type Emulator struct {
Name string
Port int
@@ -87,11 +88,75 @@ type Emulator struct {
type KeyType string
const (
- KeyTypeHex KeyType = "hex" // Hex private key with in memory signer
- KeyTypeGoogleKMS KeyType = "google-kms" // Google KMS signer
- KeyTypeShell KeyType = "shell" // Exec out to a shell script
+ KeyTypeHex KeyType = "hex" // Hex private key with in memory signer
+ KeyTypeGoogleKMS KeyType = "google-kms" // Google KMS signer
+ KeyTypeShell KeyType = "shell" // Exec out to a shell script
+ DefaultEmulatorConfigName = "default"
+ PrivateKeyField = "privateKey"
+ KMSContextField = "resourceName"
+ DefaultEmulatorServiceAccountName = "emulator-account"
)
+// DefaultEmulatorNetwork get default emulator network
+func DefaultEmulatorNetwork() Network {
+ return Network{
+ Name: "emulator",
+ Host: "127.0.0.1:3569",
+ ChainID: flow.Emulator,
+ }
+}
+
+// DefaultTestnetNetwork get default testnet network
+func DefaultTestnetNetwork() Network {
+ return Network{
+ Name: "testnet",
+ Host: "access.devnet.nodes.onflow.org:9000",
+ ChainID: flow.Testnet,
+ }
+}
+
+// DefaultMainnetNetwork get default mainnet network
+func DefaultMainnetNetwork() Network {
+ return Network{
+ Name: "mainnet",
+ Host: "access.mainnet.nodes.onflow.org:9000",
+ ChainID: flow.Mainnet,
+ }
+}
+
+// DefaultNetworks gets all default networks
+func DefaultNetworks() Networks {
+ return Networks{
+ DefaultEmulatorNetwork(),
+ DefaultTestnetNetwork(),
+ DefaultMainnetNetwork(),
+ }
+}
+
+// DefaultEmulator gets default emulator
+func DefaultEmulator() Emulator {
+ return Emulator{
+ Name: "default",
+ ServiceAccount: DefaultEmulatorServiceAccountName,
+ Port: 3569,
+ }
+}
+
+// DefaultConfig gets default configuration
+func DefaultConfig() *Config {
+ return &Config{
+ Emulators: DefaultEmulators(),
+ Networks: DefaultNetworks(),
+ }
+}
+
+// DefaultEmulators gets all default emulators
+func DefaultEmulators() Emulators {
+ return Emulators{DefaultEmulator()}
+}
+
+var ErrOutdatedFormat = errors.New("you are using old configuration format")
+
// IsAlias checks if contract has an alias
func (c *Contract) IsAlias() bool {
return c.Alias != ""
@@ -99,9 +164,13 @@ func (c *Contract) IsAlias() bool {
// GetByNameAndNetwork get contract array for account and network
func (c *Contracts) GetByNameAndNetwork(name string, network string) Contract {
- contracts := funk.Filter([]Contract(*c), func(c Contract) bool {
- return c.Network == network && c.Name == name
- }).([]Contract)
+ contracts := make(Contracts, 0)
+
+ for _, contract := range *c {
+ if contract.Network == network && contract.Name == name {
+ contracts = append(contracts, contract)
+ }
+ }
// if we don't find contract by name and network create a new contract
// and replace only name and source with existing
@@ -121,10 +190,14 @@ func (c *Contracts) GetByNameAndNetwork(name string, network string) Contract {
// TODO: this filtering can cause error if not found, better to refactor to returning
// GetByName get contract by name
-func (c *Contracts) GetByName(name string) Contract {
- return funk.Filter([]Contract(*c), func(c Contract) bool {
- return c.Name == name
- }).([]Contract)[0]
+func (c *Contracts) GetByName(name string) *Contract {
+ for _, contract := range *c {
+ if contract.Name == name {
+ return &contract
+ }
+ }
+
+ return nil
}
// GetByNetwork returns all contracts for specific network
@@ -152,7 +225,7 @@ func (c *Contracts) AddOrUpdate(name string, contract Contract) {
*c = append(*c, contract)
}
-// GetAccountByName get account by name
+// AccountByName get account by name
func (a *Accounts) GetByName(name string) *Account {
for _, account := range *a {
if account.Name == name {
@@ -163,17 +236,6 @@ func (a *Accounts) GetByName(name string) *Account {
return nil
}
-// GetByAddress get account by address
-func (a *Accounts) GetByAddress(address string) *Account {
- for _, account := range *a {
- if account.Address.String() == address {
- return &account
- }
- }
-
- return nil
-}
-
// AddOrUpdate add new or update if already present
func (a *Accounts) AddOrUpdate(name string, account Account) {
for i, existingAccount := range *a {
@@ -247,11 +309,8 @@ func (n *Networks) AddOrUpdate(name string, network Network) {
*n = append(*n, network)
}
-// DefaultEmulatorConfigName
-const DefaultEmulatorConfigName = "default"
-
-// GetDefault gets default emulator
-func (e *Emulators) GetDefault() *Emulator {
+// Default gets default emulator
+func (e *Emulators) Default() *Emulator {
for _, emulator := range *e {
if emulator.Name == DefaultEmulatorConfigName {
return &emulator
diff --git a/flow/project/cli/config/config_test.go b/pkg/flowcli/config/config_test.go
similarity index 93%
rename from flow/project/cli/config/config_test.go
rename to pkg/flowcli/config/config_test.go
index 8bec3c51c..a3c784d46 100644
--- a/flow/project/cli/config/config_test.go
+++ b/pkg/flowcli/config/config_test.go
@@ -18,12 +18,13 @@
package config_test
import (
- "github.com/onflow/flow-cli/flow/project/cli/config"
"testing"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
func generateComplexConfig() config.Config {
@@ -154,15 +155,6 @@ func Test_GetAccountByNameComplex(t *testing.T) {
assert.Equal(t, acc.Address.String(), "f8d6e0586b0a20c1")
}
-func Test_GetAccountByAddressComplex(t *testing.T) {
- conf := generateComplexConfig()
- acc1 := conf.Accounts.GetByAddress("f8d6e0586b0a20c1")
- acc2 := conf.Accounts.GetByAddress("2c1162386b0a245f")
-
- assert.Equal(t, acc1.Name, "account-4")
- assert.Equal(t, acc2.Name, "account-2")
-}
-
func Test_GetDeploymentsByNetworkComplex(t *testing.T) {
conf := generateComplexConfig()
deployments := conf.Deployments.GetByAccountAndNetwork("account-2", "testnet")
diff --git a/flow/project/cli/config/json/account.go b/pkg/flowcli/config/json/account.go
similarity index 90%
rename from flow/project/cli/config/json/account.go
rename to pkg/flowcli/config/json/account.go
index eed4cf675..8383c13ca 100644
--- a/flow/project/cli/config/json/account.go
+++ b/pkg/flowcli/config/json/account.go
@@ -20,11 +20,12 @@ package json
import (
"encoding/json"
- "strings"
- "github.com/onflow/flow-cli/flow/project/cli/config"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
)
type jsonAccounts map[string]jsonAccount
@@ -34,22 +35,25 @@ func transformChainID(rawChainID string, rawAddress string) flow.ChainID {
if rawAddress == "service" && rawChainID == "" {
return flow.Emulator
}
+
+ if rawChainID == "" {
+ address := flow.HexToAddress(rawAddress)
+ chainID, _ := util.GetAddressNetwork(address)
+ return chainID
+ }
+
return flow.ChainID(rawChainID)
}
// transformAddress returns address based on address and chain id
-func transformAddress(rawAddress string, rawChainID string) flow.Address {
- var address flow.Address
- chainID := transformChainID(rawChainID, rawAddress)
-
- if rawAddress == "service" {
- address = flow.ServiceAddress(chainID)
- } else {
- rawAddress = strings.ReplaceAll(rawAddress, "0x", "") // remove 0x if present
- address = flow.HexToAddress(rawAddress)
+func transformAddress(address string, rawChainID string) flow.Address {
+ chainID := transformChainID(rawChainID, address)
+
+ if address == "service" {
+ return flow.ServiceAddress(chainID)
}
- return address
+ return flow.HexToAddress(address)
}
// transformToConfig transforms json structures to config structure
@@ -70,7 +74,7 @@ func (j jsonAccounts) transformToConfig() config.Accounts {
SigAlgo: crypto.ECDSA_P256,
HashAlgo: crypto.SHA3_256,
Context: map[string]string{
- "privateKey": a.Simple.Keys,
+ config.PrivateKeyField: a.Simple.Keys,
},
}},
}
@@ -113,7 +117,7 @@ func transformSimpleAccountToJSON(a config.Account) jsonAccount {
Simple: jsonAccountSimple{
Address: a.Address.String(),
Chain: a.ChainID.String(),
- Keys: a.Keys[0].Context["privateKey"],
+ Keys: a.Keys[0].Context[config.PrivateKeyField],
},
}
}
@@ -166,7 +170,6 @@ type jsonAccountAdvanced struct {
Address string `json:"address"`
Chain string `json:"chain"`
Keys []jsonAccountKey `json:"keys"`
- //TODO: define more properties
}
type jsonAccountKey struct {
diff --git a/flow/project/cli/config/json/account_test.go b/pkg/flowcli/config/json/account_test.go
similarity index 97%
rename from flow/project/cli/config/json/account_test.go
rename to pkg/flowcli/config/json/account_test.go
index c663f1e18..fa78c6557 100644
--- a/flow/project/cli/config/json/account_test.go
+++ b/pkg/flowcli/config/json/account_test.go
@@ -22,7 +22,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func Test_ConfigAccountKeysSimple(t *testing.T) {
@@ -36,7 +35,7 @@ func Test_ConfigAccountKeysSimple(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
@@ -70,7 +69,7 @@ func Test_ConfigAccountKeysAdvanced(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
account := accounts.GetByName("test")
@@ -114,7 +113,7 @@ func Test_ConfigAccountKeysAdvancedMultiple(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
account := accounts.GetByName("test")
@@ -150,7 +149,7 @@ func Test_ConfigMultipleAccountsSimple(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
@@ -208,7 +207,7 @@ func Test_ConfigMultipleAccountsAdvanced(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
@@ -255,7 +254,7 @@ func Test_ConfigMixedAccounts(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
@@ -292,7 +291,7 @@ func Test_ConfigAccountsMap(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
@@ -305,7 +304,7 @@ func Test_TransformDefaultAccountToJSON(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
@@ -321,7 +320,7 @@ func Test_TransformAccountToJSON(t *testing.T) {
var jsonAccounts jsonAccounts
err := json.Unmarshal(b, &jsonAccounts)
- require.NoError(t, err)
+ assert.NoError(t, err)
accounts := jsonAccounts.transformToConfig()
diff --git a/flow/project/cli/config/json/config.go b/pkg/flowcli/config/json/config.go
similarity index 83%
rename from flow/project/cli/config/json/config.go
rename to pkg/flowcli/config/json/config.go
index 7c3acc6e6..ad615e133 100644
--- a/flow/project/cli/config/json/config.go
+++ b/pkg/flowcli/config/json/config.go
@@ -20,10 +20,9 @@ package json
import (
"encoding/json"
- "github.com/onflow/flow-cli/flow/project/cli/config"
-)
-// TODO: implement interface for all specific config implementation
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+)
type jsonConfig struct {
Emulators jsonEmulators `json:"emulators"`
@@ -53,6 +52,26 @@ func transformConfigToJSON(config *config.Config) jsonConfig {
}
}
+type oldFormat struct {
+ Host interface{} `json:"host"`
+ Accounts interface{} `json:"accounts"`
+}
+
+func oldConfigFormat(raw []byte) bool {
+ var conf oldFormat
+
+ err := json.Unmarshal(raw, &conf)
+ if err != nil { // ignore errors in this case
+ return false
+ }
+
+ if conf.Host != nil {
+ return true
+ }
+
+ return false
+}
+
// Parsers for configuration
type Parser struct{}
@@ -75,6 +94,11 @@ func (p *Parser) Serialize(conf *config.Config) ([]byte, error) {
// Deserialize configuration to config structure
func (p *Parser) Deserialize(raw []byte) (*config.Config, error) {
+ // check if old format of config and return an error
+ if oldConfigFormat(raw) {
+ return nil, config.ErrOutdatedFormat
+ }
+
var jsonConf jsonConfig
err := json.Unmarshal(raw, &jsonConf)
diff --git a/flow/project/cli/config/json/config_test.go b/pkg/flowcli/config/json/config_test.go
similarity index 96%
rename from flow/project/cli/config/json/config_test.go
rename to pkg/flowcli/config/json/config_test.go
index be2ef8065..bab46c201 100644
--- a/flow/project/cli/config/json/config_test.go
+++ b/pkg/flowcli/config/json/config_test.go
@@ -21,7 +21,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func Test_SimpleJSONConfig(t *testing.T) {
@@ -52,7 +51,7 @@ func Test_SimpleJSONConfig(t *testing.T) {
parser := NewParser()
conf, err := parser.Deserialize(b)
- require.NoError(t, err)
+ assert.NoError(t, err)
assert.Equal(t, 1, len(conf.Accounts))
assert.Equal(t, "emulator-account", conf.Accounts[0].Name)
assert.Equal(t, "11c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", conf.Accounts[0].Keys[0].Context["privateKey"])
diff --git a/flow/project/cli/config/json/contract.go b/pkg/flowcli/config/json/contract.go
similarity index 98%
rename from flow/project/cli/config/json/contract.go
rename to pkg/flowcli/config/json/contract.go
index bccd30179..b19fc0dc0 100644
--- a/flow/project/cli/config/json/contract.go
+++ b/pkg/flowcli/config/json/contract.go
@@ -21,7 +21,7 @@ package json
import (
"encoding/json"
- "github.com/onflow/flow-cli/flow/project/cli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
// jsonContracts maping
diff --git a/flow/project/cli/config/json/contract_test.go b/pkg/flowcli/config/json/contract_test.go
similarity index 97%
rename from flow/project/cli/config/json/contract_test.go
rename to pkg/flowcli/config/json/contract_test.go
index 69c770f2d..a86b95cfc 100644
--- a/flow/project/cli/config/json/contract_test.go
+++ b/pkg/flowcli/config/json/contract_test.go
@@ -22,7 +22,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func Test_ConfigContractsSimple(t *testing.T) {
@@ -33,7 +32,7 @@ func Test_ConfigContractsSimple(t *testing.T) {
var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
- require.NoError(t, err)
+ assert.NoError(t, err)
contracts := jsonContracts.transformToConfig()
@@ -54,7 +53,7 @@ func Test_ConfigContractsComplex(t *testing.T) {
var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
- require.NoError(t, err)
+ assert.NoError(t, err)
contracts := jsonContracts.transformToConfig()
@@ -91,7 +90,7 @@ func Test_ConfigContractsAliases(t *testing.T) {
var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
- require.NoError(t, err)
+ assert.NoError(t, err)
contracts := jsonContracts.transformToConfig()
@@ -127,7 +126,7 @@ func Test_TransformContractToJSON(t *testing.T) {
var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
- require.NoError(t, err)
+ assert.NoError(t, err)
contracts := jsonContracts.transformToConfig()
@@ -157,7 +156,7 @@ func Test_TransformComplexContractToJSON(t *testing.T) {
var jsonContracts jsonContracts
err := json.Unmarshal(b, &jsonContracts)
- require.NoError(t, err)
+ assert.NoError(t, err)
contracts := jsonContracts.transformToConfig()
diff --git a/flow/project/cli/config/json/deploy.go b/pkg/flowcli/config/json/deploy.go
similarity index 94%
rename from flow/project/cli/config/json/deploy.go
rename to pkg/flowcli/config/json/deploy.go
index 31a2429e3..fcc5145be 100644
--- a/flow/project/cli/config/json/deploy.go
+++ b/pkg/flowcli/config/json/deploy.go
@@ -21,7 +21,7 @@ package json
import (
"encoding/json"
- "github.com/onflow/flow-cli/flow/project/cli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
type jsonDeployments map[string]jsonDeploy
@@ -68,7 +68,6 @@ type Simple map[string][]string
type jsonDeploy struct {
Simple
- // TODO: advanced format will include initializer arguments
}
func (j *jsonDeploy) UnmarshalJSON(b []byte) error {
diff --git a/flow/project/cli/config/json/deploy_test.go b/pkg/flowcli/config/json/deploy_test.go
similarity index 98%
rename from flow/project/cli/config/json/deploy_test.go
rename to pkg/flowcli/config/json/deploy_test.go
index 2840b0052..0550579d0 100644
--- a/flow/project/cli/config/json/deploy_test.go
+++ b/pkg/flowcli/config/json/deploy_test.go
@@ -38,7 +38,7 @@ func Test_ConfigDeploymentsSimple(t *testing.T) {
var jsonDeployments jsonDeployments
err := json.Unmarshal(b, &jsonDeployments)
- require.NoError(t, err)
+ assert.NoError(t, err)
deployments := jsonDeployments.transformToConfig()
@@ -82,7 +82,7 @@ func Test_TransformDeployToJSON(t *testing.T) {
var jsonDeployments jsonDeployments
err := json.Unmarshal(b, &jsonDeployments)
- require.NoError(t, err)
+ assert.NoError(t, err)
deployments := jsonDeployments.transformToConfig()
diff --git a/flow/project/cli/config/json/emulator.go b/pkg/flowcli/config/json/emulator.go
similarity index 96%
rename from flow/project/cli/config/json/emulator.go
rename to pkg/flowcli/config/json/emulator.go
index 4f7901a8f..458f0b397 100644
--- a/flow/project/cli/config/json/emulator.go
+++ b/pkg/flowcli/config/json/emulator.go
@@ -19,7 +19,7 @@
package json
import (
- "github.com/onflow/flow-cli/flow/project/cli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
type jsonEmulators map[string]jsonEmulator
diff --git a/flow/project/cli/config/json/emulator_test.go b/pkg/flowcli/config/json/emulator_test.go
similarity index 94%
rename from flow/project/cli/config/json/emulator_test.go
rename to pkg/flowcli/config/json/emulator_test.go
index 33686338e..52dcd5917 100644
--- a/flow/project/cli/config/json/emulator_test.go
+++ b/pkg/flowcli/config/json/emulator_test.go
@@ -22,7 +22,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func Test_ConfigEmulatorSimple(t *testing.T) {
@@ -35,7 +34,7 @@ func Test_ConfigEmulatorSimple(t *testing.T) {
var jsonEmulators jsonEmulators
err := json.Unmarshal(b, &jsonEmulators)
- require.NoError(t, err)
+ assert.NoError(t, err)
emulators := jsonEmulators.transformToConfig()
diff --git a/flow/project/cli/config/json/network.go b/pkg/flowcli/config/json/network.go
similarity index 97%
rename from flow/project/cli/config/json/network.go
rename to pkg/flowcli/config/json/network.go
index ea897f28d..73d645e87 100644
--- a/flow/project/cli/config/json/network.go
+++ b/pkg/flowcli/config/json/network.go
@@ -21,8 +21,9 @@ package json
import (
"encoding/json"
- "github.com/onflow/flow-cli/flow/project/cli/config"
"github.com/onflow/flow-go-sdk"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
type jsonNetworks map[string]jsonNetwork
diff --git a/flow/project/cli/config/json/network_test.go b/pkg/flowcli/config/json/network_test.go
similarity index 95%
rename from flow/project/cli/config/json/network_test.go
rename to pkg/flowcli/config/json/network_test.go
index 3f2cb439a..fd96f6b3a 100644
--- a/flow/project/cli/config/json/network_test.go
+++ b/pkg/flowcli/config/json/network_test.go
@@ -22,7 +22,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
func Test_ConfigNetworkSimple(t *testing.T) {
@@ -32,7 +31,7 @@ func Test_ConfigNetworkSimple(t *testing.T) {
var jsonNetworks jsonNetworks
err := json.Unmarshal(b, &jsonNetworks)
- require.NoError(t, err)
+ assert.NoError(t, err)
networks := jsonNetworks.transformToConfig()
@@ -52,7 +51,7 @@ func Test_ConfigNetworkMultiple(t *testing.T) {
var jsonNetworks jsonNetworks
err := json.Unmarshal(b, &jsonNetworks)
- require.NoError(t, err)
+ assert.NoError(t, err)
networks := jsonNetworks.transformToConfig()
@@ -68,7 +67,7 @@ func Test_TransformNetworkToJSON(t *testing.T) {
var jsonNetworks jsonNetworks
err := json.Unmarshal(b, &jsonNetworks)
- require.NoError(t, err)
+ assert.NoError(t, err)
networks := jsonNetworks.transformToConfig()
diff --git a/flow/project/cli/config/loader.go b/pkg/flowcli/config/loader.go
similarity index 91%
rename from flow/project/cli/config/loader.go
rename to pkg/flowcli/config/loader.go
index 53e766851..8ced3d89b 100644
--- a/flow/project/cli/config/loader.go
+++ b/pkg/flowcli/config/loader.go
@@ -46,7 +46,7 @@ type Parser interface {
SupportsFormat(string) bool
}
-// ConfigParsers lsit of all configuration parsers
+// ConfigParsers is a list of all configuration parsers.
type ConfigParsers []Parser
// FindForFormat finds a parser that can parse a specific format based on extension
@@ -60,14 +60,14 @@ func (c *ConfigParsers) FindForFormat(extension string) Parser {
return nil
}
-// Loader contains actions for composing and modification of configuration
+// Loader contains actions for composing and modifying configuration.
type Loader struct {
af *afero.Afero
configParsers ConfigParsers
composedFromFile map[string]string
}
-// NewLoader creates a new loader
+// NewLoader returns a new loader.
func NewLoader(filesystem afero.Fs) *Loader {
af := &afero.Afero{Fs: filesystem}
return &Loader{
@@ -76,12 +76,12 @@ func NewLoader(filesystem afero.Fs) *Loader {
}
}
-// AddConfigParser adds new parssers for configuration
+// AddConfigParser adds a new configuration parser.
func (l *Loader) AddConfigParser(format Parser) {
l.configParsers = append(l.configParsers, format)
}
-// Save save configuration to a path with correct serializer
+// Save saves a configuration to a path with correct serializer
func (l *Loader) Save(conf *Config, path string) error {
configFormat := l.configParsers.FindForFormat(
filepath.Ext(path),
@@ -100,7 +100,10 @@ func (l *Loader) Save(conf *Config, path string) error {
return nil
}
-// Load and compose multiple configurations
+// Load loads configuration from one or more file paths.
+//
+// If more than one path is specified, their contents are merged
+// together into on configuration object.
func (l *Loader) Load(paths []string) (*Config, error) {
var baseConf *Config
@@ -198,11 +201,9 @@ func (l *Loader) composeConfig(baseConf *Config, conf *Config) {
func (l *Loader) loadFile(path string) ([]byte, error) {
raw, err := l.af.ReadFile(path)
- // TODO: better handle
if err != nil {
if os.IsNotExist(err) {
- return nil, err
- //return nil, ErrDoesNotExist
+ return nil, ErrDoesNotExist
}
return nil, err
diff --git a/flow/project/cli/config/loader_test.go b/pkg/flowcli/config/loader_test.go
similarity index 92%
rename from flow/project/cli/config/loader_test.go
rename to pkg/flowcli/config/loader_test.go
index 57642ed34..1645039ac 100644
--- a/flow/project/cli/config/loader_test.go
+++ b/pkg/flowcli/config/loader_test.go
@@ -18,15 +18,14 @@
package config_test
import (
- "github.com/onflow/flow-cli/flow/project/cli/config"
- "github.com/onflow/flow-cli/flow/project/cli/config/json"
- "github.com/spf13/afero"
- "github.com/stretchr/testify/require"
"os"
-
"testing"
+ "github.com/spf13/afero"
"github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/config/json"
)
func Test_JSONSimple(t *testing.T) {
@@ -57,13 +56,13 @@ func Test_JSONSimple(t *testing.T) {
mockFS := afero.NewMemMapFs()
err := afero.WriteFile(mockFS, "test2-flow.json", b, 0644)
- require.NoError(t, err)
+ assert.NoError(t, err)
composer := config.NewLoader(mockFS)
composer.AddConfigParser(json.NewParser())
conf, loadErr := composer.Load([]string{"test2-flow.json"})
- require.NoError(t, loadErr)
+ assert.NoError(t, loadErr)
assert.Equal(t, 1, len(conf.Accounts))
assert.Equal(t, "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", conf.Accounts[0].Keys[0].Context["privateKey"])
}
@@ -91,15 +90,15 @@ func Test_ComposeJSON(t *testing.T) {
err := afero.WriteFile(mockFS, "flow.json", b, 0644)
err2 := afero.WriteFile(mockFS, "flow-testnet.json", b2, 0644)
- require.NoError(t, err)
- require.NoError(t, err2)
+ assert.NoError(t, err)
+ assert.NoError(t, err2)
composer := config.NewLoader(mockFS)
composer.AddConfigParser(json.NewParser())
conf, loadErr := composer.Load([]string{"flow.json", "flow-testnet.json"})
- require.NoError(t, loadErr)
+ assert.NoError(t, loadErr)
assert.Equal(t, 2, len(conf.Accounts))
assert.Equal(t, "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
conf.Accounts.GetByName("emulator-account").Keys[0].Context["privateKey"],
@@ -133,15 +132,15 @@ func Test_ComposeJSONOverwrite(t *testing.T) {
err := afero.WriteFile(mockFS, "flow.json", b, 0644)
err2 := afero.WriteFile(mockFS, "flow-testnet.json", b2, 0644)
- require.NoError(t, err)
- require.NoError(t, err2)
+ assert.NoError(t, err)
+ assert.NoError(t, err2)
composer := config.NewLoader(mockFS)
composer.AddConfigParser(json.NewParser())
conf, loadErr := composer.Load([]string{"flow.json", "flow-testnet.json"})
- require.NoError(t, loadErr)
+ assert.NoError(t, loadErr)
assert.Equal(t, 1, len(conf.Accounts))
assert.NotNil(t, conf.Accounts.GetByName("admin-account"))
assert.Equal(t, "3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
@@ -173,15 +172,15 @@ func Test_FromFileAccountSimple(t *testing.T) {
err := afero.WriteFile(mockFS, "flow.json", b, 0644)
err2 := afero.WriteFile(mockFS, "private.json", b2, 0644)
- require.NoError(t, err)
- require.NoError(t, err2)
+ assert.NoError(t, err)
+ assert.NoError(t, err2)
composer := config.NewLoader(mockFS)
composer.AddConfigParser(json.NewParser())
conf, loadErr := composer.Load([]string{"flow.json", "private.json"})
- require.NoError(t, loadErr)
+ assert.NoError(t, loadErr)
assert.Equal(t, 2, len(conf.Accounts))
assert.NotNil(t, conf.Accounts.GetByName("admin-account"))
assert.Equal(t, conf.Accounts.GetByName("admin-account").Address.String(), "f1d6e0586b0a20c7")
@@ -232,16 +231,16 @@ func Test_FromFileAccountComplex(t *testing.T) {
err2 := afero.WriteFile(mockFS, "private.json", b2, 0644)
err3 := afero.WriteFile(mockFS, "private.testnet.json", b3, 0644)
- require.NoError(t, err)
- require.NoError(t, err2)
- require.NoError(t, err3)
+ assert.NoError(t, err)
+ assert.NoError(t, err2)
+ assert.NoError(t, err3)
composer := config.NewLoader(mockFS)
composer.AddConfigParser(json.NewParser())
conf, loadErr := composer.Load([]string{"flow.json"})
- require.NoError(t, loadErr)
+ assert.NoError(t, loadErr)
assert.Equal(t, 4, len(conf.Accounts))
assert.NotNil(t, conf.Accounts.GetByName("service-account"))
assert.NotNil(t, conf.Accounts.GetByName("admin-account-1"))
@@ -269,13 +268,13 @@ func Test_JSONEnv(t *testing.T) {
mockFS := afero.NewMemMapFs()
err := afero.WriteFile(mockFS, "test2-flow.json", b, 0644)
- require.NoError(t, err)
+ assert.NoError(t, err)
composer := config.NewLoader(mockFS)
composer.AddConfigParser(json.NewParser())
conf, loadErr := composer.Load([]string{"test2-flow.json"})
- require.NoError(t, loadErr)
+ assert.NoError(t, loadErr)
assert.Equal(t, 1, len(conf.Accounts))
assert.Equal(t, "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", conf.Accounts[0].Keys[0].Context["privateKey"])
}
diff --git a/flow/project/cli/config/processor.go b/pkg/flowcli/config/processor.go
similarity index 90%
rename from flow/project/cli/config/processor.go
rename to pkg/flowcli/config/processor.go
index 1241391cf..164c00a70 100644
--- a/flow/project/cli/config/processor.go
+++ b/pkg/flowcli/config/processor.go
@@ -19,14 +19,15 @@
package config
import (
- "github.com/a8m/envsubst"
"regexp"
"strings"
+
+ "github.com/a8m/envsubst"
)
var (
- fileRegex *regexp.Regexp = regexp.MustCompile(`"([^"]*)"\s*:\s*{\s*"fromFile"\s*:\s*"([^"]*)"\s*},?`)
- trailingComma *regexp.Regexp = regexp.MustCompile(`\,\s*}`)
+ fileRegex = regexp.MustCompile(`"([^"]*)"\s*:\s*{\s*"fromFile"\s*:\s*"([^"]*)"\s*},?`)
+ trailingComma = regexp.MustCompile(`\,\s*}`)
)
// ProcessorRun all pre-processors
diff --git a/pkg/flowcli/config/processor_test.go b/pkg/flowcli/config/processor_test.go
new file mode 100644
index 000000000..bb97661f9
--- /dev/null
+++ b/pkg/flowcli/config/processor_test.go
@@ -0,0 +1,106 @@
+/*
+* Flow CLI
+*
+* Copyright 2019-2020 Dapper Labs, Inc.
+*
+* 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 config
+
+import (
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_PrivateKeyEnv(t *testing.T) {
+ os.Setenv("TEST", "123")
+
+ test := []byte(`{
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "$TEST",
+ "chain": "flow-emulator"
+ }
+ }
+ }`)
+
+ result, _ := ProcessorRun(test)
+
+ assert.JSONEq(t, `{
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "123",
+ "chain": "flow-emulator"
+ }
+ }
+ }`, string(result))
+}
+
+func Test_PrivateKeyEnvMultipleAccounts(t *testing.T) {
+ os.Setenv("TEST", "123")
+ os.Setenv("TEST2", "333")
+
+ test := []byte(`{
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "$TEST",
+ "chain": "flow-emulator"
+ }
+ }
+ }`)
+
+ result, _ := ProcessorRun(test)
+
+ assert.JSONEq(t, `{
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "123",
+ "chain": "flow-emulator"
+ }
+ }
+ }`, string(result))
+}
+
+func Test_PrivateConfigFileAccounts(t *testing.T) {
+ b := []byte(`{
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "11c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+ "chain": "flow-emulator"
+ },
+ "admin-account": { "fromFile": "test.json" }
+ }
+ }`)
+
+ preprocessor, accFromFile := ProcessorRun(b)
+
+ assert.Equal(t, len(accFromFile), 1)
+
+ assert.JSONEq(t, `{
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "11c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+ "chain": "flow-emulator"
+ }
+ }
+ }`, string(preprocessor))
+}
diff --git a/flow/project/contracts/contracts.go b/pkg/flowcli/contracts/contracts.go
similarity index 97%
rename from flow/project/contracts/contracts.go
rename to pkg/flowcli/contracts/contracts.go
index 588d8e3a7..4325ea583 100644
--- a/flow/project/contracts/contracts.go
+++ b/pkg/flowcli/contracts/contracts.go
@@ -210,9 +210,7 @@ func (p *Preprocessor) ResolveImports() error {
if isContract {
c.addDependency(location, importContract)
} else if isAlias {
- c.addAlias(location, flow.HexToAddress(
- strings.ReplaceAll(importAlias, "0x", ""), // REF: go-sdk should handle this
- ))
+ c.addAlias(location, flow.HexToAddress(importAlias))
} else {
return fmt.Errorf("Import from %s could not be found: %s, make sure import path is correct.", c.name, importPath)
}
@@ -229,7 +227,6 @@ func (p *Preprocessor) ContractBySource(contractSource string) *Contract {
func (p *Preprocessor) ContractDeploymentOrder() ([]*Contract, error) {
sorted, err := sortByDeploymentOrder(p.contracts)
if err != nil {
- // TODO: add dedicated error types
return nil, err
}
diff --git a/flow/project/contracts/contracts_test.go b/pkg/flowcli/contracts/contracts_test.go
similarity index 98%
rename from flow/project/contracts/contracts_test.go
rename to pkg/flowcli/contracts/contracts_test.go
index f5973b0e1..9f6ed6d15 100644
--- a/flow/project/contracts/contracts_test.go
+++ b/pkg/flowcli/contracts/contracts_test.go
@@ -28,7 +28,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/onflow/flow-cli/flow/project/contracts"
+ "github.com/onflow/flow-cli/pkg/flowcli/contracts"
)
type testContract struct {
@@ -229,7 +229,7 @@ func TestResolveImports(t *testing.T) {
contract.source,
contract.target,
)
- require.NoError(t, err)
+ assert.NoError(t, err)
}
err := p.ResolveImports()
@@ -277,7 +277,7 @@ func TestContractDeploymentOrder(t *testing.T) {
contract.source,
contract.target,
)
- require.NoError(t, err)
+ assert.NoError(t, err)
}
err := p.ResolveImports()
@@ -291,7 +291,7 @@ func TestContractDeploymentOrder(t *testing.T) {
assert.IsType(t, testCase.expectedDeploymentError, err)
return
} else {
- require.NoError(t, err)
+ assert.NoError(t, err)
}
require.Equal(
diff --git a/pkg/flowcli/events.go b/pkg/flowcli/events.go
new file mode 100644
index 000000000..7051b5006
--- /dev/null
+++ b/pkg/flowcli/events.go
@@ -0,0 +1,83 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 flowcli
+
+import (
+ "strings"
+
+ "github.com/onflow/flow-go-sdk"
+)
+
+const addressLength = 16
+
+type Event struct {
+ Type string
+ Values map[string]string
+}
+
+type Events []Event
+
+func EventsFromTransaction(tx *flow.TransactionResult) Events {
+ var events Events
+ for _, event := range tx.Events {
+ events = append(events, newEvent(event))
+ }
+
+ return events
+}
+
+func newEvent(event flow.Event) Event {
+ var names []string
+
+ for _, eventType := range event.Value.EventType.Fields {
+ names = append(names, eventType.Identifier)
+ }
+ values := map[string]string{}
+ for id, field := range event.Value.Fields {
+ name := names[id]
+ values[name] = field.String()
+ }
+
+ return Event{
+ Type: event.Type,
+ Values: values,
+ }
+}
+
+func (e *Events) GetAddress() *flow.Address {
+ addr := ""
+ for _, event := range *e {
+ if strings.Contains(event.Type, flow.EventAccountCreated) {
+ addr = event.Values["address"]
+ }
+ }
+
+ if addr == "" {
+ return nil
+ }
+
+ // add 0 to beginning of address due to them being stripped
+ if len(addr) < addressLength {
+ addr = strings.Repeat("0", addressLength-len(addr)) + addr
+ }
+
+ address := flow.HexToAddress(strings.ReplaceAll(addr, `"`, ""))
+
+ return &address
+}
diff --git a/pkg/flowcli/events_test.go b/pkg/flowcli/events_test.go
new file mode 100644
index 000000000..6ce0f726d
--- /dev/null
+++ b/pkg/flowcli/events_test.go
@@ -0,0 +1,54 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 flowcli_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestEvent(t *testing.T) {
+ flowEvent := tests.NewEvent(0,
+ "flow.AccountCreated",
+ []cadence.Field{{
+ Identifier: "address",
+ Type: cadence.AddressType{},
+ }},
+ []cadence.Value{
+ cadence.NewString("00c4fef62310c807"),
+ },
+ )
+ tx := tests.NewTransactionResult([]flow.Event{*flowEvent})
+ e := flowcli.EventsFromTransaction(tx)
+
+ fmt.Println(e.GetAddress())
+ fmt.Println(flowEvent.Value.String())
+}
+
+func TestAddress(t *testing.T) {
+ address := flow.HexToAddress("cdfef0f4f0786e9")
+ assert.Equal(t, "0cdfef0f4f0786e9", address.String())
+}
diff --git a/pkg/flowcli/gateway/emulator.go b/pkg/flowcli/gateway/emulator.go
new file mode 100644
index 000000000..57b0d5198
--- /dev/null
+++ b/pkg/flowcli/gateway/emulator.go
@@ -0,0 +1,88 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 gateway
+
+import (
+ "fmt"
+
+ "github.com/onflow/cadence"
+ emulator "github.com/onflow/flow-emulator"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+type EmulatorGateway struct {
+ emulator *emulator.Blockchain
+}
+
+func NewEmulatorGateway() *EmulatorGateway {
+ return &EmulatorGateway{
+ emulator: newEmulator(),
+ }
+}
+
+func newEmulator() *emulator.Blockchain {
+ b, err := emulator.NewBlockchain()
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+func (g *EmulatorGateway) GetAccount(address flow.Address) (*flow.Account, error) {
+ return g.emulator.GetAccount(address)
+}
+
+func (g *EmulatorGateway) SendTransaction(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
+
+func (g *EmulatorGateway) GetTransactionResult(tx *flow.Transaction, waitSeal bool) (*flow.TransactionResult, error) {
+ return g.emulator.GetTransactionResult(tx.ID())
+}
+
+func (g *EmulatorGateway) GetTransaction(id flow.Identifier) (*flow.Transaction, error) {
+ return g.emulator.GetTransaction(id)
+}
+
+func (g *EmulatorGateway) ExecuteScript(script []byte, arguments []cadence.Value) (cadence.Value, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
+
+func (g *EmulatorGateway) GetLatestBlock() (*flow.Block, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
+
+func (g *EmulatorGateway) GetEvents(string, uint64, uint64) ([]client.BlockEvents, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
+
+func (g *EmulatorGateway) GetCollection(id flow.Identifier) (*flow.Collection, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
+
+func (g *EmulatorGateway) GetBlockByID(id flow.Identifier) (*flow.Block, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
+
+func (g *EmulatorGateway) GetBlockByHeight(height uint64) (*flow.Block, error) {
+ return nil, fmt.Errorf("Not Supported Yet")
+}
diff --git a/pkg/flowcli/gateway/gateway.go b/pkg/flowcli/gateway/gateway.go
new file mode 100644
index 000000000..35baec85a
--- /dev/null
+++ b/pkg/flowcli/gateway/gateway.go
@@ -0,0 +1,40 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 gateway
+
+import (
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+type Gateway interface {
+ GetAccount(flow.Address) (*flow.Account, error)
+ SendTransaction(*flow.Transaction, *project.Account) (*flow.Transaction, error)
+ GetTransactionResult(*flow.Transaction, bool) (*flow.TransactionResult, error)
+ GetTransaction(flow.Identifier) (*flow.Transaction, error)
+ ExecuteScript([]byte, []cadence.Value) (cadence.Value, error)
+ GetLatestBlock() (*flow.Block, error)
+ GetBlockByHeight(uint64) (*flow.Block, error)
+ GetBlockByID(flow.Identifier) (*flow.Block, error)
+ GetEvents(string, uint64, uint64) ([]client.BlockEvents, error)
+ GetCollection(flow.Identifier) (*flow.Collection, error)
+}
diff --git a/pkg/flowcli/gateway/grpc.go b/pkg/flowcli/gateway/grpc.go
new file mode 100644
index 000000000..4fae9ac4b
--- /dev/null
+++ b/pkg/flowcli/gateway/grpc.go
@@ -0,0 +1,171 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 gateway
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+ "google.golang.org/grpc"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+// GrpcGateway is a gateway implementation that uses the Flow Access gRPC API.
+type GrpcGateway struct {
+ client *client.Client
+ ctx context.Context
+}
+
+// NewGrpcGateway returns a new gRPC gateway.
+func NewGrpcGateway(host string) (*GrpcGateway, error) {
+ gClient, err := client.New(host, grpc.WithInsecure())
+ ctx := context.Background()
+
+ if err != nil || gClient == nil {
+ return nil, fmt.Errorf("failed to connect to host %s", host)
+ }
+
+ return &GrpcGateway{
+ client: gClient,
+ ctx: ctx,
+ }, nil
+}
+
+// GetAccount gets an account by address from the Flow Access API.
+func (g *GrpcGateway) GetAccount(address flow.Address) (*flow.Account, error) {
+ account, err := g.client.GetAccountAtLatestBlock(g.ctx, address)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get account with address %s: %w", address, err)
+ }
+
+ return account, nil
+}
+
+// TODO: replace with txsender - much nicer implemented
+// SendTransaction sends a transaction to Flow through the Access API.
+func (g *GrpcGateway) SendTransaction(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ account, err := g.GetAccount(signer.Address())
+ if err != nil {
+ return nil, fmt.Errorf("failed to get account with address %s: %w", signer.Address(), err)
+ }
+
+ // Default 0, i.e. first key
+ accountKey := account.Keys[0]
+
+ sealed, err := g.client.GetLatestBlockHeader(g.ctx, true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get latest sealed block: %w", err)
+ }
+
+ tx.SetReferenceBlockID(sealed.ID).
+ SetProposalKey(signer.Address(), accountKey.Index, accountKey.SequenceNumber).
+ SetPayer(signer.Address())
+
+ sig, err := signer.DefaultKey().Signer(g.ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ err = tx.SignEnvelope(signer.Address(), accountKey.Index, sig)
+ if err != nil {
+ return nil, fmt.Errorf("failed to sign transaction: %w", err)
+ }
+
+ err = g.client.SendTransaction(g.ctx, *tx)
+ if err != nil {
+ return nil, fmt.Errorf("failed to submit transaction: %w", err)
+ }
+
+ return tx, nil
+}
+
+// GetTransaction gets a transaction by ID from the Flow Access API.
+func (g *GrpcGateway) GetTransaction(id flow.Identifier) (*flow.Transaction, error) {
+ return g.client.GetTransaction(g.ctx, id)
+}
+
+// GetTransactionResult gets a transaction result by ID from the Flow Access API.
+func (g *GrpcGateway) GetTransactionResult(tx *flow.Transaction, waitSeal bool) (*flow.TransactionResult, error) {
+ result, err := g.client.GetTransactionResult(g.ctx, tx.ID())
+ if err != nil {
+ return nil, err
+ }
+
+ if result.Status != flow.TransactionStatusSealed && waitSeal {
+ time.Sleep(time.Second)
+ return g.GetTransactionResult(tx, waitSeal)
+ }
+
+ return result, nil
+}
+
+// ExecuteScript execute a scripts on Flow through the Access API.
+func (g *GrpcGateway) ExecuteScript(script []byte, arguments []cadence.Value) (cadence.Value, error) {
+
+ value, err := g.client.ExecuteScriptAtLatestBlock(g.ctx, script, arguments)
+ if err != nil {
+ return nil, fmt.Errorf("failed to submit executable script: %w", err)
+ }
+
+ return value, nil
+}
+
+// GetLatestBlock gets the latest block on Flow through the Access API.
+func (g *GrpcGateway) GetLatestBlock() (*flow.Block, error) {
+ return g.client.GetLatestBlock(g.ctx, true)
+}
+
+// GetBlockByID get block by ID from the Flow Access API.
+func (g *GrpcGateway) GetBlockByID(id flow.Identifier) (*flow.Block, error) {
+ return g.client.GetBlockByID(g.ctx, id)
+}
+
+// GetBlockByHeight get block by height from the Flow Access API.
+func (g *GrpcGateway) GetBlockByHeight(height uint64) (*flow.Block, error) {
+ return g.client.GetBlockByHeight(g.ctx, height)
+}
+
+// GetEvents gets events by name and block range from the Flow Access API.
+func (g *GrpcGateway) GetEvents(
+ eventType string,
+ startHeight uint64,
+ endHeight uint64,
+) ([]client.BlockEvents, error) {
+
+ events, err := g.client.GetEventsForHeightRange(
+ g.ctx,
+ client.EventRangeQuery{
+ Type: eventType,
+ StartHeight: startHeight,
+ EndHeight: endHeight,
+ },
+ )
+
+ return events, err
+}
+
+// GetCollection gets a collection by ID from the Flow Access API.
+func (g *GrpcGateway) GetCollection(id flow.Identifier) (*flow.Collection, error) {
+ return g.client.GetCollection(g.ctx, id)
+}
diff --git a/pkg/flowcli/output/logger.go b/pkg/flowcli/output/logger.go
new file mode 100644
index 000000000..5af4c1c3a
--- /dev/null
+++ b/pkg/flowcli/output/logger.go
@@ -0,0 +1,97 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 output
+
+import (
+ "fmt"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+const (
+ NoneLog = 0
+ ErrorLog = 1
+ DebugLog = 2
+ InfoLog = 3
+)
+
+type Logger interface {
+ Debug(string)
+ Info(string)
+ Error(string)
+ StartProgress(string)
+ StopProgress(string)
+}
+
+// NewStdoutLogger returns a new stdout logger.
+func NewStdoutLogger(level int) *StdoutLogger {
+ return &StdoutLogger{
+ level: level,
+ }
+}
+
+// StdoutLogger is a stdout logging implementation.
+type StdoutLogger struct {
+ level int
+ spinner *Spinner
+}
+
+func (s *StdoutLogger) log(msg string, level int) {
+ if s.level < level {
+ return
+ }
+
+ fmt.Printf("%s\n", msg)
+}
+
+func (s *StdoutLogger) Info(msg string) {
+ s.log(msg, InfoLog)
+}
+
+func (s *StdoutLogger) Debug(msg string) {
+ s.log(msg, DebugLog)
+}
+
+func (s *StdoutLogger) Error(msg string) {
+ s.log(fmt.Sprintf("❌ %s", util.Red(msg)), ErrorLog)
+}
+
+func (s *StdoutLogger) StartProgress(msg string) {
+ if s.level == NoneLog {
+ return
+ }
+
+ if s.spinner != nil {
+ s.spinner.Stop("")
+ }
+
+ s.spinner = NewSpinner(msg, "")
+ s.spinner.Start()
+}
+
+func (s *StdoutLogger) StopProgress(msg string) {
+ if s.level == NoneLog {
+ return
+ }
+
+ if s.spinner != nil {
+ s.spinner.Stop(msg)
+ s.spinner = nil
+ }
+}
diff --git a/flow/project/cli/spinner.go b/pkg/flowcli/output/spinner.go
similarity index 99%
rename from flow/project/cli/spinner.go
rename to pkg/flowcli/output/spinner.go
index d6910eaa0..e67e0e65a 100644
--- a/flow/project/cli/spinner.go
+++ b/pkg/flowcli/output/spinner.go
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package cli
+package output
import (
"fmt"
diff --git a/pkg/flowcli/project/account.go b/pkg/flowcli/project/account.go
new file mode 100644
index 000000000..bb1ecd5c7
--- /dev/null
+++ b/pkg/flowcli/project/account.go
@@ -0,0 +1,151 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 project
+
+import (
+ "fmt"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+type Account struct {
+ name string
+ address flow.Address
+ chainID flow.ChainID
+ keys []AccountKey
+}
+
+func (a *Account) Address() flow.Address {
+ return a.address
+}
+
+func (a *Account) Name() string {
+ return a.name
+}
+
+func (a *Account) Keys() []AccountKey {
+ return a.keys
+}
+
+func (a *Account) DefaultKey() AccountKey {
+ return a.keys[0]
+}
+
+func (a *Account) SetDefaultKey(key AccountKey) {
+ a.keys[0] = key
+}
+
+func accountsFromConfig(conf *config.Config) ([]*Account, error) {
+ accounts := make([]*Account, 0, len(conf.Accounts))
+
+ for _, accountConf := range conf.Accounts {
+ account, err := AccountFromConfig(accountConf)
+ if err != nil {
+ return nil, err
+ }
+
+ accounts = append(accounts, account)
+ }
+
+ return accounts, nil
+}
+
+func AccountFromAddressAndKey(address flow.Address, privateKey crypto.PrivateKey) *Account {
+ key := NewHexAccountKeyFromPrivateKey(0, crypto.SHA3_256, privateKey)
+ chainID, _ := util.GetAddressNetwork(address)
+
+ return &Account{
+ name: "",
+ address: address,
+ chainID: chainID,
+ keys: []AccountKey{key},
+ }
+}
+
+func AccountFromConfig(accountConf config.Account) (*Account, error) {
+ accountKeys := make([]AccountKey, 0, len(accountConf.Keys))
+
+ for _, key := range accountConf.Keys {
+ accountKey, err := NewAccountKey(key)
+ if err != nil {
+ return nil, err
+ }
+
+ accountKeys = append(accountKeys, accountKey)
+ }
+
+ return &Account{
+ name: accountConf.Name,
+ address: accountConf.Address,
+ chainID: accountConf.ChainID,
+ keys: accountKeys,
+ }, nil
+}
+
+func accountsToConfig(accounts []*Account) config.Accounts {
+ accountConfs := make([]config.Account, 0)
+
+ for _, account := range accounts {
+ accountConfs = append(accountConfs, accountToConfig(account))
+ }
+
+ return accountConfs
+}
+
+func accountToConfig(account *Account) config.Account {
+ keyConfigs := make([]config.AccountKey, 0, len(account.keys))
+
+ for _, key := range account.keys {
+ keyConfigs = append(keyConfigs, key.ToConfig())
+ }
+
+ return config.Account{
+ Name: account.name,
+ Address: account.address,
+ ChainID: account.chainID,
+ Keys: keyConfigs,
+ }
+}
+
+func generateEmulatorServiceAccount(sigAlgo crypto.SignatureAlgorithm, hashAlgo crypto.HashAlgorithm) (*Account, error) {
+ seed, err := util.RandomSeed(crypto.MinSeedLength)
+ if err != nil {
+ return nil, err
+ }
+
+ privateKey, err := crypto.GeneratePrivateKey(sigAlgo, seed)
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate emulator service key: %v", err)
+ }
+
+ serviceAccountKey := NewHexAccountKeyFromPrivateKey(0, hashAlgo, privateKey)
+
+ return &Account{
+ name: config.DefaultEmulatorServiceAccountName,
+ address: flow.ServiceAddress(flow.Emulator),
+ chainID: flow.Emulator,
+ keys: []AccountKey{
+ serviceAccountKey,
+ },
+ }, nil
+}
diff --git a/flow/project/cli/keys/keys.go b/pkg/flowcli/project/keys.go
similarity index 60%
rename from flow/project/cli/keys/keys.go
rename to pkg/flowcli/project/keys.go
index 056048008..2b7d63a78 100644
--- a/flow/project/cli/keys/keys.go
+++ b/pkg/flowcli/project/keys.go
@@ -16,15 +16,18 @@
* limitations under the License.
*/
-package keys
+package project
import (
+ "context"
"encoding/hex"
"fmt"
+ "github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
+ "github.com/onflow/flow-go-sdk/crypto/cloudkms"
- "github.com/onflow/flow-cli/flow/project/cli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
type AccountKey interface {
@@ -32,7 +35,7 @@ type AccountKey interface {
Index() int
SigAlgo() crypto.SignatureAlgorithm
HashAlgo() crypto.HashAlgorithm
- Signer() crypto.Signer
+ Signer(ctx context.Context) (crypto.Signer, error)
ToConfig() config.AccountKey
}
@@ -40,6 +43,8 @@ func NewAccountKey(accountKeyConf config.AccountKey) (AccountKey, error) {
switch accountKeyConf.Type {
case config.KeyTypeHex:
return newHexAccountKey(accountKeyConf)
+ case config.KeyTypeGoogleKMS:
+ return newKmsAccountKey(accountKeyConf)
}
return nil, fmt.Errorf(`invalid key type: "%s"`, accountKeyConf.Type)
@@ -77,12 +82,57 @@ func (a *baseAccountKey) Index() int {
return a.index
}
-type HexAccountKey struct {
+type KmsAccountKey struct {
*baseAccountKey
- privateKey crypto.PrivateKey
+ kmsKey cloudkms.Key
}
-const privateKeyField = "privateKey"
+func (a *KmsAccountKey) ToConfig() config.AccountKey {
+ return config.AccountKey{
+ Type: a.keyType,
+ Index: a.index,
+ SigAlgo: a.sigAlgo,
+ HashAlgo: a.hashAlgo,
+ Context: map[string]string{
+ config.KMSContextField: a.kmsKey.ResourceID(),
+ },
+ }
+}
+
+func (a *KmsAccountKey) Signer(ctx context.Context) (crypto.Signer, error) {
+ kmsClient, err := cloudkms.NewClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ accountKMSSigner, err := kmsClient.SignerForKey(
+ ctx,
+ flow.Address{}, // TODO: this is temporary workaround as SignerForKey accepts address but never uses it so should be removed
+ a.kmsKey,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return accountKMSSigner, nil
+}
+
+func newKmsAccountKey(key config.AccountKey) (AccountKey, error) {
+ accountKMSKey, err := cloudkms.KeyFromResourceID(key.Context[config.KMSContextField])
+ if err != nil {
+ return nil, err
+ }
+
+ return &KmsAccountKey{
+ baseAccountKey: &baseAccountKey{
+ keyType: config.KeyTypeGoogleKMS,
+ index: key.Index,
+ sigAlgo: key.SigAlgo,
+ hashAlgo: key.HashAlgo,
+ },
+ kmsKey: accountKMSKey,
+ }, nil
+}
func NewHexAccountKeyFromPrivateKey(
index int,
@@ -101,9 +151,9 @@ func NewHexAccountKeyFromPrivateKey(
}
func newHexAccountKey(accountKeyConf config.AccountKey) (*HexAccountKey, error) {
- privateKeyHex, ok := accountKeyConf.Context[privateKeyField]
+ privateKeyHex, ok := accountKeyConf.Context[config.PrivateKeyField]
if !ok {
- return nil, fmt.Errorf("\"%s\" field is required", privateKeyField)
+ return nil, fmt.Errorf("\"%s\" field is required", config.PrivateKeyField)
}
privateKey, err := crypto.DecodePrivateKeyHex(accountKeyConf.SigAlgo, privateKeyHex)
@@ -117,8 +167,17 @@ func newHexAccountKey(accountKeyConf config.AccountKey) (*HexAccountKey, error)
}, nil
}
-func (a *HexAccountKey) Signer() crypto.Signer {
- return crypto.NewInMemorySigner(a.privateKey, a.HashAlgo())
+type HexAccountKey struct {
+ *baseAccountKey
+ privateKey crypto.PrivateKey
+}
+
+func (a *HexAccountKey) Signer(ctx context.Context) (crypto.Signer, error) {
+ return crypto.NewInMemorySigner(a.privateKey, a.HashAlgo()), nil
+}
+
+func (a *HexAccountKey) PrivateKey() crypto.PrivateKey {
+ return a.privateKey
}
func (a *HexAccountKey) ToConfig() config.AccountKey {
@@ -128,7 +187,7 @@ func (a *HexAccountKey) ToConfig() config.AccountKey {
SigAlgo: a.sigAlgo,
HashAlgo: a.hashAlgo,
Context: map[string]string{
- "privateKey": a.PrivateKeyHex(),
+ config.PrivateKeyField: a.PrivateKeyHex(),
},
}
}
diff --git a/pkg/flowcli/project/project.go b/pkg/flowcli/project/project.go
new file mode 100644
index 000000000..a99b89283
--- /dev/null
+++ b/pkg/flowcli/project/project.go
@@ -0,0 +1,260 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 project
+
+import (
+ "errors"
+ "fmt"
+ "path"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/spf13/afero"
+ "github.com/thoas/go-funk"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/config/json"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+var (
+ DefaultConfigPaths = []string{"flow.json"}
+ DefaultConfigPath = DefaultConfigPaths[0]
+)
+
+// Project contains the configuration for a Flow project.
+type Project struct {
+ composer *config.Loader
+ conf *config.Config
+ accounts []*Account
+}
+
+// Contract is a Cadence contract definition for a project.
+type Contract struct {
+ Name string
+ Source string
+ Target flow.Address
+}
+
+// Load loads a project configuration and returns the resulting project.
+func Load(configFilePath []string) (*Project, error) {
+ composer := config.NewLoader(afero.NewOsFs())
+
+ // here we add all available parsers (more to add yaml etc...)
+ composer.AddConfigParser(json.NewParser())
+ conf, err := composer.Load(configFilePath)
+
+ if err != nil {
+ if errors.Is(err, config.ErrDoesNotExist) {
+ return nil, err
+ }
+
+ return nil, err
+ }
+
+ proj, err := newProject(conf, composer)
+ if err != nil {
+ return nil, fmt.Errorf("invalid project configuration: %s", err)
+ }
+
+ return proj, nil
+}
+
+// Save saves the project configuration to the given path.
+func (p *Project) Save(path string) error {
+ p.conf.Accounts = accountsToConfig(p.accounts)
+ err := p.composer.Save(p.conf, path)
+
+ if err != nil {
+ return fmt.Errorf("failed to save project configuration to: %s", path)
+ }
+
+ return nil
+}
+
+// Exists checks if a project configuration exists.
+func Exists(path string) bool {
+ return config.Exists(path)
+}
+
+// Init initializes a new Flow project.
+func Init(sigAlgo crypto.SignatureAlgorithm, hashAlgo crypto.HashAlgorithm) (*Project, error) {
+ emulatorServiceAccount, err := generateEmulatorServiceAccount(sigAlgo, hashAlgo)
+ if err != nil {
+ return nil, err
+ }
+
+ composer := config.NewLoader(afero.NewOsFs())
+ composer.AddConfigParser(json.NewParser())
+
+ return &Project{
+ composer: composer,
+ conf: config.DefaultConfig(),
+ accounts: []*Account{emulatorServiceAccount},
+ }, nil
+}
+
+// newProject creates a new project from a configuration object.
+func newProject(conf *config.Config, composer *config.Loader) (*Project, error) {
+ accounts, err := accountsFromConfig(conf)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Project{
+ composer: composer,
+ conf: conf,
+ accounts: accounts,
+ }, nil
+}
+
+// CheckContractConflict returns true if the same contract is configured to deploy
+// to more than one account in the same network.
+//
+// The CLI currently does not allow the same contract to be deployed to multiple
+// accounts in the same network.
+func (p *Project) ContractConflictExists(network string) bool {
+ contracts := p.ContractsByNetwork(network)
+
+ uniq := funk.Uniq(
+ funk.Map(contracts, func(c Contract) string {
+ return c.Name
+ }).([]string),
+ ).([]string)
+
+ all := funk.Map(contracts, func(c Contract) string {
+ return c.Name
+ }).([]string)
+
+ return len(all) != len(uniq)
+}
+
+// NetworkByName returns a network by name.
+func (p *Project) NetworkByName(name string) *config.Network {
+ return p.conf.Networks.GetByName(name)
+}
+
+// EmulatorServiceAccount returns the service account for the default emulator profilee.
+func (p *Project) EmulatorServiceAccount() (*Account, error) {
+ emulator := p.conf.Emulators.Default()
+ acc := p.conf.Accounts.GetByName(emulator.ServiceAccount)
+ return AccountFromConfig(*acc)
+}
+
+// SetEmulatorServiceKey sets the default emulator service account private key.
+func (p *Project) SetEmulatorServiceKey(privateKey crypto.PrivateKey) {
+ acc := p.AccountByName(config.DefaultEmulatorServiceAccountName)
+ acc.SetDefaultKey(
+ NewHexAccountKeyFromPrivateKey(
+ acc.DefaultKey().Index(),
+ acc.DefaultKey().HashAlgo(),
+ privateKey,
+ ),
+ )
+}
+
+// ContractsByNetwork returns all contracts for a network.
+func (p *Project) ContractsByNetwork(network string) []Contract {
+ contracts := make([]Contract, 0)
+
+ // get deployments for the specified network
+ for _, deploy := range p.conf.Deployments.GetByNetwork(network) {
+ account := p.AccountByName(deploy.Account)
+
+ // go through each contract in this deployment
+ for _, contractName := range deploy.Contracts {
+ c := p.conf.Contracts.GetByNameAndNetwork(contractName, network)
+
+ contract := Contract{
+ Name: c.Name,
+ Source: path.Clean(c.Source),
+ Target: account.address,
+ }
+
+ contracts = append(contracts, contract)
+ }
+ }
+
+ return contracts
+}
+
+// AccountNamesForNetwork returns all configured account names for a network.
+func (p *Project) AccountNamesForNetwork(network string) []string {
+ names := make([]string, 0)
+
+ for _, account := range p.accounts {
+ if len(p.conf.Deployments.GetByAccountAndNetwork(account.name, network)) > 0 {
+ if !util.StringContains(names, account.name) {
+ names = append(names, account.name)
+ }
+ }
+ }
+
+ return names
+}
+
+// AddOrUpdateAccount adds or updates an account.
+func (p *Project) AddOrUpdateAccount(account *Account) {
+ for i, existingAccount := range p.accounts {
+ if existingAccount.name == account.name {
+ (*p).accounts[i] = account
+ return
+ }
+ }
+
+ p.accounts = append(p.accounts, account)
+}
+
+// AccountByAddress returns an account by address.
+func (p *Project) AccountByAddress(address string) *Account {
+ for _, account := range p.accounts {
+ if account.address.String() == flow.HexToAddress(address).String() {
+ return account
+ }
+ }
+
+ return nil
+}
+
+// AccountByName returns an account by name.
+func (p *Project) AccountByName(name string) *Account {
+ var account *Account
+
+ for _, acc := range p.accounts {
+ if acc.name == name {
+ account = acc
+ }
+ }
+
+ return account
+}
+
+// AliasesForNetwork returns all deployment aliases for a network.
+func (p *Project) AliasesForNetwork(network string) map[string]string {
+ aliases := make(map[string]string)
+
+ // get all contracts for selected network and if any has an address as target make it an alias
+ for _, contract := range p.conf.Contracts.GetByNetwork(network) {
+ if contract.IsAlias() {
+ aliases[path.Clean(contract.Source)] = contract.Alias
+ }
+ }
+
+ return aliases
+}
diff --git a/flow/project/cli/project_test.go b/pkg/flowcli/project/project_test.go
similarity index 83%
rename from flow/project/cli/project_test.go
rename to pkg/flowcli/project/project_test.go
index e3f11f2d0..a89af259c 100644
--- a/flow/project/cli/project_test.go
+++ b/pkg/flowcli/project/project_test.go
@@ -16,19 +16,20 @@
* limitations under the License.
*/
-package cli
+package project
import (
"fmt"
- "github.com/spf13/afero"
"sort"
"testing"
- "github.com/onflow/flow-cli/flow/project/cli/config"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
+ "github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/thoas/go-funk"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
)
var composer = config.NewLoader(afero.NewOsFs())
@@ -317,7 +318,7 @@ Project Tests
func Test_GetContractsByNameSimple(t *testing.T) {
p := generateSimpleProject()
- contracts := p.GetContractsByNetwork("emulator")
+ contracts := p.ContractsByNetwork("emulator")
assert.Len(t, contracts, 1)
assert.Equal(t, contracts[0].Name, "NonFungibleToken")
@@ -327,23 +328,23 @@ func Test_GetContractsByNameSimple(t *testing.T) {
func Test_EmulatorConfigSimple(t *testing.T) {
p := generateSimpleProject()
- emulatorServiceAccount := p.EmulatorServiceAccount()
+ emulatorServiceAccount, _ := p.EmulatorServiceAccount()
- assert.Equal(t, emulatorServiceAccount.Name, "emulator-account")
- assert.Equal(t, emulatorServiceAccount.Keys[0].Context["privateKey"], "dd72967fd2bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad74b47")
- assert.Equal(t, flow.ServiceAddress("flow-emulator"), emulatorServiceAccount.Address)
+ assert.Equal(t, emulatorServiceAccount.name, "emulator-account")
+ assert.Equal(t, emulatorServiceAccount.keys[0].ToConfig().Context["privateKey"], "dd72967fd2bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad74b47")
+ assert.Equal(t, flow.ServiceAddress("flow-emulator").String(), emulatorServiceAccount.Address().String())
}
func Test_AccountByAddressSimple(t *testing.T) {
p := generateSimpleProject()
- acc := p.GetAccountByAddress(flow.ServiceAddress("flow-emulator").String())
+ acc := p.AccountByAddress(flow.ServiceAddress("flow-emulator").String())
assert.Equal(t, acc.name, "emulator-account")
}
func Test_AccountByNameSimple(t *testing.T) {
p := generateSimpleProject()
- acc := p.GetAccountByName("emulator-account")
+ acc := p.AccountByName("emulator-account")
assert.Equal(t, flow.ServiceAddress("flow-emulator").String(), acc.Address().String())
assert.Equal(t, acc.DefaultKey().ToConfig().Context["privateKey"], "dd72967fd2bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad74b47")
@@ -351,7 +352,7 @@ func Test_AccountByNameSimple(t *testing.T) {
func Test_HostSimple(t *testing.T) {
p := generateSimpleProject()
- host := p.Host("emulator")
+ host := p.NetworkByName("emulator").Host
assert.Equal(t, host, "127.0.0.1.3569")
}
@@ -359,7 +360,7 @@ func Test_HostSimple(t *testing.T) {
func Test_GetContractsByNameComplex(t *testing.T) {
p := generateComplexProject()
- contracts := p.GetContractsByNetwork("emulator")
+ contracts := p.ContractsByNetwork("emulator")
assert.Equal(t, 7, len(contracts))
@@ -406,17 +407,17 @@ func Test_GetContractsByNameComplex(t *testing.T) {
func Test_EmulatorConfigComplex(t *testing.T) {
p := generateComplexProject()
- emulatorServiceAccount := p.EmulatorServiceAccount()
+ emulatorServiceAccount, _ := p.EmulatorServiceAccount()
- assert.Equal(t, emulatorServiceAccount.Name, "emulator-account")
- assert.Equal(t, emulatorServiceAccount.Keys[0].Context["privateKey"], "dd72967fd2bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad74b47")
- assert.Equal(t, emulatorServiceAccount.Address, flow.ServiceAddress("flow-emulator"))
+ assert.Equal(t, emulatorServiceAccount.name, "emulator-account")
+ assert.Equal(t, emulatorServiceAccount.keys[0].ToConfig().Context["privateKey"], "dd72967fd2bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad74b47")
+ assert.Equal(t, emulatorServiceAccount.Address().String(), flow.ServiceAddress("flow-emulator").String())
}
func Test_AccountByAddressComplex(t *testing.T) {
p := generateComplexProject()
- acc1 := p.GetAccountByAddress("f8d6e0586b0a20c1")
- acc2 := p.GetAccountByAddress("0x2c1162386b0a245f")
+ acc1 := p.AccountByAddress("f8d6e0586b0a20c1")
+ acc2 := p.AccountByAddress("0x2c1162386b0a245f")
assert.Equal(t, acc1.name, "account-4")
assert.Equal(t, acc2.name, "account-2")
@@ -424,7 +425,7 @@ func Test_AccountByAddressComplex(t *testing.T) {
func Test_AccountByNameComplex(t *testing.T) {
p := generateComplexProject()
- acc := p.GetAccountByName("account-2")
+ acc := p.AccountByName("account-2")
assert.Equal(t, acc.Address().String(), "2c1162386b0a245f")
assert.Equal(t, acc.DefaultKey().ToConfig().Context["privateKey"], "dd72967fd2bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad74b47")
@@ -432,7 +433,7 @@ func Test_AccountByNameComplex(t *testing.T) {
func Test_HostComplex(t *testing.T) {
p := generateComplexProject()
- host := p.Host("emulator")
+ host := p.NetworkByName("emulator").Host
assert.Equal(t, host, "127.0.0.1.3569")
}
@@ -450,8 +451,8 @@ func Test_ContractConflictComplex(t *testing.T) {
func Test_GetAliases(t *testing.T) {
p := generateAliasesProject()
- aliases := p.GetAliases("emulator")
- contracts := p.GetContractsByNetwork("emulator")
+ aliases := p.AliasesForNetwork("emulator")
+ contracts := p.ContractsByNetwork("emulator")
assert.Len(t, aliases, 1)
assert.Equal(t, aliases["../hungry-kitties/cadence/contracts/FungibleToken.cdc"], "ee82856bf20e2aa6")
@@ -462,11 +463,11 @@ func Test_GetAliases(t *testing.T) {
func Test_GetAliasesComplex(t *testing.T) {
p := generateAliasesComplexProject()
- aEmulator := p.GetAliases("emulator")
- cEmulator := p.GetContractsByNetwork("emulator")
+ aEmulator := p.AliasesForNetwork("emulator")
+ cEmulator := p.ContractsByNetwork("emulator")
- aTestnet := p.GetAliases("testnet")
- cTestnet := p.GetContractsByNetwork("testnet")
+ aTestnet := p.AliasesForNetwork("testnet")
+ cTestnet := p.ContractsByNetwork("testnet")
assert.Len(t, cEmulator, 1)
assert.Equal(t, cEmulator[0].Name, "NonFungibleToken")
@@ -482,3 +483,35 @@ func Test_GetAliasesComplex(t *testing.T) {
assert.Equal(t, cTestnet[0].Name, "NonFungibleToken")
assert.Equal(t, cTestnet[1].Name, "FungibleToken")
}
+
+func Test_SDKParsing(t *testing.T) {
+
+ t.Run("Address Parsing", func(t *testing.T) {
+ addr1 := flow.HexToAddress("0xf8d6e0586b0a20c7")
+ addr2 := flow.HexToAddress("f8d6e0586b0a20c7")
+
+ assert.True(t, addr1.IsValid(flow.Emulator))
+ assert.True(t, addr2.IsValid(flow.Emulator))
+ assert.Equal(t, addr1.String(), addr2.String())
+ })
+ /* TODO test this after it is implemented in sdk
+ t.Run("Tx ID Parsing", func(t *testing.T) {
+ txid := "09f24d9dcde4c4d63d2f790e42905427ba04e6b0d601a7ec790b663f7cf2d942"
+ id1 := flow.HexToID(txid)
+ id2 := flow.HexToID("0x" + txid)
+
+ assert.Equal(t, id1.String(), id2.String())
+ })
+
+ t.Run("Public Key Hex Parsing", func(t *testing.T) {
+ pubKey := "642fcceac4b0af1ea7b78c11d5ce2ed505bb41b13c9e3f57725246b75d828651d9387e0cd19c5ebb1a44d571ce58cc3a83f0d92a6d3a70a45fe359d5d25d15d7"
+ k1, err := crypto.DecodePublicKeyHex(crypto.ECDSA_P256, pubKey)
+ assert.NoError(t, err)
+
+ k2, err := crypto.DecodePublicKeyHex(crypto.ECDSA_P256, "0x"+pubKey)
+ assert.NoError(t, err)
+
+ assert.Equal(t, k1.String(), k2.String())
+ })
+ */
+}
diff --git a/pkg/flowcli/services/accounts.go b/pkg/flowcli/services/accounts.go
new file mode 100644
index 000000000..632f643c9
--- /dev/null
+++ b/pkg/flowcli/services/accounts.go
@@ -0,0 +1,491 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/onflow/cadence"
+ tmpl "github.com/onflow/flow-core-contracts/lib/go/templates"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/onflow/flow-go-sdk/templates"
+
+ "github.com/onflow/flow-cli/pkg/flowcli"
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+// Accounts is a service that handles all account-related interactions.
+type Accounts struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewAccounts returns a new accounts service.
+func NewAccounts(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Accounts {
+ return &Accounts{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// Get returns an account by on address.
+func (a *Accounts) Get(address string) (*flow.Account, error) {
+ a.logger.StartProgress(fmt.Sprintf("Loading %s...", address))
+
+ flowAddress := flow.HexToAddress(address)
+
+ account, err := a.gateway.GetAccount(flowAddress)
+ a.logger.StopProgress("")
+
+ return account, err
+}
+
+func (a *Accounts) Add(
+ name string,
+ accountAddress string,
+ signatureAlgorithm string,
+ hashingAlgorithm string,
+ keyIndex int,
+ keyHex string,
+ overwrite bool,
+ path []string,
+) (*project.Account, error) {
+ if a.project == nil {
+ return nil, fmt.Errorf("missing configuration, initialize it: flow init")
+ }
+
+ existingAccount := a.project.AccountByName(name)
+ if existingAccount != nil && !overwrite {
+ return nil, fmt.Errorf("account with name [%s] already exists in the config, use `overwrite` if you want to overwrite it", name)
+ }
+
+ sigAlgo, hashAlgo, err := util.ConvertSigAndHashAlgo(signatureAlgorithm, hashingAlgorithm)
+ if err != nil {
+ return nil, err
+ }
+
+ if keyIndex < 0 {
+ return nil, fmt.Errorf("key index must be positive number")
+ }
+
+ address := flow.HexToAddress(accountAddress)
+ chainID, err := util.GetAddressNetwork(address)
+ if err != nil {
+ return nil, err
+ }
+
+ confAccount := config.Account{
+ Name: name,
+ Address: address,
+ ChainID: chainID,
+ }
+
+ accountKey := config.AccountKey{
+ Index: keyIndex,
+ SigAlgo: sigAlgo,
+ HashAlgo: hashAlgo,
+ }
+
+ // TODO: discuss refactor to accounts
+ if keyHex != "" {
+ _, err := crypto.DecodePrivateKeyHex(sigAlgo, keyHex)
+ if err != nil {
+ return nil, fmt.Errorf("key hex could not be parsed")
+ }
+
+ accountKey.Type = config.KeyTypeHex
+ accountKey.Context = make(map[string]string)
+ accountKey.Context[config.PrivateKeyField] = keyHex
+
+ } else {
+ return nil, fmt.Errorf("private key must be provided")
+ }
+
+ confAccount.Keys = []config.AccountKey{accountKey}
+
+ account, err := project.AccountFromConfig(confAccount)
+ if err != nil {
+ return nil, err
+ }
+ // TODO: refactor context
+ sig, err := account.DefaultKey().Signer(context.Background())
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = sig.Sign([]byte("test"))
+ if err != nil {
+ return nil, fmt.Errorf("could not sign with the new key")
+ }
+
+ a.project.AddOrUpdateAccount(account)
+
+ err = a.project.Save(path[0]) // only allow saving to one config for now
+ if err != nil {
+ return nil, err
+ }
+
+ a.logger.Info("Account added to configuration\n")
+
+ return account, nil
+}
+
+// StakingInfo returns the staking information for an account.
+func (a *Accounts) StakingInfo(accountAddress string) (*cadence.Value, *cadence.Value, error) {
+ a.logger.StartProgress(fmt.Sprintf("Fetching info for %s...", accountAddress))
+
+ address := flow.HexToAddress(accountAddress)
+
+ cadenceAddress := []cadence.Value{cadence.NewAddress(address)}
+
+ chain, err := util.GetAddressNetwork(address)
+ if err != nil {
+ return nil, nil, fmt.Errorf(
+ "failed to determine network from address, check the address and network",
+ )
+ }
+
+ if chain == flow.Emulator {
+ return nil, nil, fmt.Errorf("emulator chain not supported")
+ }
+
+ env := util.EnvFromNetwork(chain)
+
+ stakingInfoScript := tmpl.GenerateGetLockedStakerInfoScript(env)
+ delegationInfoScript := tmpl.GenerateGetLockedDelegatorInfoScript(env)
+
+ stakingValue, err := a.gateway.ExecuteScript(stakingInfoScript, cadenceAddress)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error getting staking info: %s", err.Error())
+ }
+
+ delegationValue, err := a.gateway.ExecuteScript(delegationInfoScript, cadenceAddress)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error getting delegation info: %s", err.Error())
+ }
+
+ a.logger.StopProgress("")
+
+ return &stakingValue, &delegationValue, nil
+}
+
+// Create creates and returns a new account.
+//
+// The new account is created with the given public keys and contracts.
+//
+// The account creation transaction is signed by the specified signer.
+func (a *Accounts) Create(
+ signerName string,
+ keys []string,
+ signatureAlgorithm string,
+ hashingAlgorithm string,
+ contracts []string,
+) (*flow.Account, error) {
+ if a.project == nil {
+ return nil, fmt.Errorf("missing configuration, initialize it: flow init")
+ }
+
+ signer := a.project.AccountByName(signerName)
+ if signer == nil {
+ return nil, fmt.Errorf("signer account: [%s] doesn't exists in configuration", signerName)
+ }
+
+ a.logger.StartProgress("Creating account...")
+
+ accountKeys := make([]*flow.AccountKey, len(keys))
+
+ sigAlgo, hashAlgo, err := util.ConvertSigAndHashAlgo(signatureAlgorithm, hashingAlgorithm)
+ if err != nil {
+ return nil, err
+ }
+
+ for i, publicKeyHex := range keys {
+ publicKey, err := crypto.DecodePublicKeyHex(
+ sigAlgo,
+ strings.ReplaceAll(publicKeyHex, "0x", ""),
+ )
+ if err != nil {
+ return nil, fmt.Errorf(
+ "could not decode public key for key: %s, with signature algorith: %s",
+ publicKeyHex,
+ sigAlgo,
+ )
+ }
+
+ accountKeys[i] = &flow.AccountKey{
+ PublicKey: publicKey,
+ SigAlgo: sigAlgo,
+ HashAlgo: hashAlgo,
+ Weight: flow.AccountKeyWeightThreshold,
+ }
+ }
+
+ var contractTemplates []templates.Contract
+
+ for _, contract := range contracts {
+ contractFlagContent := strings.SplitN(contract, ":", 2)
+ if len(contractFlagContent) != 2 {
+ return nil, fmt.Errorf("wrong format for contract. Correct format is name:path, but got: %s", contract)
+ }
+ contractName := contractFlagContent[0]
+ contractPath := contractFlagContent[1]
+
+ contractSource, err := util.LoadFile(contractPath)
+ if err != nil {
+ return nil, err
+ }
+
+ contractTemplates = append(contractTemplates,
+ templates.Contract{
+ Name: contractName,
+ Source: string(contractSource),
+ },
+ )
+ }
+
+ tx := templates.CreateAccount(accountKeys, contractTemplates, signer.Address())
+ tx, err = a.gateway.SendTransaction(tx, signer)
+ if err != nil {
+ return nil, err
+ }
+
+ a.logger.StartProgress("Waiting for transaction to be sealed...")
+
+ result, err := a.gateway.GetTransactionResult(tx, true)
+ if err != nil {
+ return nil, err
+ }
+
+ if result.Error != nil {
+ return nil, result.Error
+ }
+
+ events := flowcli.EventsFromTransaction(result)
+ newAccountAddress := events.GetAddress()
+
+ if newAccountAddress == nil {
+ return nil, fmt.Errorf("new account address couldn't be fetched")
+ }
+
+ a.logger.StopProgress("")
+
+ return a.gateway.GetAccount(*newAccountAddress)
+}
+
+// AddContract adds a new contract to an account and returns the updated account.
+func (a *Accounts) AddContract(
+ accountName string,
+ contractName string,
+ contractFilename string,
+ updateExisting bool,
+) (*flow.Account, error) {
+ if a.project == nil {
+ return nil, fmt.Errorf("missing configuration, initialize it: flow init")
+ }
+
+ account := a.project.AccountByName(accountName)
+ if account == nil {
+ return nil, fmt.Errorf("account: [%s] doesn't exists in configuration", accountName)
+ }
+
+ contractSource, err := util.LoadFile(contractFilename)
+ if err != nil {
+ return nil, err
+ }
+
+ return a.addContract(account, contractName, contractSource, updateExisting)
+}
+
+// AddContractForAddress adds a new contract to an address using private key specified
+func (a *Accounts) AddContractForAddress(
+ accountAddress string,
+ accountPrivateKey string,
+ contractName string,
+ contractFilename string,
+ updateExisting bool,
+) (*flow.Account, error) {
+ account, err := accountFromAddressAndKey(accountAddress, accountPrivateKey)
+ if err != nil {
+ return nil, err
+ }
+
+ contractSource, err := util.LoadFile(contractFilename)
+ if err != nil {
+ return nil, err
+ }
+
+ return a.addContract(account, contractName, contractSource, updateExisting)
+}
+
+// AddContractForAddressWithCode adds a new contract to an address using private key and code specified
+func (a *Accounts) AddContractForAddressWithCode(
+ accountAddress string,
+ accountPrivateKey string,
+ contractName string,
+ contractCode []byte,
+ updateExisting bool,
+) (*flow.Account, error) {
+ account, err := accountFromAddressAndKey(accountAddress, accountPrivateKey)
+ if err != nil {
+ return nil, err
+ }
+
+ return a.addContract(account, contractName, contractCode, updateExisting)
+}
+
+func (a *Accounts) addContract(
+ account *project.Account,
+ contractName string,
+ contractSource []byte,
+ updateExisting bool,
+) (*flow.Account, error) {
+ a.logger.StartProgress(
+ fmt.Sprintf(
+ "Adding contract '%s' to account '%s'...",
+ contractName,
+ account.Address(),
+ ),
+ )
+
+ if account.DefaultKey().Type() == config.KeyTypeGoogleKMS {
+ a.logger.StartProgress("Connecting to KMS...")
+ resourceID := account.DefaultKey().ToConfig().Context[config.KMSContextField]
+ err := util.GcloudApplicationSignin(resourceID)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ tx := templates.AddAccountContract(
+ account.Address(),
+ templates.Contract{
+ Name: contractName,
+ Source: string(contractSource),
+ },
+ )
+
+ // if we are updating contract
+ if updateExisting {
+ tx = templates.UpdateAccountContract(
+ account.Address(),
+ templates.Contract{
+ Name: contractName,
+ Source: string(contractSource),
+ },
+ )
+ }
+
+ // send transaction with contract
+ tx, err := a.gateway.SendTransaction(tx, account)
+ if err != nil {
+ return nil, err
+ }
+
+ // we wait for transaction to be sealed
+ trx, err := a.gateway.GetTransactionResult(tx, true)
+ if err != nil {
+ return nil, err
+ }
+
+ if trx.Error != nil {
+ a.logger.Error("Failed to deploy contract")
+ return nil, trx.Error
+ }
+
+ update, err := a.gateway.GetAccount(account.Address())
+
+ a.logger.StopProgress("")
+
+ if updateExisting {
+ a.logger.Info(fmt.Sprintf(
+ "Contract '%s' updated on the account '%s'.",
+ contractName,
+ account.Address(),
+ ))
+ } else {
+ a.logger.Info(fmt.Sprintf(
+ "Contract '%s' deployed to the account '%s'.",
+ contractName,
+ account.Address(),
+ ))
+ }
+
+ return update, err
+}
+
+// RemoveContracts removes a contract from an account and returns the updated account.
+func (a *Accounts) RemoveContract(
+ contractName string,
+ accountName string,
+) (*flow.Account, error) {
+ account := a.project.AccountByName(accountName)
+ if account == nil {
+ return nil, fmt.Errorf("account: [%s] doesn't exists in configuration", accountName)
+ }
+
+ a.logger.StartProgress(
+ fmt.Sprintf("Removing Contract %s from %s...", contractName, account.Address()),
+ )
+
+ tx := templates.RemoveAccountContract(account.Address(), contractName)
+ tx, err := a.gateway.SendTransaction(tx, account)
+ if err != nil {
+ return nil, err
+ }
+
+ txr, err := a.gateway.GetTransactionResult(tx, true)
+ if err != nil {
+ return nil, err
+ }
+ if txr != nil && txr.Error != nil {
+ a.logger.Error("Removing contract failed")
+ return nil, txr.Error
+ }
+
+ a.logger.StopProgress("")
+ a.logger.Info(fmt.Sprintf("Contract %s removed from account %s\n", contractName, account.Address()))
+
+ return a.gateway.GetAccount(account.Address())
+}
+
+// AccountFromAddressAndKey get account from address and private key
+func accountFromAddressAndKey(address string, accountPrivateKey string) (*project.Account, error) {
+ privateKey, err := crypto.DecodePrivateKeyHex(crypto.ECDSA_P256, accountPrivateKey)
+ if err != nil {
+ return nil, fmt.Errorf("private key is not correct")
+ }
+
+ return project.AccountFromAddressAndKey(
+ flow.HexToAddress(address),
+ privateKey,
+ ), nil
+}
diff --git a/pkg/flowcli/services/accounts_test.go b/pkg/flowcli/services/accounts_test.go
new file mode 100644
index 000000000..2a7f0b141
--- /dev/null
+++ b/pkg/flowcli/services/accounts_test.go
@@ -0,0 +1,189 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+const (
+ serviceAddress = "f8d6e0586b0a20c7"
+ serviceName = "emulator-account"
+ pubKey = "858a7d978b25d61f348841a343f79131f4b9fab341dd8a476a6f4367c25510570bf69b795fc9c3d2b7191327d869bcf848508526a3c1cafd1af34f71c7765117"
+ sigAlgo = "ECDSA_P256"
+ hashAlgo = "SHA3_256"
+)
+
+func TestAccounts(t *testing.T) {
+
+ mock := &tests.MockGateway{}
+
+ proj, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ accounts := NewAccounts(mock, proj, output.NewStdoutLogger(output.NoneLog))
+
+ t.Run("Get an Account", func(t *testing.T) {
+ mock.GetAccountMock = func(address flow.Address) (*flow.Account, error) {
+ return tests.NewAccountWithAddress(address.String()), nil
+ }
+
+ account, err := accounts.Get(serviceAddress)
+
+ assert.NoError(t, err)
+ assert.Equal(t, account.Address.String(), serviceAddress)
+ })
+
+ t.Run("Create an Account", func(t *testing.T) {
+ newAddress := "192440c99cb17282"
+
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ assert.Equal(t, tx.Authorizers[0].String(), serviceAddress)
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+
+ return tests.NewTransaction(), nil
+ }
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ return tests.NewAccountCreateResult(newAddress), nil
+ }
+
+ mock.GetAccountMock = func(address flow.Address) (*flow.Account, error) {
+ assert.Equal(t, address.String(), newAddress)
+
+ return tests.NewAccountWithAddress(newAddress), nil
+ }
+
+ a, err := accounts.Create(serviceName, []string{pubKey}, sigAlgo, hashAlgo, nil)
+
+ assert.NotNil(t, a)
+ assert.NoError(t, err)
+ assert.Equal(t, len(a.Address), 8)
+ })
+
+ t.Run("Create an Account with Contract", func(t *testing.T) {
+ newAddress := "192440c99cb17282"
+
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ assert.Equal(t, tx.Authorizers[0].String(), serviceAddress)
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+ assert.True(t, strings.Contains(string(tx.Script), "acct.contracts.add"))
+
+ return tests.NewTransaction(), nil
+ }
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ return tests.NewAccountCreateResult(newAddress), nil
+ }
+
+ mock.GetAccountMock = func(address flow.Address) (*flow.Account, error) {
+ assert.Equal(t, address.String(), newAddress)
+
+ return tests.NewAccountWithAddress(newAddress), nil
+ }
+
+ a, err := accounts.Create(serviceName, []string{pubKey}, sigAlgo, hashAlgo, []string{"Hello:../../../tests/Hello.cdc"})
+
+ assert.NotNil(t, a)
+ assert.NoError(t, err)
+ assert.Equal(t, len(a.Address), 8)
+ })
+
+ t.Run("Contract Add for Account", func(t *testing.T) {
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ assert.Equal(t, tx.Authorizers[0].String(), serviceAddress)
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+ assert.True(t, strings.Contains(string(tx.Script), "signer.contracts.add"))
+
+ return tests.NewTransaction(), nil
+ }
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ return tests.NewTransactionResult(nil), nil
+ }
+
+ mock.GetAccountMock = func(address flow.Address) (*flow.Account, error) {
+ return tests.NewAccountWithAddress(address.String()), nil
+ }
+
+ a, err := accounts.AddContract(serviceName, "Hello", "../../../tests/Hello.cdc", false)
+
+ assert.NotNil(t, a)
+ assert.NoError(t, err)
+ assert.Equal(t, len(a.Address), 8)
+ })
+
+ t.Run("Contract Update for Account", func(t *testing.T) {
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ assert.Equal(t, tx.Authorizers[0].String(), serviceAddress)
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+ assert.True(t, strings.Contains(string(tx.Script), "signer.contracts.update__experimental"))
+
+ return tests.NewTransaction(), nil
+ }
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ return tests.NewTransactionResult(nil), nil
+ }
+
+ mock.GetAccountMock = func(address flow.Address) (*flow.Account, error) {
+ return tests.NewAccountWithAddress(address.String()), nil
+ }
+
+ account, err := accounts.AddContract(serviceName, "Hello", "../../../tests/Hello.cdc", true)
+
+ assert.NotNil(t, account)
+ assert.Equal(t, len(account.Address), 8)
+ assert.NoError(t, err)
+ })
+
+ t.Run("Contract Remove for Account", func(t *testing.T) {
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ assert.Equal(t, tx.Authorizers[0].String(), serviceAddress)
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+ assert.True(t, strings.Contains(string(tx.Script), "signer.contracts.remove"))
+
+ return tests.NewTransaction(), nil
+ }
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ return tests.NewTransactionResult(nil), nil
+ }
+
+ mock.GetAccountMock = func(address flow.Address) (*flow.Account, error) {
+ return tests.NewAccountWithAddress(address.String()), nil
+ }
+
+ account, err := accounts.RemoveContract("Hello", serviceName)
+
+ assert.NotNil(t, account)
+ assert.Equal(t, len(account.Address), 8)
+ assert.NoError(t, err)
+ })
+
+}
diff --git a/pkg/flowcli/services/blocks.go b/pkg/flowcli/services/blocks.go
new file mode 100644
index 000000000..b91262412
--- /dev/null
+++ b/pkg/flowcli/services/blocks.go
@@ -0,0 +1,109 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+// Blocks is a service that handles all block-related interactions.
+type Blocks struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewBlocks returns a new blocks service.
+func NewBlocks(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Blocks {
+ return &Blocks{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// GetBlock returns a block based on the provided query string.
+//
+// Query string options:
+// - "latest" : return the latest block
+// - height (e.g. 123456789) : return block at this height
+// - ID : return block with this ID
+func (e *Blocks) GetBlock(
+ query string,
+ eventType string,
+ verbose bool,
+) (*flow.Block, []client.BlockEvents, []*flow.Collection, error) {
+ e.logger.StartProgress("Fetching Block...")
+
+ // smart parsing of query
+ var err error
+ var block *flow.Block
+ if query == "latest" {
+ block, err = e.gateway.GetLatestBlock()
+ } else if height, ce := strconv.ParseUint(query, 10, 64); ce == nil {
+ block, err = e.gateway.GetBlockByHeight(height)
+ } else {
+ block, err = e.gateway.GetBlockByID(flow.HexToID(query))
+ }
+
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("error fetching block: %s", err.Error())
+ }
+
+ if block == nil {
+ return nil, nil, nil, fmt.Errorf("block not found")
+ }
+
+ // if we specify event get events by the type
+ var events []client.BlockEvents
+ if eventType != "" {
+ events, err = e.gateway.GetEvents(eventType, block.Height, block.Height)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ }
+
+ // if verbose fetch all collections from block too
+ collections := make([]*flow.Collection, 0)
+ if verbose {
+ for _, guarantee := range block.CollectionGuarantees {
+ collection, err := e.gateway.GetCollection(guarantee.CollectionID)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ collections = append(collections, collection)
+ }
+ }
+
+ e.logger.StopProgress("")
+
+ return block, events, collections, err
+}
diff --git a/pkg/flowcli/services/blocks_test.go b/pkg/flowcli/services/blocks_test.go
new file mode 100644
index 000000000..a98f555c0
--- /dev/null
+++ b/pkg/flowcli/services/blocks_test.go
@@ -0,0 +1,129 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "testing"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestBlocks(t *testing.T) {
+
+ mock := &tests.MockGateway{}
+
+ project, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ blocks := NewBlocks(mock, project, output.NewStdoutLogger(output.InfoLog))
+
+ t.Run("Get Latest Block", func(t *testing.T) {
+ called := false
+ mock.GetLatestBlockMock = func() (*flow.Block, error) {
+ called = true
+ return tests.NewBlock(), nil
+ }
+
+ mock.GetBlockByIDMock = func(identifier flow.Identifier) (*flow.Block, error) {
+ assert.Fail(t, "shouldn't be called")
+ return nil, nil
+ }
+
+ mock.GetBlockByHeightMock = func(height uint64) (*flow.Block, error) {
+ assert.Fail(t, "shouldn't be called")
+ return nil, nil
+ }
+
+ mock.GetEventsMock = func(name string, start uint64, end uint64) ([]client.BlockEvents, error) {
+ assert.Equal(t, name, "flow.AccountCreated")
+ return nil, nil
+ }
+
+ _, _, _, err := blocks.GetBlock("latest", "flow.AccountCreated", false)
+
+ assert.NoError(t, err)
+ assert.True(t, called)
+ })
+
+ t.Run("Get Block by Height", func(t *testing.T) {
+ called := false
+ mock.GetBlockByHeightMock = func(height uint64) (*flow.Block, error) {
+ called = true
+ assert.Equal(t, height, uint64(10))
+ return tests.NewBlock(), nil
+ }
+
+ mock.GetBlockByIDMock = func(identifier flow.Identifier) (*flow.Block, error) {
+ assert.Fail(t, "shouldn't be called")
+ return nil, nil
+ }
+
+ mock.GetLatestBlockMock = func() (*flow.Block, error) {
+ assert.Fail(t, "shouldn't be called")
+ return nil, nil
+ }
+
+ mock.GetEventsMock = func(name string, start uint64, end uint64) ([]client.BlockEvents, error) {
+ assert.Equal(t, name, "flow.AccountCreated")
+ return nil, nil
+ }
+
+ _, _, _, err := blocks.GetBlock("10", "flow.AccountCreated", false)
+
+ assert.NoError(t, err)
+ assert.True(t, called)
+ })
+
+ t.Run("Get Block by ID", func(t *testing.T) {
+ called := false
+ mock.GetBlockByIDMock = func(id flow.Identifier) (*flow.Block, error) {
+ called = true
+
+ assert.Equal(t, id.String(), "a310685082f0b09f2a148b2e8905f08ea458ed873596b53b200699e8e1f6536f")
+ return tests.NewBlock(), nil
+ }
+
+ mock.GetBlockByHeightMock = func(u uint64) (*flow.Block, error) {
+ assert.Fail(t, "shouldn't be called")
+ return nil, nil
+ }
+
+ mock.GetLatestBlockMock = func() (*flow.Block, error) {
+ assert.Fail(t, "shouldn't be called")
+ return nil, nil
+ }
+
+ mock.GetEventsMock = func(name string, start uint64, end uint64) ([]client.BlockEvents, error) {
+ assert.Equal(t, name, "flow.AccountCreated")
+ return nil, nil
+ }
+
+ _, _, _, err := blocks.GetBlock("a310685082f0b09f2a148b2e8905f08ea458ed873596b53b200699e8e1f6536f", "flow.AccountCreated", false)
+
+ assert.NoError(t, err)
+ assert.True(t, called)
+ })
+}
diff --git a/pkg/flowcli/services/collections.go b/pkg/flowcli/services/collections.go
new file mode 100644
index 000000000..f191c035f
--- /dev/null
+++ b/pkg/flowcli/services/collections.go
@@ -0,0 +1,53 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "github.com/onflow/flow-go-sdk"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+// Collections is aa service that handles all collection-related interactions.
+type Collections struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewCollections returns a new collections service.
+func NewCollections(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Collections {
+ return &Collections{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// Get returns a collection by ID.
+func (c *Collections) Get(id string) (*flow.Collection, error) {
+ collectionID := flow.HexToID(id)
+ return c.gateway.GetCollection(collectionID)
+}
diff --git a/pkg/flowcli/services/collections_test.go b/pkg/flowcli/services/collections_test.go
new file mode 100644
index 000000000..5fda78b74
--- /dev/null
+++ b/pkg/flowcli/services/collections_test.go
@@ -0,0 +1,53 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "testing"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestCollections(t *testing.T) {
+ mock := &tests.MockGateway{}
+
+ proj, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ collections := NewCollections(mock, proj, output.NewStdoutLogger(output.InfoLog))
+
+ t.Run("Get Collection", func(t *testing.T) {
+ called := false
+ mock.GetCollectionMock = func(id flow.Identifier) (*flow.Collection, error) {
+ called = true
+ return tests.NewCollection(), nil
+ }
+
+ _, err := collections.Get("a310685082f0b09f2a148b2e8905f08ea458ed873596b53b200699e8e1f6536f")
+
+ assert.NoError(t, err)
+ assert.True(t, called)
+ })
+}
diff --git a/pkg/flowcli/services/events.go b/pkg/flowcli/services/events.go
new file mode 100644
index 000000000..3a4607d8a
--- /dev/null
+++ b/pkg/flowcli/services/events.go
@@ -0,0 +1,98 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/onflow/flow-go-sdk/client"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+// Events is a service that handles all event-related interactions.
+type Events struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewEvents returns a new events service.
+func NewEvents(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Events {
+ return &Events{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// Get queries for an event by name and block range.
+func (e *Events) Get(name string, start string, end string) ([]client.BlockEvents, error) {
+ if name == "" {
+ return nil, fmt.Errorf("cannot use empty string as event name")
+ }
+
+ startHeight, err := strconv.ParseUint(start, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse start height of block range: %v", start)
+ }
+
+ e.logger.StartProgress("Fetching Events...")
+
+ var endHeight uint64
+ if end == "" {
+ endHeight = startHeight
+ } else if end == "latest" {
+ latestBlock, err := e.gateway.GetLatestBlock()
+ if err != nil {
+ return nil, err
+ }
+
+ endHeight = latestBlock.Height
+ } else {
+ endHeight, err = strconv.ParseUint(end, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse end height of block range: %s", end)
+ }
+ }
+
+ if endHeight < startHeight {
+ return nil, fmt.Errorf("cannot have end height (%d) of block range less that start height (%d)", endHeight, startHeight)
+ }
+
+ maxBlockRange := uint64(10000)
+ if endHeight-startHeight > maxBlockRange {
+ return nil, fmt.Errorf("block range is too big: %d, maximum block range is %d", endHeight-startHeight, maxBlockRange)
+ }
+
+ events, err := e.gateway.GetEvents(name, startHeight, endHeight)
+ if err != nil {
+ return nil, err
+ }
+
+ e.logger.StopProgress("")
+ return events, nil
+}
diff --git a/pkg/flowcli/services/events_test.go b/pkg/flowcli/services/events_test.go
new file mode 100644
index 000000000..642767ccf
--- /dev/null
+++ b/pkg/flowcli/services/events_test.go
@@ -0,0 +1,92 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "testing"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestEvents(t *testing.T) {
+ mock := &tests.MockGateway{}
+
+ proj, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ events := NewEvents(mock, proj, output.NewStdoutLogger(output.InfoLog))
+
+ t.Run("Get Events", func(t *testing.T) {
+ called := false
+ mock.GetEventsMock = func(s string, u uint64, u2 uint64) ([]client.BlockEvents, error) {
+ called = true
+ return nil, nil
+ }
+
+ _, err := events.Get("flow.CreateAccount", "0", "1")
+
+ assert.NoError(t, err)
+ assert.True(t, called)
+ })
+
+ t.Run("Get Events Latest", func(t *testing.T) {
+ called := 0
+ mock.GetEventsMock = func(s string, u uint64, u2 uint64) ([]client.BlockEvents, error) {
+ called++
+ return nil, nil
+ }
+
+ mock.GetLatestBlockMock = func() (*flow.Block, error) {
+ called++
+ return tests.NewBlock(), nil
+ }
+
+ _, err := events.Get("flow.CreateAccount", "0", "latest")
+
+ assert.NoError(t, err)
+ assert.Equal(t, called, 2)
+ })
+
+ t.Run("Fails to get events without name", func(t *testing.T) {
+ _, err := events.Get("", "0", "1")
+ assert.Equal(t, err.Error(), "cannot use empty string as event name")
+ })
+
+ t.Run("Fails to get events with wrong height", func(t *testing.T) {
+ _, err := events.Get("test", "-1", "1")
+ assert.Equal(t, err.Error(), "failed to parse start height of block range: -1")
+ })
+
+ t.Run("Fails to get events with wrong end height", func(t *testing.T) {
+ _, err := events.Get("test", "1", "-1")
+ assert.Equal(t, err.Error(), "failed to parse end height of block range: -1")
+ })
+
+ t.Run("Fails to get events with wrong start height", func(t *testing.T) {
+ _, err := events.Get("test", "10", "5")
+ assert.Equal(t, err.Error(), "cannot have end height (5) of block range less that start height (10)")
+ })
+}
diff --git a/pkg/flowcli/services/keys.go b/pkg/flowcli/services/keys.go
new file mode 100644
index 000000000..e2a67a980
--- /dev/null
+++ b/pkg/flowcli/services/keys.go
@@ -0,0 +1,93 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "encoding/hex"
+ "fmt"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+// Keys is a service that handles all key-related interactions.
+type Keys struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewKeys returns a new keys service.
+func NewKeys(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Keys {
+ return &Keys{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// Generate generates a new private key from the given seed and signature algorithm.
+func (k *Keys) Generate(inputSeed string, signatureAlgo string) (*crypto.PrivateKey, error) {
+ var seed []byte
+ var err error
+
+ if inputSeed == "" {
+ seed, err = util.RandomSeed(crypto.MinSeedLength)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ seed = []byte(inputSeed)
+ }
+
+ sigAlgo := crypto.StringToSignatureAlgorithm(signatureAlgo)
+ if sigAlgo == crypto.UnknownSignatureAlgorithm {
+ return nil, fmt.Errorf("invalid signature algorithm: %s", signatureAlgo)
+ }
+
+ privateKey, err := crypto.GeneratePrivateKey(sigAlgo, seed)
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate private key: %v", err)
+ }
+
+ return &privateKey, nil
+}
+
+func (k *Keys) Decode(publicKey string) (*flow.AccountKey, error) {
+ publicKeyBytes, err := hex.DecodeString(publicKey)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode public key: %v", err)
+ }
+
+ accountKey, err := flow.DecodeAccountKey(publicKeyBytes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode private key bytes: %v", err)
+ }
+
+ return accountKey, nil
+}
diff --git a/pkg/flowcli/services/keys_test.go b/pkg/flowcli/services/keys_test.go
new file mode 100644
index 000000000..59a7806c6
--- /dev/null
+++ b/pkg/flowcli/services/keys_test.go
@@ -0,0 +1,66 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "testing"
+
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestKeys(t *testing.T) {
+ mock := &tests.MockGateway{}
+
+ proj, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ keys := NewKeys(mock, proj, output.NewStdoutLogger(output.InfoLog))
+
+ t.Run("Generate Keys", func(t *testing.T) {
+ key, err := keys.Generate("", "ECDSA_P256")
+
+ assert.NoError(t, err)
+ assert.Equal(t, len(key.PrivateKey.String()), 66)
+ })
+
+ t.Run("Generate Keys with seed", func(t *testing.T) {
+ key, err := keys.Generate("aaaaaaaaaaaaaaaaaaaaaaannndddddd_its_gone", "ECDSA_P256")
+
+ assert.NoError(t, err)
+ assert.Equal(t, key.PrivateKey.String(), "0x134f702d0872dba9c7aea15498aab9b2ffedd5aeebfd8ac3cf47c591f0d7ce52")
+ })
+
+ t.Run("Fail generate keys, too short seed", func(t *testing.T) {
+ _, err := keys.Generate("im_short", "ECDSA_P256")
+
+ assert.Equal(t, err.Error(), "failed to generate private key: crypto: insufficient seed length 8, must be at least 32 bytes for ECDSA_P256")
+ })
+
+ t.Run("Fail generate keys, invalid sig algo", func(t *testing.T) {
+ _, err := keys.Generate("", "JUSTNO")
+
+ assert.Equal(t, err.Error(), "invalid signature algorithm: JUSTNO")
+ })
+
+}
diff --git a/pkg/flowcli/services/project.go b/pkg/flowcli/services/project.go
new file mode 100644
index 000000000..1dacecd09
--- /dev/null
+++ b/pkg/flowcli/services/project.go
@@ -0,0 +1,239 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/onflow/flow-go-sdk/templates"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/contracts"
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+// Project is a service that handles all interactions for a project.
+type Project struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewProject returns a new project service.
+func NewProject(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Project {
+ return &Project{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+func (p *Project) Init(
+ reset bool,
+ serviceKeySigAlgo string,
+ serviceKeyHashAlgo string,
+ servicePrivateKey string,
+) (*project.Project, error) {
+ if !project.Exists(project.DefaultConfigPath) || reset {
+
+ sigAlgo, hashAlgo, err := util.ConvertSigAndHashAlgo(serviceKeySigAlgo, serviceKeyHashAlgo)
+ if err != nil {
+ return nil, err
+ }
+
+ proj, err := project.Init(sigAlgo, hashAlgo)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(servicePrivateKey) > 0 {
+ serviceKey, err := crypto.DecodePrivateKeyHex(sigAlgo, servicePrivateKey)
+ if err != nil {
+ return nil, fmt.Errorf("could not decode private key for a service account, provided private key: %s", servicePrivateKey)
+ }
+
+ proj.SetEmulatorServiceKey(serviceKey)
+ }
+
+ err = proj.Save(project.DefaultConfigPath)
+ if err != nil {
+ return nil, err
+ }
+
+ return proj, nil
+ }
+
+ return nil, fmt.Errorf(
+ "configuration already exists at: %s, if you want to reset configuration use the reset flag",
+ project.DefaultConfigPath,
+ )
+}
+
+func (p *Project) Deploy(network string, update bool) ([]*contracts.Contract, error) {
+ if p.project == nil {
+ return nil, fmt.Errorf("missing configuration, initialize it: flow init")
+ }
+
+ // check there are not multiple accounts with same contract
+ // TODO: specify which contract by name is a problem
+ if p.project.ContractConflictExists(network) {
+ return nil, fmt.Errorf(
+ "the same contract cannot be deployed to multiple accounts on the same network",
+ )
+ }
+
+ processor := contracts.NewPreprocessor(
+ contracts.FilesystemLoader{},
+ p.project.AliasesForNetwork(network),
+ )
+
+ for _, contract := range p.project.ContractsByNetwork(network) {
+ err := processor.AddContractSource(
+ contract.Name,
+ contract.Source,
+ contract.Target,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ err := processor.ResolveImports()
+ if err != nil {
+ return nil, err
+ }
+
+ orderedContracts, err := processor.ContractDeploymentOrder()
+ if err != nil {
+ return nil, err
+ }
+
+ p.logger.Info(fmt.Sprintf(
+ "\nDeploying %d contracts for accounts: %s\n",
+ len(orderedContracts),
+ strings.Join(p.project.AccountNamesForNetwork(network), ","),
+ ))
+
+ deployErr := false
+ for _, contract := range orderedContracts {
+ targetAccount := p.project.AccountByAddress(contract.Target().String())
+
+ if targetAccount == nil {
+ return nil, fmt.Errorf("target account for deploying contract not found in configuration")
+ }
+
+ targetAccountInfo, err := p.gateway.GetAccount(targetAccount.Address())
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch information for account %s with error %s", targetAccount.Address(), err.Error())
+ }
+
+ var tx *flow.Transaction
+
+ _, exists := targetAccountInfo.Contracts[contract.Name()]
+ if exists {
+ if !update {
+ p.logger.Error(fmt.Sprintf(
+ "contract %s is already deployed to this account. Use the --update flag to force update",
+ contract.Name(),
+ ))
+
+ deployErr = true
+ continue
+ }
+
+ tx = prepareUpdateContractTransaction(targetAccount.Address(), contract)
+ } else {
+ tx = prepareAddContractTransaction(targetAccount.Address(), contract)
+ }
+
+ tx, err = p.gateway.SendTransaction(tx, targetAccount)
+ if err != nil {
+ p.logger.Error(err.Error())
+ deployErr = true
+ }
+
+ p.logger.StartProgress(
+ fmt.Sprintf("%s deploying...", util.Bold(contract.Name())),
+ )
+
+ result, err := p.gateway.GetTransactionResult(tx, true)
+ if err != nil {
+ p.logger.Error(err.Error())
+ deployErr = true
+ }
+
+ if result.Error == nil && !deployErr {
+ p.logger.StopProgress(
+ fmt.Sprintf("%s -> 0x%s", util.Green(contract.Name()), contract.Target()),
+ )
+ } else {
+ p.logger.StopProgress(
+ fmt.Sprintf("%s error", util.Red(contract.Name())),
+ )
+ p.logger.Error(
+ fmt.Sprintf("%s error: %s", contract.Name(), result.Error),
+ )
+ }
+ }
+
+ if !deployErr {
+ p.logger.Info("\n✨ All contracts deployed successfully")
+ } else {
+ err = fmt.Errorf("failed to deploy contracts")
+ p.logger.Error(err.Error())
+ return nil, err
+ }
+
+ return orderedContracts, nil
+}
+
+func prepareUpdateContractTransaction(
+ targetAccount flow.Address,
+ contract *contracts.Contract,
+) *flow.Transaction {
+ return templates.UpdateAccountContract(
+ targetAccount,
+ templates.Contract{
+ Name: contract.Name(),
+ Source: contract.TranspiledCode(),
+ },
+ )
+}
+
+func prepareAddContractTransaction(
+ targetAccount flow.Address,
+ contract *contracts.Contract,
+) *flow.Transaction {
+ return templates.AddAccountContract(
+ targetAccount,
+ templates.Contract{
+ Name: contract.Name(),
+ Source: contract.TranspiledCode(),
+ },
+ )
+}
diff --git a/pkg/flowcli/services/scripts.go b/pkg/flowcli/services/scripts.go
new file mode 100644
index 000000000..c2f6973fe
--- /dev/null
+++ b/pkg/flowcli/services/scripts.go
@@ -0,0 +1,73 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "github.com/onflow/cadence"
+
+ "github.com/onflow/flow-cli/pkg/flowcli"
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+// Scripts is a service that handles all script-related interactions.
+type Scripts struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewScripts returns a new scripts service.
+func NewScripts(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Scripts {
+ return &Scripts{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// Execute executes a Cadence script from a file.
+func (s *Scripts) Execute(scriptFilename string, args []string, argsJSON string) (cadence.Value, error) {
+ script, err := util.LoadFile(scriptFilename)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.execute(script, args, argsJSON)
+}
+
+// Execute executes a Cadence script from a source code string.
+func (s *Scripts) ExecuteWithCode(code []byte, args []string, argsJSON string) (cadence.Value, error) {
+ return s.execute(code, args, argsJSON)
+}
+
+func (s *Scripts) execute(code []byte, args []string, argsJSON string) (cadence.Value, error) {
+ scriptArgs, err := flowcli.ParseArguments(args, argsJSON)
+ if err != nil {
+ return nil, err
+ }
+
+ return s.gateway.ExecuteScript(code, scriptArgs)
+}
diff --git a/pkg/flowcli/services/scripts_test.go b/pkg/flowcli/services/scripts_test.go
new file mode 100644
index 000000000..47c6778f4
--- /dev/null
+++ b/pkg/flowcli/services/scripts_test.go
@@ -0,0 +1,52 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "testing"
+
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestScripts(t *testing.T) {
+ mock := &tests.MockGateway{}
+
+ proj, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ scripts := NewScripts(mock, proj, output.NewStdoutLogger(output.InfoLog))
+
+ t.Run("Execute Script", func(t *testing.T) {
+ mock.ExecuteScriptMock = func(script []byte, arguments []cadence.Value) (cadence.Value, error) {
+ assert.Equal(t, len(string(script)), 69)
+ assert.Equal(t, arguments[0].String(), "\"Foo\"")
+ return arguments[0], nil
+ }
+
+ _, err := scripts.Execute("../../../tests/script.cdc", []string{"String:Foo"}, "")
+
+ assert.NoError(t, err)
+ })
+}
diff --git a/pkg/flowcli/services/services.go b/pkg/flowcli/services/services.go
new file mode 100644
index 000000000..c93cf25aa
--- /dev/null
+++ b/pkg/flowcli/services/services.go
@@ -0,0 +1,57 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+// Services is a collection of services that provide domain-specific functionality
+// for the different components of a Flow project.
+type Services struct {
+ Accounts *Accounts
+ Scripts *Scripts
+ Transactions *Transactions
+ Keys *Keys
+ Events *Events
+ Collections *Collections
+ Project *Project
+ Blocks *Blocks
+}
+
+// NewServices returns a new services collection for a project,
+// initialized with a gateway and logger.
+func NewServices(
+ gateway gateway.Gateway,
+ proj *project.Project,
+ logger output.Logger,
+) *Services {
+ return &Services{
+ Accounts: NewAccounts(gateway, proj, logger),
+ Scripts: NewScripts(gateway, proj, logger),
+ Transactions: NewTransactions(gateway, proj, logger),
+ Keys: NewKeys(gateway, proj, logger),
+ Events: NewEvents(gateway, proj, logger),
+ Collections: NewCollections(gateway, proj, logger),
+ Project: NewProject(gateway, proj, logger),
+ Blocks: NewBlocks(gateway, proj, logger),
+ }
+}
diff --git a/pkg/flowcli/services/transactions.go b/pkg/flowcli/services/transactions.go
new file mode 100644
index 000000000..2cd1ee8f0
--- /dev/null
+++ b/pkg/flowcli/services/transactions.go
@@ -0,0 +1,192 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+
+ "github.com/onflow/flow-cli/pkg/flowcli"
+ "github.com/onflow/flow-cli/pkg/flowcli/config"
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/util"
+)
+
+// Transactions is a service that handles all transaction-related interactions.
+type Transactions struct {
+ gateway gateway.Gateway
+ project *project.Project
+ logger output.Logger
+}
+
+// NewTransactions returns a new transactions service.
+func NewTransactions(
+ gateway gateway.Gateway,
+ project *project.Project,
+ logger output.Logger,
+) *Transactions {
+ return &Transactions{
+ gateway: gateway,
+ project: project,
+ logger: logger,
+ }
+}
+
+// Send sends a transaction from a file.
+func (t *Transactions) Send(
+ transactionFilename string,
+ signerName string,
+ args []string,
+ argsJSON string,
+) (*flow.Transaction, *flow.TransactionResult, error) {
+ if t.project == nil {
+ return nil, nil, fmt.Errorf("missing configuration, initialize it: flow init")
+ }
+
+ signer := t.project.AccountByName(signerName)
+ if signer == nil {
+ return nil, nil, fmt.Errorf("signer account: [%s] doesn't exists in configuration", signerName)
+ }
+
+ code, err := util.LoadFile(transactionFilename)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return t.send(code, signer, args, argsJSON)
+}
+
+// SendForAddress send transaction for address and private key, code passed via filename
+func (t *Transactions) SendForAddress(
+ transactionFilename string,
+ signerAddress string,
+ signerPrivateKey string,
+ args []string,
+ argsJSON string,
+) (*flow.Transaction, *flow.TransactionResult, error) {
+ code, err := util.LoadFile(transactionFilename)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return t.SendForAddressWithCode(code, signerAddress, signerPrivateKey, args, argsJSON)
+}
+
+// SendForAddressWithCode send transaction for address and private key, code passed via byte array
+func (t *Transactions) SendForAddressWithCode(
+ code []byte,
+ signerAddress string,
+ signerPrivateKey string,
+ args []string,
+ argsJSON string,
+) (*flow.Transaction, *flow.TransactionResult, error) {
+ address := flow.HexToAddress(signerAddress)
+
+ privateKey, err := crypto.DecodePrivateKeyHex(crypto.ECDSA_P256, signerPrivateKey)
+ if err != nil {
+ return nil, nil, fmt.Errorf("private key is not correct")
+ }
+
+ account := project.AccountFromAddressAndKey(address, privateKey)
+
+ return t.send(code, account, args, argsJSON)
+}
+
+func (t *Transactions) send(
+ code []byte,
+ signer *project.Account,
+ args []string,
+ argsJSON string,
+) (*flow.Transaction, *flow.TransactionResult, error) {
+
+ // if google kms account then sign in
+ // TODO discuss refactor - move to account
+ if signer.DefaultKey().Type() == config.KeyTypeGoogleKMS {
+ resourceID := signer.DefaultKey().ToConfig().Context[config.KMSContextField]
+ err := util.GcloudApplicationSignin(resourceID)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ t.logger.StartProgress("Sending transaction...")
+
+ tx := flow.NewTransaction().
+ SetScript(code).
+ AddAuthorizer(signer.Address())
+
+ transactionArguments, err := flowcli.ParseArguments(args, argsJSON)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ for _, arg := range transactionArguments {
+ err := tx.AddArgument(arg)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to add %s argument to a transaction", arg)
+ }
+ }
+
+ t.logger.Info(fmt.Sprintf("Sending transaction with ID %s", tx.ID()))
+
+ tx, err = t.gateway.SendTransaction(tx, signer)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ t.logger.StartProgress("Waiting for transaction to be sealed...")
+
+ res, err := t.gateway.GetTransactionResult(tx, true)
+
+ t.logger.StopProgress("")
+
+ return tx, res, err
+}
+
+// GetStatus of transaction
+func (t *Transactions) GetStatus(
+ transactionID string,
+ waitSeal bool,
+) (*flow.Transaction, *flow.TransactionResult, error) {
+ txID := flow.HexToID(
+ strings.ReplaceAll(transactionID, "0x", ""),
+ )
+
+ t.logger.StartProgress("Fetching Transaction...")
+
+ tx, err := t.gateway.GetTransaction(txID)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if waitSeal {
+ t.logger.StartProgress("Waiting for transaction to be sealed...")
+ }
+
+ result, err := t.gateway.GetTransactionResult(tx, waitSeal)
+
+ t.logger.StopProgress("")
+
+ return tx, result, err
+}
diff --git a/pkg/flowcli/services/transactions_test.go b/pkg/flowcli/services/transactions_test.go
new file mode 100644
index 000000000..4893822ab
--- /dev/null
+++ b/pkg/flowcli/services/transactions_test.go
@@ -0,0 +1,127 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 services
+
+import (
+ "testing"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/tests"
+)
+
+func TestTransactions(t *testing.T) {
+ mock := &tests.MockGateway{}
+
+ proj, err := project.Init(crypto.ECDSA_P256, crypto.SHA3_256)
+ assert.NoError(t, err)
+
+ transactions := NewTransactions(mock, proj, output.NewStdoutLogger(output.NoneLog))
+
+ t.Run("Get Transaction", func(t *testing.T) {
+ called := 0
+ txs := tests.NewTransaction()
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ called++
+ assert.Equal(t, tx.ID().String(), txs.ID().String())
+ return tests.NewTransactionResult(nil), nil
+ }
+
+ mock.GetTransactionMock = func(id flow.Identifier) (*flow.Transaction, error) {
+ called++
+ return txs, nil
+ }
+
+ _, _, err := transactions.GetStatus(txs.ID().String(), true)
+
+ assert.NoError(t, err)
+ assert.Equal(t, called, 2)
+ })
+
+ t.Run("Send Transaction args", func(t *testing.T) {
+ called := 0
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ called++
+ return tests.NewTransactionResult(nil), nil
+ }
+
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ called++
+ arg, err := tx.Argument(0)
+
+ assert.NoError(t, err)
+ assert.Equal(t, arg.String(), "\"Bar\"")
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+ assert.Equal(t, len(string(tx.Script)), 209)
+ return tests.NewTransaction(), nil
+ }
+
+ _, _, err := transactions.Send("../../../tests/transaction.cdc", serviceName, []string{"String:Bar"}, "")
+
+ assert.NoError(t, err)
+ assert.Equal(t, called, 2)
+ })
+
+ t.Run("Send Transaction JSON args", func(t *testing.T) {
+ called := 0
+
+ mock.GetTransactionResultMock = func(tx *flow.Transaction) (*flow.TransactionResult, error) {
+ called++
+ return tests.NewTransactionResult(nil), nil
+ }
+
+ mock.SendTransactionMock = func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ called++
+ assert.Equal(t, signer.Address().String(), serviceAddress)
+ assert.Equal(t, len(string(tx.Script)), 209)
+ return tests.NewTransaction(), nil
+ }
+
+ _, _, err := transactions.Send(
+ "../../../tests/transaction.cdc",
+ serviceName,
+ nil,
+ "[{\"type\": \"String\", \"value\": \"Bar\"}]",
+ )
+
+ assert.NoError(t, err)
+ assert.Equal(t, called, 2)
+ })
+
+ t.Run("Send Transaction Fails wrong args", func(t *testing.T) {
+ _, _, err := transactions.Send("../../../tests/transaction.cdc", serviceName, []string{"Bar"}, "")
+ assert.Equal(t, err.Error(), "argument not passed in correct format, correct format is: Type:Value, got Bar")
+ })
+
+ t.Run("Send Transaction Fails wrong filename", func(t *testing.T) {
+ _, _, err := transactions.Send("nooo.cdc", serviceName, []string{"Bar"}, "")
+ assert.Equal(t, err.Error(), "Failed to load file: nooo.cdc")
+ })
+
+ t.Run("Send Transaction Fails wrong args", func(t *testing.T) {
+ _, _, err := transactions.Send("../../../tests/transaction.cdc", serviceName, nil, "[{\"Bar\":\"No\"}]")
+ assert.Equal(t, err.Error(), "failed to decode value: invalid JSON Cadence structure")
+ })
+}
diff --git a/flow/cli.go b/pkg/flowcli/util/cli.go
similarity index 56%
rename from flow/cli.go
rename to pkg/flowcli/util/cli.go
index e859005b3..c79238c6c 100644
--- a/flow/cli.go
+++ b/pkg/flowcli/util/cli.go
@@ -17,7 +17,7 @@
*/
// Package cli defines constants, configurations, and utilities that are used across the Flow CLI.
-package cli
+package util
import (
"crypto/rand"
@@ -26,20 +26,13 @@ import (
"os/exec"
"regexp"
- "github.com/onflow/flow-go-sdk/crypto"
+ "github.com/onflow/flow-go-sdk/crypto/cloudkms"
)
const (
- EnvPrefix = "FLOW"
- DefaultSigAlgo = crypto.ECDSA_P256
- DefaultHashAlgo = crypto.SHA3_256
- DefaultHost = "127.0.0.1:3569"
- MaxGRPCMessageSize = 1024 * 1024 * 16
- Indent = " "
+ EnvPrefix = "FLOW"
)
-var ConfigPath = []string{"flow.json"}
-
func Exit(code int, msg string) {
fmt.Println(msg)
os.Exit(code)
@@ -50,31 +43,15 @@ func Exitf(code int, msg string, args ...interface{}) {
os.Exit(code)
}
-func MustDecodePrivateKeyHex(sigAlgo crypto.SignatureAlgorithm, prKeyHex string) crypto.PrivateKey {
- prKey, err := crypto.DecodePrivateKeyHex(sigAlgo, prKeyHex)
- if err != nil {
- Exitf(1, "Failed to decode private key: %v", err)
- }
- return prKey
-}
-
-func MustDecodePublicKeyHex(sigAlgo crypto.SignatureAlgorithm, pubKeyHex string) crypto.PublicKey {
- pubKey, err := crypto.DecodePublicKeyHex(sigAlgo, pubKeyHex)
- if err != nil {
- Exitf(1, "Failed to decode public key: %v", err)
- }
- return pubKey
-}
-
-func RandomSeed(n int) []byte {
+func RandomSeed(n int) ([]byte, error) {
seed := make([]byte, n)
_, err := rand.Read(seed)
if err != nil {
- Exitf(1, "Failed to generate random seed: %v", err)
+ return nil, fmt.Errorf("failed to generate random seed: %v", err)
}
- return seed
+ return seed, nil
}
var squareBracketRegex = regexp.MustCompile(`(?s)\[(.*)\]`)
@@ -82,24 +59,36 @@ var squareBracketRegex = regexp.MustCompile(`(?s)\[(.*)\]`)
// GcloudApplicationSignin signs in as an application user using gcloud command line tool
// currently assumes gcloud is already installed on the machine
// will by default pop a browser window to sign in
-func GcloudApplicationSignin(project string) {
- // os.Exec(fmt.Sprintf("gcloud auth application-default login --%s"))
- loginCmd := exec.Command("gcloud", "auth", "application-default", "login", fmt.Sprintf("--project=%s", project))
+func GcloudApplicationSignin(resourceID string) error {
+ googleAppCreds := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
+ if len(googleAppCreds) > 0 {
+ return nil
+ }
+
+ kms, err := cloudkms.KeyFromResourceID(resourceID)
+ if err != nil {
+ return err
+ }
+
+ proj := kms.ProjectID
+ if len(proj) == 0 {
+ return fmt.Errorf(
+ "could not get GOOGLE_APPLICATION_CREDENTIALS, no google service account JSON provided but private key type is KMS",
+ )
+ }
+
+ loginCmd := exec.Command("gcloud", "auth", "application-default", "login", fmt.Sprintf("--project=%s", proj))
output, err := loginCmd.CombinedOutput()
if err != nil {
- Exitf(1, "Failed to run %q: %s\n", loginCmd.String(), err)
+ return fmt.Errorf("Failed to run %q: %s\n", loginCmd.String(), err)
}
+
regexResult := squareBracketRegex.FindAllStringSubmatch(string(output), -1)
// Should only be one value. Second index since first index contains the square brackets
googleApplicationCreds := regexResult[0][1]
- fmt.Printf("Saving credentials and setting GOOGLE_APPLICATION_CREDENTIALS to file: %s\n", googleApplicationCreds)
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", googleApplicationCreds)
-}
-func PrintIndent(numberOfIndents int) {
- for i := 0; i < numberOfIndents; i++ {
- fmt.Print(Indent)
- }
+ return nil
}
diff --git a/flow/env.go b/pkg/flowcli/util/env.go
similarity index 99%
rename from flow/env.go
rename to pkg/flowcli/util/env.go
index b8c02dd3b..c51580b8e 100644
--- a/flow/env.go
+++ b/pkg/flowcli/util/env.go
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-package cli
+package util
import (
"github.com/onflow/flow-core-contracts/lib/go/templates"
diff --git a/pkg/flowcli/util/utilities.go b/pkg/flowcli/util/utilities.go
new file mode 100644
index 000000000..da14ec420
--- /dev/null
+++ b/pkg/flowcli/util/utilities.go
@@ -0,0 +1,106 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 util
+
+import (
+ "fmt"
+ "io/ioutil"
+
+ "github.com/fatih/color"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/crypto"
+)
+
+var Green = color.New(color.FgGreen, color.Bold).SprintfFunc()
+var Red = color.New(color.FgRed, color.Bold).SprintfFunc()
+var Bold = color.New(color.Bold).SprintfFunc()
+
+// LoadFile loads a file by filename.
+func LoadFile(filename string) ([]byte, error) {
+ var code []byte
+ var err error
+
+ if filename != "" {
+ code, err = ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to load file: %s", filename)
+ }
+ }
+
+ return code, nil
+}
+
+func IsByteSlice(v interface{}) bool {
+ slice, isSlice := v.([]interface{})
+ if !isSlice {
+ return false
+ }
+ _, isBytes := slice[0].(byte)
+ return isBytes
+}
+
+// ConvertSigAndHashAlgo parses and validates a signature and hash algorithm pair.
+func ConvertSigAndHashAlgo(
+ signatureAlgorithm string,
+ hashingAlgorithm string,
+) (crypto.SignatureAlgorithm, crypto.HashAlgorithm, error) {
+ sigAlgo := crypto.StringToSignatureAlgorithm(signatureAlgorithm)
+ if sigAlgo == crypto.UnknownSignatureAlgorithm {
+ return crypto.UnknownSignatureAlgorithm,
+ crypto.UnknownHashAlgorithm,
+ fmt.Errorf("failed to determine signature algorithm from %s", sigAlgo)
+ }
+
+ hashAlgo := crypto.StringToHashAlgorithm(hashingAlgorithm)
+ if hashAlgo == crypto.UnknownHashAlgorithm {
+ return crypto.UnknownSignatureAlgorithm,
+ crypto.UnknownHashAlgorithm,
+ fmt.Errorf("failed to determine hash algorithm from %s", hashAlgo)
+ }
+
+ return sigAlgo, hashAlgo, nil
+}
+
+// StringContains returns true if the slice contains the given string.
+func StringContains(s []string, e string) bool {
+ for _, a := range s {
+ if a == e {
+ return true
+ }
+ }
+
+ return false
+}
+
+// GetAddressNetwork returns the chain ID for an address.
+func GetAddressNetwork(address flow.Address) (flow.ChainID, error) {
+ networks := []flow.ChainID{
+ flow.Mainnet,
+ flow.Testnet,
+ flow.Emulator,
+ }
+ for _, net := range networks {
+ if address.IsValid(net) {
+ return net, nil
+ }
+ }
+
+ return "", fmt.Errorf("address not valid for any known chain: %s", address)
+}
diff --git a/flow/collections.go b/pkg/flowcli/values.go
similarity index 54%
rename from flow/collections.go
rename to pkg/flowcli/values.go
index 7476ab1f1..b9301f3d9 100644
--- a/flow/collections.go
+++ b/pkg/flowcli/values.go
@@ -16,27 +16,28 @@
* limitations under the License.
*/
-package cli
+package flowcli
import (
- "context"
-
- "github.com/onflow/flow-go-sdk"
- "github.com/onflow/flow-go-sdk/client"
- "google.golang.org/grpc"
+ "github.com/onflow/cadence"
)
-func GetCollectionByID(host string, collectionID flow.Identifier) *flow.Collection {
- ctx := context.Background()
+func NewStakingInfoFromValue(value cadence.Value) map[string]interface{} {
+ stakingInfo := make(map[string]interface{})
+ arrayValue := value.(cadence.Array)
+
+ if len(arrayValue.Values) == 0 {
+ return stakingInfo
+ }
- flowClient, err := client.New(host, grpc.WithInsecure())
- if err != nil {
- Exitf(1, "Failed to connect to host: %s", err)
+ keys := make([]string, 0)
+ for _, field := range arrayValue.Values[0].(cadence.Struct).StructType.Fields {
+ keys = append(keys, field.Identifier)
}
- collection, err := flowClient.GetCollection(ctx, collectionID)
- if err != nil {
- Exitf(1, "Failed to retrieve collection by ID %s: %s", collectionID, err)
+ for i, value := range arrayValue.Values[0].(cadence.Struct).Fields {
+ stakingInfo[keys[i]] = value
}
- return collection
+
+ return stakingInfo
}
diff --git a/tests/Foo.cdc b/tests/Foo.cdc
new file mode 100644
index 000000000..0cd5410cc
--- /dev/null
+++ b/tests/Foo.cdc
@@ -0,0 +1,6 @@
+import NonFungibleToken from "./NonFungibleToken.cdc"
+import FungibleToken from "./FungibleToken.cdc"
+
+pub contract Foo {
+ init() {}
+}
diff --git a/tests/FungibleToken.cdc b/tests/FungibleToken.cdc
new file mode 100644
index 000000000..eca373794
--- /dev/null
+++ b/tests/FungibleToken.cdc
@@ -0,0 +1,199 @@
+/**
+
+# The Flow Fungible Token standard
+
+## `FungibleToken` contract interface
+
+The interface that all fungible token contracts would have to conform to.
+If a users wants to deploy a new token contract, their contract
+would need to implement the FungibleToken interface.
+
+Their contract would have to follow all the rules and naming
+that the interface specifies.
+
+## `Vault` resource
+
+Each account that owns tokens would need to have an instance
+of the Vault resource stored in their account storage.
+
+The Vault resource has methods that the owner and other users can call.
+
+## `Provider`, `Receiver`, and `Balance` resource interfaces
+
+These interfaces declare pre-conditions and post-conditions that restrict
+the execution of the functions in the Vault.
+
+They are separate because it gives the user the ability to share
+a reference to their Vault that only exposes the fields functions
+in one or more of the interfaces.
+
+It also gives users the ability to make custom resources that implement
+these interfaces to do various things with the tokens.
+For example, a faucet can be implemented by conforming
+to the Provider interface.
+
+By using resources and interfaces, users of FungibleToken contracts
+can send and receive tokens peer-to-peer, without having to interact
+with a central ledger smart contract. To send tokens to another user,
+a user would simply withdraw the tokens from their Vault, then call
+the deposit function on another user's Vault to complete the transfer.
+
+*/
+
+/// FungibleToken
+///
+/// The interface that fungible token contracts implement.
+///
+pub contract interface FungibleToken {
+
+ /// The total number of tokens in existence.
+ /// It is up to the implementer to ensure that the total supply
+ /// stays accurate and up to date
+ ///
+ pub var totalSupply: UFix64
+
+ /// TokensInitialized
+ ///
+ /// The event that is emitted when the contract is created
+ ///
+ pub event TokensInitialized(initialSupply: UFix64)
+
+ /// TokensWithdrawn
+ ///
+ /// The event that is emitted when tokens are withdrawn from a Vault
+ ///
+ pub event TokensWithdrawn(amount: UFix64, from: Address?)
+
+ /// TokensDeposited
+ ///
+ /// The event that is emitted when tokens are deposited into a Vault
+ ///
+ pub event TokensDeposited(amount: UFix64, to: Address?)
+
+ /// Provider
+ ///
+ /// The interface that enforces the requirements for withdrawing
+ /// tokens from the implementing type.
+ ///
+ /// It does not enforce requirements on `balance` here,
+ /// because it leaves open the possibility of creating custom providers
+ /// that do not necessarily need their own balance.
+ ///
+ pub resource interface Provider {
+
+ /// withdraw subtracts tokens from the owner's Vault
+ /// and returns a Vault with the removed tokens.
+ ///
+ /// The function's access level is public, but this is not a problem
+ /// because only the owner storing the resource in their account
+ /// can initially call this function.
+ ///
+ /// The owner may grant other accounts access by creating a private
+ /// capability that allows specific other users to access
+ /// the provider resource through a reference.
+ ///
+ /// The owner may also grant all accounts access by creating a public
+ /// capability that allows all users to access the provider
+ /// resource through a reference.
+ ///
+ pub fun withdraw(amount: UFix64): @Vault {
+ post {
+ // `result` refers to the return value
+ result.balance == amount:
+ "Withdrawal amount must be the same as the balance of the withdrawn Vault"
+ }
+ }
+ }
+
+ /// Receiver
+ ///
+ /// The interface that enforces the requirements for depositing
+ /// tokens into the implementing type.
+ ///
+ /// We do not include a condition that checks the balance because
+ /// we want to give users the ability to make custom receivers that
+ /// can do custom things with the tokens, like split them up and
+ /// send them to different places.
+ ///
+ pub resource interface Receiver {
+
+ /// deposit takes a Vault and deposits it into the implementing resource type
+ ///
+ pub fun deposit(from: @Vault)
+ }
+
+ /// Balance
+ ///
+ /// The interface that contains the `balance` field of the Vault
+ /// and enforces that when new Vaults are created, the balance
+ /// is initialized correctly.
+ ///
+ pub resource interface Balance {
+
+ /// The total balance of a vault
+ ///
+ pub var balance: UFix64
+
+ init(balance: UFix64) {
+ post {
+ self.balance == balance:
+ "Balance must be initialized to the initial balance"
+ }
+ }
+ }
+
+ /// Vault
+ ///
+ /// The resource that contains the functions to send and receive tokens.
+ ///
+ pub resource Vault: Provider, Receiver, Balance {
+
+ // The declaration of a concrete type in a contract interface means that
+ // every Fungible Token contract that implements the FungibleToken interface
+ // must define a concrete `Vault` resource that conforms to the `Provider`, `Receiver`,
+ // and `Balance` interfaces, and declares their required fields and functions
+
+ /// The total balance of the vault
+ ///
+ pub var balance: UFix64
+
+ // The conforming type must declare an initializer
+ // that allows prioviding the initial balance of the Vault
+ //
+ init(balance: UFix64)
+
+ /// withdraw subtracts `amount` from the Vault's balance
+ /// and returns a new Vault with the subtracted balance
+ ///
+ pub fun withdraw(amount: UFix64): @Vault {
+ pre {
+ self.balance >= amount:
+ "Amount withdrawn must be less than or equal than the balance of the Vault"
+ }
+ post {
+ // use the special function `before` to get the value of the `balance` field
+ // at the beginning of the function execution
+ //
+ self.balance == before(self.balance) - amount:
+ "New Vault balance must be the difference of the previous balance and the withdrawn Vault"
+ }
+ }
+
+ /// deposit takes a Vault and adds its balance to the balance of this Vault
+ ///
+ pub fun deposit(from: @Vault) {
+ post {
+ self.balance == before(self.balance) + before(from.balance):
+ "New Vault balance must be the sum of the previous balance and the deposited Vault"
+ }
+ }
+ }
+
+ /// createEmptyVault allows any user to create a new Vault that has a zero balance
+ ///
+ pub fun createEmptyVault(): @Vault {
+ post {
+ result.balance == 0.0: "The newly created Vault must have zero balance"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Hello.cdc b/tests/Hello.cdc
new file mode 100644
index 000000000..d0cf95897
--- /dev/null
+++ b/tests/Hello.cdc
@@ -0,0 +1,9 @@
+pub contract Hello {
+ pub let greeting: String
+ init() {
+ self.greeting = "Hello, World!"
+ }
+ pub fun hello(): String {
+ return self.greeting
+ }
+}
diff --git a/tests/NonFungibleToken.cdc b/tests/NonFungibleToken.cdc
new file mode 100644
index 000000000..8b8cd27b2
--- /dev/null
+++ b/tests/NonFungibleToken.cdc
@@ -0,0 +1,144 @@
+/**
+
+## The Flow Non-Fungible Token standard
+
+## `NonFungibleToken` contract interface
+
+The interface that all non-fungible token contracts could conform to.
+If a user wants to deploy a new nft contract, their contract would need
+to implement the NonFungibleToken interface.
+
+Their contract would have to follow all the rules and naming
+that the interface specifies.
+
+## `NFT` resource
+
+The core resource type that represents an NFT in the smart contract.
+
+## `Collection` Resource
+
+The resource that stores a user's NFT collection.
+It includes a few functions to allow the owner to easily
+move tokens in and out of the collection.
+
+## `Provider` and `Receiver` resource interfaces
+
+These interfaces declare functions with some pre and post conditions
+that require the Collection to follow certain naming and behavior standards.
+
+They are separate because it gives the user the ability to share a reference
+to their Collection that only exposes the fields and functions in one or more
+of the interfaces. It also gives users the ability to make custom resources
+that implement these interfaces to do various things with the tokens.
+
+By using resources and interfaces, users of NFT smart contracts can send
+and receive tokens peer-to-peer, without having to interact with a central ledger
+smart contract.
+
+To send an NFT to another user, a user would simply withdraw the NFT
+from their Collection, then call the deposit function on another user's
+Collection to complete the transfer.
+
+*/
+
+// The main NFT contract interface. Other NFT contracts will
+// import and implement this interface
+//
+pub contract interface NonFungibleToken {
+
+ // The total number of tokens of this type in existence
+ pub var totalSupply: UInt64
+
+ // Event that emitted when the NFT contract is initialized
+ //
+ pub event ContractInitialized()
+
+ // Event that is emitted when a token is withdrawn,
+ // indicating the owner of the collection that it was withdrawn from.
+ //
+ // If the collection is not in an account's storage, `from` will be `nil`.
+ //
+ pub event Withdraw(id: UInt64, from: Address?)
+
+ // Event that emitted when a token is deposited to a collection.
+ //
+ // It indicates the owner of the collection that it was deposited to.
+ //
+ pub event Deposit(id: UInt64, to: Address?)
+
+ // Interface that the NFTs have to conform to
+ //
+ pub resource interface INFT {
+ // The unique ID that each NFT has
+ pub let id: UInt64
+ }
+
+ // Requirement that all conforming NFT smart contracts have
+ // to define a resource called NFT that conforms to INFT
+ pub resource NFT: INFT {
+ pub let id: UInt64
+ }
+
+ // Interface to mediate withdraws from the Collection
+ //
+ pub resource interface Provider {
+ // withdraw removes an NFT from the collection and moves it to the caller
+ pub fun withdraw(withdrawID: UInt64): @NFT {
+ post {
+ result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID"
+ }
+ }
+ }
+
+ // Interface to mediate deposits to the Collection
+ //
+ pub resource interface Receiver {
+
+ // deposit takes an NFT as an argument and adds it to the Collection
+ //
+ pub fun deposit(token: @NFT)
+ }
+
+ // Interface that an account would commonly
+ // publish for their collection
+ pub resource interface CollectionPublic {
+ pub fun deposit(token: @NFT)
+ pub fun getIDs(): [UInt64]
+ pub fun borrowNFT(id: UInt64): &NFT
+ }
+
+ // Requirement for the the concrete resource type
+ // to be declared in the implementing contract
+ //
+ pub resource Collection: Provider, Receiver, CollectionPublic {
+
+ // Dictionary to hold the NFTs in the Collection
+ pub var ownedNFTs: @{UInt64: NFT}
+
+ // withdraw removes an NFT from the collection and moves it to the caller
+ pub fun withdraw(withdrawID: UInt64): @NFT
+
+ // deposit takes a NFT and adds it to the collections dictionary
+ // and adds the ID to the id array
+ pub fun deposit(token: @NFT)
+
+ // getIDs returns an array of the IDs that are in the collection
+ pub fun getIDs(): [UInt64]
+
+ // Returns a borrowed reference to an NFT in the collection
+ // so that the caller can read data and call methods from it
+ pub fun borrowNFT(id: UInt64): &NFT {
+ pre {
+ self.ownedNFTs[id] != nil: "NFT does not exist in the collection!"
+ }
+ }
+ }
+
+ // createEmptyCollection creates an empty Collection
+ // and returns it to the caller so that they can own NFTs
+ pub fun createEmptyCollection(): @Collection {
+ post {
+ result.getIDs().length == 0: "The created collection must be empty!"
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/e2e_test.go b/tests/e2e_test.go
new file mode 100644
index 000000000..ed0d6db39
--- /dev/null
+++ b/tests/e2e_test.go
@@ -0,0 +1,263 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 tests
+
+import (
+ "os"
+ "testing"
+
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go/utils/io"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/output"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+ "github.com/onflow/flow-cli/pkg/flowcli/services"
+)
+
+const (
+ serviceAddress = "f8d6e0586b0a20c7"
+ contractPath = "./Hello.cdc"
+ emulatorAccount = "emulator-account"
+ host = "127.0.0.1:3569"
+ conf = "./flow.json"
+)
+
+var logger = output.NewStdoutLogger(output.NoneLog)
+var e2e = os.Getenv("E2E")
+
+func TestAccount(t *testing.T) {
+ if e2e == "" {
+ t.Skip("Skipping end-to-end tests")
+ }
+
+ helloContract, _ := io.ReadFile(contractPath)
+
+ gw, err := gateway.NewGrpcGateway(host)
+ assert.NoError(t, err)
+
+ project, err := project.Load([]string{conf})
+ assert.NoError(t, err)
+
+ accounts := services.NewAccounts(gw, project, logger)
+
+ t.Run("Get an Account", func(t *testing.T) {
+ account, err := accounts.Get(serviceAddress)
+
+ assert.NoError(t, err)
+ assert.Equal(t, account.Address.String(), serviceAddress)
+ })
+
+ t.Run("Creates an Account", func(t *testing.T) {
+ keys := []string{"0x640a5a359bf3536d15192f18d872d57c98a96cb871b92b70cecb0739c2d5c37b4be12548d3526933c2cda9b0b9c69412f45ffb6b85b6840d8569d969fe84e5b7"}
+ account, err := accounts.Create(
+ emulatorAccount,
+ keys,
+ "ECDSA_P256",
+ "SHA3_256",
+ []string{},
+ )
+
+ assert.NoError(t, err)
+ assert.Equal(t, account.Keys[0].PublicKey.String(), keys[0])
+ assert.Equal(t, string(account.Code), "")
+ })
+
+ t.Run("Account Add Contract", func(t *testing.T) {
+ acc, err := accounts.AddContract(
+ emulatorAccount,
+ "Hello",
+ contractPath,
+ false,
+ )
+
+ assert.NoError(t, err)
+ assert.Equal(t, string(acc.Contracts["Hello"]), string(helloContract))
+ })
+
+ t.Run("Account Update Contract", func(t *testing.T) {
+ acc, err := accounts.AddContract(
+ emulatorAccount,
+ "Hello",
+ contractPath,
+ true,
+ )
+
+ assert.NoError(t, err)
+ assert.Equal(t, string(acc.Contracts["Hello"]), string(helloContract))
+ })
+
+ t.Run("Account Update Contract", func(t *testing.T) {
+ acc, err := accounts.AddContract(
+ emulatorAccount,
+ "Hello",
+ contractPath,
+ true,
+ )
+
+ assert.NoError(t, err)
+ assert.Equal(t, string(acc.Contracts["Hello"]), string(helloContract))
+ })
+
+ t.Run("Account Remove Contract", func(t *testing.T) {
+ acc, err := accounts.RemoveContract("Hello", emulatorAccount)
+
+ assert.NoError(t, err)
+ assert.Equal(t, string(acc.Contracts["Hello"]), "")
+ })
+
+}
+
+func TestEvents(t *testing.T) {
+ if e2e == "" {
+ t.Skip("Skipping end-to-end tests")
+ }
+
+ gw, err := gateway.NewGrpcGateway(host)
+ assert.NoError(t, err)
+
+ project, err := project.Load([]string{conf})
+ assert.NoError(t, err)
+
+ events := services.NewEvents(gw, project, logger)
+
+ t.Run("Get Event", func(t *testing.T) {
+ event, err := events.Get("flow.createAccount", "0", "100")
+
+ assert.NoError(t, err)
+ require.Greater(t, len(event), 0)
+ })
+}
+
+func TestKeys(t *testing.T) {
+ if e2e == "" {
+ t.Skip("Skipping end-to-end tests")
+ }
+
+ gw, err := gateway.NewGrpcGateway(host)
+ assert.NoError(t, err)
+
+ proj, err := project.Load([]string{conf})
+ assert.NoError(t, err)
+
+ keys := services.NewKeys(gw, proj, logger)
+
+ t.Run("Generate keys", func(t *testing.T) {
+ key, err := keys.Generate("", "ECDSA_P256")
+
+ assert.NoError(t, err)
+ assert.Equal(t, key.Algorithm().String(), "ECDSA_P256")
+ assert.Equal(t, len(key.PublicKey().String()), 130)
+ })
+}
+
+func TestProject(t *testing.T) {
+ if e2e == "" {
+ t.Skip("Skipping end-to-end tests")
+ }
+
+ gw, err := gateway.NewGrpcGateway(host)
+ assert.NoError(t, err)
+
+ project, err := project.Load([]string{conf})
+ assert.NoError(t, err)
+
+ projects := services.NewProject(gw, project, logger)
+
+ t.Run("Deploy project", func(t *testing.T) {
+ contracts, err := projects.Deploy("emulator", true)
+
+ assert.NoError(t, err)
+ assert.Equal(t, contracts[0].Name(), "NonFungibleToken")
+ assert.Equal(t, contracts[1].Name(), "Foo")
+ assert.Equal(t, contracts[1].Dependencies()["./NonFungibleToken.cdc"].Target(), contracts[0].Target())
+ assert.Equal(t, len(contracts), 2)
+ })
+}
+
+func TestScripts(t *testing.T) {
+ if e2e == "" {
+ t.Skip("Skipping end-to-end tests")
+ }
+
+ gateway, err := gateway.NewGrpcGateway(host)
+ assert.NoError(t, err)
+
+ project, err := project.Load([]string{conf})
+ assert.NoError(t, err)
+
+ scripts := services.NewScripts(gateway, project, logger)
+
+ t.Run("Test Script", func(t *testing.T) {
+ val, err := scripts.Execute("./script.cdc", []string{"String:Mr G"}, "")
+
+ assert.NoError(t, err)
+ assert.Equal(t, val.String(), `"Hello Mr G"`)
+ })
+
+ t.Run("Test Script JSON args", func(t *testing.T) {
+ val, err := scripts.Execute("./script.cdc", []string{}, "[{\"type\": \"String\", \"value\": \"Mr G\"}]")
+
+ assert.NoError(t, err)
+ assert.Equal(t, val.String(), `"Hello Mr G"`)
+ })
+}
+
+func TestTransactions(t *testing.T) {
+ if e2e == "" {
+ t.Skip("Skipping end-to-end tests")
+ }
+
+ gw, err := gateway.NewGrpcGateway(host)
+ assert.NoError(t, err)
+
+ project, err := project.Load([]string{conf})
+ assert.NoError(t, err)
+
+ transactions := services.NewTransactions(gw, project, logger)
+ var txID1 flow.Identifier
+
+ t.Run("Test Transactions", func(t *testing.T) {
+ tx, tr, err := transactions.Send("./transaction.cdc", emulatorAccount, []string{"String:Hello"}, "")
+ txID1 = tx.ID()
+
+ assert.NoError(t, err)
+ assert.Equal(t, tx.Payer.String(), serviceAddress)
+ assert.Equal(t, tr.Status.String(), "SEALED")
+ })
+
+ t.Run("Test Failed Transactions", func(t *testing.T) {
+ tx, tr, err := transactions.Send("./transactionErr.cdc", emulatorAccount, []string{}, "")
+
+ assert.NoError(t, err)
+ assert.Equal(t, tx.Payer.String(), serviceAddress)
+ assert.Equal(t, tr.Status.String(), "SEALED")
+ require.Greater(t, len(tr.Error.Error()), 100)
+ })
+
+ t.Run("Get Transaction", func(t *testing.T) {
+ tx, tr, err := transactions.GetStatus(txID1.Hex(), true)
+
+ assert.NoError(t, err)
+ assert.Equal(t, tx.Payer.String(), serviceAddress)
+ assert.Equal(t, tr.Status.String(), "SEALED")
+ })
+}
diff --git a/tests/fixtures.go b/tests/fixtures.go
new file mode 100644
index 000000000..02896c26f
--- /dev/null
+++ b/tests/fixtures.go
@@ -0,0 +1,99 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 tests
+
+import (
+ "github.com/onflow/cadence"
+ "github.com/onflow/cadence/runtime/common"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/test"
+)
+
+var accounts = test.AccountGenerator()
+var transactions = test.TransactionGenerator()
+var transactionResults = test.TransactionResultGenerator()
+
+func NewAccountWithAddress(address string) *flow.Account {
+ account := accounts.New()
+ account.Address = flow.HexToAddress(address)
+ return account
+}
+
+func NewTransaction() *flow.Transaction {
+ return transactions.New()
+}
+
+func NewBlock() *flow.Block {
+ return test.BlockGenerator().New()
+}
+
+func NewCollection() *flow.Collection {
+ return test.CollectionGenerator().New()
+}
+
+func NewEvent(index int, eventId string, fields []cadence.Field, values []cadence.Value) *flow.Event {
+ location := common.StringLocation("test")
+
+ testEventType := &cadence.EventType{
+ Location: location,
+ QualifiedIdentifier: eventId,
+ Fields: fields,
+ }
+
+ testEvent := cadence.
+ NewEvent(values).
+ WithType(testEventType)
+
+ typeID := location.TypeID(eventId)
+
+ event := flow.Event{
+ Type: string(typeID),
+ TransactionID: flow.Identifier{},
+ TransactionIndex: index,
+ EventIndex: index,
+ Value: testEvent,
+ }
+
+ return &event
+}
+
+func NewTransactionResult(events []flow.Event) *flow.TransactionResult {
+ res := transactionResults.New()
+ res.Events = events
+ res.Error = nil
+
+ return &res
+}
+
+func NewAccountCreateResult(address string) *flow.TransactionResult {
+ events := []flow.Event{
+ *NewEvent(0,
+ "flow.AccountCreated",
+ []cadence.Field{{
+ Identifier: "address",
+ Type: cadence.AddressType{},
+ }},
+ []cadence.Value{
+ cadence.NewString(address),
+ },
+ ),
+ }
+
+ return NewTransactionResult(events)
+}
diff --git a/tests/flow.json b/tests/flow.json
new file mode 100644
index 000000000..8389216b8
--- /dev/null
+++ b/tests/flow.json
@@ -0,0 +1,36 @@
+{
+ "emulators": {
+ "default": {
+ "port": 3569,
+ "serviceAccount": "emulator-account"
+ }
+ },
+ "contracts": {
+ "NonFungibleToken": "./NonFungibleToken.cdc",
+ "Foo": "./Foo.cdc",
+ "FungibleToken": {
+ "source": "./FungibleToken.cdc",
+ "aliases": {
+ "emulator": "ee82856bf20e2aa6"
+ }
+ }
+ },
+ "networks": {
+ "emulator": {
+ "host": "127.0.0.1:3569",
+ "chain": "flow-emulator"
+ }
+ },
+ "accounts": {
+ "emulator-account": {
+ "address": "f8d6e0586b0a20c7",
+ "keys": "11c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+ "chain": "flow-emulator"
+ }
+ },
+ "deployments": {
+ "emulator": {
+ "emulator-account": ["NonFungibleToken", "Foo"]
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/mockGateway.go b/tests/mockGateway.go
new file mode 100644
index 000000000..0a4a08be7
--- /dev/null
+++ b/tests/mockGateway.go
@@ -0,0 +1,85 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * 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 tests
+
+import (
+ "github.com/onflow/cadence"
+ "github.com/onflow/flow-go-sdk"
+ "github.com/onflow/flow-go-sdk/client"
+
+ "github.com/onflow/flow-cli/pkg/flowcli/gateway"
+ "github.com/onflow/flow-cli/pkg/flowcli/project"
+)
+
+type MockGateway struct {
+ GetAccountMock func(address flow.Address) (*flow.Account, error)
+ SendTransactionMock func(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error)
+ GetTransactionResultMock func(tx *flow.Transaction) (*flow.TransactionResult, error)
+ GetTransactionMock func(id flow.Identifier) (*flow.Transaction, error)
+ ExecuteScriptMock func(script []byte, arguments []cadence.Value) (cadence.Value, error)
+ GetLatestBlockMock func() (*flow.Block, error)
+ GetEventsMock func(string, uint64, uint64) ([]client.BlockEvents, error)
+ GetCollectionMock func(id flow.Identifier) (*flow.Collection, error)
+ GetBlockByHeightMock func(uint64) (*flow.Block, error)
+ GetBlockByIDMock func(flow.Identifier) (*flow.Block, error)
+}
+
+func NewMockGateway() gateway.Gateway {
+ return &MockGateway{}
+}
+
+func (g *MockGateway) GetAccount(address flow.Address) (*flow.Account, error) {
+ return g.GetAccountMock(address)
+}
+
+func (g *MockGateway) SendTransaction(tx *flow.Transaction, signer *project.Account) (*flow.Transaction, error) {
+ return g.SendTransactionMock(tx, signer)
+}
+
+func (g *MockGateway) GetTransactionResult(tx *flow.Transaction, waitSeal bool) (*flow.TransactionResult, error) {
+ return g.GetTransactionResultMock(tx)
+}
+
+func (g *MockGateway) GetTransaction(id flow.Identifier) (*flow.Transaction, error) {
+ return g.GetTransactionMock(id)
+}
+
+func (g *MockGateway) ExecuteScript(script []byte, arguments []cadence.Value) (cadence.Value, error) {
+ return g.ExecuteScriptMock(script, arguments)
+}
+
+func (g *MockGateway) GetLatestBlock() (*flow.Block, error) {
+ return g.GetLatestBlockMock()
+}
+
+func (g *MockGateway) GetBlockByID(id flow.Identifier) (*flow.Block, error) {
+ return g.GetBlockByIDMock(id)
+}
+
+func (g *MockGateway) GetBlockByHeight(height uint64) (*flow.Block, error) {
+ return g.GetBlockByHeightMock(height)
+}
+
+func (g *MockGateway) GetEvents(name string, start uint64, end uint64) ([]client.BlockEvents, error) {
+ return g.GetEventsMock(name, start, end)
+}
+
+func (g *MockGateway) GetCollection(id flow.Identifier) (*flow.Collection, error) {
+ return g.GetCollectionMock(id)
+}
diff --git a/tests/script.cdc b/tests/script.cdc
new file mode 100644
index 000000000..3e98f6068
--- /dev/null
+++ b/tests/script.cdc
@@ -0,0 +1,3 @@
+pub fun main(name: String): String {
+ return "Hello ".concat(name)
+}
\ No newline at end of file
diff --git a/tests/transaction.cdc b/tests/transaction.cdc
new file mode 100644
index 000000000..3f4065bbf
--- /dev/null
+++ b/tests/transaction.cdc
@@ -0,0 +1,11 @@
+transaction(greeting: String) {
+ let guest: Address
+
+ prepare(authorizer: AuthAccount) {
+ self.guest = authorizer.address
+ }
+
+ execute {
+ log(greeting.concat(",").concat(self.guest.toString()))
+ }
+}
\ No newline at end of file
diff --git a/tests/transactionErr.cdc b/tests/transactionErr.cdc
new file mode 100644
index 000000000..f3b18563d
--- /dev/null
+++ b/tests/transactionErr.cdc
@@ -0,0 +1,7 @@
+transaction() {
+ prepare(authorizer: AuthAccount) {}
+
+ execute {
+ panic("Error error")
+ }
+}
\ No newline at end of file