Skip to content

Commit 6f5f486

Browse files
authored
Merge pull request #2054 from onflow/cf/acct-name-funding
Allow `fund` command to accept account names from `flow.json`
2 parents e5c63ac + 3643c16 commit 6f5f486

2 files changed

Lines changed: 146 additions & 12 deletions

File tree

internal/accounts/fund.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,36 @@ func getTestnetAccounts(state *flowkit.State) []accounts.Account {
5656
return testnetAccounts
5757
}
5858

59+
// resolveAddressOrAccountName resolves a string that could be either an address or account name
60+
func resolveAddressOrAccountName(input string, state *flowkit.State) (flowsdk.Address, error) {
61+
address := flowsdk.HexToAddress(input)
62+
63+
if address.IsValid(flowsdk.Mainnet) || address.IsValid(flowsdk.Testnet) || address.IsValid(flowsdk.Emulator) {
64+
// For direct addresses, we'll let the caller handle testnet validation
65+
return address, nil
66+
}
67+
68+
account, err := state.Accounts().ByName(input)
69+
if err != nil {
70+
accountName := branding.GrayStyle.Render(input)
71+
return flowsdk.EmptyAddress, fmt.Errorf("could not find account with name %s", accountName)
72+
}
73+
74+
if !account.Address.IsValid(flowsdk.Testnet) {
75+
accountName := branding.PurpleStyle.Render(input)
76+
addressStr := branding.GrayStyle.Render(account.Address.String())
77+
errorMsg := branding.ErrorStyle.Render("The faucet can only fund testnet addresses")
78+
return flowsdk.EmptyAddress, fmt.Errorf("account %s has address %s which is not valid for testnet. %s", accountName, addressStr, errorMsg)
79+
}
80+
81+
return account.Address, nil
82+
}
83+
5984
var fundCommand = &command.Command{
6085
Cmd: &cobra.Command{
61-
Use: "fund [address]",
62-
Short: "Funds an account by address through the Testnet Faucet",
63-
Example: "flow accounts fund 8e94eaa81771313a\nflow accounts fund",
86+
Use: "fund [address|name]",
87+
Short: "Funds an account by address or account name through the Testnet Faucet",
88+
Example: "flow accounts fund 8e94eaa81771313a\nflow accounts fund testnet-account\nflow accounts fund",
6489
Args: cobra.MaximumNArgs(1),
6590
},
6691
Flags: &fundFlags,
@@ -88,12 +113,13 @@ func fund(
88113

89114
options := make([]string, len(testnetAccounts))
90115
for i, account := range testnetAccounts {
91-
options[i] = fmt.Sprintf("0x%s (%s)", account.Address.String(), account.Name)
116+
options[i] = fmt.Sprintf("%s (%s)", account.Address.HexWithPrefix(), account.Name)
92117
}
93118

94119
selected, err := prompt.RunSingleSelect(options, "Select a testnet account to fund:")
95120
if err != nil {
96-
return nil, fmt.Errorf("account selection cancelled: %w", err)
121+
errorMsg := branding.ErrorStyle.Render("account selection cancelled")
122+
return nil, fmt.Errorf("%s: %w", errorMsg, err)
97123
}
98124

99125
for i, option := range options {
@@ -103,19 +129,28 @@ func fund(
103129
}
104130
}
105131
} else {
106-
address = flowsdk.HexToAddress(args[0])
132+
var err error
133+
address, err = resolveAddressOrAccountName(args[0], state)
134+
if err != nil {
135+
return nil, err
136+
}
107137
}
108138

109139
if !address.IsValid(flowsdk.Testnet) {
110-
return nil, fmt.Errorf("unsupported address %s, faucet can only work for valid Testnet addresses", address.String())
140+
addressStr := branding.GrayStyle.Render(address.String())
141+
errorMsg := branding.ErrorStyle.Render("faucet can only work for valid Testnet addresses")
142+
return nil, fmt.Errorf("unsupported address %s, %s", addressStr, errorMsg)
111143
}
112144

145+
addressStr := branding.PurpleStyle.Render(address.HexWithPrefix())
146+
linkStr := branding.GreenStyle.Render(testnetFaucetURL(address))
147+
113148
logger.Info(
114149
fmt.Sprintf(
115-
"Opening the Testnet faucet to fund 0x%s on your native browser."+
150+
"Opening the Testnet faucet to fund %s on your native browser."+
116151
"\n\nIf there is an issue, please use this link instead: %s",
117-
address.String(),
118-
testnetFaucetURL(address),
152+
addressStr,
153+
linkStr,
119154
))
120155
// wait for the user to read the message
121156
time.Sleep(5 * time.Second)

internal/accounts/fund_test.go

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/onflow/flow-go-sdk"
2525
"github.com/onflow/flow-go-sdk/crypto"
2626
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/require"
2728

2829
"github.com/onflow/flowkit/v2/accounts"
2930

@@ -111,6 +112,80 @@ func Test_GetTestnetAccounts(t *testing.T) {
111112
})
112113
}
113114

115+
func Test_ResolveAddressOrAccountName(t *testing.T) {
116+
t.Run("Resolves valid hex address", func(t *testing.T) {
117+
_, state, _ := util.TestMocks(t)
118+
119+
address, err := resolveAddressOrAccountName("8efde57e98c557fa", state)
120+
121+
require.NoError(t, err)
122+
assert.Equal(t, "8efde57e98c557fa", address.String())
123+
})
124+
125+
t.Run("Resolves address with 0x prefix", func(t *testing.T) {
126+
_, state, _ := util.TestMocks(t)
127+
128+
address, err := resolveAddressOrAccountName("0x8efde57e98c557fa", state)
129+
130+
require.NoError(t, err)
131+
assert.Equal(t, "8efde57e98c557fa", address.String())
132+
})
133+
134+
t.Run("Resolves account name", func(t *testing.T) {
135+
_, state, _ := util.TestMocks(t)
136+
137+
// Add a testnet account to state
138+
testnetAddr := flow.HexToAddress("8efde57e98c557fa")
139+
testnetAccount := &accounts.Account{
140+
Name: "my-testnet-account",
141+
Address: testnetAddr,
142+
Key: accounts.NewHexKeyFromPrivateKey(0, crypto.SHA3_256, generateTestPrivateKey()),
143+
}
144+
state.Accounts().AddOrUpdate(testnetAccount)
145+
146+
address, err := resolveAddressOrAccountName("my-testnet-account", state)
147+
148+
require.NoError(t, err)
149+
assert.Equal(t, testnetAddr, address)
150+
})
151+
152+
t.Run("Fails with invalid account name", func(t *testing.T) {
153+
_, state, _ := util.TestMocks(t)
154+
155+
address, err := resolveAddressOrAccountName("non-existent-account", state)
156+
157+
assert.Equal(t, flow.EmptyAddress, address)
158+
assert.Error(t, err)
159+
})
160+
161+
t.Run("Fails with invalid hex string", func(t *testing.T) {
162+
_, state, _ := util.TestMocks(t)
163+
164+
address, err := resolveAddressOrAccountName("invalid-hex-123", state)
165+
166+
assert.Equal(t, flow.EmptyAddress, address)
167+
assert.Error(t, err)
168+
})
169+
170+
t.Run("Fails when account name resolves to non-testnet address", func(t *testing.T) {
171+
_, state, _ := util.TestMocks(t)
172+
173+
// Add an emulator account (not testnet-valid)
174+
emulatorAddr := flow.HexToAddress("f8d6e0586b0a20c7")
175+
emulatorAccount := &accounts.Account{
176+
Name: "emulator-account",
177+
Address: emulatorAddr,
178+
Key: accounts.NewHexKeyFromPrivateKey(0, crypto.SHA3_256, generateTestPrivateKey()),
179+
}
180+
state.Accounts().AddOrUpdate(emulatorAccount)
181+
182+
address, err := resolveAddressOrAccountName("emulator-account", state)
183+
184+
assert.Equal(t, flow.EmptyAddress, address)
185+
assert.Error(t, err)
186+
})
187+
}
188+
114189
func Test_Fund(t *testing.T) {
115190
srv, state, _ := util.TestMocks(t)
116191

@@ -126,7 +201,31 @@ func Test_Fund(t *testing.T) {
126201
)
127202

128203
assert.Nil(t, result)
129-
assert.Contains(t, err.Error(), "faucet can only work for valid Testnet addresses")
204+
assert.Error(t, err)
205+
})
206+
207+
t.Run("Fail with non-testnet account name", func(t *testing.T) {
208+
// Add an emulator account to the state
209+
emulatorAddr := flow.HexToAddress("f8d6e0586b0a20c7")
210+
emulatorAccount := &accounts.Account{
211+
Name: "emulator-account",
212+
Address: emulatorAddr,
213+
Key: accounts.NewHexKeyFromPrivateKey(0, crypto.SHA3_256, generateTestPrivateKey()),
214+
}
215+
state.Accounts().AddOrUpdate(emulatorAccount)
216+
217+
args := []string{"emulator-account"} // Non-testnet account name
218+
219+
result, err := fund(
220+
args,
221+
command.GlobalFlags{},
222+
util.NoLogger,
223+
srv.Mock,
224+
state,
225+
)
226+
227+
assert.Nil(t, result)
228+
assert.Error(t, err)
130229
})
131230

132231
t.Run("Fail with no address and no testnet accounts", func(t *testing.T) {
@@ -155,7 +254,7 @@ func Test_Fund(t *testing.T) {
155254
)
156255

157256
assert.Nil(t, result)
158-
assert.Contains(t, err.Error(), "no testnet accounts found in flow.json")
257+
assert.Error(t, err)
159258
})
160259
}
161260

0 commit comments

Comments
 (0)