mirror of
https://github.com/onsonr/sonr.git
synced 2025-03-10 13:07:09 +00:00
feat: add basic UI for block explorer
This commit is contained in:
parent
73997ad670
commit
1ec797e36f
@ -14,6 +14,7 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
rootCmd := NewRootCmd()
|
rootCmd := NewRootCmd()
|
||||||
rootCmd.AddCommand(tui.NewBuildProtoMsgCmd())
|
rootCmd.AddCommand(tui.NewBuildProtoMsgCmd())
|
||||||
|
// rootCmd.AddCommand(tui.NewExplorerCmd())
|
||||||
|
|
||||||
if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil {
|
if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil {
|
||||||
log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err)
|
log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err)
|
||||||
|
173
internal/tui/explorer.go
Normal file
173
internal/tui/explorer.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/table"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
|
||||||
|
highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
|
||||||
|
special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"}
|
||||||
|
|
||||||
|
titleStyle = lipgloss.NewStyle().
|
||||||
|
MarginLeft(1).
|
||||||
|
MarginRight(5).
|
||||||
|
Padding(0, 1).
|
||||||
|
Italic(true).
|
||||||
|
Foreground(lipgloss.Color("#FFF7DB")).
|
||||||
|
SetString("Cosmos Block Explorer")
|
||||||
|
|
||||||
|
infoStyle = lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
|
BorderTop(true).
|
||||||
|
BorderForeground(subtle)
|
||||||
|
)
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
blocks []string
|
||||||
|
transactionTable table.Model
|
||||||
|
stats map[string]string
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialModel() model {
|
||||||
|
columns := []table.Column{
|
||||||
|
{Title: "Hash", Width: 10},
|
||||||
|
{Title: "Type", Width: 15},
|
||||||
|
{Title: "Height", Width: 10},
|
||||||
|
{Title: "Time", Width: 20},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := []table.Row{
|
||||||
|
{"abc123", "Transfer", "1000", time.Now().Format(time.RFC3339)},
|
||||||
|
{"def456", "Delegate", "999", time.Now().Add(-1 * time.Minute).Format(time.RFC3339)},
|
||||||
|
{"ghi789", "Vote", "998", time.Now().Add(-2 * time.Minute).Format(time.RFC3339)},
|
||||||
|
}
|
||||||
|
|
||||||
|
t := table.New(
|
||||||
|
table.WithColumns(columns),
|
||||||
|
table.WithRows(rows),
|
||||||
|
table.WithFocused(true),
|
||||||
|
table.WithHeight(7),
|
||||||
|
)
|
||||||
|
|
||||||
|
s := table.DefaultStyles()
|
||||||
|
s.Header = s.Header.
|
||||||
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("240")).
|
||||||
|
BorderBottom(true).
|
||||||
|
Bold(false)
|
||||||
|
s.Selected = s.Selected.
|
||||||
|
Foreground(lipgloss.Color("229")).
|
||||||
|
Background(lipgloss.Color("57")).
|
||||||
|
Bold(false)
|
||||||
|
t.SetStyles(s)
|
||||||
|
|
||||||
|
return model{
|
||||||
|
blocks: []string{"Block 1", "Block 2", "Block 3"},
|
||||||
|
transactionTable: t,
|
||||||
|
stats: map[string]string{
|
||||||
|
"Latest Block": "1000",
|
||||||
|
"Validators": "100",
|
||||||
|
"Bonded Tokens": "1,000,000",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Init() tea.Cmd {
|
||||||
|
return tick
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "q", "ctrl+c":
|
||||||
|
return m, tea.Quit
|
||||||
|
case "enter":
|
||||||
|
return m, tea.Batch(
|
||||||
|
tea.Printf("Selected transaction: %s", m.transactionTable.SelectedRow()[0]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.height = msg.Height
|
||||||
|
m.width = msg.Width
|
||||||
|
case tickMsg:
|
||||||
|
// Update data here
|
||||||
|
m.blocks = append([]string{"New Block"}, m.blocks...)
|
||||||
|
if len(m.blocks) > 5 {
|
||||||
|
m.blocks = m.blocks[:5]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new transaction to the table
|
||||||
|
newRow := table.Row{
|
||||||
|
fmt.Sprintf("tx%d", time.Now().Unix()),
|
||||||
|
"NewTxType",
|
||||||
|
fmt.Sprintf("%d", 1000+len(m.transactionTable.Rows())),
|
||||||
|
time.Now().Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
m.transactionTable.SetRows(append([]table.Row{newRow}, m.transactionTable.Rows()...))
|
||||||
|
if len(m.transactionTable.Rows()) > 10 {
|
||||||
|
m.transactionTable.SetRows(m.transactionTable.Rows()[:10])
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, tick
|
||||||
|
}
|
||||||
|
m.transactionTable, cmd = m.transactionTable.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) View() string {
|
||||||
|
s := titleStyle.Render("Cosmos Block Explorer")
|
||||||
|
s += "\n\n"
|
||||||
|
|
||||||
|
// Blocks
|
||||||
|
s += lipgloss.NewStyle().Bold(true).Render("Recent Blocks") + "\n"
|
||||||
|
for _, block := range m.blocks {
|
||||||
|
s += "• " + block + "\n"
|
||||||
|
}
|
||||||
|
s += "\n"
|
||||||
|
|
||||||
|
// Transactions
|
||||||
|
s += lipgloss.NewStyle().Bold(true).Render("Recent Transactions") + "\n"
|
||||||
|
s += m.transactionTable.View() + "\n\n"
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
s += lipgloss.NewStyle().Bold(true).Render("Network Statistics") + "\n"
|
||||||
|
for key, value := range m.stats {
|
||||||
|
s += fmt.Sprintf("%s: %s\n", key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type tickMsg time.Time
|
||||||
|
|
||||||
|
func tick() tea.Msg {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
return tickMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runExplorer(cmd *cobra.Command, args []string) error {
|
||||||
|
p := tea.NewProgram(initialModel(), tea.WithAltScreen())
|
||||||
|
if _, err := p.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error running explorer: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExplorerCmd() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "cosmos-explorer",
|
||||||
|
Short: "A terminal-based Cosmos blockchain explorer",
|
||||||
|
RunE: runExplorer,
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
@ -14,77 +15,261 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxWidth = 100
|
||||||
|
|
||||||
|
var (
|
||||||
|
red = lipgloss.AdaptiveColor{Light: "#FE5F86", Dark: "#FE5F86"}
|
||||||
|
indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
|
||||||
|
green = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Styles struct {
|
||||||
|
Base,
|
||||||
|
HeaderText,
|
||||||
|
Status,
|
||||||
|
StatusHeader,
|
||||||
|
Highlight,
|
||||||
|
ErrorHeaderText,
|
||||||
|
Help lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStyles(lg *lipgloss.Renderer) *Styles {
|
||||||
|
s := Styles{}
|
||||||
|
s.Base = lg.NewStyle().
|
||||||
|
Padding(1, 2, 0, 1)
|
||||||
|
s.HeaderText = lg.NewStyle().
|
||||||
|
Foreground(indigo).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1)
|
||||||
|
s.Status = lg.NewStyle().
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(indigo).
|
||||||
|
PaddingLeft(1).
|
||||||
|
MarginTop(1)
|
||||||
|
s.StatusHeader = lg.NewStyle().
|
||||||
|
Foreground(green).
|
||||||
|
Bold(true)
|
||||||
|
s.Highlight = lg.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("212"))
|
||||||
|
s.ErrorHeaderText = s.HeaderText.
|
||||||
|
Foreground(red)
|
||||||
|
s.Help = lg.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("240"))
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
type state int
|
||||||
|
|
||||||
|
const (
|
||||||
|
statusNormal state = iota
|
||||||
|
stateDone
|
||||||
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
|
state state
|
||||||
|
lg *lipgloss.Renderer
|
||||||
|
styles *Styles
|
||||||
form *huh.Form
|
form *huh.Form
|
||||||
|
width int
|
||||||
message *tx.TxBody
|
message *tx.TxBody
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModel() Model {
|
func NewModel() Model {
|
||||||
return Model{
|
m := Model{width: maxWidth}
|
||||||
form: huh.NewForm(
|
m.lg = lipgloss.DefaultRenderer()
|
||||||
huh.NewGroup(
|
m.styles = NewStyles(m.lg)
|
||||||
huh.NewInput().
|
|
||||||
Key("from").
|
m.form = huh.NewForm(
|
||||||
Title("From Address").
|
huh.NewGroup(
|
||||||
Placeholder("cosmos1...").
|
huh.NewInput().
|
||||||
Validate(func(s string) error {
|
Key("from").
|
||||||
if !strings.HasPrefix(s, "cosmos1") {
|
Title("From Address").
|
||||||
return fmt.Errorf("invalid address format")
|
Placeholder("cosmos1...").
|
||||||
}
|
Validate(func(s string) error {
|
||||||
return nil
|
if !strings.HasPrefix(s, "cosmos1") {
|
||||||
}),
|
return fmt.Errorf("invalid address format")
|
||||||
huh.NewInput().
|
}
|
||||||
Key("to").
|
return nil
|
||||||
Title("To Address").
|
}),
|
||||||
Placeholder("cosmos1...").
|
|
||||||
Validate(func(s string) error {
|
huh.NewInput().
|
||||||
if !strings.HasPrefix(s, "cosmos1") {
|
Key("to").
|
||||||
return fmt.Errorf("invalid address format")
|
Title("To Address").
|
||||||
}
|
Placeholder("cosmos1...").
|
||||||
return nil
|
Validate(func(s string) error {
|
||||||
}),
|
if !strings.HasPrefix(s, "cosmos1") {
|
||||||
huh.NewInput().
|
return fmt.Errorf("invalid address format")
|
||||||
Key("amount").
|
}
|
||||||
Title("Amount").
|
return nil
|
||||||
Placeholder("100").
|
}),
|
||||||
Validate(func(s string) error {
|
|
||||||
if _, err := sdk.ParseCoinNormalized(s + "atom"); err != nil {
|
huh.NewInput().
|
||||||
return fmt.Errorf("invalid coin amount")
|
Key("amount").
|
||||||
}
|
Title("Amount").
|
||||||
return nil
|
Placeholder("100").
|
||||||
}),
|
Validate(func(s string) error {
|
||||||
huh.NewSelect[string]().
|
if _, err := sdk.ParseCoinNormalized(s + "atom"); err != nil {
|
||||||
Key("denom").
|
return fmt.Errorf("invalid coin amount")
|
||||||
Title("Denom").
|
}
|
||||||
Options(huh.NewOptions("atom", "osmo", "usnr", "snr")...),
|
return nil
|
||||||
huh.NewInput().
|
}),
|
||||||
Key("memo").
|
|
||||||
Title("Memo").
|
huh.NewSelect[string]().
|
||||||
Placeholder("Optional"),
|
Key("denom").
|
||||||
),
|
Title("Denom").
|
||||||
|
Options(huh.NewOptions("atom", "osmo", "usnr", "snr")...),
|
||||||
|
|
||||||
|
huh.NewInput().
|
||||||
|
Key("memo").
|
||||||
|
Title("Memo").
|
||||||
|
Placeholder("Optional"),
|
||||||
|
|
||||||
|
huh.NewConfirm().
|
||||||
|
Key("done").
|
||||||
|
Title("Ready to convert?").
|
||||||
|
Validate(func(v bool) error {
|
||||||
|
if !v {
|
||||||
|
return fmt.Errorf("Please confirm when you're ready to convert")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
|
Affirmative("Yes, convert!").
|
||||||
|
Negative("Not yet"),
|
||||||
),
|
),
|
||||||
}
|
).
|
||||||
|
WithWidth(60).
|
||||||
|
WithShowHelp(false).
|
||||||
|
WithShowErrors(false)
|
||||||
|
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Init() tea.Cmd {
|
func (m Model) Init() tea.Cmd {
|
||||||
return m.form.Init()
|
return m.form.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func min(x, y int) int {
|
||||||
|
if x > y {
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = min(msg.Width, maxWidth) - m.styles.Base.GetHorizontalFrameSize()
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "esc", "ctrl+c", "q":
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
form, cmd := m.form.Update(msg)
|
form, cmd := m.form.Update(msg)
|
||||||
if f, ok := form.(*huh.Form); ok {
|
if f, ok := form.(*huh.Form); ok {
|
||||||
m.form = f
|
m.form = f
|
||||||
if m.form.State == huh.StateCompleted {
|
cmds = append(cmds, cmd)
|
||||||
m.buildMessage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return m, cmd
|
|
||||||
|
if m.form.State == huh.StateCompleted {
|
||||||
|
m.buildMessage()
|
||||||
|
cmds = append(cmds, tea.Quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) View() string {
|
func (m Model) View() string {
|
||||||
formView := m.form.View()
|
s := m.styles
|
||||||
messageView := m.getMessageView()
|
|
||||||
return fmt.Sprintf("%s\n\n%s", formView, messageView)
|
switch m.form.State {
|
||||||
|
case huh.StateCompleted:
|
||||||
|
pklCode := m.generatePkl()
|
||||||
|
messageView := m.getMessageView()
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, "Final Tx:\n\n%s\n\n%s", pklCode, messageView)
|
||||||
|
return s.Status.Margin(0, 1).Padding(1, 2).Width(80).Render(b.String()) + "\n\n"
|
||||||
|
default:
|
||||||
|
var schemaType string
|
||||||
|
if m.form.GetString("schemaType") != "" {
|
||||||
|
schemaType = "Schema Type: " + m.form.GetString("schemaType")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := strings.TrimSuffix(m.form.View(), "\n\n")
|
||||||
|
form := m.lg.NewStyle().Margin(1, 0).Render(v)
|
||||||
|
|
||||||
|
var status string
|
||||||
|
{
|
||||||
|
preview := "(Preview will appear here)"
|
||||||
|
if m.form.GetString("schema") != "" {
|
||||||
|
preview = m.generatePkl()
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusWidth = 40
|
||||||
|
statusMarginLeft := m.width - statusWidth - lipgloss.Width(form) - s.Status.GetMarginRight()
|
||||||
|
status = s.Status.
|
||||||
|
Height(lipgloss.Height(form)).
|
||||||
|
Width(statusWidth).
|
||||||
|
MarginLeft(statusMarginLeft).
|
||||||
|
Render(s.StatusHeader.Render("Pkl Preview") + "\n" +
|
||||||
|
schemaType + "\n\n" +
|
||||||
|
preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := m.form.Errors()
|
||||||
|
header := m.appBoundaryView("Sonr TX Builder")
|
||||||
|
if len(errors) > 0 {
|
||||||
|
header = m.appErrorBoundaryView(m.errorView())
|
||||||
|
}
|
||||||
|
body := lipgloss.JoinHorizontal(lipgloss.Top, form, status)
|
||||||
|
|
||||||
|
footer := m.appBoundaryView(m.form.Help().ShortHelpView(m.form.KeyBinds()))
|
||||||
|
if len(errors) > 0 {
|
||||||
|
footer = m.appErrorBoundaryView("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Base.Render(header + "\n" + body + "\n\n" + footer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) errorView() string {
|
||||||
|
var s string
|
||||||
|
for _, err := range m.form.Errors() {
|
||||||
|
s += err.Error()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) appBoundaryView(text string) string {
|
||||||
|
return lipgloss.PlaceHorizontal(
|
||||||
|
m.width,
|
||||||
|
lipgloss.Left,
|
||||||
|
m.styles.HeaderText.Render(text),
|
||||||
|
lipgloss.WithWhitespaceChars("="),
|
||||||
|
lipgloss.WithWhitespaceForeground(indigo),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) appErrorBoundaryView(text string) string {
|
||||||
|
return lipgloss.PlaceHorizontal(
|
||||||
|
m.width,
|
||||||
|
lipgloss.Left,
|
||||||
|
m.styles.ErrorHeaderText.Render(text),
|
||||||
|
lipgloss.WithWhitespaceChars("="),
|
||||||
|
lipgloss.WithWhitespaceForeground(red),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) generatePkl() string {
|
||||||
|
schemaType := m.form.GetString("schemaType")
|
||||||
|
schema := m.form.GetString("schema")
|
||||||
|
|
||||||
|
// This is a placeholder for the actual conversion logic
|
||||||
|
// In a real implementation, you would parse the schema and generate Pkl code
|
||||||
|
return fmt.Sprintf("// Converted from %s\n\nclass ConvertedSchema {\n // TODO: Implement conversion from %s\n // Original schema:\n /*\n%s\n */\n}", schemaType, schemaType, schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) buildMessage() {
|
func (m *Model) buildMessage() {
|
||||||
@ -120,7 +305,7 @@ func (m Model) getMessageView() string {
|
|||||||
return fmt.Sprintf("Current Message:\n%s", string(jsonBytes))
|
return fmt.Sprintf("Current Message:\n%s", string(jsonBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTUIForm() (*tx.TxBody, error) {
|
func RunTUIForm() (*tx.TxBody, error) {
|
||||||
m := NewModel()
|
m := NewModel()
|
||||||
p := tea.NewProgram(m)
|
p := tea.NewProgram(m)
|
||||||
|
|
||||||
@ -139,10 +324,10 @@ func runTUIForm() (*tx.TxBody, error) {
|
|||||||
|
|
||||||
func NewBuildProtoMsgCmd() *cobra.Command {
|
func NewBuildProtoMsgCmd() *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "build-proto-msg",
|
Use: "dash",
|
||||||
Short: "Build a Cosmos SDK protobuf message using a TUI form",
|
Short: "TUI for managing the local Sonr validator node",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
txBody, err := runTUIForm()
|
txBody, err := RunTUIForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
8
pkg/builder/builder.go
Normal file
8
pkg/builder/builder.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
type Builder interface {
|
||||||
|
Build() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() Builder {
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user