Skip to content

Commit 1d77bd1

Browse files
authored
Merge pull request #51 from initia-labs/feat/clipboard
Click to copy mnemonic!
2 parents 69b5ff5 + daf1fd7 commit 1d77bd1

File tree

9 files changed

+320
-38
lines changed

9 files changed

+320
-38
lines changed

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ require (
88
github.com/BurntSushi/toml v1.4.0
99
github.com/PuerkitoBio/goquery v1.10.0
1010
github.com/amplitude/analytics-go v1.0.2
11+
github.com/atotto/clipboard v0.1.4
1112
github.com/btcsuite/btcd/btcec/v2 v2.3.4
1213
github.com/btcsuite/btcutil v1.0.2
1314
github.com/charmbracelet/bubbles v0.20.0
1415
github.com/charmbracelet/bubbletea v1.1.0
1516
github.com/charmbracelet/lipgloss v0.13.0
1617
github.com/charmbracelet/x/term v0.2.0
1718
github.com/fynelabs/selfupdate v0.2.0
19+
github.com/google/uuid v1.6.0
1820
github.com/muesli/reflow v0.3.0
1921
github.com/pelletier/go-toml/v2 v2.2.2
2022
github.com/spf13/cobra v1.8.1
@@ -39,7 +41,6 @@ require (
3941
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
4042
github.com/fsnotify/fsnotify v1.7.0 // indirect
4143
github.com/golang/protobuf v1.5.3 // indirect
42-
github.com/google/uuid v1.6.0 // indirect
4344
github.com/hashicorp/hcl v1.0.0 // indirect
4445
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4546
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ github.com/amplitude/analytics-go v1.0.2 h1:GiKYxFwuuEcEgNpw8p4lPiIXHL6z9iEXa8Ql
1111
github.com/amplitude/analytics-go v1.0.2/go.mod h1:kAQG8OQ6aPOxZrEZ3+/NFCfxdYSyjqXZhgkjWFD3/vo=
1212
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
1313
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
14+
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
15+
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
1416
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
1517
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
1618
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=

io/filesystem.go

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"path/filepath"
1111
"runtime"
1212

13+
"github.com/atotto/clipboard"
14+
1315
"github.com/initia-labs/weave/client"
1416
)
1517

@@ -149,3 +151,12 @@ func CopyDirectory(src, des string) error {
149151
}
150152
return nil
151153
}
154+
155+
// CopyToClipboard copies the given string to the clipboard.
156+
func CopyToClipboard(text string) error {
157+
// Copy the text to clipboard
158+
if err := clipboard.WriteAll(text); err != nil {
159+
return fmt.Errorf("failed to copy to clipboard: %v", err)
160+
}
161+
return nil
162+
}

models/initialize.go

+36-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package models
33
import (
44
"context"
55
"fmt"
6+
"strings"
67
"time"
78

89
tea "github.com/charmbracelet/bubbletea"
@@ -11,6 +12,7 @@ import (
1112
"github.com/initia-labs/weave/config"
1213
weavecontext "github.com/initia-labs/weave/context"
1314
"github.com/initia-labs/weave/crypto"
15+
"github.com/initia-labs/weave/io"
1416
"github.com/initia-labs/weave/styles"
1517
"github.com/initia-labs/weave/types"
1618
"github.com/initia-labs/weave/ui"
@@ -167,7 +169,8 @@ func (m *GenerateGasStationLoading) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
167169
m.Ctx = m.Loading.EndContext
168170
state := weavecontext.PushPageAndGetState[ExistingCheckerState](m)
169171
state.weave.PushPreviousResponse(styles.RenderPreviousResponse(styles.NoSeparator, "Gas Station account has been successfully generated.", []string{}, ""))
170-
return NewSystemKeysMnemonicDisplayInput(weavecontext.SetCurrentState(m.Ctx, state)), nil
172+
model := NewSystemKeysMnemonicDisplayInput(weavecontext.SetCurrentState(m.Ctx, state))
173+
return model, model.Init()
171174
}
172175
return m, cmd
173176
}
@@ -179,15 +182,28 @@ func (m *GenerateGasStationLoading) View() string {
179182

180183
type GasStationMnemonicDisplayInput struct {
181184
ui.TextInput
185+
ui.Clickable
182186
weavecontext.BaseModel
183-
question string
187+
question string
188+
generatedMnemonic string
184189
}
185190

186191
func NewSystemKeysMnemonicDisplayInput(ctx context.Context) *GasStationMnemonicDisplayInput {
192+
state := weavecontext.GetCurrentState[ExistingCheckerState](ctx)
187193
model := &GasStationMnemonicDisplayInput{
188194
TextInput: ui.NewTextInput(true),
189-
BaseModel: weavecontext.BaseModel{Ctx: ctx, CannotBack: true},
190-
question: "Please type `continue` to proceed after you have securely stored the mnemonic.",
195+
Clickable: *ui.NewClickable(
196+
ui.NewClickableItem(
197+
map[bool]string{
198+
true: "Copied! Click to copy again",
199+
false: "Click here to copy",
200+
}, func() error {
201+
return io.CopyToClipboard(state.generatedMnemonic)
202+
},
203+
)),
204+
BaseModel: weavecontext.BaseModel{Ctx: ctx, CannotBack: true},
205+
question: "Please type `continue` to proceed after you have securely stored the mnemonic.",
206+
generatedMnemonic: state.generatedMnemonic,
191207
}
192208
model.WithPlaceholder("Type `continue` to continue, Ctrl+C to quit.")
193209
model.WithValidatorFn(common.ValidateExactString("continue"))
@@ -199,19 +215,24 @@ func (m *GasStationMnemonicDisplayInput) GetQuestion() string {
199215
}
200216

201217
func (m *GasStationMnemonicDisplayInput) Init() tea.Cmd {
202-
return nil
218+
return m.Clickable.Init()
203219
}
204220

205221
func (m *GasStationMnemonicDisplayInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
206222
if model, cmd, handled := weavecontext.HandleCommonCommands[ExistingCheckerState](m, msg); handled {
207223
return model, cmd
208224
}
209225

226+
err := m.Clickable.ClickableUpdate(msg)
227+
if err != nil {
228+
return m, m.HandlePanic(err)
229+
}
230+
210231
input, cmd, done := m.TextInput.Update(msg)
211232
if done {
212233
state := weavecontext.PushPageAndGetState[ExistingCheckerState](m)
213234
model := NewWeaveAppInitialization(m.Ctx, state.generatedMnemonic)
214-
return model, model.Init()
235+
return model, tea.Batch(model.Init(), m.Clickable.PostUpdate())
215236
}
216237
m.TextInput = input
217238
return m, cmd
@@ -225,12 +246,17 @@ func (m *GasStationMnemonicDisplayInput) View() string {
225246
}
226247

227248
var mnemonicText string
228-
mnemonicText += styles.RenderMnemonic("Gas Station", gasStationAddress, state.generatedMnemonic)
249+
mnemonicText += styles.RenderMnemonic("Gas Station", gasStationAddress, m.generatedMnemonic, m.Clickable.ClickableView(0))
229250

230-
return m.WrapView(InitHeader(state.isFirstTime) + "\n" + state.weave.Render() + "\n" +
251+
viewText := m.WrapView(InitHeader(state.isFirstTime) + "\n" + state.weave.Render() + "\n" +
231252
styles.BoldUnderlineText("Important", styles.Yellow) + "\n" +
232253
styles.Text("Write down these mnemonic phrases and store them in a safe place. \nIt is the only way to recover your system keys.", styles.Yellow) + "\n\n" +
233-
mnemonicText + styles.RenderPrompt(m.GetQuestion(), []string{"`continue`"}, styles.Question) + m.TextInput.View())
254+
mnemonicText + "\n" + styles.RenderPrompt(m.GetQuestion(), []string{"`continue`"}, styles.Question) + m.TextInput.View())
255+
err = m.Clickable.ClickableUpdatePositions(viewText)
256+
if err != nil {
257+
m.HandlePanic(err)
258+
}
259+
return viewText
234260
}
235261

236262
type GasStationMnemonicInput struct {
@@ -269,7 +295,7 @@ func (m *GasStationMnemonicInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
269295
state.weave.PushPreviousResponse(
270296
styles.RenderPreviousResponse(styles.DotsSeparator, "Please set up a Gas Station account", []string{"Gas Station account"}, styles.HiddenMnemonicText),
271297
)
272-
model := NewWeaveAppInitialization(weavecontext.SetCurrentState(m.Ctx, state), input.Text)
298+
model := NewWeaveAppInitialization(weavecontext.SetCurrentState(m.Ctx, state), strings.Trim(input.Text, "\n"))
273299
return model, model.Init()
274300
}
275301
m.TextInput = input

models/minitia/launch.go

+68-9
Original file line numberDiff line numberDiff line change
@@ -2520,7 +2520,8 @@ func (m *GenerateOrRecoverSystemKeysLoading) Update(msg tea.Msg) (tea.Model, tea
25202520

25212521
if state.generateKeys {
25222522
state.weave.PushPreviousResponse(styles.RenderPreviousResponse(styles.NoSeparator, "System keys have been successfully generated.", []string{}, ""))
2523-
return NewSystemKeysMnemonicDisplayInput(weavecontext.SetCurrentState(m.Ctx, state)), nil
2523+
model := NewSystemKeysMnemonicDisplayInput(weavecontext.SetCurrentState(m.Ctx, state))
2524+
return model, model.Init()
25242525
} else {
25252526
state.weave.PushPreviousResponse(styles.RenderPreviousResponse(styles.NoSeparator, "System keys have been successfully recovered.", []string{}, ""))
25262527
model, err := NewFundGasStationConfirmationInput(weavecontext.SetCurrentState(m.Ctx, state))
@@ -2540,13 +2541,49 @@ func (m *GenerateOrRecoverSystemKeysLoading) View() string {
25402541

25412542
type SystemKeysMnemonicDisplayInput struct {
25422543
ui.TextInput
2544+
//ui.Clickable
25432545
weavecontext.BaseModel
25442546
question string
25452547
}
25462548

2549+
// NewSystemKeysMnemonicDisplayInput
2550+
// TODO: Switch to the comment version if we manage to fix the height content overflow issue
25472551
func NewSystemKeysMnemonicDisplayInput(ctx context.Context) *SystemKeysMnemonicDisplayInput {
2552+
//state := weavecontext.GetCurrentState[LaunchState](ctx)
25482553
model := &SystemKeysMnemonicDisplayInput{
25492554
TextInput: ui.NewTextInput(true),
2555+
//Clickable: *ui.NewClickable([]*ui.ClickableItem{
2556+
// ui.NewClickableItem(map[bool]string{
2557+
// true: "Copied! Click to copy again",
2558+
// false: "Click here to copy",
2559+
// }, func() error {
2560+
// return io.CopyToClipboard(state.systemKeyOperatorMnemonic)
2561+
// }),
2562+
// ui.NewClickableItem(map[bool]string{
2563+
// true: "Copied! Click to copy again",
2564+
// false: "Click here to copy",
2565+
// }, func() error {
2566+
// return io.CopyToClipboard(state.systemKeyBridgeExecutorMnemonic)
2567+
// }),
2568+
// ui.NewClickableItem(map[bool]string{
2569+
// true: "Copied! Click to copy again",
2570+
// false: "Click here to copy",
2571+
// }, func() error {
2572+
// return io.CopyToClipboard(state.systemKeyOutputSubmitterMnemonic)
2573+
// }),
2574+
// ui.NewClickableItem(map[bool]string{
2575+
// true: "Copied! Click to copy again",
2576+
// false: "Click here to copy",
2577+
// }, func() error {
2578+
// return io.CopyToClipboard(state.systemKeyBatchSubmitterMnemonic)
2579+
// }),
2580+
// ui.NewClickableItem(map[bool]string{
2581+
// true: "Copied! Click to copy again",
2582+
// false: "Click here to copy",
2583+
// }, func() error {
2584+
// return io.CopyToClipboard(state.systemKeyChallengerMnemonic)
2585+
// }),
2586+
//}...),
25502587
BaseModel: weavecontext.BaseModel{Ctx: ctx, CannotBack: true},
25512588
question: "Type `continue` to proceed after you have securely stored the mnemonic.",
25522589
}
@@ -2560,6 +2597,7 @@ func (m *SystemKeysMnemonicDisplayInput) GetQuestion() string {
25602597
}
25612598

25622599
func (m *SystemKeysMnemonicDisplayInput) Init() tea.Cmd {
2600+
//return m.Clickable.Init()
25632601
return nil
25642602
}
25652603

@@ -2568,13 +2606,19 @@ func (m *SystemKeysMnemonicDisplayInput) Update(msg tea.Msg) (tea.Model, tea.Cmd
25682606
return model, cmd
25692607
}
25702608

2609+
//err := m.Clickable.ClickableUpdate(msg)
2610+
//if err != nil {
2611+
// return m, m.HandlePanic(err)
2612+
//}
2613+
25712614
input, cmd, done := m.TextInput.Update(msg)
25722615
if done {
25732616
_ = weavecontext.PushPageAndGetState[LaunchState](m)
25742617
model, err := NewFundGasStationConfirmationInput(m.Ctx)
25752618
if err != nil {
25762619
return m, m.HandlePanic(err)
25772620
}
2621+
//return model, m.Clickable.PostUpdate()
25782622
return model, nil
25792623
}
25802624
m.TextInput = input
@@ -2585,16 +2629,31 @@ func (m *SystemKeysMnemonicDisplayInput) View() string {
25852629
state := weavecontext.GetCurrentState[LaunchState](m.Ctx)
25862630

25872631
var mnemonicText string
2588-
mnemonicText += styles.RenderMnemonic("Operator", state.systemKeyOperatorAddress, state.systemKeyOperatorMnemonic)
2589-
mnemonicText += styles.RenderMnemonic("Bridge Executor", state.systemKeyBridgeExecutorAddress, state.systemKeyBridgeExecutorMnemonic)
2590-
mnemonicText += styles.RenderMnemonic("Output Submitter", state.systemKeyOutputSubmitterAddress, state.systemKeyOutputSubmitterMnemonic)
2591-
mnemonicText += styles.RenderMnemonic("Batch Submitter", state.systemKeyBatchSubmitterAddress, state.systemKeyBatchSubmitterMnemonic)
2592-
mnemonicText += styles.RenderMnemonic("Challenger", state.systemKeyChallengerAddress, state.systemKeyChallengerMnemonic)
2593-
2594-
return m.WrapView(state.weave.Render() + "\n" +
2632+
//mnemonicText += styles.RenderMnemonic("Operator", state.systemKeyOperatorAddress, state.systemKeyOperatorMnemonic, m.Clickable.ClickableView(0))
2633+
//mnemonicText += styles.RenderMnemonic("Bridge Executor", state.systemKeyBridgeExecutorAddress, state.systemKeyBridgeExecutorMnemonic, m.Clickable.ClickableView(1))
2634+
//mnemonicText += styles.RenderMnemonic("Output Submitter", state.systemKeyOutputSubmitterAddress, state.systemKeyOutputSubmitterMnemonic, m.Clickable.ClickableView(2))
2635+
//mnemonicText += styles.RenderMnemonic("Batch Submitter", state.systemKeyBatchSubmitterAddress, state.systemKeyBatchSubmitterMnemonic, m.Clickable.ClickableView(3))
2636+
//mnemonicText += styles.RenderMnemonic("Challenger", state.systemKeyChallengerAddress, state.systemKeyChallengerMnemonic, m.Clickable.ClickableView(4))
2637+
mnemonicText += styles.RenderMnemonic("Operator", state.systemKeyOperatorAddress, state.systemKeyOperatorMnemonic, "")
2638+
mnemonicText += styles.RenderMnemonic("Bridge Executor", state.systemKeyBridgeExecutorAddress, state.systemKeyBridgeExecutorMnemonic, "")
2639+
mnemonicText += styles.RenderMnemonic("Output Submitter", state.systemKeyOutputSubmitterAddress, state.systemKeyOutputSubmitterMnemonic, "")
2640+
mnemonicText += styles.RenderMnemonic("Batch Submitter", state.systemKeyBatchSubmitterAddress, state.systemKeyBatchSubmitterMnemonic, "")
2641+
mnemonicText += styles.RenderMnemonic("Challenger", state.systemKeyChallengerAddress, state.systemKeyChallengerMnemonic, "")
2642+
2643+
userHome, err := os.UserHomeDir()
2644+
if err != nil {
2645+
m.HandlePanic(err)
2646+
}
2647+
configFilePath := filepath.Join(userHome, common.WeaveDataDirectory, LaunchConfigFilename)
2648+
viewText := m.WrapView(state.weave.Render() + "\n" +
25952649
styles.BoldUnderlineText("Important", styles.Yellow) + "\n" +
2596-
styles.Text("Write down these mnemonic phrases and store them in a safe place. \nIt is the only way to recover your system keys.", styles.Yellow) + "\n\n" +
2650+
styles.Text(fmt.Sprintf("Write down these mnemonic phrases and store them in a safe place. \nIt is the only way to recover your system keys.\n\nNote that before launching, these mnemonic phrases along with other configuration details will be stored\nin %s. You can revisit them anytime.", configFilePath), styles.Yellow) + "\n\n" +
25972651
mnemonicText + styles.RenderPrompt(m.GetQuestion(), []string{"`continue`"}, styles.Question) + m.TextInput.View())
2652+
//err = m.Clickable.ClickableUpdatePositions(viewText)
2653+
if err != nil {
2654+
m.HandlePanic(err)
2655+
}
2656+
return viewText
25982657
}
25992658

26002659
type FundGasStationConfirmationInput struct {

models/minitia/tx.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ func (lsk *L1SystemKeys) FundAccountsWithGasStation(state *LaunchState) (*FundAc
8484
return nil, fmt.Errorf("failed to get active rpc for celestia: %v", err)
8585
}
8686

87-
celestiaMinGasPrice, err := celestiaRegistry.GetMinGasPriceByDenom(DefaultCelestiaGasDenom)
88-
if err != nil {
89-
return nil, fmt.Errorf("failed to get celestia minimum gas price: %v", err)
90-
}
87+
//celestiaMinGasPrice, err := celestiaRegistry.GetMinGasPriceByDenom(DefaultCelestiaGasDenom)
88+
//if err != nil {
89+
// return nil, fmt.Errorf("failed to get celestia minimum gas price: %v", err)
90+
//}
9191

9292
celestiaChainId := celestiaRegistry.GetChainId()
9393
sendCmd := exec.Command(state.celestiaBinaryPath, "tx", "bank", "send", common.WeaveGasStationKeyName,
9494
lsk.BatchSubmitter.Address, fmt.Sprintf("%sutia", lsk.BatchSubmitter.Coins), "--node", celestiaRpc,
95-
"--chain-id", celestiaChainId, "--gas", "200000", "--gas-prices", celestiaMinGasPrice, "--output", "json", "-y",
95+
"--chain-id", celestiaChainId, "--gas", "200000", "--gas-prices", "0.1utia", "--output", "json", "-y",
9696
)
9797
broadcastRes, err := sendCmd.CombinedOutput()
9898
if err != nil {

0 commit comments

Comments
 (0)