feat(@desktop/wallet): Add derivation path to wallet account generation

fixes #5074
This commit is contained in:
Khushboo Mehta 2022-03-31 13:46:25 +02:00 committed by Khushboo-dev-cpp
parent a379c34ceb
commit feaa91d062
31 changed files with 1306 additions and 369 deletions

View File

@ -23,17 +23,25 @@ proc init*(self: Controller) =
proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] =
return self.walletAccountService.getWalletAccounts()
proc generateNewAccount*(self: Controller, password: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.generateNewAccount(password, accountName, color, emoji)
proc generateNewAccount*(self: Controller, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
return self.walletAccountService.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
proc addAccountsFromPrivateKey*(self: Controller, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
proc addAccountsFromSeed*(self: Controller, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
return self.walletAccountService.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
proc addWatchOnlyAccount*(self: Controller, address: string, accountName: string, color: string, emoji: string): string =
return self.walletAccountService.addWatchOnlyAccount(address, accountName, color, emoji)
proc deleteAccount*(self: Controller, address: string) =
self.walletAccountService.deleteAccount(address)
method getDerivedAddressList*(self: Controller, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
return self.walletAccountService.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
method getDerivedAddressListForMnemonic*(self: Controller, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
return self.walletAccountService.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)

View File

@ -0,0 +1,33 @@
import strformat
type
DerivedAddressItem* = object
address: string
path: string
hasActivity: bool
proc initDerivedAddressItem*(
address: string,
path: string,
hasActivity: bool
): DerivedAddressItem =
result.address = address
result.path = path
result.hasActivity = hasActivity
proc `$`*(self: DerivedAddressItem): string =
result = fmt"""DerivedAddressItem(
address: {self.address},
path: {self.path},
hasActivity: {self.hasActivity}
]"""
proc getAddress*(self: DerivedAddressItem): string =
return self.address
proc getPath*(self: DerivedAddressItem): string =
return self.path
proc getHasActivity*(self: DerivedAddressItem): bool =
return self.hasActivity

View File

@ -0,0 +1,94 @@
import NimQml, Tables, strutils, strformat
import ./derived_address_item
type
ModelRole {.pure.} = enum
Address = UserRole + 1,
Path,
HasActivity,
QtObject:
type
DerivedAddressModel* = ref object of QAbstractListModel
derivedWalletAddresses: seq[DerivedAddressItem]
proc delete(self: DerivedAddressModel) =
self.derivedWalletAddresses = @[]
self.QAbstractListModel.delete
proc setup(self: DerivedAddressModel) =
self.QAbstractListModel.setup
proc newDerivedAddressModel*(): DerivedAddressModel =
new(result, delete)
result.setup
proc `$`*(self: DerivedAddressModel): string =
for i in 0 ..< self.derivedWalletAddresses.len:
result &= fmt"""[{i}]:({$self.derivedWalletAddresses[i]})"""
proc countChanged(self: DerivedAddressModel) {.signal.}
proc getCount(self: DerivedAddressModel): int {.slot.} =
self.derivedWalletAddresses.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: DerivedAddressModel, index: QModelIndex = nil): int =
return self.derivedWalletAddresses.len
method roleNames(self: DerivedAddressModel): Table[int, string] =
{
ModelRole.Address.int: "address",
ModelRole.Path.int: "path",
ModelRole.HasActivity.int: "hasActivity",
}.toTable
method data(self: DerivedAddressModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.derivedWalletAddresses.len):
return
let item = self.derivedWalletAddresses[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Address:
result = newQVariant(item.getAddress())
of ModelRole.Path:
result = newQVariant(item.getPath())
of ModelRole.HasActivity:
result = newQVariant(item.getHasActivity())
proc setItems*(self: DerivedAddressModel, items: seq[DerivedAddressItem]) =
self.beginResetModel()
self.derivedWalletAddresses = items
self.endResetModel()
self.countChanged()
proc getDerivedAddressAtIndex*(self: DerivedAddressModel, index: int): string =
if (index < 0 or index > self.getCount()):
return
let item = self.derivedWalletAddresses[index]
result = item.getAddress()
proc getDerivedAddressPathAtIndex*(self: DerivedAddressModel, index: int): string =
if (index < 0 or index > self.getCount()):
return
let item = self.derivedWalletAddresses[index]
result = item.getPath()
proc getDerivedAddressHasActivityAtIndex*(self: DerivedAddressModel, index: int): bool =
if (index < 0 or index > self.getCount()):
return
let item = self.derivedWalletAddresses[index]
result = item.getHasActivity()

View File

@ -0,0 +1,40 @@
import strformat
import ./model
type
GeneratedWalletItem* = object
name: string
iconName: string
generatedModel: Model
derivedfrom: string
proc initGeneratedWalletItem*(
name: string,
iconName: string,
generatedModel: Model,
derivedfrom: string
): GeneratedWalletItem =
result.name = name
result.iconName = iconName
result.generatedModel = generatedModel
result.derivedfrom = derivedfrom
proc `$`*(self: GeneratedWalletItem): string =
result = fmt"""GeneratedWalletItem(
name: {self.name},
iconName: {self.iconName},
generatedModel: {self.generatedModel},
derivedfrom: {self.derivedfrom}
]"""
proc getName*(self: GeneratedWalletItem): string =
return self.name
proc getIconName*(self: GeneratedWalletItem): string =
return self.iconName
proc getGeneratedModel*(self: GeneratedWalletItem): Model =
return self.generatedModel
proc getDerivedfrom*(self: GeneratedWalletItem): string =
return self.derivedfrom

View File

@ -0,0 +1,76 @@
import NimQml, Tables, strutils, strformat
import ./generated_wallet_item
type
ModelRole {.pure.} = enum
Name = UserRole + 1,
IconName,
GeneratedModel,
DerivedFrom
QtObject:
type
GeneratedWalletModel* = ref object of QAbstractListModel
generatedWalletItems: seq[GeneratedWalletItem]
proc delete(self: GeneratedWalletModel) =
self.generatedWalletItems = @[]
self.QAbstractListModel.delete
proc setup(self: GeneratedWalletModel) =
self.QAbstractListModel.setup
proc newGeneratedWalletModel*(): GeneratedWalletModel =
new(result, delete)
result.setup
proc `$`*(self: GeneratedWalletModel): string =
for i in 0 ..< self.generatedWalletItems.len:
result &= fmt"""[{i}]:({$self.generatedWalletItems[i]})"""
proc countChanged(self: GeneratedWalletModel) {.signal.}
proc getCount(self: GeneratedWalletModel): int {.slot.} =
self.generatedWalletItems.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: GeneratedWalletModel, index: QModelIndex = nil): int =
return self.generatedWalletItems.len
method roleNames(self: GeneratedWalletModel): Table[int, string] =
{
ModelRole.Name.int: "name",
ModelRole.IconName.int: "iconName",
ModelRole.GeneratedModel.int: "generatedModel",
ModelRole.DerivedFrom.int: "derivedfrom",
}.toTable
method data(self: GeneratedWalletModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.generatedWalletItems.len):
return
let item = self.generatedWalletItems[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Name:
result = newQVariant(item.getName())
of ModelRole.IconName:
result = newQVariant(item.getIconName())
of ModelRole.GeneratedModel:
result = newQVariant(item.getGeneratedModel())
of ModelRole.DerivedFrom:
result = newQVariant(item.getDerivedFrom())
proc setItems*(self: GeneratedWalletModel, items: seq[GeneratedWalletItem]) =
self.beginResetModel()
self.generatedWalletItems = items
self.endResetModel()
self.countChanged()

View File

@ -1,3 +1,5 @@
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
@ -11,13 +13,13 @@ method load*(self: AccessInterface) {.base.} =
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method generateNewAccount*(self: AccessInterface, password: string, accountName: string, color: string, emoji: string): string {.base.} =
method generateNewAccount*(self: AccessInterface, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method addAccountsFromPrivateKey*(self: AccessInterface, privateKey: string, password: string, accountName: string, color: string, emoji: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method addAccountsFromSeed*(self: AccessInterface, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string {.base.} =
method addAccountsFromSeed*(self: AccessInterface, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method addWatchOnlyAccount*(self: AccessInterface, address: string, accountName: string, color: string, emoji: string): string {.base.} =
@ -29,6 +31,12 @@ method deleteAccount*(self: AccessInterface, address: string) {.base.} =
method refreshWalletAccounts*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddressList*(self: AccessInterface, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddressListForMnemonic*(self: AccessInterface, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.

View File

@ -14,6 +14,7 @@ type
currencyBalance: float64
assets: token_model.Model
emoji: string
derivedfrom: string
proc initItem*(
name: string,
@ -26,7 +27,8 @@ proc initItem*(
isChat: bool,
currencyBalance: float64,
assets: token_model.Model,
emoji: string
emoji: string,
derivedfrom: string
): Item =
result.name = name
result.address = address
@ -39,6 +41,7 @@ proc initItem*(
result.currencyBalance = currencyBalance
result.assets = assets
result.emoji = emoji
result.derivedfrom = derivedfrom
proc `$`*(self: Item): string =
result = fmt"""WalletAccountItem(
@ -52,7 +55,8 @@ proc `$`*(self: Item): string =
isChat: {self.isChat},
currencyBalance: {self.currencyBalance},
assets.len: {self.assets.getCount()},
emoji: {self.emoji}
emoji: {self.emoji},
derivedfrom: {self.derivedfrom}
]"""
proc getName*(self: Item): string =
@ -87,3 +91,6 @@ proc getCurrencyBalance*(self: Item): float64 =
proc getAssets*(self: Item): token_model.Model =
return self.assets
proc getDerivedFrom*(self: Item): string =
return self.derivedfrom

View File

@ -14,7 +14,8 @@ type
IsChat,
CurrencyBalance,
Assets,
Emoji
Emoji,
DerivedFrom
QtObject:
type
@ -38,7 +39,7 @@ QtObject:
proc countChanged(self: Model) {.signal.}
proc getCount(self: Model): int {.slot.} =
proc getCount*(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
@ -60,7 +61,8 @@ QtObject:
ModelRole.IsChat.int:"isChat",
ModelRole.Assets.int:"assets",
ModelRole.CurrencyBalance.int:"currencyBalance",
ModelRole.Emoji.int: "emoji"
ModelRole.Emoji.int: "emoji",
ModelRole.DerivedFrom.int: "derivedfrom"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -96,6 +98,8 @@ QtObject:
result = newQVariant(item.getAssets())
of ModelRole.Emoji:
result = newQVariant(item.getEmoji())
of ModelRole.DerivedFrom:
result = newQVariant(item.getDerivedFrom())
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()

View File

@ -63,7 +63,8 @@ method refreshWalletAccounts*(self: Module) =
w.isChat,
w.getCurrencyBalance(),
assets,
w.emoji
w.emoji,
w.derivedfrom
))
self.view.setItems(items)
@ -101,17 +102,26 @@ method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.accountsModuleDidLoad()
method generateNewAccount*(self: Module, password: string, accountName: string, color: string, emoji: string): string =
return self.controller.generateNewAccount(password, accountName, color, emoji)
method generateNewAccount*(self: Module, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
return self.controller.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
method addAccountsFromPrivateKey*(self: Module, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
return self.controller.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
method addAccountsFromSeed*(self: Module, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string =
return self.controller.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
method addAccountsFromSeed*(self: Module, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
return self.controller.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
method addWatchOnlyAccount*(self: Module, address: string, accountName: string, color: string, emoji: string): string =
return self.controller.addWatchOnlyAccount(address, accountName, color, emoji)
method deleteAccount*(self: Module, address: string) =
self.controller.deleteAccount(address)
method getDerivedAddressList*(self: Module, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
return self.controller.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
method getDerivedAddressListForMnemonic*(self: Module, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
return self.controller.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)

View File

@ -1,11 +1,17 @@
import NimQml
import NimQml, sequtils, strutils, sugar
import ./model
import ./item
import ./io_interface
import ./generated_wallet_model
import ./generated_wallet_item
import ./derived_address_model
import ./derived_address_item
const WATCH = "watch"
const GENERATED = "generated"
const SEED = "seed"
const KEY = "key"
QtObject:
type
@ -15,10 +21,14 @@ QtObject:
generated: Model
watchOnly: Model
imported: Model
generatedAccounts: GeneratedWalletModel
derivedAddresses: DerivedAddressModel
modelVariant: QVariant
generatedVariant: QVariant
importedVariant: QVariant
watchOnlyVariant: QVariant
generatedAccountsVariant: QVariant
derivedAddressesVariant: QVariant
tmpAddress: string
proc delete*(self: View) =
@ -30,6 +40,10 @@ QtObject:
self.generatedVariant.delete
self.watchOnly.delete
self.watchOnlyVariant.delete
self.generatedAccounts.delete
self.generatedAccountsVariant.delete
self.derivedAddresses.delete
self.derivedAddressesVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
@ -44,6 +58,10 @@ QtObject:
result.generatedVariant = newQVariant(result.generated)
result.watchOnly = newModel()
result.watchOnlyVariant = newQVariant(result.watchOnly)
result.generatedAccounts = newGeneratedWalletModel()
result.generatedAccountsVariant = newQVariant(result.generatedAccounts)
result.derivedAddresses = newDerivedAddressModel()
result.derivedAddressesVariant = newQVariant(result.derivedAddresses)
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -84,6 +102,24 @@ QtObject:
read = getGenereated
notify = generatedChanged
proc generatedAccountsChanged*(self: View) {.signal.}
proc getGeneratedAccounts(self: View): QVariant {.slot.} =
return self.generatedAccountsVariant
QtProperty[QVariant] generatedAccounts:
read = getGeneratedAccounts
notify = generatedAccountsChanged
proc derivedAddressesChanged*(self: View) {.signal.}
proc getDerivedAddresses(self: View): QVariant {.slot.} =
return self.derivedAddressesVariant
QtProperty[QVariant] derivedAddresses:
read = getDerivedAddresses
notify = derivedAddressesChanged
proc setItems*(self: View, items: seq[Item]) =
self.model.setItems(items)
@ -98,19 +134,37 @@ QtObject:
watchOnly.add(item)
else:
imported.add(item)
self.watchOnly.setItems(watchOnly)
self.imported.setItems(imported)
self.generated.setItems(generated)
proc generateNewAccount*(self: View, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
return self.delegate.generateNewAccount(password, accountName, color, emoji)
# create a list of imported seeds/default account created from where more accounts can be derived
var generatedAccounts: seq[GeneratedWalletItem] = @[]
var importedSeedIndex: int = 1
for item in items:
if item.getWalletType() == "":
var generatedAccs: Model = newModel()
generatedAccs.setItems(generated.filter(x => cmpIgnoreCase(x.getDerivedFrom(), item.getDerivedFrom()) == 0))
generatedAccounts.add(initGeneratedWalletItem("Default", "status", generatedAccs, item.getDerivedFrom()))
elif item.getWalletType() == SEED:
var generatedAccs1: Model = newModel()
var filterItems: seq[Item] = generated.filter(x => cmpIgnoreCase(x.getDerivedFrom(), item.getDerivedFrom()) == 0)
filterItems.insert(item, 0)
generatedAccs1.setItems(filterItems)
generatedAccounts.add(initGeneratedWalletItem("Seed " & $importedSeedIndex , "seed-phrase", generatedAccs1, item.getDerivedFrom()))
importedSeedIndex += 1
self.generatedAccounts.setItems(generatedAccounts)
proc generateNewAccount*(self: View, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string {.slot.} =
return self.delegate.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
proc addAccountsFromPrivateKey*(self: View, privateKey: string, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
return self.delegate.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
proc addAccountsFromSeed*(self: View, seedPhrase: string, password: string, accountName: string, color: string, emoji: string): string {.slot.} =
return self.delegate.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
proc addAccountsFromSeed*(self: View, seedPhrase: string, password: string, accountName: string, color: string, emoji: string, path: string): string {.slot.} =
return self.delegate.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
proc addWatchOnlyAccount*(self: View, address: string, accountName: string, color: string, emoji: string): string {.slot.} =
return self.delegate.addWatchOnlyAccount(address, accountName, color, emoji)
@ -129,3 +183,36 @@ QtObject:
proc getAccountAssetsByAddress*(self: View): QVariant {.slot.} =
return self.model.getAccountAssetsByAddress(self.tmpAddress)
proc getDerivedAddressList*(self: View, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): string {.slot.} =
var items: seq[DerivedAddressItem] = @[]
let (result, error) = self.delegate.getDerivedAddressList(password, derivedfrom, path, pageSize, pageNumber)
for item in result:
items.add(initDerivedAddressItem(item.address, item.path, item.hasActivity))
self.derivedAddresses.setItems(items)
self.derivedAddressesChanged()
return error
proc getDerivedAddressListForMnemonic*(self: View, mnemonic: string, path: string, pageSize: int, pageNumber: int): string {.slot.} =
var items: seq[DerivedAddressItem] = @[]
let (result, error) = self.delegate.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
for item in result:
items.add(initDerivedAddressItem(item.address, item.path, item.hasActivity))
self.derivedAddresses.setItems(items)
self.derivedAddressesChanged()
return error
proc resetDerivedAddressModel*(self: View) {.slot.} =
var items: seq[DerivedAddressItem] = @[]
self.derivedAddresses.setItems(items)
self.derivedAddressesChanged()
proc getDerivedAddressAtIndex*(self: View, index: int): string {.slot.} =
return self.derivedAddresses.getDerivedAddressAtIndex(index)
proc getDerivedAddressPathAtIndex*(self: View, index: int): string {.slot.} =
return self.derivedAddresses.getDerivedAddressPathAtIndex(index)
proc getDerivedAddressHasActivityAtIndex*(self: View, index: int): bool {.slot.} =
return self.derivedAddresses.getDerivedAddressHasActivityAtIndex(index)

View File

@ -122,6 +122,14 @@ proc storeDerivedAccounts(self: Service, accountId, hashedPassword: string,
result = toDerivedAccounts(response.result)
proc storeAccount(self: Service, accountId, hashedPassword: string): GeneratedAccountDto =
let response = status_account.storeAccounts(accountId, hashedPassword)
if response.result.contains("error"):
raise newException(Exception, response.result["error"].getStr)
result = toGeneratedAccountDto(response.result)
proc saveAccountAndLogin(self: Service, hashedPassword: string, account,
subaccounts, settings, config: JsonNode): AccountDto =
try:
@ -168,14 +176,16 @@ proc prepareSubaccountJsonObject(self: Service, account: GeneratedAccountDto, di
"color": "#4360df",
"wallet": true,
"path": PATH_DEFAULT_WALLET,
"name": "Status account"
"name": "Status account",
"derived-from": account.address
},
{
"public-key": account.derivedAccounts.whisper.publicKey,
"address": account.derivedAccounts.whisper.address,
"name": if displayName == "": account.alias else: displayName,
"path": PATH_WHISPER,
"chat": true
"chat": true,
"derived-from": ""
}
]
@ -271,6 +281,7 @@ proc setupAccount*(self: Service, accountId, password, displayName: string): str
return description
let hashedPassword = hashString(password)
discard self.storeAccount(accountId, hashedPassword)
discard self.storeDerivedAccounts(accountId, hashedPassword, PATHS)
self.loggedInAccount = self.saveAccountAndLogin(hashedPassword, accountDataJson,

View File

@ -0,0 +1,14 @@
import json
include ../../common/json_utils
type DerivedAddressDto* = object
address*: string
path*: string
hasActivity*: bool
proc toDerivedAddressDto*(jsonObj: JsonNode): DerivedAddressDto =
result = DerivedAddressDto()
discard jsonObj.getProp("address", result.address)
discard jsonObj.getProp("path", result.path)
discard jsonObj.getProp("hasActivity", result.hasActivity)

View File

@ -26,6 +26,7 @@ type
isChat*: bool
tokens*: seq[WalletTokenDto]
emoji*: string
derivedfrom*: string
proc newDto*(
name: string,
@ -36,7 +37,8 @@ proc newDto*(
walletType: string,
isWallet: bool,
isChat: bool,
emoji: string
emoji: string,
derivedfrom: string
): WalletAccountDto =
return WalletAccountDto(
name: name,
@ -47,7 +49,8 @@ proc newDto*(
walletType: walletType,
isWallet: isWallet,
isChat: isChat,
emoji: emoji
emoji: emoji,
derivedfrom: derivedfrom
)
proc toWalletAccountDto*(jsonObj: JsonNode): WalletAccountDto =
@ -61,6 +64,7 @@ proc toWalletAccountDto*(jsonObj: JsonNode): WalletAccountDto =
discard jsonObj.getProp("public-key", result.publicKey)
discard jsonObj.getProp("type", result.walletType)
discard jsonObj.getProp("emoji", result.emoji)
discard jsonObj.getProp("derived-from", result.derivedfrom)
proc getCurrencyBalance*(self: WalletAccountDto): float64 =
return self.tokens.map(t => t.currencyBalance).foldl(a + b, 0.0)

View File

@ -8,6 +8,7 @@ import ../network/service as network_service
import ../../common/account_constants
import dto
import derived_address
import ../../../app/core/eventemitter
import ../../../backend/accounts as status_go_accounts
@ -16,6 +17,7 @@ import ../../../backend/eth as status_go_eth
import ../../../backend/cache
export dto
export derived_address
logScope:
topics = "wallet-account-service"
@ -252,13 +254,15 @@ proc addNewAccountToLocalStore(self: Service) =
self.accounts[newAccount.address] = newAccount
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount))
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string): string =
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
try:
discard backend.generateAccount(
discard backend.generateAccountWithDerivedPath(
hashPassword(password),
accountName,
color,
emoji)
emoji,
path,
derivedFrom)
except Exception as e:
return fmt"Error generating new account: {e.msg}"
@ -271,21 +275,21 @@ proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: str
hashPassword(password),
accountName,
color,
emoji
)
emoji)
except Exception as e:
return fmt"Error adding account with private key: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string): string =
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
try:
discard backend.addAccountWithMnemonic(
discard backend.addAccountWithMnemonicAndPath(
mnemonic,
hashPassword(password),
accountName,
color,
emoji
emoji,
path
)
except Exception as e:
return fmt"Error adding account with mnemonic: {e.msg}"
@ -349,3 +353,25 @@ proc updateWalletAccount*(self: Service, address: string, accountName: string, c
account.emoji = emoji
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, WalletAccountUpdated(account: account))
proc getDerivedAddressList*(self: Service, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
var derivedAddress: seq[DerivedAddressDto] = @[]
var error: string = ""
try:
let response = status_go_accounts.getDerivedAddressList(hashPassword(password), derivedFrom, path, pageSize, pageNumber)
derivedAddress = response.result.getElems().map(x => x.toDerivedAddressDto())
except Exception as e:
error = fmt"Error getting derived address list: {e.msg}"
return (derivedAddress, error)
proc getDerivedAddressListForMnemonic*(self: Service, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
var derivedAddress: seq[DerivedAddressDto] = @[]
var error: string = ""
try:
let response = status_go_accounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
derivedAddress = response.result.getElems().map(x => x.toDerivedAddressDto())
except Exception as e:
error = fmt"Error getting derived address list for mnemonic: {e.msg}"
return (derivedAddress, error)

View File

@ -288,3 +288,11 @@ proc deleteIdentityImage*(keyUID: string): RpcResponse[JsonNode] {.raises: [Exce
proc setDisplayName*(displayName: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [displayName]
result = core.callPrivateRPC("setDisplayName".prefix, payload)
proc getDerivedAddressList*(password: string, derivedFrom: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [password, derivedFrom, path, pageSize, pageNumber ]
result = core.callPrivateRPC("accounts_getDerivedAddressesForPath", payload)
proc getDerivedAddressListForMnemonic*(mnemonic: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [mnemonic, path, pageSize, pageNumber ]
result = core.callPrivateRPC("accounts_getDerivedAddressesForMenominicWithPath", payload)

View File

@ -107,18 +107,21 @@ rpc(fetchPrices, "wallet"):
symbols: seq[string]
currency: string
rpc(generateAccount, "accounts"):
rpc(generateAccountWithDerivedPath, "accounts"):
password: string
name: string
color: string
emoji: string
path: string
derivedFrom: string
rpc(addAccountWithMnemonic, "accounts"):
rpc(addAccountWithMnemonicAndPath, "accounts"):
mnemonic: string
password: string
name: string
color: string
emoji: string
path: string
rpc(addAccountWithPrivateKey, "accounts"):
privateKey: string
@ -194,4 +197,4 @@ rpc(addDappPermissions, "permissions"):
rpc(deleteDappPermissionsByNameAndAddress, "permissions"):
dapp: string
address: string
address: string

View File

@ -6,6 +6,7 @@ import StatusQ.Popups 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import shared.stores 1.0
import "../controls"
import "../stores"

View File

@ -0,0 +1,58 @@
import QtQuick 2.12
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import utils 1.0
import "../stores"
StatusSelect {
id: derivationPathSelect
property string path: ""
function reset() {
derivationPathSelectedItem.title = DerivationPathModel.derivationPaths.get(0).name
derivationPathSelectedItem.subTitle = DerivationPathModel.derivationPaths.get(0).path
}
label: qsTr("Derivation Path")
selectMenu.width: 351
menuAlignment: StatusSelect.MenuAlignment.Left
model: DerivationPathModel.derivationPaths
selectedItemComponent: StatusListItem {
id: derivationPathSelectedItem
implicitWidth: parent.width
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: parent.width - Style.current.padding
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: DerivationPathModel.derivationPaths.get(0).name
subTitle: DerivationPathModel.derivationPaths.get(0).path
Component.onCompleted: {
derivationPathSelect.path = Qt.binding(function() { return derivationPathSelectedItem.subTitle})
}
}
selectMenu.delegate: StatusListItem {
implicitWidth: parent.width
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: parent.width - Style.current.padding
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
title: model.name
subTitle: model.path
onClicked: {
derivationPathSelectedItem.title = title
derivationPathSelectedItem.subTitle = subTitle
derivationPathSelect.selectMenu.close()
}
}
}

View File

@ -0,0 +1,153 @@
import QtQuick 2.12
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import utils 1.0
import "../stores"
Item {
id: derivedAddresses
property string pathSubFix: ""
function reset() {
RootStore.resetDerivedAddressModel()
selectedDerivedAddress.pathSubFix = 0
selectedDerivedAddress.title = "---"
selectedDerivedAddress.subTitle = qsTr("No activity")
}
QtObject {
id: _internal
property int pageSize: 6
property int noOfPages: Math.ceil(RootStore.derivedAddressesList.count/pageSize)
property int lastPageSize: RootStore.derivedAddressesList.count - ((noOfPages -1) * pageSize)
property bool isLastPage: stackLayout.currentIndex == (noOfPages - 1)
// dimensions
property int popupWidth: 359
property int maxAddressWidth: 102
}
Connections {
target: RootStore.derivedAddressesList
onModelReset: {
_internal.pageSize = 0
_internal.pageSize = 6
}
}
ColumnLayout {
id: layout
width: parent.width
spacing: 7
StatusBaseText {
id: inputLabel
width: parent.width
text: qsTr("Account")
font.pixelSize: 15
color: selectedDerivedAddress.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
}
StatusListItem {
id: selectedDerivedAddress
property int pathSubFix: 0
implicitWidth: parent.width
color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: "---"
subTitle: qsTr("No activity")
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: _internal.maxAddressWidth
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
components: [
StatusIcon {
width: 24
height: 24
icon: "chevron-down"
color: Theme.palette.baseColor1
}
]
onClicked: derivedAddressPopup.popup(derivedAddresses.x - layout.width - Style.current.bigPadding , derivedAddresses.y + layout.height + 8)
enabled: RootStore.derivedAddressesList.count > 0
Component.onCompleted: derivedAddresses.pathSubFix = Qt.binding(function() { return pathSubFix})
}
}
StatusPopupMenu {
id: derivedAddressPopup
width: _internal.popupWidth
contentItem: Column {
StackLayout {
id: stackLayout
Layout.fillWidth:true
Layout.fillHeight: true
Repeater {
id: pageModel
model: _internal.noOfPages
delegate: Page {
id: page
contentItem: ColumnLayout {
Repeater {
id: repeater
model: _internal.isLastPage ? _internal.lastPageSize : _internal.pageSize
delegate: StatusListItem {
id: element
property int actualIndex: index + (stackLayout.currentIndex* _internal.pageSize)
implicitWidth: derivedAddressPopup.width
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: _internal.maxAddressWidth
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
title: RootStore.getDerivedAddressData(actualIndex)
subTitle: RootStore.getDerivedAddressHasActivityData(actualIndex) ? qsTr("Has Activity"): qsTr("No Activity")
components: [
StatusBaseText {
text: element.actualIndex
font.pixelSize: 15
color: Theme.palette.baseColor1
}
]
onClicked: {
selectedDerivedAddress.title = title
selectedDerivedAddress.subTitle = subTitle
selectedDerivedAddress.pathSubFix = actualIndex
derivedAddressPopup.close()
}
Component.onCompleted: {
if(index === 0) {
selectedDerivedAddress.title = title
selectedDerivedAddress.subTitle = subTitle
selectedDerivedAddress.pathSubFix = actualIndex
stackLayout.currentIndex = 0
}
}
}
}
}
}
}
}
PageIndicator {
id: pageIndicator
anchors.horizontalCenter: parent.horizontalCenter
interactive: true
currentIndex: stackLayout.currentIndex
count: stackLayout.count
onCurrentIndexChanged: stackLayout.currentIndex = currentIndex
}
}
}
}

View File

@ -0,0 +1,69 @@
import QtQuick 2.12
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../stores"
StatusInput {
id: privateKey
function resetMe() {
_internal.errorString = ""
privateKey.text = ""
privateKey.reset()
reset()
}
function validateMe() {
if (privateKey.text === "") {
//% "You need to enter a private key"
_internal.errorString = qsTrId("you-need-to-enter-a-private-key")
} else if (!Utils.isPrivateKey(privateKey.text)) {
//% "Enter a valid private key (64 characters hexadecimal string)"
_internal.errorString = qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
} else {
_internal.errorString = ""
}
return _internal.errorString === ""
}
QtObject {
id: _internal
property int privateKeyCharLimit: 66
property string errorString: ""
}
//% "Private key"
label: qsTrId("private-key")
charLimit: _internal.privateKeyCharLimit
input.multiline: true
input.minimumHeight: 80
input.maximumHeight: 108
//% "Paste the contents of your private key"
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
errorMessage: _internal.errorString
validators: [
StatusMinLengthValidator {
minLength: 1
//% "You need to enter a private key"
errorMessage: qsTrId("you-need-to-enter-a-private-key")
},
StatusValidator {
property var validate: function (value) {
return Utils.isPrivateKey(value)
}
//% "Enter a valid private key (64 characters hexadecimal string)"
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
}
]
onVisibleChanged: {
if(visible)
privateKey.input.edit.forceActiveFocus();
}
}

View File

@ -0,0 +1,210 @@
import QtQuick 2.12
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared.stores 1.0
import "../stores"
GridView {
id: grid
property bool isValid: false
property string mnemonicString: ""
property int preferredHeight: (cellHeight * model/2) + footerItem.height
function reset() {
_internal.errorString = ""
mnemonicString = ""
_internal.mnemonicInput = [];
if (!grid.atXBeginning) {
grid.positionViewAtBeginning();
}
for(var i = 0; i < grid.model; i++) {
if(grid.itemAtIndex(i)) {
grid.itemAtIndex(i).textEdit.text = ""
grid.itemAtIndex(i).textEdit.reset()
}
}
}
function validate() {
_internal.errorString = ""
if (!Utils.isMnemonic(mnemonicString)) {
//% "Invalid seed phrase"
_internal.errorString = qsTrId("custom-seed-phrase")
} else {
_internal.errorString = RootStore.vaildateMnemonic(mnemonicString)
const regex = new RegExp('word [a-z]+ not found in the dictionary', 'i');
if (regex.test(_internal.errorString)) {
//% "Invalid seed phrase"
_internal.errorString = qsTrId("custom-seed-phrase") + '. ' +
//% "This seed phrase doesn't match our supported dictionary. Check for misspelled words."
qsTrId("custom-seed-phrase-text-1")
}
}
return _internal.errorString === ""
}
QtObject {
id: _internal
property int seedPhraseInputHeight: 44
property int seedPhraseInputWidth: 220
property var mnemonicInput: []
property string errorString: ""
readonly property int twelveWordsModel: 12
readonly property int twentyFourWordsModel: 24
function getSeedPhraseString() {
var seedPhrase = ""
for(var i = 0; i < grid.model; i++) {
if(!!grid.itemAtIndex(i)) {
seedPhrase += grid.itemAtIndex(i).text + " "
}
}
return seedPhrase
}
}
cellHeight: _internal.seedPhraseInputHeight + Style.current.halfPadding
cellWidth: _internal.seedPhraseInputWidth + Style.current.halfPadding
interactive: false
z: 100000
model: _internal.twelveWordsModel
onModelChanged: {
mnemonicString = "";
_internal.mnemonicInput = [];
}
onIsValidChanged: {
if(isValid) {
mnemonicString = _internal.getSeedPhraseString()
}
}
onVisibleChanged: {
if(visible) {
grid.itemAtIndex(0).textEdit.input.edit.forceActiveFocus()
}
}
delegate: StatusSeedPhraseInput {
id: statusSeedInput
width: _internal.seedPhraseInputWidth
height: _internal.seedPhraseInputHeight
textEdit.errorMessageCmp.visible: false
textEdit.input.anchors.topMargin: 11
leftComponentText: index + 1
inputList: BIP39_en { }
property int itemIndex: index
z: (grid.currentIndex === index) ? 150000000 : 0
onDoneInsertingWord: {
_internal.mnemonicInput.push({"pos": leftComponentText, "seed": word.replace(/\s/g, '')});
for (var j = 0; j < _internal.mnemonicInput.length; j++) {
if (_internal.mnemonicInput[j].pos === leftComponentText && _internal.mnemonicInput[j].seed !== word) {
_internal.mnemonicInput[j].seed = word;
}
}
//remove duplicates
var valueArr = _internal.mnemonicInput.map(function(item){ return item.pos });
var isDuplicate = valueArr.some(function(item, idx){
if (valueArr.indexOf(item) !== idx) {
_internal.mnemonicInput.splice(idx, 1);
}
return valueArr.indexOf(item) !== idx
});
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === (parseInt(leftComponentText)+1)) {
grid.currentIndex = grid.itemAtIndex(i).itemIndex;
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
if (grid.currentIndex === 11) {
grid.positionViewAtEnd();
if (grid.count === 20) {
grid.contentX = 1500;
}
}
}
}
grid.isValid = (_internal.mnemonicInput.length === grid.model);
}
onEditClicked: {
grid.currentIndex = index;
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus();
}
onKeyPressed: {
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Right) {
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)+1) <= grid.count ? (parseInt(leftComponentText)+1) : grid.count)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit;
}
}
} else if (event.key === Qt.Key_Left) {
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)-1) >= 0 ? (parseInt(leftComponentText)-1) : 0)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
}
}
} else if (event.key === Qt.Key_Down) {
grid.itemAtIndex((index+1 < grid.count) ? (index+1) : (grid.count-1)).textEdit.input.edit.forceActiveFocus();
} else if (event.key === Qt.Key_Up) {
grid.itemAtIndex((index-1 >= 0) ? (index-1) : 0).textEdit.input.edit.forceActiveFocus();
}
if (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) {
var wordIndex = _internal.mnemonicInput.findIndex(x => x.pos === leftComponentText);
if (wordIndex > -1) {
_internal.mnemonicInput.splice(wordIndex , 1);
grid.isValid = _internal.mnemonicInput.length === grid.model
}
}
grid.currentIndex = index;
}
}
footer: Item {
width: grid.width - Style.current.padding
height: button.height + errorMessage.height + Style.current.padding*2
StatusBaseText {
id: errorMessage
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Style.current.padding
height: visible ? implicitHeight : 0
visible: !!text
text: _internal.errorString
font.pixelSize: 12
color: Theme.palette.dangerColor1
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
StatusButton {
id: button
anchors.top: errorMessage.bottom
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
text: grid.model === _internal.twelveWordsModel ? qsTr("Use 24 word seed phrase"):
qsTr("Use 12 word seed phrase")
onClicked: {
if(grid.model === _internal.twelveWordsModel) {
grid.model = _internal.twentyFourWordsModel
}
else {
grid.model = _internal.twelveWordsModel
}
}
}
}
}

View File

@ -0,0 +1,136 @@
import QtQuick 2.12
import QtQml.Models 2.14
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import "../stores"
StatusSelect {
id: selectAccountType
property int addAccountType
property string derivedFromAddress: ""
enum AddAccountType {
GenerateNew,
ImportSeedPhrase,
ImportPrivateKey,
WatchOnly
}
function resetMe() {
_internal.getGeneratedAccountsModel()
addAccountType = SelectGeneratedAccount.AddAccountType.GenerateNew
}
Connections {
target: RootStore.generatedAccountsViewModel
onModelReset: {
_internal.delegateModel.model = RootStore.generatedAccountsViewModel
_internal.getGeneratedAccountsModel()
}
}
QtObject {
id: _internal
property string importSeedPhraseString : qsTr("Import new Seed Phrase")
property string importPrivateKeyString : qsTr("Import new Private Key")
//% "Add a watch-only address"
property string addWatchOnlyAccountString : qsTrId("add-a-watch-account")
property var delegateModel: DelegateModel {
model: RootStore.generatedAccountsViewModel
onModelUpdated: {
_internal.getGeneratedAccountsModel()
}
}
property ListModel generatedAccountsModel: ListModel{}
function getGeneratedAccountsModel() {
if(generatedAccountsModel) {
generatedAccountsModel.clear()
for (var row = 0; row < _internal.delegateModel.model.count; row++) {
var item = _internal.delegateModel.items.get(row).model;
generatedAccountsModel.append({"name": item.name, "iconName": item.iconName, "generatedModel": item.generatedModel, "derivedfrom": item.derivedfrom, "isHeader": false})
if(row === 0 && _internal.delegateModel.model.count > 1) {
generatedAccountsModel.append({"name": qsTr("Imported"), "iconName": "", "derivedfrom": "", "isHeader": true})
}
}
generatedAccountsModel.append({"name": qsTr("Add new"), "iconName": "", "derivedfrom": "", "isHeader": true})
generatedAccountsModel.append({"name": _internal.importSeedPhraseString, "iconName": "seed-phrase", "derivedfrom": "", "isHeader": false})
generatedAccountsModel.append({"name": _internal.importPrivateKeyString, "iconName": "password", "derivedfrom": "", "isHeader": false})
generatedAccountsModel.append({"name": _internal.addWatchOnlyAccountString, "iconName": "show", "derivedfrom": "", "isHeader": false})
}
}
}
label: qsTr("Origin")
model: _internal.generatedAccountsModel
selectedItemComponent: StatusListItem {
id: selectedItem
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
tagsDelegate: StatusListItemTag {
color: model.color
height: Style.current.bigPadding
radius: 6
closeButtonVisible: false
icon.emoji: model.emoji
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
}
selectMenu.delegate: StatusListItem {
id: defaultListItem
title: model.name
icon.name: model.iconName
tagsModel : model.generatedModel
enabled: !model.isHeader
icon.background.color: "transparent"
icon.color: model.generatedModel ? Theme.palette.primaryColor1 : Theme.palette.directColor5
tagsDelegate: StatusListItemTag {
color: model.color
height: 24
radius: 6
closeButtonVisible: false
icon.emoji: model.emoji
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
onClicked: {
selectAccountType.addAccountType = (model.name === _internal.importSeedPhraseString) ? SelectGeneratedAccount.AddAccountType.ImportSeedPhrase :
(model.name === _internal.importPrivateKeyString) ? SelectGeneratedAccount.AddAccountType.ImportPrivateKey :
(model.name === _internal.addWatchOnlyAccountString) ? SelectGeneratedAccount.AddAccountType.WatchOnly :
SelectGeneratedAccount.AddAccountType.GenerateNew
selectedItem.title = model.name
selectedItem.icon.name = model.iconName
selectedItem.tagsModel = model.generatedModel
selectedItem.enabled = !model.isHeader
selectAccountType.derivedFromAddress = model.derivedfrom
selectMenu.close()
}
Component.onCompleted: {
if(index === 0) {
selectedItem.title = model.name
selectedItem.icon.name = model.iconName
selectedItem.tagsModel = model.generatedModel
selectedItem.enabled = !model.isHeader
selectAccountType.derivedFromAddress = model.derivedfrom
}
}
}
}

View File

@ -17,6 +17,7 @@ import shared.controls 1.0
import "../stores"
import "../views"
import "../panels"
StatusModal {
id: popup
@ -31,6 +32,40 @@ StatusModal {
//% "Generate an account"
header.title: qsTrId("generate-a-new-account")
QtObject {
id: _internal
property int numOfItems: 100
property int pageNumber: 1
function getDerivedAddressList() {
if(advancedSelection.expandableItem) {
var errMessage = ""
if(advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase &&
!!advancedSelection.expandableItem.path &&
!!advancedSelection.expandableItem.mnemonicText) {
errMessage = RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText, advancedSelection.expandableItem.path, numOfItems, pageNumber)
_internal.showPasswordError(errMessage)
}
else if(!!advancedSelection.expandableItem.path && !!advancedSelection.expandableItem.derivedFromAddress && (passwordInput.text.length >= 6)) {
errMessage = RootStore.getDerivedAddressList(passwordInput.text, advancedSelection.expandableItem.derivedFromAddress, advancedSelection.expandableItem.path, numOfItems, pageNumber)
_internal.showPasswordError(errMessage)
}
}
}
function showPasswordError(errMessage) {
if (errMessage) {
if (Utils.isInvalidPasswordMessage(errMessage)) {
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
} else {
accountError.text = errMessage;
accountError.open();
}
}
}
}
function validate() {
if (passwordInput.text === "") {
//% "You need to enter a password"
@ -41,8 +76,8 @@ StatusModal {
} else {
passwordValidationError = ""
}
return passwordValidationError === "" && accountNameInput.valid
}
return passwordValidationError === "" && accountNameInput.valid
}
onOpened: {
passwordValidationError = "";
@ -96,6 +131,10 @@ StatusModal {
validationError: popup.passwordValidationError
inputLabel.font.pixelSize: 15
inputLabel.font.weight: Font.Normal
onTextChanged: {
popup.passwordValidationError = ""
_internal.getDerivedAddressList()
}
}
}
@ -124,7 +163,6 @@ StatusModal {
StatusColorSelectorGrid {
id: colorSelectionGrid
anchors.horizontalCenter: parent.horizontalCenter
//% "color"
titleText: qsTr("color").toUpperCase()
}
@ -148,7 +186,6 @@ StatusModal {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
//% "Advanced"
primaryText: qsTr("Advanced")
type: StatusExpandableItem.Type.Tertiary
expandable: true
@ -156,6 +193,7 @@ StatusModal {
width: parent.width
Layout.margins: Style.current.padding
Component.onCompleted: advancedSelection.isValid = Qt.binding(function(){return isValid})
onCalculateDerivedPath: _internal.getDerivedAddressList()
}
}
}
@ -192,35 +230,26 @@ StatusModal {
var errMessage = ""
if(advancedSelection.expandableItem) {
switch(advancedSelection.expandableItem.addAccountType) {
case AdvancedAddAccountView.AddAccountType.GenerateNew:
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
case SelectGeneratedAccount.AddAccountType.GenerateNew:
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji, advancedSelection.expandableItem.completePath, advancedSelection.expandableItem.derivedFromAddress)
break
case AdvancedAddAccountView.AddAccountType.ImportSeedPhrase:
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
case SelectGeneratedAccount.AddAccountType.ImportSeedPhrase:
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji, advancedSelection.expandableItem.completePath)
break
case AdvancedAddAccountView.AddAccountType.ImportPrivateKey:
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
case SelectGeneratedAccount.AddAccountType.ImportPrivateKey:
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
break
case AdvancedAddAccountView.AddAccountType.WatchOnly:
case SelectGeneratedAccount.AddAccountType.WatchOnly:
errMessage = RootStore.addWatchOnlyAccount(advancedSelection.expandableItem.watchAddress, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
break
}
} else {
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, emoji)
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji, advancedSelection.expandableItem.completePath, advancedSelection.expandableItem.derivedFromAddress)
}
loading = false
if (errMessage) {
Global.playErrorSound();
if (Utils.isInvalidPasswordMessage(errMessage)) {
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
} else {
accountError.text = errMessage;
accountError.open();
}
return
}
_internal.showPasswordError(errMessage)
popup.afterAddAccount();
popup.close();
}

View File

@ -0,0 +1,37 @@
pragma Singleton
import QtQuick 2.13
QtObject {
id: root
property ListModel derivationPaths: ListModel {
ListElement {
name: "Default"
path: "m/44'/60'/0'/0"
}
ListElement {
name: "Ethereum Classic"
path: "m/44'/61'/0'/0"
}
ListElement {
name: "Ethereum (Ledger)"
path: "m/44'/60'/0'"
}
ListElement {
name: "Ethereum Classic (Ledger)"
path: "m/44'/60'/160720'/0"
}
ListElement {
name: "Ethereum Classic (Ledger, Vintage MEW)"
path: "m/44'/60'/160720'/0'"
}
ListElement {
name: "Ethereum (KeepKey)"
path: "m/44'/60'"
}
ListElement {
name: "Ethereum Classic (KeepKey)"
path: "m/44'/61'"
}
}
}

View File

@ -28,6 +28,9 @@ QtObject {
property var savedAddresses: walletSectionSavedAddresses.model
// Used for new wallet account generation
property var generatedAccountsViewModel: walletSectionAccounts.generatedAccounts
property var derivedAddressesList: walletSectionAccounts.derivedAddresses
property var layer1Networks: networksModule.layer1
property var layer2Networks: networksModule.layer2
@ -100,16 +103,16 @@ QtObject {
walletSection.switchAccount(newIndex)
}
function generateNewAccount(password, accountName, color, emoji) {
return walletSectionAccounts.generateNewAccount(password, accountName, color, emoji)
function generateNewAccount(password, accountName, color, emoji, path, derivedFrom) {
return walletSectionAccounts.generateNewAccount(password, accountName, color, emoji, path, derivedFrom)
}
function addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji) {
return walletSectionAccounts.addAccountsFromPrivateKey(privateKey, password, accountName, color, emoji)
}
function addAccountsFromSeed(seedPhrase, password, accountName, color, emoji) {
return walletSectionAccounts.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji)
function addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path) {
return walletSectionAccounts.addAccountsFromSeed(seedPhrase, password, accountName, color, emoji, path)
}
function addWatchOnlyAccount(address, accountName,color, emoji) {
@ -172,4 +175,32 @@ QtObject {
function copyToClipboard(text) {
globalUtils.copyToClipboard(text)
}
function getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber) {
return walletSectionAccounts.getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber)
}
function getDerivedAddressData(index) {
return walletSectionAccounts.getDerivedAddressAtIndex(index)
}
function getDerivedAddressPathData(index) {
return walletSectionAccounts.getDerivedAddressPathAtIndex(index)
}
function getDerivedAddressHasActivityData(index) {
return walletSectionAccounts.getDerivedAddressHasActivityAtIndex(index)
}
function getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber) {
return walletSectionAccounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber)
}
function resetDerivedAddressModel() {
walletSectionAccounts.resetDerivedAddressModel()
}
function vaildateMnemonic(mnemonic) {
return onboardingModule.validateMnemonic(mnemonic)
}
}

View File

@ -1 +1,2 @@
singleton RootStore 1.0 RootStore.qml
singleton DerivationPathModel 1.0 DerivationPathModel.qml

View File

@ -7,329 +7,125 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../stores"
import "../panels"
ColumnLayout {
id: advancedSection
property alias privateKey: privateKey.text
property int addAccountType: AdvancedAddAccountView.AddAccountType.GenerateNew
property string mnemonicText: getSeedPhraseString()
property int addAccountType: SelectGeneratedAccount.AddAccountType.GenerateNew
property string derivedFromAddress: ""
property string mnemonicText: ""
property alias privateKey: importPrivateKeyPanel.text
property string path: ""
property string pathSubFix: ""
property string completePath: path + "/" + pathSubFix
property alias watchAddress: addressInput.text
property string errorString: ""
property bool isValid: addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase ? grid.isValid :
addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey ? (privateKey.text !== "" && privateKey.valid) :
advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly ? (addressInput.text !== "" && addressInput.valid) : true
property bool isValid: addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase ? importSeedPhrasePanel.isValid :
addAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey ? (importPrivateKeyPanel.text !== "" && importPrivateKeyPanel.valid) :
addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly ? (addressInput.text !== "" && addressInput.valid) : true
enum AddAccountType {
GenerateNew,
ImportSeedPhrase,
ImportPrivateKey,
WatchOnly
}
signal calculateDerivedPath()
function reset() {
mnemonicText = ""
errorString = ""
select.currentIndex = 0
addAccountType = AdvancedAddAccountView.AddAccountType.GenerateNew
privateKey.text = ""
privateKey.reset()
//reset selectGeneratedAccount
selectGeneratedAccount.resetMe()
// reset privateKey
importPrivateKeyPanel.resetMe()
// reset importSeedPhrasePanel
importSeedPhrasePanel.reset()
// reset derivation path
derivationPathsPanel.reset()
// reset derviedAccountsList
derivedAddressesPanel.reset()
// reset watch only address input
addressInput.text = ""
addressInput.reset()
for(var i = 0; i < grid.model; i++) {
if(grid.itemAtIndex(i)) {
grid.itemAtIndex(i).textEdit.text = ""
grid.itemAtIndex(i).textEdit.reset()
}
}
}
function validate() {
errorString = "";
if(addAccountType == AdvancedAddAccountView.AddAccountType.ImportSeedPhrase) {
mnemonicText = getSeedPhraseString()
if (!Utils.isMnemonic(mnemonicText)) {
//% "Invalid seed phrase"
errorString = qsTrId("custom-seed-phrase")
} else {
errorString = onboardingModule.validateMnemonic(mnemonicText)
const regex = new RegExp('word [a-z]+ not found in the dictionary', 'i');
if (regex.test(errorString)) {
//% "Invalid seed phrase"
errorString = qsTrId("custom-seed-phrase") + '. ' +
//% "This seed phrase doesn't match our supported dictionary. Check for misspelled words."
qsTrId("custom-seed-phrase-text-1")
}
}
return errorString === ""
if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase) {
// validate mnemonic
return importSeedPhrasePanel.validate()
}
else if(addAccountType == AdvancedAddAccountView.AddAccountType.ImportPrivateKey) {
if (privateKey.text === "") {
//% "You need to enter a private key"
errorString = qsTrId("you-need-to-enter-a-private-key")
} else if (!Utils.isPrivateKey(privateKey.text)) {
//% "Enter a valid private key (64 characters hexadecimal string)"
errorString = qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
} else {
errorString = ""
}
return errorString === ""
else if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey) {
// validate privateKey
return importPrivateKeyPanel.validateMe()
}
else if(advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly) {
else if(addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly) {
return addressInput.valid
}
return true
}
function getSeedPhraseString() {
var seedPhrase = ""
for(var i = 0; i < grid.model; i++) {
seedPhrase += grid.itemAtIndex(i).text + " "
onPathChanged: {
if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase) {
if(importSeedPhrasePanel.isValid) {
calculateDerivedPath()
}
}
else {
calculateDerivedPath()
}
return seedPhrase
}
QtObject {
id: _internal
property int seedPhraseInputHeight: 44
property int seedPhraseInputWidth: 220
onDerivedFromAddressChanged: {
// reset derviedAccountsList
derivedAddressesPanel.reset()
if(addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase) {
if(importSeedPhrasePanel.isValid) {
calculateDerivedPath()
}
}
else {
calculateDerivedPath()
}
}
spacing: Style.current.padding
StatusSelect {
id: select
//% "Origin"
label: qsTr("Origin")
SelectGeneratedAccount {
id: selectGeneratedAccount
Layout.margins: Style.current.padding
property int currentIndex: 0
selectedItemComponent: StatusListItem {
id: selectedItem
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
tagsDelegate: StatusListItemTag {
color: model.color
height: Style.current.bigPadding
radius: 6
closeButtonVisible: false
icon.emoji: model.emoji
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
}
model: ListModel {
Component.onCompleted: {
//% "Default"
append({"name": qsTr("Default"), "iconName": "status", "accountsModel": RootStore.generatedAccounts, "enabled": true})
//% "Add new"
append({"name": qsTr("Add new"), "iconName": "", "enabled": false})
//% "Import new Seed Phrase"
append({"name": qsTr("Import new Seed Phrase"), "iconName": "seed-phrase", "enabled": true})
//% "Import new Private Key"
append({"name": qsTr("Import new Private Key"), "iconName": "password", "enabled": true})
//% "Import new Private Key"
append({"name": qsTrId("add-a-watch-account"), "iconName": "show", "enabled": true})
selectedItem.title = Qt.binding(function() {return get(select.currentIndex).name})
selectedItem.icon.name = Qt.binding(function() {return get(select.currentIndex).iconName})
selectedItem.tagsModel = Qt.binding(function() {return get(select.currentIndex).accountsModel})
}
}
selectMenu.delegate: StatusListItem {
id: defaultListItem
title: model.name
icon.name: model.iconName
tagsModel : model.accountsModel
enabled: model.enabled
icon.background.color: "transparent"
icon.color: model.accountsModel ? Theme.palette.primaryColor1 : Theme.palette.directColor5
tagsDelegate: StatusListItemTag {
color: model.color
height: 24
radius: 6
closeButtonVisible: false
icon.emoji: model.emoji
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
onClicked: {
advancedSection.addAccountType = (index === 2) ? AdvancedAddAccountView.AddAccountType.ImportSeedPhrase :
(index === 3) ? AdvancedAddAccountView.AddAccountType.ImportPrivateKey :
(index === 4) ? AdvancedAddAccountView.AddAccountType.WatchOnly :
AdvancedAddAccountView.AddAccountType.GenerateNew
select.currentIndex = index
select.selectMenu.close()
}
Component.onCompleted: {
advancedSection.addAccountType = Qt.binding(function() {return addAccountType})
advancedSection.derivedFromAddress = Qt.binding(function() {return derivedFromAddress})
}
}
StatusInput {
id: privateKey
//% "Private key"
label: qsTrId("private-key")
charLimit: 66
input.multiline: true
input.minimumHeight: 80
input.maximumHeight: 108
//% "Paste the contents of your private key"
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey && advancedSection.visible
errorMessage: advancedSection.errorString
validators: [
StatusMinLengthValidator {
minLength: 1
//% "You need to enter a private key"
errorMessage: qsTrId("you-need-to-enter-a-private-key")
},
StatusValidator {
property var validate: function (value) {
return Utils.isPrivateKey(value)
}
//% "Enter a valid private key (64 characters hexadecimal string)"
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
}
]
onVisibleChanged: {
if(visible)
privateKey.input.edit.forceActiveFocus();
}
ImportPrivateKeyPanel {
id: importPrivateKeyPanel
visible: advancedSection.addAccountType === SelectGeneratedAccount.AddAccountType.ImportPrivateKey && advancedSection.visible
}
GridView {
id: grid
ImportSeedPhrasePanel {
id: importSeedPhrasePanel
Layout.preferredWidth: parent.width
Layout.preferredHeight: visible ? (cellHeight * model/2) + footerItem.height: 0
Layout.preferredHeight: visible ? importSeedPhrasePanel.preferredHeight: 0
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase && advancedSection.visible
cellHeight: _internal.seedPhraseInputHeight + Style.current.halfPadding
cellWidth: _internal.seedPhraseInputWidth + Style.current.halfPadding
model: 12
interactive: false
property bool isValid: checkIsValid()
function checkIsValid() {
var valid = model > 0 ? true: false
for(var i = 0; i < model; i++) {
if(grid.itemAtIndex(i))
valid &= grid.itemAtIndex(i).isValid
}
return valid
}
onVisibleChanged: {
if(visible)
grid.itemAtIndex(0).textEdit.input.edit.forceActiveFocus();
}
// To-do Alex has introduced a model for bip39 dictonary, need to use it once its available
// https://github.com/status-im/status-desktop/pull/5058
delegate: StatusSeedPhraseInput {
id: statusSeedInput
width: _internal.seedPhraseInputWidth
height: _internal.seedPhraseInputHeight
textEdit.errorMessageCmp.visible: false
textEdit.input.anchors.topMargin: 11
leftComponentText: index + 1
property bool isValid: !!text
onIsValidChanged: {
grid.isValid = grid.checkIsValid()
}
onTextChanged: {
if (text !== "") {
grid.currentIndex = index;
}
}
// To-do Alex has introduced a model for bip39 dictonary, need to use it once its available
// https://github.com/status-im/status-desktop/pull/5058
// onDoneInsertingWord: {
// advancedSection.mnemonicText += (index === 0) ? word : (" " + word);
// for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
// if (parseInt(grid.itemAtIndex(i).leftComponentText) === (parseInt(leftComponentText)+1)) {
// grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
// }
// }
// }
onEditClicked: {
grid.currentIndex = index;
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus();
}
onKeyPressed: {
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Right) {
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)+1) <= grid.count ? (parseInt(leftComponentText)+1) : grid.count)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit;
}
}
} else if (event.key === Qt.Key_Left) {
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)-1) >= 0 ? (parseInt(leftComponentText)-1) : 0)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
}
}
} else if (event.key === Qt.Key_Down) {
grid.itemAtIndex((index+1 < grid.count) ? (index+1) : (grid.count-1)).textEdit.input.edit.forceActiveFocus();
} else if (event.key === Qt.Key_Up) {
grid.itemAtIndex((index-1 >= 0) ? (index-1) : 0).textEdit.input.edit.forceActiveFocus();
}
}
textEdit.validators: [
StatusMinLengthValidator {
errorMessage: qsTr("Enter a valid word")
minLength: 3
}
]
}
footer: Item {
width: grid.width - Style.current.padding
height: button.height + errorMessage.height + 16*2
StatusBaseText {
id: errorMessage
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Style.current.padding
height: visible ? implicitHeight : 0
visible: !!text
text: errorString
font.pixelSize: 12
color: Theme.palette.dangerColor1
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
StatusButton {
id: button
visible: false
anchors.top: errorMessage.bottom
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
//% "Use 24 word seed phrase"
text: grid.model === 12 ? qsTr("Use 24 word seed phrase"):
qsTr("Use 12 word seed phrase")
onClicked: grid.model = grid.model === 12 ? 24 : 12
visible: advancedSection.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase && advancedSection.visible
onMnemonicStringChanged: {
advancedSection.mnemonicText = mnemonicString
if(isValid) {
calculateDerivedPath()
}
}
}
StatusInput {
id: addressInput
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly && advancedSection.visible
visible: advancedSection.addAccountType === SelectGeneratedAccount.AddAccountType.WatchOnly && advancedSection.visible
//% "Enter address..."
input.placeholderText: qsTrId("enter-address...")
//% "Account address"
@ -351,37 +147,19 @@ ColumnLayout {
Layout.margins: Style.current.padding
Layout.preferredWidth: parent.width
spacing: Style.current.bigPadding
visible: advancedSection.addAccountType !== AdvancedAddAccountView.AddAccountType.WatchOnly &&
advancedSection.addAccountType !== AdvancedAddAccountView.AddAccountType.ImportPrivateKey
StatusSelect {
visible: advancedSection.addAccountType !== SelectGeneratedAccount.AddAccountType.ImportPrivateKey &&
advancedSection.addAccountType !== SelectGeneratedAccount.AddAccountType.WatchOnly
DerivationPathsPanel {
id: derivationPathsPanel
Layout.preferredWidth: 213
//% "Origin"
label: qsTr("Derivation Path")
selectedItemComponent: StatusListItem {
width: parent.width
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: "Default"
subTitle: "m/44/61/0/1"
enabled: false
}
enabled: false
Layout.alignment: Qt.AlignTop
Component.onCompleted: advancedSection.path = Qt.binding(function() { return derivationPathsPanel.path})
}
StatusSelect {
DerivedAddressesPanel {
id: derivedAddressesPanel
Layout.preferredWidth: 213
//% "Origin"
label: qsTr("Account")
width: parent.width
enabled: false
selectedItemComponent: StatusListItem {
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: "0x1234...abcd"
subTitle: "No activity"
enabled: false
}
Layout.alignment: Qt.AlignTop
Component.onCompleted: advancedSection.pathSubFix = Qt.binding(function() { return derivedAddressesPanel.pathSubFix})
}
}
}

View File

@ -1 +1,2 @@
singleton RootStore 1.0 RootStore.qml
BIP39_en 1.0 BIP39_en.qml

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit e1d98de2440a8a631e8df7516a6a4cb13c26f1a6
Subproject commit b83f4a6c83d4f2856a44e5fee4137f4f8ffbbaad