Repository: vegaprotocol/vegawallet (go)

Issue:
Add token dApp to network configuration
Some browsers on some operating systems (difficult to pin down exactly what) will not allow connections from https sites to localhost via http. 

Meaning that connecting a website e.g. https://token.vega.xyz to your local wallet can be difficult. This is why the `--console-proxy` option exists and why there is a line in a network config to define where "Console" lives for that network.

However, Console isn't the only thing that people will want to connect from.

Suggestion: allow the user to specify any url (as well as have a default in the network config.
```bash
machine:~ me$ ./vegawallet service run -n fairground -p https://token.fairground.wtf
```
-----
Bonus: In some cases I may wish to connect to more than one URL. I'm not sure if something like this is a goer?

```bash
machine:~ me$ ./vegawallet service run -n fairground -p https://token.fairground.wtf, https://console.fairground.wtf
```

Pull request description:
Support launching the token dApp in `service run` command
Close #408 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3229017..2539d5d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,8 @@
 - [384](https://github.com/vegaprotocol/vegawallet/pull/384) - Remove private key from key generate JSON output
 
 ### 🗑️ Deprecation
-- [418](https://github.com/vegaprotocol/vegawallet/pull/418) - Wallet creation is deprecated with `key generate`, use `create` instead.
+- [418](https://github.com/vegaprotocol/vegawallet/pull/418) - Wallet creation is deprecated with `key generate`, use `create` instead
+- [423](https://github.com/vegaprotocol/vegawallet/pull/423) - Flag `--console-proxy` is deprecated in favor of `--with-console`
 
 ### 🛠 Improvements
 - [383](https://github.com/vegaprotocol/vegawallet/pull/383) - Add wallet version to `key generate` output
@@ -23,6 +24,7 @@
 - [418](https://github.com/vegaprotocol/vegawallet/pull/418) - Add the `create` command to create a wallet
 - [420](https://github.com/vegaprotocol/vegawallet/pull/420) - Verify initialisation state on service run
 - [421](https://github.com/vegaprotocol/vegawallet/pull/421) - Improve help texts
+- [423](https://github.com/vegaprotocol/vegawallet/pull/423) - Support launching the token dApp in `service run` command
 
 ### 🔥 Removal
 - [387](https://github.com/vegaprotocol/vegawallet/pull/387) - Remove network initialisation in favor of `network import`
diff --git a/cmd/README.md b/cmd/README.md
deleted file mode 100644
index 0b8fd3d..0000000
--- a/cmd/README.md
+++ /dev/null
@@ -1,273 +0,0 @@
-# Wallet CLI
-
-## Root flags
-
-By default, the wallet will be stored at a specific location. If you want to
-specify a different location for test or isolation purposes, use the `--home`
-flag to do so.
-
-## List of available commands
-
-There are 3 ways to list the available commands
-
-```sh
-vegawallet
-vegawallet -h
-vegawallet help
-```
-
-## Add auto-completion to your shell
-
-To let you shell auto-complete the commands and flags, you will have to install
-vegawallet completion script generated through the following command:
-
-```sh
-vegawallet completion <SHELL>
-```
-
-The supported shells are: bash, zsh, fish and powershell.
-
-To know how to install the completion script, refer to the help section using:
-```sh
-vegawallet completion --help
-```
-
-## Initialise the software
-
-Before using the wallet, you need to initialise it with the following command:
-
-```sh
-vegawallet init
-```
-
-This creates the folders, the configuration file, the default networks and the
-RSA keys needed by the wallet to operate.
-
-## Create a wallet
-
-To create a new wallet, generate your first key pair using the following
-command:
-
-```sh
-vegawallet key generate --wallet "YOUR_WALLET"
-```
-
-The `--wallet` flag sets the name of your wallet.
-
-It will then prompt you to input a passphrase, and then confirm that passphrase.
-You'll use this username and passphrase to login to Vega Console. You can also
-specify the passphrase with the `--passphrase-file` flag.
-
-You have the opportunity to attach metadata to your key with the ``--meta``
-flag (more on this below).
-
-This command will generate a "mnemonic", along with a public and private key and
-print it on the output.
-
-### Important
-
-**The mnemonic is very important as it acts as a backup key, from which the
-wallet can restore all your keys.** As a result, it has to be kept safe and
-secret. If lost, you won't be able to retrieve your keys. If stolen, the thief
-will be able to use your keys.
-
-Also, you’ll see an output with your public and private key. **Do not share your
-private key.** You don’t need to save this information anywhere, as you’ll be
-able to retrieve it with specific commands.
-
-## Import a wallet
-
-If you want to restore your wallet, use the following command:
-
-```sh
-vegawallet import --wallet "YOUR_WALLET" --mnemonic-file "PATH_TO_YOUR_MNEMONIC"
-```
-
-The flag `--mnemonic-file` is used to locate the file that contains the
-mnemonic.
-
-It will then prompt you to input a passphrase, and then confirm that passphrase.
-You'll use this username and passphrase to login to Vega Console. You can also
-specify the passphrase with the `--passphrase-file` flag.
-
-This command is only able to import the wallet from which you can re-generate
-your key pairs.
-
-## List registered wallets
-
-If you want to list all the registered wallets, use the following command:
-
-```sh
-vegawallet list
-```
-
-## Generate a key pair
-
-To generate a key pair on the given wallet, use the following command:
-
-```sh
-vegawallet key generate --wallet "YOUR_WALLET"
-```
-
-It will then prompt you to input a passphrase. You can also specify the
-passphrase with the `--passphrase-file` flag.
-
-If the wallet does not exist, it will automatically create one. See
-["Create a wallet"](#create-a-wallet) for more information.
-
-You have the opportunity to attach metadata to your key with the ``--meta``
-flag (more on this below).
-
-## Add metadata to your keys
-
-For better key management, you may want to add metadata to your key pairs. This
-is done with the following command:
-
-```sh
-vegawallet key annotate --wallet "YOUR_WALLET" --meta "key1:value1;key2:value2" --pubkey "YOUR_HEX_PUBLIC_KEY"
-```
-
-An item of metadata is represented as a key-value property.
-
-### Give an alias to a key
-
-You can give to each key pair a nickname/alias with a metadata `name`. For
-example:
-
-```sh
-vegawallet key annotate --wallet "YOUR_WALLET" --meta "name:my-alias" --pubkey "YOUR_HEX_PUBLIC_KEY"
-```
-
-### Important
-
-This command does not insert the new metadata into the existing ones, **it
-replaces them**. If you want to keep the previous metadata, ensure to add them
-to your update.
-
-## Tainting a key pair
-
-You may want to prevent the use of a key by "tainting" it with the following
-command:
-
-```sh
-vegawallet key taint --wallet "YOUR_WALLET" --pubkey "YOUR_HEX_PUBIC_KEY"
-```
-
-It will then prompt you to input a passphrase. You can also specify the
-passphrase with the `--passphrase-file` flag.
-
-## Untainting a key pair
-
-You may have tainted a key by mistake. If you want to untaint it, use the
-following command:
-
-```sh
-vegawallet key untaint --wallet "YOUR_WALLET" --pubkey "YOUR_HEX_PUBIC_KEY"
-```
-
-It will then prompt you to input a passphrase. You can also specify the
-passphrase with the `--passphrase-file` flag.
-
-### Important
-
-If you tainted a key for security reasons, you should not untaint it.
-
-## List the key pairs
-
-To list your key pairs, use the following command:
-
-```sh
-vegawallet key list --wallet "YOUR_WALLET"
-```
-
-It will then prompt you to input a passphrase. You can also specify the
-passphrase with the `--passphrase-file` flag.
-
-### Important
-
-This will also return the private key. **Never expose this command or its
-content to the outside world.**
-
-## Sign and verify messages
-
-To sign and verify any kind of base-64 encoded messages, use the following
-commands:
-
-```sh
-vegawallet sign --wallet "YOUR_WALLET" --pubkey "YOUR_HEX_PUBIC_KEY" --message "c3BpY2Ugb2YgZHVuZQo="
-vegawallet verify --pubkey "YOUR_HEX_PUBIC_KEY" --message "c3BpY2Ugb2YgZHVuZQo=" --signature "76f978asd6fa8s76f"
-```
-
-It will then prompt you to input a passphrase. You can also specify the
-passphrase with the `--passphrase-file` flag.
-
-## List the networks
-
-During wallet initialisation, default networks are installed. You can list them
-with the following command:
-
-```sh
-vegawallet network list
-```
-
-## Import a network
-
-If you want to import a network configuration from a local file, use the
-following command:
-
-```sh
-vegawallet network import --from-file "PATH_TO_FILE"
-```
-
-Or, from a URL:
-
-```sh
-vegawallet network import --from-url "URL_TO_FILE"
-```
-
-You can override the imported network name using the `--with-name` flag.
-
-## Run the service
-
-Once a wallet and a network have been set up, you can run the wallet with the
-following command:
-
-```sh
-vegawallet service run --network "YOUR_NETWORK"
-```
-
-To run the wallet and open up a local version of Vega Console, the trading UI,
-use the following command:
-
-```sh
-vegawallet service run --network "YOUR_NETWORK" --console-proxy
-```
-
-To terminate the process, such as if you want to run other commands in Wallet,
-use `ctrl+c`.
-
-### Ad-blockers
-
-If you're running an ad/tracker blocker, and you're getting errors, it may be
-blocking the node from connecting. Try allowlisting `lb.testnet.vega.xyz` and
-refreshing.
-
-## Send a command
-
-Instead of sending a command through the API, you can send it through the
-command line, use the following command:
-
-```sh
-vegawallet command --pubkey "YOUR_HEX_PUBIC_KEY" --wallet "YOUR_WALLET" --network "YOUR_NETWORK" '{"THE_COMMAND": {...}, "propagate": true}'
-```
-
-## Isolate a key pair
-
-On HD wallets, the wallet node is used to generate (and retrieve) key pairs. For
-security purpose, you may not want to store the wallet node on the machine
-running the node, because it can be compromised. So, you might want to isolate a
-single key pair, without the wallet node, in an "isolated wallet".
-
-```sh
-vegawallet key isolate --pubkey "YOUR_HEX_PUBIC_KEY" --wallet "YOUR_WALLET"
-```
diff --git a/cmd/flags/errors.go b/cmd/flags/errors.go
index da3d5d2..6b27b54 100644
--- a/cmd/flags/errors.go
+++ b/cmd/flags/errors.go
@@ -61,9 +61,24 @@ func UnsupportedFlagValueError(name string, unsupported interface{}, supported [
 	}
 }
 
-func ParentFlagMustBeSpecifiedError(name string, parent string) error {
+func OneOfParentsFlagMustBeSpecifiedError(name string, parents ...string) error {
+	var resultFmt string
+	if len(parents) > 1 {
+		fmtFlags := make([]string, len(parents))
+		for i, pf := range parents {
+			fmtFlags[i] = fmt.Sprintf("--%s", pf)
+		}
+		flagsFmt := strings.Join([]string{
+			strings.Join(parents[0:len(fmtFlags)-1], ", "),
+			parents[len(fmtFlags)-1],
+		}, " or ")
+		resultFmt = fmt.Sprintf("%s flags", flagsFmt)
+	} else {
+		resultFmt = fmt.Sprintf("--%s flag", parents[0])
+	}
+
 	return FlagError{
-		message: fmt.Sprintf("--%s flag requires --%s flag to be set", name, parent),
+		message: fmt.Sprintf("--%s flag requires %s to be set", name, resultFmt),
 	}
 }
 
diff --git a/cmd/init.go b/cmd/init.go
index 4259426..cf650e1 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -113,8 +113,8 @@ func PrintInitResponse(w io.Writer, resp *InitResponse) {
 	p.CheckMark().SuccessText("Initialisation succeeded").NextSection()
 
 	p.BlueArrow().InfoText("Create a wallet").NextLine()
-	p.Text("To create a wallet, generate your first key pair using the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s key generate --wallet \"YOUR_USERNAME\"", os.Args[0])).NextSection()
+	p.Text("To create a wallet, use the following command:").NextSection()
+	p.Code(fmt.Sprintf("%s create --wallet \"YOUR_USERNAME\"", os.Args[0])).NextSection()
 	p.Text("The ").Bold("--wallet").Text(" flag sets the name of your wallet and will be used to login to Vega Console.").NextSection()
 	p.Text("For more information, use ").Bold("--help").Text(" flag.").NextLine()
 }
diff --git a/cmd/key.go b/cmd/key.go
index a3e5cee..8b8d965 100644
--- a/cmd/key.go
+++ b/cmd/key.go
@@ -36,10 +36,7 @@ func printMeta(p *printer.InteractivePrinter, meta []wallet.Meta) {
 		}
 	}
 
-	for i, m := range meta {
-		if i != 0 {
-			p.NextLine()
-		}
-		p.WarningText(fmt.Sprintf("%-*s", padding, m.Key)).Text(" | ").WarningText(m.Value)
+	for _, m := range meta {
+		p.WarningText(fmt.Sprintf("%-*s", padding, m.Key)).Text(" | ").WarningText(m.Value).NextLine()
 	}
 }
diff --git a/cmd/key_describe.go b/cmd/key_describe.go
index 69068a7..0f83415 100644
--- a/cmd/key_describe.go
+++ b/cmd/key_describe.go
@@ -137,5 +137,4 @@ func PrintDescribeKeyResponse(w io.Writer, resp *wallet.DescribeKeyResponse) {
 
 	p.Text("Metadata:").NextLine()
 	printMeta(p, resp.Meta)
-	p.NextSection()
 }
diff --git a/cmd/key_generate.go b/cmd/key_generate.go
index 27bdacb..602b014 100644
--- a/cmd/key_generate.go
+++ b/cmd/key_generate.go
@@ -176,5 +176,5 @@ func PrintGenerateKeyResponse(w io.Writer, resp *wallet.GenerateKeyResponse) {
 
 	p.BlueArrow().InfoText("Run the service").NextLine()
 	p.Text("Now, you can run the service. See the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextSection()
+	p.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextLine()
 }
diff --git a/cmd/key_isolate.go b/cmd/key_isolate.go
index 72c17e3..ef4bac1 100644
--- a/cmd/key_isolate.go
+++ b/cmd/key_isolate.go
@@ -132,5 +132,5 @@ func PrintIsolateKeyResponse(w io.Writer, resp *wallet.IsolateKeyResponse) {
 	p := printer.NewInteractivePrinter(w)
 
 	p.CheckMark().Text("Key pair has been isolated in wallet ").Bold(resp.Wallet).Text(" at: ").SuccessText(resp.FilePath).NextLine()
-	p.CheckMark().SuccessText("Key isolation succeeded").NextSection()
+	p.CheckMark().SuccessText("Key isolation succeeded").NextLine()
 }
diff --git a/cmd/key_list.go b/cmd/key_list.go
index 2d1d0a4..b3160be 100644
--- a/cmd/key_list.go
+++ b/cmd/key_list.go
@@ -109,8 +109,11 @@ func (f *ListKeysFlags) Validate() (*wallet.ListKeysRequest, error) {
 func PrintListKeysResponse(w io.Writer, resp *wallet.ListKeysResponse) {
 	p := printer.NewInteractivePrinter(w)
 
-	for _, key := range resp.Keys {
+	for i, key := range resp.Keys {
+		if i != 0 {
+			p.NextLine()
+		}
 		p.Text("Name:       ").WarningText(key.Name).NextLine()
-		p.Text("Public key: ").WarningText(key.PublicKey).NextSection()
+		p.Text("Public key: ").WarningText(key.PublicKey).NextLine()
 	}
 }
diff --git a/cmd/key_taint.go b/cmd/key_taint.go
index 1b468db..9fc6f44 100644
--- a/cmd/key_taint.go
+++ b/cmd/key_taint.go
@@ -130,5 +130,5 @@ func PrintTaintKeyResponse(w io.Writer) {
 
 	p.BlueArrow().InfoText("Untaint a key").NextLine()
 	p.Text("You may have tainted a key pair by mistake. If you want to untaint it, see the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s key untaint --help", os.Args[0])).NextSection()
+	p.Code(fmt.Sprintf("%s key untaint --help", os.Args[0])).NextLine()
 }
diff --git a/cmd/key_untaint.go b/cmd/key_untaint.go
index 485dac1..315f9a2 100644
--- a/cmd/key_untaint.go
+++ b/cmd/key_untaint.go
@@ -129,5 +129,5 @@ func PrintUntaintKeyResponse(w io.Writer) {
 
 	p.BlueArrow().InfoText("Taint a key").NextLine()
 	p.Text("To taint a key pair, see the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s key taint --help", os.Args[0])).NextSection()
+	p.Code(fmt.Sprintf("%s key taint --help", os.Args[0])).NextLine()
 }
diff --git a/cmd/service_run.go b/cmd/service_run.go
index f95029b..9929c6b 100644
--- a/cmd/service_run.go
+++ b/cmd/service_run.go
@@ -15,11 +15,11 @@ import (
 	"code.vegaprotocol.io/vegawallet/cmd/cli"
 	"code.vegaprotocol.io/vegawallet/cmd/flags"
 	"code.vegaprotocol.io/vegawallet/cmd/printer"
-	"code.vegaprotocol.io/vegawallet/console"
 	vglog "code.vegaprotocol.io/vegawallet/libs/zap"
 	"code.vegaprotocol.io/vegawallet/network"
 	netstore "code.vegaprotocol.io/vegawallet/network/store/v1"
 	"code.vegaprotocol.io/vegawallet/node"
+	"code.vegaprotocol.io/vegawallet/proxy"
 	"code.vegaprotocol.io/vegawallet/service"
 	svcstore "code.vegaprotocol.io/vegawallet/service/store/v1"
 	"code.vegaprotocol.io/vegawallet/wallets"
@@ -75,12 +75,17 @@ var (
 		# Start the service with a log level set to debug
 		vegawallet service run --network NETWORK --level debug
 
-		# Start the service with the console proxy and open the console in the 
-		# default browser
-		vegawallet service run --network NETWORK --console-proxy
+		# Start the service and open the console in the default browser
+		vegawallet service run --network NETWORK --with-console
 
-		# Start the service with the console proxy without opening the console
-		vegawallet service run --network NETWORK --console-proxy --no-browser
+		# Start the service without opening the console
+		vegawallet service run --network NETWORK --with-console --no-browser
+
+		# Start the service and open the token dApp in the default browser
+		vegawallet service run --network NETWORK --with-token-dapp
+
+		# Start the service without opening the token dApp
+		vegawallet service run --network NETWORK --with-token-dapp --no-browser
 	`)
 )
 
@@ -116,15 +121,25 @@ func BuildCmdRunService(w io.Writer, handler RunServiceHandler, rf *RootFlags) *
 		"",
 		"Network configuration to use",
 	)
-	cmd.Flags().BoolVar(&f.StartConsole,
+	cmd.Flags().BoolVar(&f.WithConsole,
 		"console-proxy",
 		false,
-		"Start the Vega console proxy and open the console in the default browser",
+		"Start the Vega console proxy and open the console in the default browser (DEPRECATED, use --with-console, instead)",
+	)
+	cmd.Flags().BoolVar(&f.WithConsole,
+		"with-console",
+		false,
+		"Start the Vega console behind a proxy and open it in the default browser",
+	)
+	cmd.Flags().BoolVar(&f.WithTokenDApp,
+		"with-token-dapp",
+		false,
+		"Start the Vega Token dApp behind a proxy and open it in the default browser",
 	)
 	cmd.Flags().BoolVar(&f.NoBrowser,
 		"no-browser",
 		false,
-		"Do not open the default browser when starting the console proxy (requires: --console-proxy)",
+		"Do not open the default browser when starting applications",
 	)
 	cmd.Flags().StringVar(&f.LogLevel,
 		"level",
@@ -139,10 +154,11 @@ func BuildCmdRunService(w io.Writer, handler RunServiceHandler, rf *RootFlags) *
 }
 
 type RunServiceFlags struct {
-	Network      string
-	StartConsole bool
-	NoBrowser    bool
-	LogLevel     string
+	Network       string
+	WithConsole   bool
+	WithTokenDApp bool
+	NoBrowser     bool
+	LogLevel      string
 }
 
 func (f *RunServiceFlags) Validate() error {
@@ -150,8 +166,8 @@ func (f *RunServiceFlags) Validate() error {
 		return flags.FlagMustBeSpecifiedError("network")
 	}
 
-	if f.NoBrowser && !f.StartConsole {
-		return flags.ParentFlagMustBeSpecifiedError("no-browser", "console-proxy")
+	if f.NoBrowser && !f.WithConsole && !f.WithTokenDApp {
+		return flags.OneOfParentsFlagMustBeSpecifiedError("no-browser", "with-console", "with-token-dapp")
 	}
 
 	if len(f.LogLevel) != 0 {
@@ -234,8 +250,7 @@ func RunService(w io.Writer, rf *RootFlags, f *RunServiceFlags) error {
 
 	go func() {
 		defer cancel()
-		err := srv.Start()
-		if err != nil && !errors.Is(err, http.ErrServerClosed) {
+		if err := srv.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
 			log.Error("error while starting HTTP server", zap.Error(err))
 		}
 	}()
@@ -243,35 +258,18 @@ func RunService(w io.Writer, rf *RootFlags, f *RunServiceFlags) error {
 	serviceHost := fmt.Sprintf("http://%v:%v", cfg.Host, cfg.Port)
 	if rf.Output == flags.InteractiveOutput {
 		p.CheckMark().Text("HTTP service started at: ").SuccessText(serviceHost).NextLine()
-	} else if rf.Output == "json" {
+	} else if rf.Output == flags.JSONOutput {
 		log.Info(fmt.Sprintf("HTTP service started at: %s", serviceHost))
 	}
 
-	var cs *console.Console
-	if f.StartConsole {
-		cs = console.NewConsole(cfg.Console.LocalPort, cfg.Console.URL, cfg.API.GRPC.Hosts[0])
-		go func() {
-			defer cancel()
-			err := cs.Start()
-			if err != nil && !errors.Is(err, http.ErrServerClosed) {
-				log.Error("error while starting the console proxy", zap.Error(err))
-			}
-		}()
-
-		consoleLocalHost := fmt.Sprintf("http://127.0.0.1:%v", cfg.Console.LocalPort)
-		if rf.Output == flags.InteractiveOutput {
-			p.CheckMark().Text("Console proxy pointing to ").Bold(cfg.Console.URL).Text(" started at: ").SuccessText(consoleLocalHost).NextLine()
-		} else if rf.Output == "json" {
-			log.Info(fmt.Sprintf("console proxy pointing to %s started at: %s", cfg.Console.URL, consoleLocalHost))
-		}
+	var cs *proxy.Proxy
+	if f.WithConsole {
+		cs = startConsole(log, rf, !f.NoBrowser, cfg, cancel, p)
+	}
 
-		if !f.NoBrowser {
-			err := open.Run(cs.GetBrowserURL())
-			if err != nil {
-				log.Error("unable to open the console in the default browser",
-					zap.Error(err))
-			}
-		}
+	var tokenDApp *proxy.Proxy
+	if f.WithTokenDApp {
+		tokenDApp = startTokenDApp(log, rf, !f.NoBrowser, cfg, cancel, p)
 	}
 
 	if rf.Output == flags.InteractiveOutput {
@@ -284,22 +282,28 @@ func RunService(w io.Writer, rf *RootFlags, f *RunServiceFlags) error {
 
 	waitSig(ctx, cancel, log)
 
-	err = srv.Stop()
-	if err != nil {
-		log.Error("error while stopping HTTP server", zap.Error(err))
-	} else {
-		log.Info("HTTP server stopped with success")
-	}
-
-	if f.StartConsole {
-		err = cs.Stop()
-		if err != nil {
+	if cs != nil {
+		if err = cs.Stop(); err != nil {
 			log.Error("error while stopping console proxy", zap.Error(err))
 		} else {
 			log.Info("console proxy stopped with success")
 		}
 	}
 
+	if tokenDApp != nil {
+		if err = tokenDApp.Stop(); err != nil {
+			log.Error("error while stopping token dApp proxy", zap.Error(err))
+		} else {
+			log.Info("token dApp proxy stopped with success")
+		}
+	}
+
+	if err = srv.Stop(); err != nil {
+		log.Error("error while stopping HTTP server", zap.Error(err))
+	} else {
+		log.Info("HTTP server stopped with success")
+	}
+
 	if rf.Output == flags.InteractiveOutput {
 		p.CheckMark().SuccessText("Service stopped").NextLine()
 	}
@@ -307,6 +311,55 @@ func RunService(w io.Writer, rf *RootFlags, f *RunServiceFlags) error {
 	return nil
 }
 
+func startConsole(log *zap.Logger, rf *RootFlags, openBrowser bool, cfg *network.Network, cancel context.CancelFunc, p *printer.InteractivePrinter) *proxy.Proxy {
+	cs := proxy.NewProxy(cfg.Console.LocalPort, cfg.Console.URL, cfg.API.GRPC.Hosts[0])
+	go func() {
+		defer cancel()
+		if err := cs.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
+			log.Error("error while starting the console proxy", zap.Error(err))
+		}
+	}()
+
+	consoleLocalHost := fmt.Sprintf("http://127.0.0.1:%v", cfg.Console.LocalPort)
+	if rf.Output == flags.InteractiveOutput {
+		p.CheckMark().Text("Console proxy pointing to ").Bold(cfg.Console.URL).Text(" started at: ").SuccessText(consoleLocalHost).NextLine()
+	} else if rf.Output == "json" {
+		log.Info(fmt.Sprintf("console proxy pointing to %s started at: %s", cfg.Console.URL, consoleLocalHost))
+	}
+
+	if openBrowser {
+		if err := open.Run(cs.GetBrowserURL()); err != nil {
+			log.Error("unable to open the application in the default browser", zap.Error(err))
+		}
+	}
+
+	return cs
+}
+
+func startTokenDApp(log *zap.Logger, rf *RootFlags, openBrowser bool, cfg *network.Network, cancel context.CancelFunc, p *printer.InteractivePrinter) *proxy.Proxy {
+	tokenDApp := proxy.NewProxy(cfg.TokenDApp.LocalPort, cfg.TokenDApp.URL, cfg.API.GRPC.Hosts[0])
+	go func() {
+		defer cancel()
+		if err := tokenDApp.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
+			log.Error("error while starting the token dApp proxy", zap.Error(err))
+		}
+	}()
+
+	tokenDAppLocalHost := fmt.Sprintf("http://127.0.0.1:%v", cfg.TokenDApp.LocalPort)
+	if rf.Output == flags.InteractiveOutput {
+		p.CheckMark().Text("token dApp proxy pointing to ").Bold(cfg.TokenDApp.URL).Text(" started at: ").SuccessText(tokenDAppLocalHost).NextLine()
+	} else if rf.Output == "json" {
+		log.Info(fmt.Sprintf("token dApp proxy pointing to %s started at: %s", cfg.TokenDApp.URL, tokenDAppLocalHost))
+	}
+
+	if openBrowser {
+		if err := open.Run(tokenDApp.GetBrowserURL()); err != nil {
+			log.Error("unable to open the token dApp in the default browser", zap.Error(err))
+		}
+	}
+	return tokenDApp
+}
+
 // waitSig will wait for a sigterm or sigint interrupt.
 func waitSig(ctx context.Context, cfunc func(), log *zap.Logger) {
 	gracefulStop := make(chan os.Signal, 1)
diff --git a/cmd/verify.go b/cmd/verify.go
index 5a9b50c..b5935d0 100644
--- a/cmd/verify.go
+++ b/cmd/verify.go
@@ -131,5 +131,5 @@ func PrintVerifyMessageResponse(w io.Writer, isValid bool) {
 
 	p.BlueArrow().InfoText("Sign a message").NextLine()
 	p.Text("To sign a message, see the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s sign --help", os.Args[0])).NextSection()
+	p.Code(fmt.Sprintf("%s sign --help", os.Args[0])).NextLine()
 }
diff --git a/cmd/wallet_create.go b/cmd/wallet_create.go
index 777313b..943ecdb 100644
--- a/cmd/wallet_create.go
+++ b/cmd/wallet_create.go
@@ -130,5 +130,5 @@ func PrintCreateWalletResponse(w io.Writer, resp *wallet.CreateWalletResponse) {
 
 	p.BlueArrow().InfoText("Run the service").NextLine()
 	p.Text("Now, you can run the service. See the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextSection()
+	p.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextLine()
 }
diff --git a/cmd/wallet_import.go b/cmd/wallet_import.go
index 20b5329..0e0d8d7 100644
--- a/cmd/wallet_import.go
+++ b/cmd/wallet_import.go
@@ -161,5 +161,5 @@ func PrintImportWalletResponse(w io.Writer, resp *wallet.ImportWalletResponse) {
 
 	p.BlueArrow().InfoText("Run the service").NextLine()
 	p.Text("Now, you can run the service. See the following command:").NextSection()
-	p.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextSection()
+	p.Code(fmt.Sprintf("%s service run --help", os.Args[0])).NextLine()
 }
diff --git a/go.sum b/go.sum
index 409ab4a..6ef8e39 100644
--- a/go.sum
+++ b/go.sum
@@ -10,10 +10,6 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7
 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-code.vegaprotocol.io/protos v0.46.1-0.20211124140330-6f1ee8ac737e h1:nru0Y8/D/ipsBTUYvtGFsNjJErJreZZa4Kvas2HDYRo=
-code.vegaprotocol.io/protos v0.46.1-0.20211124140330-6f1ee8ac737e/go.mod h1:NORK22Evpwn0GoetL7H6/oVUzGcfjAJEJTzw+zTkSyE=
-code.vegaprotocol.io/protos v0.46.1-0.20211129190702-538bee8fb036 h1:sEJsMLbFt5W1iLU5ntkbPquJjNTHCVDWkWc7+yq5JUA=
-code.vegaprotocol.io/protos v0.46.1-0.20211129190702-538bee8fb036/go.mod h1:NORK22Evpwn0GoetL7H6/oVUzGcfjAJEJTzw+zTkSyE=
 code.vegaprotocol.io/protos v0.46.1-0.20211201150331-b536716fea70 h1:zqWTK+nnd1HK7oCCXIK/H3uoyync4R1flKX+A5XKsmQ=
 code.vegaprotocol.io/protos v0.46.1-0.20211201150331-b536716fea70/go.mod h1:NORK22Evpwn0GoetL7H6/oVUzGcfjAJEJTzw+zTkSyE=
 code.vegaprotocol.io/shared v0.0.0-20211015074835-9ed837d93090 h1:SF7ZRvZ/JASJNEQigbuUwk1rQuugLy6gqbOFs8D8VXI=
diff --git a/network/network.go b/network/network.go
index a46c272..e81f80a 100644
--- a/network/network.go
+++ b/network/network.go
@@ -9,6 +9,7 @@ type Network struct {
 	Port        int
 	Host        string
 	API         APIConfig
+	TokenDApp   TokenDAppConfig
 	Console     ConsoleConfig
 
 	// TO REMOVE Once the tools use the new API.GRPC
@@ -38,3 +39,8 @@ type ConsoleConfig struct {
 	URL       string
 	LocalPort int
 }
+
+type TokenDAppConfig struct {
+	URL       string
+	LocalPort int
+}
diff --git a/console/console.go b/proxy/proxy.go
similarity index 80%
rename from console/console.go
rename to proxy/proxy.go
index f5b1c17..7556ba9 100644
--- a/console/console.go
+++ b/proxy/proxy.go
@@ -1,4 +1,4 @@
-package console
+package proxy
 
 import (
 	"context"
@@ -9,11 +9,11 @@ import (
 	"code.vegaprotocol.io/vegawallet/version"
 )
 
-type Console struct {
+type Proxy struct {
 	server *http.Server
 }
 
-func NewConsole(port int, consoleURL, nodeURL string) *Console {
+func NewProxy(port int, consoleURL, nodeURL string) *Proxy {
 	proxy := httputil.ReverseProxy{
 		Director: func(req *http.Request) {
 			req.Header.Set("Referer", nodeURL)
@@ -34,7 +34,7 @@ func NewConsole(port int, consoleURL, nodeURL string) *Console {
 	}
 
 	addr := fmt.Sprintf("127.0.0.1:%v", port)
-	return &Console{
+	return &Proxy{
 		server: &http.Server{
 			Addr:    addr,
 			Handler: &proxy,
@@ -42,14 +42,14 @@ func NewConsole(port int, consoleURL, nodeURL string) *Console {
 	}
 }
 
-func (c *Console) Start() error {
+func (c *Proxy) Start() error {
 	return c.server.ListenAndServe()
 }
 
-func (c *Console) Stop() error {
+func (c *Proxy) Stop() error {
 	return c.server.Shutdown(context.Background())
 }
 
-func (c *Console) GetBrowserURL() string {
+func (c *Proxy) GetBrowserURL() string {
 	return fmt.Sprintf("http://%s", c.server.Addr)
 }


Test patch:
diff --git a/cmd/service_run_test.go b/cmd/service_run_test.go
index e6209e2..e0aa16a 100644
--- a/cmd/service_run_test.go
+++ b/cmd/service_run_test.go
@@ -14,7 +14,7 @@ func TestRunServiceFlags(t *testing.T) {
 	t.Run("Valid flags succeeds", testRunServiceFlagsValidFlagsSucceeds)
 	t.Run("Missing network fails", testRunServiceFlagsMissingNetworkFails)
 	t.Run("Unsupported log level fails", testRunServiceFlagsUnsupportedLogLevelFails)
-	t.Run("No browser without console proxy fails", testRunServiceFlagsNoBrowserWithoutConsoleProxyFails)
+	t.Run("No browser without console nor token dApp fails", testRunServiceFlagsNoBrowserWithoutConsoleNorTokenDAppFails)
 }
 
 func testRunServiceFlagsValidFlagsSucceeds(t *testing.T) {
@@ -22,10 +22,11 @@ func testRunServiceFlagsValidFlagsSucceeds(t *testing.T) {
 	networkName := vgrand.RandomStr(10)
 
 	f := &cmd.RunServiceFlags{
-		Network:      networkName,
-		StartConsole: true,
-		NoBrowser:    true,
-		LogLevel:     "debug",
+		Network:       networkName,
+		WithConsole:   true,
+		WithTokenDApp: true,
+		NoBrowser:     true,
+		LogLevel:      "debug",
 	}
 
 	// when
@@ -59,17 +60,18 @@ func testRunServiceFlagsUnsupportedLogLevelFails(t *testing.T) {
 	assert.ErrorIs(t, err, cmd.NewUnsupportedFlagValueError(f.LogLevel))
 }
 
-func testRunServiceFlagsNoBrowserWithoutConsoleProxyFails(t *testing.T) {
+func testRunServiceFlagsNoBrowserWithoutConsoleNorTokenDAppFails(t *testing.T) {
 	// given
 	f := newRunServiceFlags(t)
-	f.StartConsole = false
+	f.WithConsole = false
+	f.WithTokenDApp = false
 	f.NoBrowser = true
 
 	// when
 	err := f.Validate()
 
 	// then
-	assert.ErrorIs(t, err, flags.ParentFlagMustBeSpecifiedError("no-browser", "console-proxy"))
+	assert.ErrorIs(t, err, flags.OneOfParentsFlagMustBeSpecifiedError("no-browser", "with-console", "with-token-dapp"))
 }
 
 func newRunServiceFlags(t *testing.T) *cmd.RunServiceFlags {
@@ -78,9 +80,10 @@ func newRunServiceFlags(t *testing.T) *cmd.RunServiceFlags {
 	networkName := vgrand.RandomStr(10)
 
 	return &cmd.RunServiceFlags{
-		Network:      networkName,
-		StartConsole: true,
-		NoBrowser:    true,
-		LogLevel:     "debug",
+		Network:       networkName,
+		WithConsole:   true,
+		WithTokenDApp: true,
+		NoBrowser:     true,
+		LogLevel:      "debug",
 	}
 }