feat: onboarding generate new account

Generates 5 random accounts with identicons, allows user to enter password, then stores the account and logs in to the statusgo node.

Add EventEmitter that notifies nim_status_client.nim once node has started and is logged in (likely needs some refactoring to include the eventemitter in the base controller class).

Add QML StateMachine for the onboarding view.

Add nimcrytpo, uuids, eventemitter, isaac dependencies via submodules.

Add button to Intro view to randomly gen account.
This commit is contained in:
emizzle 2020-05-19 23:22:38 +10:00 committed by Iuri Matias
parent ed683fd2f0
commit dafd11fbc0
32 changed files with 1752 additions and 56 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ vendor/.nimble
*.AppImage
tmp
nimcache
.DS_Store

14
.gitmodules vendored
View File

@ -34,3 +34,17 @@
[submodule "vendor/nim-serialization"]
path = vendor/nim-serialization
url = https://github.com/status-im/nim-serialization/
[submodule "vendor/nimcrypto"]
path = vendor/nimcrypto
url = https://github.com/cheatfate/nimcrypto
[submodule "vendor/uuids"]
path = vendor/uuids
url = https://github.com/pragmagic/uuids
[submodule "vendor/isaac"]
path = vendor/isaac
url = https://github.com/pragmagic/isaac
[submodule "vendor/eventemitter"]
path = vendor/eventemitter
url = https://github.com/al-bimani/eventemitter
ignore = dirty
branch = master

View File

@ -10,4 +10,4 @@ skipExt = @["nim"]
# Deps
requires "nim >= 1.0.0", " nimqml >= 0.7.0", "stint"
requires "nim >= 1.0.0", " nimqml >= 0.7.0", "stint", "nimcrypto >= 0.4.11", "uuids >= 0.1.10"

172
src/constants/constants.nim Normal file
View File

@ -0,0 +1,172 @@
import json
const PATH_WALLET_ROOT* = "m/44'/60'/0'/0"
# EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived
const PATH_EIP_1581* = "m/43'/60'/1581'"
# BIP44-0 Wallet key, the default wallet key
const PATH_DEFAULT_WALLET* = PATH_WALLET_ROOT & "/0"
# EIP1581 Chat Key 0, the default whisper key
const PATH_WHISPER* = PATH_EIP_1581 & "/0'/0"
let DEFAULT_NETWORKS* = %* [
{
"id": "testnet_rpc",
"etherscan-link": "https://ropsten.etherscan.io/address/",
"name": "Ropsten with upstream RPC",
"config": {
"NetworkId": 3,
"DataDir": "/ethereum/testnet_rpc",
"UpstreamConfig": {
"Enabled": true,
"URL": "https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a"
}
}
},
{
"id": "rinkeby_rpc",
"etherscan-link": "https://rinkeby.etherscan.io/address/",
"name": "Rinkeby with upstream RPC",
"config": {
"NetworkId": 4,
"DataDir": "/ethereum/rinkeby_rpc",
"UpstreamConfig": {
"Enabled": true,
"URL": "https://rinkeby.infura.io/v3/f315575765b14720b32382a61a89341a"
}
}
},
{
"id": "goerli_rpc",
"etherscan-link": "https://goerli.etherscan.io/address/",
"name": "Goerli with upstream RPC",
"config": {
"NetworkId": 5,
"DataDir": "/ethereum/goerli_rpc",
"UpstreamConfig": {
"Enabled": true,
"URL": "https://goerli.blockscout.com/"
}
}
},
{
"id": "mainnet_rpc",
"etherscan-link": "https://etherscan.io/address/",
"name": "Mainnet with upstream RPC",
"config": {
"NetworkId": 1,
"DataDir": "/ethereum/mainnet_rpc",
"UpstreamConfig": {
"Enabled": true,
"URL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"
}
}
},
{
"id": "xdai_rpc",
"name": "xDai Chain",
"config": {
"NetworkId": 100,
"DataDir": "/ethereum/xdai_rpc",
"UpstreamConfig": {
"Enabled": true,
"URL": "https://dai.poa.network"
}
}
},
{
"id": "poa_rpc",
"name": "POA Network",
"config": {
"NetworkId": 99,
"DataDir": "/ethereum/poa_rpc",
"UpstreamConfig": {
"Enabled": true,
"URL": "https://core.poa.network"
}
}
}
]
let NODE_CONFIG* = %* {
"BrowsersConfig": {
"Enabled": true
},
"ClusterConfig": {
"BootNodes": [
"enode://23d0740b11919358625d79d4cac7d50a34d79e9c69e16831c5c70573757a1f5d7d884510bc595d7ee4da3c1508adf87bbc9e9260d804ef03f8c1e37f2fb2fc69@47.52.106.107:443",
"enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@178.128.140.188:443",
"enode://6e6554fb3034b211398fcd0f0082cbb6bd13619e1a7e76ba66e1809aaa0c5f1ac53c9ae79cf2fd4a7bacb10d12010899b370c75fed19b991d9c0cdd02891abad@47.75.99.169:443",
"enode://5405c509df683c962e7c9470b251bb679dd6978f82d5b469f1f6c64d11d50fbd5dd9f7801c6ad51f3b20a5f6c7ffe248cc9ab223f8bcbaeaf14bb1c0ef295fd0@35.223.215.156:443"
],
"Enabled": true,
"Fleet": "eth.prod",
"RendezvousNodes": [
"/ip4/34.70.75.208/tcp/30703/ethv4/16Uiu2HAm6ZsERLx2BwVD2UM9SVPnnMU6NBycG8XPtu8qKys5awsU",
"/ip4/178.128.140.188/tcp/30703/ethv4/16Uiu2HAmLqTXuY4Sb6G28HNooaFUXUKzpzKXCcgyJxgaEE2i5vnf",
"/ip4/47.52.106.107/tcp/30703/ethv4/16Uiu2HAmEHiptiDDd9gqNY8oQqo8hHUWMHJzfwt5aLRdD6W2zcXR"
],
"StaticNodes": [
"enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@178.128.141.0:443",
"enode://fbeddac99d396b91d59f2c63a3cb5fc7e0f8a9f7ce6fe5f2eed5e787a0154161b7173a6a73124a4275ef338b8966dc70a611e9ae2192f0f2340395661fad81c0@34.67.230.193:443"
],
"TrustedMailServers": [
"enode://2c8de3cbb27a3d30cbb5b3e003bc722b126f5aef82e2052aaef032ca94e0c7ad219e533ba88c70585ebd802de206693255335b100307645ab5170e88620d2a81@47.244.221.14:443",
"enode://ee2b53b0ace9692167a410514bca3024695dbf0e1a68e1dff9716da620efb195f04a4b9e873fb9b74ac84de801106c465b8e2b6c4f0d93b8749d1578bfcaf03e@104.197.238.144:443",
"enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@178.128.142.94:443",
"enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@178.128.142.26:443",
"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@178.128.142.54:443",
"enode://30211cbd81c25f07b03a0196d56e6ce4604bb13db773ff1c0ea2253547fafd6c06eae6ad3533e2ba39d59564cfbdbb5e2ce7c137a5ebb85e99dcfc7a75f99f55@23.236.58.92:443"
]
},
"DataDir": "./ethereum/mainnet",
"EnableNTPSync": true,
"KeyStoreDir": "./keystore",
"ListenAddr": ":30304",
"LogEnabled": true,
"LogFile": "geth.log",
"LogLevel": "INFO",
"MailserversConfig": {
"Enabled": true
},
"Name": "StatusIM",
"NetworkId": 1,
"NoDiscovery": false,
"PermissionsConfig": {
"Enabled": true
},
"Rendezvous": true,
"RequireTopics": {
"whisper": {
"Max": 2,
"Min": 2
}
},
"ShhextConfig": {
"BackupDisabledDataDir": "./",
"DataSyncEnabled": true,
"InstallationID": "aef27732-8d86-5039-a32e-bdbe094d8791",
"MailServerConfirmations": true,
"MaxMessageDeliveryAttempts": 6,
"PFSEnabled": true,
"VerifyENSContractAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"VerifyENSURL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a",
"VerifyTransactionChainID": 1,
"VerifyTransactionURL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"
},
"StatusAccountsConfig": {
"Enabled": true
},
"UpstreamConfig": {
"Enabled": true,
"URL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"
},
"WakuConfig": {
"BloomFilterMode": nil,
"Enabled": true,
"LightClient": true,
"MinimumPoW": 0.001
},
"WalletConfig": {
"Enabled": true
}
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,83 @@
{
"BrowsersConfig": {
"Enabled": true
},
"ClusterConfig": {
"BootNodes": [
"enode://23d0740b11919358625d79d4cac7d50a34d79e9c69e16831c5c70573757a1f5d7d884510bc595d7ee4da3c1508adf87bbc9e9260d804ef03f8c1e37f2fb2fc69@47.52.106.107:443",
"enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@178.128.140.188:443",
"enode://6e6554fb3034b211398fcd0f0082cbb6bd13619e1a7e76ba66e1809aaa0c5f1ac53c9ae79cf2fd4a7bacb10d12010899b370c75fed19b991d9c0cdd02891abad@47.75.99.169:443",
"enode://5405c509df683c962e7c9470b251bb679dd6978f82d5b469f1f6c64d11d50fbd5dd9f7801c6ad51f3b20a5f6c7ffe248cc9ab223f8bcbaeaf14bb1c0ef295fd0@35.223.215.156:443"
],
"Enabled": true,
"Fleet": "eth.prod",
"RendezvousNodes": [
"/ip4/34.70.75.208/tcp/30703/ethv4/16Uiu2HAm6ZsERLx2BwVD2UM9SVPnnMU6NBycG8XPtu8qKys5awsU",
"/ip4/178.128.140.188/tcp/30703/ethv4/16Uiu2HAmLqTXuY4Sb6G28HNooaFUXUKzpzKXCcgyJxgaEE2i5vnf",
"/ip4/47.52.106.107/tcp/30703/ethv4/16Uiu2HAmEHiptiDDd9gqNY8oQqo8hHUWMHJzfwt5aLRdD6W2zcXR"
],
"StaticNodes": [
"enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@178.128.141.0:443",
"enode://fbeddac99d396b91d59f2c63a3cb5fc7e0f8a9f7ce6fe5f2eed5e787a0154161b7173a6a73124a4275ef338b8966dc70a611e9ae2192f0f2340395661fad81c0@34.67.230.193:443"
],
"TrustedMailServers": [
"enode://2c8de3cbb27a3d30cbb5b3e003bc722b126f5aef82e2052aaef032ca94e0c7ad219e533ba88c70585ebd802de206693255335b100307645ab5170e88620d2a81@47.244.221.14:443",
"enode://ee2b53b0ace9692167a410514bca3024695dbf0e1a68e1dff9716da620efb195f04a4b9e873fb9b74ac84de801106c465b8e2b6c4f0d93b8749d1578bfcaf03e@104.197.238.144:443",
"enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@178.128.142.94:443",
"enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@178.128.142.26:443",
"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@178.128.142.54:443",
"enode://30211cbd81c25f07b03a0196d56e6ce4604bb13db773ff1c0ea2253547fafd6c06eae6ad3533e2ba39d59564cfbdbb5e2ce7c137a5ebb85e99dcfc7a75f99f55@23.236.58.92:443"
]
},
"DataDir": "./ethereum/mainnet",
"EnableNTPSync": true,
"KeyStoreDir": "./keystore",
"ListenAddr": ":30304",
"LogEnabled": true,
"LogFile": "geth.log",
"LogLevel": "INFO",
"MailserversConfig": {
"Enabled": true
},
"Name": "StatusIM",
"NetworkId": 1,
"NoDiscovery": false,
"PermissionsConfig": {
"Enabled": true
},
"Rendezvous": true,
"RequireTopics": {
"whisper": {
"Max": 2,
"Min": 2
}
},
"ShhextConfig": {
"BackupDisabledDataDir": "./",
"DataSyncEnabled": true,
"InstallationID": "aef27732-8d86-5039-a32e-bdbe094d8791",
"MailServerConfirmations": true,
"MaxMessageDeliveryAttempts": 6,
"PFSEnabled": true,
"VerifyENSContractAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"VerifyENSURL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a",
"VerifyTransactionChainID": 1,
"VerifyTransactionURL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"
},
"StatusAccountsConfig": {
"Enabled": true
},
"UpstreamConfig": {
"Enabled": true,
"URL": "https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"
},
"WakuConfig": {
"BloomFilterMode": null,
"Enabled": true,
"LightClient": true,
"MinimumPoW": 0.001
},
"WalletConfig": {
"Enabled": true
}
}

View File

@ -0,0 +1,623 @@
const phrases*: seq[string] = @[
"acid",
"alto",
"apse",
"arch",
"area",
"army",
"atom",
"aunt",
"babe",
"baby",
"back",
"bail",
"bait",
"bake",
"ball",
"band",
"bank",
"barn",
"base",
"bass",
"bath",
"bead",
"beak",
"beam",
"bean",
"bear",
"beat",
"beef",
"beer",
"beet",
"bell",
"belt",
"bend",
"bike",
"bill",
"bird",
"bite",
"blow",
"blue",
"boar",
"boat",
"body",
"bolt",
"bomb",
"bone",
"book",
"boot",
"bore",
"boss",
"bowl",
"brow",
"bulb",
"bull",
"burn",
"bush",
"bust",
"cafe",
"cake",
"calf",
"call",
"calm",
"camp",
"cane",
"cape",
"card",
"care",
"carp",
"cart",
"case",
"cash",
"cast",
"cave",
"cell",
"cent",
"chap",
"chef",
"chin",
"chip",
"chop",
"chub",
"chug",
"city",
"clam",
"clef",
"clip",
"club",
"clue",
"coal",
"coat",
"code",
"coil",
"coin",
"coke",
"cold",
"colt",
"comb",
"cone",
"cook",
"cope",
"copy",
"cord",
"cork",
"corn",
"cost",
"crab",
"craw",
"crew",
"crib",
"crop",
"crow",
"curl",
"cyst",
"dame",
"dare",
"dark",
"dart",
"dash",
"data",
"date",
"dead",
"deal",
"dear",
"debt",
"deck",
"deep",
"deer",
"desk",
"dhow",
"diet",
"dill",
"dime",
"dirt",
"dish",
"disk",
"dock",
"doll",
"door",
"dory",
"drag",
"draw",
"drop",
"drug",
"drum",
"duck",
"dump",
"dust",
"duty",
"ease",
"east",
"eave",
"eddy",
"edge",
"envy",
"epee",
"exam",
"exit",
"face",
"fact",
"fail",
"fall",
"fame",
"fang",
"farm",
"fawn",
"fear",
"feed",
"feel",
"feet",
"file",
"fill",
"film",
"find",
"fine",
"fire",
"fish",
"flag",
"flat",
"flax",
"flow",
"foam",
"fold",
"font",
"food",
"foot",
"fork",
"form",
"fort",
"fowl",
"frog",
"fuel",
"full",
"gain",
"gale",
"galn",
"game",
"garb",
"gate",
"gear",
"gene",
"gift",
"girl",
"give",
"glad",
"glen",
"glue",
"glut",
"goal",
"goat",
"gold",
"golf",
"gong",
"good",
"gown",
"grab",
"gram",
"gray",
"grey",
"grip",
"grit",
"gyro",
"hail",
"hair",
"half",
"hall",
"hand",
"hang",
"harm",
"harp",
"hate",
"hawk",
"head",
"heat",
"heel",
"hell",
"helo",
"help",
"hemp",
"herb",
"hide",
"high",
"hill",
"hire",
"hive",
"hold",
"hole",
"home",
"hood",
"hoof",
"hook",
"hope",
"hops",
"horn",
"hose",
"host",
"hour",
"hunt",
"hurt",
"icon",
"idea",
"inch",
"iris",
"iron",
"item",
"jail",
"jeep",
"jeff",
"joey",
"join",
"joke",
"judo",
"jump",
"junk",
"jury",
"jute",
"kale",
"keep",
"kick",
"kill",
"kilt",
"kind",
"king",
"kiss",
"kite",
"knee",
"knot",
"lace",
"lack",
"lady",
"lake",
"lamb",
"lamp",
"land",
"lark",
"lava",
"lawn",
"lead",
"leaf",
"leek",
"lier",
"life",
"lift",
"lily",
"limo",
"line",
"link",
"lion",
"lisa",
"list",
"load",
"loaf",
"loan",
"lock",
"loft",
"long",
"look",
"loss",
"lout",
"love",
"luck",
"lung",
"lute",
"lynx",
"lyre",
"maid",
"mail",
"main",
"make",
"male",
"mall",
"manx",
"many",
"mare",
"mark",
"mask",
"mass",
"mate",
"math",
"meal",
"meat",
"meet",
"menu",
"mess",
"mice",
"midi",
"mile",
"milk",
"mime",
"mind",
"mine",
"mini",
"mint",
"miss",
"mist",
"moat",
"mode",
"mole",
"mood",
"moon",
"most",
"moth",
"move",
"mule",
"mutt",
"nail",
"name",
"neat",
"neck",
"need",
"neon",
"nest",
"news",
"node",
"nose",
"note",
"oboe",
"okra",
"open",
"oval",
"oven",
"oxen",
"pace",
"pack",
"page",
"pail",
"pain",
"pair",
"palm",
"pard",
"park",
"part",
"pass",
"past",
"path",
"peak",
"pear",
"peen",
"peer",
"pelt",
"perp",
"pest",
"pick",
"pier",
"pike",
"pile",
"pimp",
"pine",
"ping",
"pink",
"pint",
"pipe",
"piss",
"pith",
"plan",
"play",
"plot",
"plow",
"poem",
"poet",
"pole",
"polo",
"pond",
"pony",
"poof",
"pool",
"port",
"post",
"prow",
"pull",
"puma",
"pump",
"pupa",
"push",
"quit",
"race",
"rack",
"raft",
"rage",
"rail",
"rain",
"rake",
"rank",
"rate",
"read",
"rear",
"reef",
"rent",
"rest",
"rice",
"rich",
"ride",
"ring",
"rise",
"risk",
"road",
"robe",
"rock",
"role",
"roll",
"roof",
"room",
"root",
"rope",
"rose",
"ruin",
"rule",
"rush",
"ruth",
"sack",
"safe",
"sage",
"sail",
"sale",
"salt",
"sand",
"sari",
"sash",
"save",
"scow",
"seal",
"seat",
"seed",
"self",
"sell",
"shed",
"shin",
"ship",
"shoe",
"shop",
"shot",
"show",
"sick",
"side",
"sign",
"silk",
"sill",
"silo",
"sing",
"sink",
"site",
"size",
"skin",
"sled",
"slip",
"smog",
"snob",
"snow",
"soap",
"sock",
"soda",
"sofa",
"soft",
"soil",
"song",
"soot",
"sort",
"soup",
"spot",
"spur",
"stag",
"star",
"stay",
"stem",
"step",
"stew",
"stop",
"stud",
"suck",
"suit",
"swan",
"swim",
"tail",
"tale",
"talk",
"tank",
"tard",
"task",
"taxi",
"team",
"tear",
"teen",
"tell",
"temp",
"tent",
"term",
"test",
"text",
"thaw",
"tile",
"till",
"time",
"tire",
"toad",
"toga",
"togs",
"tone",
"tool",
"toot",
"tote",
"tour",
"town",
"tram",
"tray",
"tree",
"trim",
"trip",
"tuba",
"tube",
"tuna",
"tune",
"turn",
"tutu",
"twig",
"type",
"unit",
"user",
"vane",
"vase",
"vast",
"veal",
"veil",
"vein",
"vest",
"vibe",
"view",
"vise",
"wait",
"wake",
"walk",
"wall",
"wash",
"wasp",
"wave",
"wear",
"weed",
"week",
"well",
"west",
"whip",
"wife",
"will",
"wind",
"wine",
"wing",
"wire",
"wish",
"wolf",
"wood",
"wool",
"word",
"work",
"worm",
"wrap",
"wren",
"yard",
"yarn",
"yawl",
"year",
"yoga",
"yoke",
"yurt",
"zinc",
"zone"]

10
src/models/accounts.nim Normal file
View File

@ -0,0 +1,10 @@
import json
type
GeneratedAccount* = object
publicKey*: string
address*: string
id*: string
keyUid*: string
mnemonic*: string
derived*: JsonNode

View File

@ -6,6 +6,10 @@ import app/node/core as node
import app/profile/core as profile
import app/signals/core as signals
import state
import onboarding
import status/utils
import strformat
import strutils
import strformat
import strutils
import json
@ -16,16 +20,43 @@ import status/types as types
import status/wallet as status_wallet
import status/libstatus
import state
import status/libstatusqml
import status/types
import eventemitter
import os
var signalsQObjPointer: pointer
logScope:
topics = "main"
proc ensureDir(dirname: string) =
if not existsDir(dirname):
# removeDir(dirname)
createDir(dirname)
proc initNode(): string =
const datadir = "./data/"
const keystoredir = "./data/keystore/"
const nobackupdir = "./noBackup/"
ensureDir(datadir)
ensureDir(keystoredir)
ensureDir(nobackupdir)
# 1
result = $libstatus.initKeystore(keystoredir);
# 2
result = $libstatus.openAccounts(datadir);
proc mainProc() =
discard initNode()
let app = newQApplication()
let engine = newQQmlApplicationEngine()
let signalController = signals.newController(app)
let events = createEventEmitter()
defer: # Defer will run this just before mainProc() function ends
app.delete()
@ -42,10 +73,12 @@ proc mainProc() =
var accounts = status_test.setupNewAccount()
debug "Accounts", accounts0 = parseJSON(accounts)[0], accounts1 = parseJSON(accounts)[1]
status_chat.startMessenger()
events.on("node:ready") do(a: Args):
status_chat.startMessenger()
var wallet = wallet.newController()
wallet.init()
events.on("node:ready") do(a: Args):
wallet.init()
engine.setRootContextProperty("assetsModel", wallet.variant)
var chat = chat.newController()
@ -54,7 +87,23 @@ proc mainProc() =
var node = node.newController()
node.init()
engine.setRootContextProperty("nodeModel", node.variant)
var onboarding = newOnboarding(events);
defer: onboarding.delete
let onboardingVariant = newQVariant(onboarding)
defer: onboardingVariant.delete
engine.setRootContextProperty("onboardingLogic", onboardingVariant)
# TODO: figure out a way to prevent this from breaking Qt Creator
# var initLibStatusQml = proc(): LibStatusQml =
# let libStatus = newLibStatusQml();
# return libStatus;
# discard qmlRegisterSingletonType[LibStatusQml]("im.status.desktop.Status", 1, 0, "Status", initLibStatusQml)
var profile = profile.newController()
profile.init(accounts) # TODO: use correct account
@ -73,9 +122,14 @@ proc mainProc() =
chat.join(channel.name)
)
appState.addChannel("test")
appState.addChannel("test2")
events.on("node:ready") do(a: Args):
appState.addChannel("test")
appState.addChannel("test2")
engine.load("../ui/main.qml")
# Please note that this must use the `cdecl` calling convention because

147
src/onboarding.nim Normal file
View File

@ -0,0 +1,147 @@
import NimQml
import json
import status/accounts
import nimcrypto
import status/utils
import status/libstatus
import models/accounts as Models
import constants/constants
import uuids
import eventemitter
import status/test as status_test
# Probably all QT classes will look like this:
QtObject:
type Onboarding* = ref object of QObject
m_generatedAddresses: string
events: EventEmitter
# ¯\_(ツ)_/¯ dunno what is this
proc setup(self: Onboarding) =
self.QObject.setup
# ¯\_(ツ)_/¯ seems to be a method for garbage collection
proc delete*(self: Onboarding) =
self.QObject.delete
# Constructor
proc newOnboarding*(events: EventEmitter): Onboarding =
new(result, delete)
result.events = events
result.setup()
# Read more about slots and signals here: https://doc.qt.io/qt-5/signalsandslots.html
# Accesors
proc getGeneratedAddresses*(self: Onboarding): string {.slot.} =
result = self.m_generatedAddresses
proc generatedAddressesChanged*(self: Onboarding,
generatedAddresses: string) {.signal.}
proc setGeneratedAddresses*(self: Onboarding, generatedAddresses: string) {.slot.} =
if self.m_generatedAddresses == generatedAddresses:
return
self.m_generatedAddresses = generatedAddresses
self.generatedAddressesChanged(generatedAddresses)
QtProperty[string]generatedAddresses:
read = getGeneratedAddresses
write = setGeneratedAddresses
notify = generatedAddressesChanged
# QML functions
proc generateAddresses*(self: Onboarding) {.slot.} =
self.setGeneratedAddresses(generateAddresses())
proc generateAlias*(self: Onboarding, publicKey: string): string {.slot.} =
result = $libstatus.generateAlias(publicKey.toGoString)
proc identicon*(self: Onboarding, publicKey: string): string {.slot.} =
result = $libstatus.identicon(publicKey.toGoString)
proc storeAccountAndLogin(self: Onboarding, selectedAccount: string, password: string): string {.slot.} =
let account = to(json.parseJson(selectedAccount), Models.GeneratedAccount)
let password = "0x" & $keccak_256.digest(password)
let multiAccount = %* {
"accountID": account.id,
"paths": [constants.PATH_WALLET_ROOT, constants.PATH_EIP_1581, constants.PATH_WHISPER,
constants.PATH_DEFAULT_WALLET],
"password": password
}
let storeResult = $libstatus.multiAccountStoreDerivedAccounts($multiAccount);
let multiAccounts = storeResult.parseJson
let whisperPubKey = account.derived[constants.PATH_WHISPER]["publicKey"].getStr
let alias = $libstatus.generateAlias(whisperPubKey.toGoString)
let identicon = $libstatus.identicon(whisperPubKey.toGoString)
let accountData = %* {
"name": alias,
"address": account.address,
"photo-path": identicon,
"key-uid": account.keyUid,
"keycard-pairing": nil
}
var nodeConfig = constants.NODE_CONFIG
let defaultNetworks = constants.DEFAULT_NETWORKS
let settingsJSON = %* {
"key-uid": account.keyUid,
"mnemonic": account.mnemonic,
"public-key": multiAccounts[constants.PATH_WHISPER]["publicKey"].getStr,
"name": alias,
"address": account.address,
"eip1581-address": multiAccounts[constants.PATH_EIP_1581]["address"].getStr,
"dapps-address": multiAccounts[constants.PATH_DEFAULT_WALLET]["address"].getStr,
"wallet-root-address": multiAccounts[constants.PATH_WALLET_ROOT]["address"].getStr,
"preview-privacy?": true,
"signing-phrase": generateSigningPhrase(3),
"log-level": "INFO",
"latest-derived-path": 0,
"networks/networks": $defaultNetworks,
"currency": "usd",
"photo-path": identicon,
"waku-enabled": true,
"wallet/visible-tokens": {
"mainnet": ["SNT"]
},
"appearance": 0,
"networks/current-network": "mainnet_rpc",
"installation-id": $genUUID()
}
let subaccountData = %* [
{
"public-key": multiAccounts[constants.PATH_DEFAULT_WALLET]["publicKey"],
"address": multiAccounts[constants.PATH_DEFAULT_WALLET]["address"],
"color": "#4360df",
"wallet": true,
"path": constants.PATH_DEFAULT_WALLET,
"name": "Status account"
},
{
"public-key": multiAccounts[constants.PATH_WHISPER]["publicKey"],
"address": multiAccounts[constants.PATH_WHISPER]["address"],
"name": alias,
"photo-path": identicon,
"path": constants.PATH_WHISPER,
"chat": true
}
]
result = $libstatus.saveAccountAndLogin($accountData, password, $settingsJSON,
$nodeConfig, $subaccountData)
let saveResult = result.parseJson
if saveResult["error"].getStr == "":
self.events.emit("node:ready", Args())
echo "Account saved succesfully"
proc generateRandomAccountAndLogin*(self: Onboarding) {.slot.} =
status_test.setupNewAccount()
self.events.emit("node:ready", Args())
# This class has the metaObject property available which lets
# access all the QProperties which are stored as QVariants

15
src/status/accounts.nim Normal file
View File

@ -0,0 +1,15 @@
import libstatus
import json
import utils
proc generateAddresses*(): string =
let multiAccountConfig = %* {
"n": 5,
"mnemonicPhraseLength": 12,
"bip39Passphrase": "",
"paths": ["m/43'/60'/1581'/0'/0", "m/44'/60'/0'/0/0"]
}
result = $libstatus.multiAccountGenerateAndDeriveAddresses($multiAccountConfig)
proc generateAlias*(publicKey: string): string =
result = $libstatus.generateAlias(publicKey.toGoString)

View File

@ -21,3 +21,7 @@ proc addPeer*(peer: cstring): cstring {.importc: "AddPeer".}
proc setSignalEventCallback*(callback: SignalCallback) {.importc: "SetSignalEventCallback".}
proc sendTransaction*(jsonArgs: cstring, password: cstring): cstring {.importc: "SendTransaction".}
proc generateAlias*(p0: GoString): cstring {.importc: "GenerateAlias".}
proc identicon*(p0: GoString): cstring {.importc: "Identicon".}

View File

@ -0,0 +1,51 @@
import NimQml
import libstatus
import utils
QtObject:
type
LibStatusQml* = ref object of QObject
# ¯\_(ツ)_/¯ dunno what is this
proc setup(self: LibStatusQml) =
self.QObject.setup
# ¯\_(ツ)_/¯ seems to be a method for garbage collection
proc delete*(self: LibStatusQml) =
self.QObject.delete
# Constructor
proc newLibStatusQml*(): LibStatusQml =
new(result, delete)
result.setup
proc hashMessage*(self: LibStatusQml, p0: string): string {.slot.} =
return $libstatus.hashMessage(p0)
proc initKeystore*(self: LibStatusQml, keydir: string): string {.slot.} =
return $libstatus.initKeystore(keydir)
proc openAccounts*(self: LibStatusQml, datadir: string): string {.slot.} =
return $libstatus.openAccounts(datadir)
proc multiAccountGenerateAndDeriveAddresses*(self: LibStatusQml, paramsJSON: string): string {.slot.} =
return $libstatus.multiAccountGenerateAndDeriveAddresses(paramsJSON)
proc multiAccountStoreDerivedAccounts*(self: LibStatusQml, paramsJSON: string): string {.slot.} =
return $libstatus.multiAccountStoreDerivedAccounts(paramsJSON)
proc saveAccountAndLogin*(self: LibStatusQml, accountData: string, password: string, settingsJSON: string, configJSON: string, subaccountData: string): string {.slot.} =
return $libstatus.saveAccountAndLogin(accountData, password, settingsJSON, configJSON, subaccountData)
proc callRPC*(self: LibStatusQml, inputJSON: string): string {.slot.} =
return $libstatus.callRPC(inputJSON)
proc callPrivateRPC*(self: LibStatusQml, inputJSON: string): string {.slot.} =
return $libstatus.callPrivateRPC(inputJSON)
proc addPeer*(self: LibStatusQml, peer: string): string {.slot.} =
return $libstatus.addPeer(peer)
proc generateAlias*(self: LibStatusQml, p0: string): string {.slot.} =
return $libstatus.generateAlias(p0.toGoString)

View File

@ -31,17 +31,17 @@ proc queryAccounts*(): string =
proc setupNewAccount*(): string =
# Deleting directories
recreateDir(datadir)
recreateDir(keystoredir)
recreateDir(nobackupdir)
# recreateDir(datadir)
# recreateDir(keystoredir)
# recreateDir(nobackupdir)
var result: string
# 1
result = $libstatus.initKeystore(keystoredir);
# # 1
# result = $libstatus.initKeystore(keystoredir);
# 2
result = $libstatus.openAccounts(datadir);
# # 2
# result = $libstatus.openAccounts(datadir);
# 3
let multiAccountConfig = %* {

View File

@ -14,3 +14,8 @@ type SignalType* {.pure.} = enum
SubscriptionsError = "subscriptions.error"
WhisperFilterAdded = "whisper.filter.added"
Unknown
type
GoString* = object
str*: cstring
length*: cint

View File

@ -1,4 +1,9 @@
import json
import types
import random
from times import getTime, toUnix, nanosecond
import strutils
import ../constants/signing_phrases
proc isWakuEnabled(): bool =
true # TODO:
@ -8,4 +13,22 @@ proc prefix*(methodName: string): string =
result = result & methodName
proc isOneToOneChat*(chatId: string): bool =
result = chatId.startsWith("0x") # There is probably a better way to do this
result = chatId.startsWith("0x") # There is probably a better way to do this
proc keys*(obj: JsonNode): seq[string] =
result = newSeq[string]()
for k, _ in obj:
result.add k
proc toGoString*(str: string): GoString =
result = GoString(str: str, length: cint(str.len))
proc generateSigningPhrase*(count: int): string =
let now = getTime()
var rng = initRand(now.toUnix * 1000000000 + now.nanosecond)
var phrases: seq[string] = @[]
for i in 1..count:
phrases.add(rng.sample(signing_phrases.phrases))
result = phrases.join(" ")

View File

@ -30,27 +30,18 @@ ApplicationWindow {
}
}
Rectangle {
id: rctAppBg
color: "#FFFFFF"
Layout.fillHeight: true
Layout.fillWidth: true
OnboardingMain {
id: onboarding
visible: !app.visible
anchors.fill: parent
border.width: 0
}
Intro {
id: onboarding
visible: !app.visible
anchors.fill: parent
}
AppMain {
id: app
// TODO: Set this to a logic result determining when we need to show the onboarding screens
// Set to true to hide the onboarding screens manually
// Set to false to show the onboarding screens manually
visible: false // logic.accountResult !== ""
anchors.fill: parent
}
AppMain {
id: app
// TODO: Set this to a logic result determining when we need to show the onboarding screens
// Set to true to hide the onboarding screens manually
// Set to false to show the onboarding screens manually
visible: false // logic.accountResult !== ""
anchors.fill: parent
}
}

View File

@ -70,7 +70,11 @@ DISTFILES += \
app/img/walletActive.svg \
app/qmldir \
imports/qmldir \
onboarding/ExistingKey.qml \
onboarding/GenKey.qml \
onboarding/Intro.qml \
onboarding/KeysMain.qml \
onboarding/OnboardingMain.qml \
onboarding/img/browser-dark@2x.jpg \
onboarding/img/browser-dark@3x.jpg \
onboarding/img/browser@2x.jpg \
@ -79,11 +83,14 @@ DISTFILES += \
onboarding/img/chat-dark@3x.jpg \
onboarding/img/chat@2x.jpg \
onboarding/img/chat@3x.jpg \
onboarding/img/key.png \
onboarding/img/key@2x.png \
onboarding/img/next.svg \
onboarding/img/wallet-dark@2x.jpg \
onboarding/img/wallet-dark@3x.jpg \
onboarding/img/wallet@2x.jpg \
onboarding/img/wallet@3x.jpg \
onboarding/qmldir \
shared/StyledButton.qml \
shared/RoundedIcon.qml \
shared/qmldir

View File

@ -0,0 +1,18 @@
import QtQuick 2.3
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtQuick.Window 2.11
import QtQuick.Dialogs 1.3
Text {
text: "Existing key flow [COMING SOON]"
font.pointSize: 36
anchors.centerIn: parent
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

241
ui/onboarding/GenKey.qml Normal file
View File

@ -0,0 +1,241 @@
import QtQuick 2.3
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtQuick.Window 2.11
import QtQuick.Dialogs 1.3
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: 0
property string strGeneratedAccounts: onboardingLogic.generatedAddresses
property var generatedAccounts: {}
signal storeAccountAndLoginResult(response: var)
onCurrentItemChanged: {
currentItem.txtPassword.focus = true;
}
ListModel {
id: generatedAccountsModel
}
Item {
id: wizardStep2
property int selectedIndex: 0
Text {
text: "Generated accounts"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Item {
anchors.top: parent.top
anchors.topMargin: 50
Column {
spacing: 10
ButtonGroup {
id: accountGroup
}
Repeater {
model: generatedAccountsModel
Rectangle {
height: 32
width: 32
anchors.leftMargin: 20
anchors.rightMargin: 20
Row {
RadioButton {
checked: index == 0 ? true : false
ButtonGroup.group: accountGroup
onClicked: {
wizardStep2.selectedIndex = index;
}
}
Column {
Image {
source: identicon
}
}
Column {
Text {
text: alias
}
Text {
text: publicKey
width: 160
elide: Text.ElideMiddle
}
}
}
}
}
}
}
Button {
text: "Select"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("button: " + wizardStep2.selectedIndex);
swipeView.incrementCurrentIndex();
}
}
}
Item {
id: wizardStep3
property Item txtPassword: txtPassword
Text {
text: "Enter password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
anchors.left: parent.left
anchors.right: parent.right
anchors.centerIn: parent
height: 32
width: parent.width - 40
TextInput {
id: txtPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
}
}
Button {
text: "Next"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("password: " + txtPassword.text);
swipeView.incrementCurrentIndex();
}
}
}
Item {
id: wizardStep4
property Item txtPassword: txtConfirmPassword
Text {
text: "Confirm password"
font.pointSize: 36
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
color: "#EEEEEE"
anchors.left: parent.left
anchors.right: parent.right
anchors.centerIn: parent
height: 32
width: parent.width - 40
TextInput {
id: txtConfirmPassword
anchors.fill: parent
focus: true
echoMode: TextInput.Password
selectByMouse: true
}
}
MessageDialog {
id: passwordsDontMatchError
title: "Error"
text: "Passwords don't match"
icon: StandardIcon.Warning
standardButtons: StandardButton.Ok
onAccepted: {
txtConfirmPassword.clear();
swipeView.currentIndex = 1
txtPassword.focus = true
}
}
MessageDialog {
id: storeAccountAndLoginError
title: "Error storing account and logging in"
text: "An error occurred while storing your account and logging in: "
icon: StandardIcon.Error
standardButtons: StandardButton.Ok
}
Button {
text: "Finish"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
onClicked: {
console.log("confirm clicked " + txtConfirmPassword.text + " : " + txtPassword.text);
if (txtConfirmPassword.text != txtPassword.text) {
passwordsDontMatchError.open();
}
else {
const selectedAccount = swipeView.generatedAccounts[wizardStep2.selectedIndex];
const storeResponse = onboardingLogic.storeAccountAndLogin(JSON.stringify(selectedAccount), txtPassword.text)
const response = JSON.parse(storeResponse);
if (response.error) {
storeAccountAndLoginError.text += response.error;
return storeAccountAndLoginError.open();
}
swipeView.storeAccountAndLoginResult(response);
}
}
}
}
// handle the serialised result coming from node and deserialise into JSON
// TODO: maybe we should figure out a clever to avoid this?
onStrGeneratedAccountsChanged: {
if (generatedAccounts === null || generatedAccounts === "") {
return;
}
swipeView.generatedAccounts = JSON.parse(strGeneratedAccounts);
}
// handle deserialised data coming from the node
onGeneratedAccountsChanged: {
generatedAccountsModel.clear();
generatedAccounts.forEach(acc => {
generatedAccountsModel.append({
publicKey: acc.publicKey,
alias: onboardingLogic.generateAlias(acc.publicKey),
identicon: onboardingLogic.identicon(acc.publicKey)
});
});
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -2,8 +2,11 @@ import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import "../shared"
RowLayout {
property alias btnGetStarted: btnGetStarted
id: obLayout
anchors.fill: parent
Layout.fillWidth: true
@ -26,6 +29,21 @@ RowLayout {
Item {
id: itmSlide1
StyledButton {
id: btnGenRandomAcct
width: 250
height: 50
anchors.top: parent.top
anchors.topMargin: 50
anchors.left: parent.left
anchors.leftMargin: 15
label: "Generate random acct and login"
onClicked: {
onboardingLogic.generateRandomAccountAndLogin()
app.visible = true
}
}
Image {
id: img1
anchors.horizontalCenter: parent.horizontalCenter
@ -301,32 +319,15 @@ RowLayout {
}
}
Button {
StyledButton {
id: btnGetStarted
rightPadding: 32
leftPadding: 32
bottomPadding: 11
topPadding: 11
width: 146
height: 44
label: "Get started"
anchors.top: rctPageIndicator.bottom
anchors.topMargin: 87
anchors.horizontalCenter: parent.horizontalCenter
onClicked: app.visible = true
background: Rectangle {
color: "#ECEFFC"
radius: 8
}
Text {
id: txtGetStarted
color: "#4360DF"
text: qsTr("Get started")
font.weight: Font.DemiBold
font.pointSize: 15
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
// onClicked: app.visible = true
width: 146
height: 44
}
Text {

View File

@ -0,0 +1,91 @@
import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import "../shared"
Page {
property alias btnExistingKey: btnExistingKey
property alias btnGenKey: btnGenKey
Image {
id: img1
anchors.horizontalCenter: parent.horizontalCenter
sourceSize.width: 160
sourceSize.height: 160
anchors.topMargin: 24
anchors.top: parent.top
fillMode: Image.PreserveAspectFit
source: "img/key@2x.png"
}
Text {
id: txtTitle1
text: qsTr("Get your keys")
anchors.right: parent.right
anchors.rightMargin: 177
anchors.left: parent.left
anchors.leftMargin: 177
anchors.top: img1.bottom
anchors.topMargin: 16
font.letterSpacing: -0.2
font.weight: Font.Bold
lineHeight: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
transformOrigin: Item.Center
font.bold: true
font.pixelSize: 22
font.kerning: true
}
Text {
id: txtDesc1
x: 772
color: "#939BA1"
text: qsTr("A set of keys controls your account. Your keys live on\nyour device, so only you can use them.")
horizontalAlignment: Text.AlignHCenter
font.weight: Font.Normal
style: Text.Normal
anchors.horizontalCenterOffset: 0
anchors.top: txtTitle1.bottom
anchors.topMargin: 14
font.bold: true
font.family: "Inter"
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 15
}
StyledButton {
id: btnExistingKey
label: "Access existing key"
anchors.top: txtDesc1.bottom
anchors.topMargin: 87
anchors.horizontalCenter: parent.horizontalCenter
// onClicked: logic.generateAddresses()
width: 142
height: 44
}
StyledButton {
id: btnGenKey
width: 194
height: 44
anchors.top: btnExistingKey.bottom
anchors.topMargin: 19
anchors.horizontalCenter: parent.horizontalCenter
label: "I'm new, generate me a key"
background: Rectangle {color: "transparent"}
onClicked: onboardingLogic.generateAddresses()
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:720}
}
##^##*/

View File

@ -0,0 +1,101 @@
import QtQuick 2.3
import QtQml.StateMachine 1.14 as DSM
import QtQuick.Controls 2.3
Page {
id: onboardingMain
property string state
anchors.fill: parent
DSM.StateMachine {
id: stateMachine
initialState: stateIntro
running: onboardingMain.visible
DSM.State {
id: stateIntro
onEntered: intro.visible = true
onExited: intro.visible = false
DSM.SignalTransition {
targetState: keysMainState
signal: intro.btnGetStarted.clicked
}
}
DSM.State {
id: keysMainState
onEntered: keysMain.visible = true
onExited: keysMain.visible = false
DSM.SignalTransition {
targetState: existingKeyState
signal: keysMain.btnExistingKey.clicked
}
DSM.SignalTransition {
targetState: genKeyState
signal: keysMain.btnGenKey.clicked
}
}
DSM.State {
id: existingKeyState
onEntered: existingKey.visible = true
onExited: existingKey.visible = false
// DSM.SignalTransition {
// targetState: keysMainState
// signal: keysMain.btnExistingKey.clicked
// }
}
DSM.State {
id: genKeyState
onEntered: genKey.visible = true
onExited: genKey.visible = false
DSM.SignalTransition {
targetState: appState
signal: genKey.storeAccountAndLoginResult
guard: !response.error
}
}
DSM.FinalState {
id: appState
onEntered: app.visible = true
onExited: app.visible = false
}
}
Intro {
id: intro
anchors.fill: parent
visible: true
}
KeysMain {
id: keysMain
anchors.fill: parent
visible: false
}
ExistingKey {
id: existingKey
anchors.fill: parent
visible: false
}
GenKey {
id: genKey
anchors.fill: parent
visible: false
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:770;width:1232}
}
##^##*/

BIN
ui/onboarding/img/key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1 +1,5 @@
Intro 1.0 Intro.qml
ExistingKey 1.0 ExistingKey.qml
GenKey 1.0 GenKey.qml
Intro 1.0 Intro.qml
KeysMain 1.0 KeysMain.qml
OnboardingMain 1.0 OnboardingMain.qml

View File

@ -0,0 +1,34 @@
import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import QtQml 2.14
Button {
property alias label: txtBtnLabel.text
font.weight: Font.Medium
id: btnStyled
rightPadding: 32
leftPadding: 32
bottomPadding: 11
topPadding: 11
background: Rectangle {
color: "#ECEFFC"
radius: 8
}
Text {
id: txtBtnLabel
color: "#4360DF"
font.family: "Inter"
font.pointSize: 15
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
text: "Get started"
}
}

View File

@ -1 +1,2 @@
StyledButton 1.0 StyledButton.qml
RoundedIcon 1.0 RoundedIcon.qml

1
vendor/eventemitter vendored Submodule

@ -0,0 +1 @@
Subproject commit 49cfa2f3135139c3488b68fdd061cc069d31d651

1
vendor/isaac vendored Submodule

@ -0,0 +1 @@
Subproject commit 45a5cbbd54ff59ba3ed94242620c818b9aad1b5b

1
vendor/nimcrypto vendored Submodule

@ -0,0 +1 @@
Subproject commit 30d0ceaba02c0b966515f98873a0404786fbf796

1
vendor/uuids vendored Submodule

@ -0,0 +1 @@
Subproject commit c5039c1cc6a8a93fc2f3c03a206372eb4412e63b