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:
parent
ed683fd2f0
commit
dafd11fbc0
|
@ -10,3 +10,4 @@ vendor/.nimble
|
|||
*.AppImage
|
||||
tmp
|
||||
nimcache
|
||||
.DS_Store
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"]
|
|
@ -0,0 +1,10 @@
|
|||
import json
|
||||
|
||||
type
|
||||
GeneratedAccount* = object
|
||||
publicKey*: string
|
||||
address*: string
|
||||
id*: string
|
||||
keyUid*: string
|
||||
mnemonic*: string
|
||||
derived*: JsonNode
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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".}
|
||||
|
|
|
@ -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)
|
|
@ -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 = %* {
|
||||
|
|
|
@ -14,3 +14,8 @@ type SignalType* {.pure.} = enum
|
|||
SubscriptionsError = "subscriptions.error"
|
||||
WhisperFilterAdded = "whisper.filter.added"
|
||||
Unknown
|
||||
|
||||
type
|
||||
GoString* = object
|
||||
str*: cstring
|
||||
length*: cint
|
||||
|
|
|
@ -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(" ")
|
31
ui/main.qml
31
ui/main.qml
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
##^##*/
|
|
@ -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}
|
||||
}
|
||||
##^##*/
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
##^##*/
|
|
@ -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}
|
||||
}
|
||||
##^##*/
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
StyledButton 1.0 StyledButton.qml
|
||||
RoundedIcon 1.0 RoundedIcon.qml
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 49cfa2f3135139c3488b68fdd061cc069d31d651
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 45a5cbbd54ff59ba3ed94242620c818b9aad1b5b
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 30d0ceaba02c0b966515f98873a0404786fbf796
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c5039c1cc6a8a93fc2f3c03a206372eb4412e63b
|
Loading…
Reference in New Issue