github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/cmd/commands/account/command.go (about) 1 /* 2 * Copyright (C) 2021 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package account 19 20 import ( 21 "encoding/json" 22 "errors" 23 "fmt" 24 "strconv" 25 "strings" 26 27 "github.com/mysteriumnetwork/payments/exchange" 28 "github.com/urfave/cli/v2" 29 30 "github.com/mysteriumnetwork/node/cmd/commands/cli/clio" 31 "github.com/mysteriumnetwork/node/config" 32 "github.com/mysteriumnetwork/node/config/remote" 33 "github.com/mysteriumnetwork/node/identity" 34 "github.com/mysteriumnetwork/node/identity/registry" 35 "github.com/mysteriumnetwork/node/money" 36 tequilapi_client "github.com/mysteriumnetwork/node/tequilapi/client" 37 "github.com/mysteriumnetwork/node/tequilapi/contract" 38 ) 39 40 // CommandName is the name of the main command. 41 const CommandName = "account" 42 43 var ( 44 flagAmount = cli.Float64Flag{ 45 Name: "amount", 46 Usage: "Amount of MYST for the top-up", 47 } 48 49 flagCurrency = cli.StringFlag{ 50 Name: "currency", 51 Usage: "Currency you want to use when paying for your top-up", 52 } 53 54 flagGateway = cli.StringFlag{ 55 Name: "gateway", 56 Usage: "Gateway to use", 57 } 58 59 flagGwData = cli.StringFlag{ 60 Name: "data", 61 Usage: "Data required to use the gateway", 62 } 63 64 flagLastTopup = cli.BoolFlag{ 65 Name: "last-topup", 66 Usage: "Include last top-up information", 67 } 68 69 flagToken = cli.StringFlag{ 70 Name: "token", 71 Usage: "Either a referral or affiliate token which can be used when registering", 72 } 73 74 flagCountry = cli.StringFlag{ 75 Name: "country", 76 Usage: "Country code", 77 } 78 79 flagState = cli.StringFlag{ 80 Name: "state", 81 Usage: "State code", 82 } 83 ) 84 85 // NewCommand function creates license command. 86 func NewCommand() *cli.Command { 87 var cmd *command 88 89 return &cli.Command{ 90 Name: CommandName, 91 Usage: "Manage your account", 92 Description: "Using account subcommands you can manage your account details and get information about it", 93 Flags: []cli.Flag{&config.FlagTequilapiAddress, &config.FlagTequilapiPort}, 94 Before: func(ctx *cli.Context) error { 95 tc, err := clio.NewTequilApiClient(ctx) 96 if err != nil { 97 return err 98 } 99 100 cfg, err := remote.NewConfig(tc) 101 if err != nil { 102 return err 103 } 104 105 cmd = &command{ 106 tequilapi: tc, 107 cfg: cfg, 108 } 109 return nil 110 }, 111 Subcommands: []*cli.Command{ 112 { 113 Name: "register", 114 Usage: "Submit a registration request", 115 Flags: []cli.Flag{&flagToken}, 116 Action: func(ctx *cli.Context) error { 117 cmd.register(ctx) 118 return nil 119 }, 120 }, 121 { 122 Name: "topup", 123 Usage: "Create a new top-up for your account", 124 Flags: []cli.Flag{&flagAmount, &flagCurrency, &flagGateway, &flagGwData, &flagCountry, &flagState}, 125 Action: func(ctx *cli.Context) error { 126 cmd.topup(ctx) 127 return nil 128 }, 129 }, 130 { 131 Name: "info", 132 Usage: "Display information about identity account currently in use", 133 Flags: []cli.Flag{&flagLastTopup}, 134 Action: func(ctx *cli.Context) error { 135 cmd.info(ctx) 136 return nil 137 }, 138 }, 139 { 140 Name: "set-identity", 141 Usage: "Sets a new identity for your account which will be used in commands that require it", 142 ArgsUsage: "[IdentityAddress]", 143 Action: func(ctx *cli.Context) error { 144 cmd.setIdentity(ctx) 145 return nil 146 }, 147 }, 148 }, 149 } 150 } 151 152 type command struct { 153 tequilapi *tequilapi_client.Client 154 cfg *remote.Config 155 } 156 157 func (c *command) setIdentity(ctx *cli.Context) { 158 givenID := ctx.Args().First() 159 if givenID == "" { 160 clio.Warn("No identity provided") 161 return 162 } 163 164 if _, err := c.tequilapi.CurrentIdentity(givenID, ""); err != nil { 165 clio.Error("Failed to set identity as default") 166 return 167 } 168 169 clio.Success(fmt.Sprintf("Identity %s set as default", givenID)) 170 } 171 172 func (c *command) info(ctx *cli.Context) { 173 id, err := c.tequilapi.CurrentIdentity("", "") 174 if err != nil { 175 clio.Error("Failed to display information: could not get current identity") 176 return 177 } 178 179 clio.Status("SECTION", "General account information:") 180 c.infoGeneral(id.Address) 181 182 if ctx.Bool(flagLastTopup.Name) { 183 clio.Status("SECTION", "Last top-up information:") 184 c.infoTopUp(id.Address) 185 } 186 } 187 188 func (c *command) topup(ctx *cli.Context) { 189 id, err := c.tequilapi.CurrentIdentity("", "") 190 if err != nil { 191 clio.Warn("Could not get your identity") 192 return 193 } 194 195 ok, err := c.identityIsUnregistered(id.Address) 196 if err != nil { 197 clio.Error(err.Error()) 198 return 199 } 200 201 if ok { 202 clio.Warn("Your identity is not registered, please execute `myst account register` first") 203 return 204 } 205 206 gws, err := c.tequilapi.PaymentOrderGateways(exchange.CurrencyMYST) 207 if err != nil { 208 clio.Info("Failed to get enabled gateways and their information") 209 } 210 211 gatewayName := ctx.String(flagGateway.Name) 212 213 gw, ok := findGateway(gatewayName, gws) 214 if !ok { 215 clio.Error("Can't continue, no such gateway:", gatewayName) 216 return 217 } 218 219 amount := ctx.String(flagAmount.Name) 220 221 amountF, err := strconv.ParseFloat(amount, 64) 222 if err != nil || amountF <= 0 { 223 clio.Warn("Top-up amount is required and must be greater than 0") 224 return 225 } 226 227 if gw.OrderOptions.Minimum != 0 && amountF <= gw.OrderOptions.Minimum { 228 clio.Error( 229 fmt.Sprintf( 230 "Top-up amount must be greater than %v%s", 231 gw.OrderOptions.Minimum, 232 config.GetString(config.FlagDefaultCurrency))) 233 return 234 } 235 236 currency := ctx.String(flagCurrency.Name) 237 if !contains(currency, gw.Currencies) { 238 clio.Warn("Given currency cannot be used") 239 clio.Info("Supported currencies are:", strings.Join(gw.Currencies, ", ")) 240 return 241 } 242 243 country := ctx.String(flagCountry.Name) 244 if country == "" { 245 clio.Warn("Country is required") 246 return 247 } else if len(country) != 2 { 248 clio.Warn("Country code must be 2 characters long") 249 return 250 } 251 252 state := ctx.String(flagState.Name) 253 if state != "" && len(state) != 2 { 254 clio.Warn("State code must be 2 characters long") 255 return 256 } 257 258 callerData := json.RawMessage("{}") 259 if gwData := ctx.String(flagGwData.Name); len(gwData) > 0 { 260 data := map[string]interface{}{} 261 parts := strings.Split(gwData, ",") 262 for _, part := range parts { 263 kv := strings.Split(part, "=") 264 if len(kv) != 2 { 265 clio.Error("Gateway data wrong, example: lightning_network=true,custom_id=123") 266 return 267 } 268 269 if b, err := strconv.ParseBool(kv[1]); err == nil { 270 data[kv[0]] = b 271 continue 272 } 273 274 if b, err := strconv.ParseFloat(kv[1], 64); err == nil { 275 data[kv[0]] = b 276 continue 277 } 278 279 data[kv[0]] = kv[1] 280 } 281 282 callerData, err = json.Marshal(data) 283 if err != nil { 284 clio.Error("Failed to make caller data") 285 return 286 } 287 } 288 289 resp, err := c.tequilapi.OrderCreate(identity.FromAddress(id.Address), gatewayName, contract.PaymentOrderRequest{ 290 MystAmount: amount, 291 PayCurrency: currency, 292 CallerData: callerData, 293 Country: country, 294 State: state, 295 }) 296 if err != nil { 297 clio.Error("Failed to create a top-up request, make sure your requested amount is equal or more than 0.0001 BTC") 298 return 299 } 300 301 clio.Info(fmt.Sprintf("New top-up request for identity %s has been created: ", id.Address)) 302 printOrder(resp) 303 } 304 305 func (c *command) register(ctx *cli.Context) { 306 id, err := c.tequilapi.CurrentIdentity("", "") 307 if err != nil { 308 clio.Warn("Could not get or create identity") 309 return 310 } 311 312 ok, err := c.identityIsUnregistered(id.Address) 313 if err != nil { 314 clio.Error(err.Error()) 315 return 316 } 317 if !ok { 318 clio.Infof("Already have an identity: %s\n", id.Address) 319 return 320 } 321 322 if err := c.tequilapi.Unlock(id.Address, ""); err != nil { 323 clio.Warn("Failed to unlock the identity") 324 return 325 } 326 327 c.registerIdentity(id.Address, c.parseToken(ctx)) 328 } 329 330 func (c *command) parseToken(ctx *cli.Context) *string { 331 if val := ctx.String(flagToken.Name); val != "" { 332 return &val 333 } 334 335 return nil 336 } 337 338 func (c *command) registerIdentity(identity string, token *string) { 339 err := c.tequilapi.RegisterIdentity(identity, "", token) 340 if err != nil { 341 clio.Error("Failed to register the identity") 342 return 343 } 344 345 msg := "Registration started. Top up the identities channel to finish it." 346 clio.Success(msg) 347 } 348 349 func (c *command) identityIsUnregistered(identityAddress string) (bool, error) { 350 identityStatus, err := c.tequilapi.Identity(identityAddress) 351 if err != nil { 352 return false, errors.New("failed to get identity status") 353 } 354 355 return identityStatus.RegistrationStatus == registry.Unregistered.String(), nil 356 } 357 358 func (c *command) infoGeneral(identityAddress string) { 359 identityStatus, err := c.tequilapi.Identity(identityAddress) 360 if err != nil { 361 clio.Error("Failed to display general account information: failed to fetch data") 362 return 363 } 364 365 clio.Info("Using identity:", identityAddress) 366 clio.Info("Registration Status:", identityStatus.RegistrationStatus) 367 clio.Info("Channel address:", identityStatus.ChannelAddress) 368 clio.Info(fmt.Sprintf("Balance: %s", money.New(identityStatus.Balance))) 369 } 370 371 func (c *command) infoTopUp(identityAddress string) { 372 resp, err := c.tequilapi.OrderGetAll(identity.FromAddress(identityAddress)) 373 if err != nil { 374 clio.Error("Failed to get top-up information") 375 return 376 } 377 378 if len(resp) == 0 { 379 clio.Info("You have no top-up requests") 380 return 381 } 382 printOrder(resp[len(resp)-1]) 383 }