feat: integrate nim-waku within the nim-status library
Previous usage of nim-waku code in the example client was implemented in the example client itself. Move that code and logic inside the library so that nim-waku is an integral part of nim-status. Closes #286. Closes #272. Closes #258. Closes #241.
This commit is contained in:
parent
18d4c35f85
commit
0850ab20ef
|
@ -1,9 +1,12 @@
|
|||
## client.nim is an example program demonstrating usage of nim-status,
|
||||
## nim-waku, nim-task-runner, and nim-ncurses
|
||||
## nim-task-runner, and nim-ncurses
|
||||
|
||||
when not(compileOption("threads")):
|
||||
{.fatal: "Please compile this program with the --threads:on option!".}
|
||||
|
||||
import # std libs
|
||||
std/random
|
||||
|
||||
import # client modules
|
||||
./client/tui
|
||||
|
||||
|
@ -22,6 +25,10 @@ proc main() {.async.} =
|
|||
notice "program exited"
|
||||
|
||||
when isMainModule:
|
||||
# initialize default random number generator, only needs to be called once:
|
||||
# https://nim-lang.org/docs/random.html#randomize
|
||||
randomize()
|
||||
|
||||
# client program will handle all control characters with ncurses in raw mode
|
||||
proc nop() {.noconv.} = discard
|
||||
setControlCHook(nop)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import # std libs
|
||||
std/sugar
|
||||
import # vendor libs
|
||||
eth/common as eth_common
|
||||
|
||||
import # client modules
|
||||
./client/tasks
|
||||
./client/[common, events, tasks]
|
||||
|
||||
import # vendor libs
|
||||
eth/common
|
||||
export # modules
|
||||
events
|
||||
|
||||
export tasks
|
||||
export # symbols
|
||||
Client, clientEvents
|
||||
|
||||
logScope:
|
||||
topics = "client"
|
||||
|
@ -18,19 +19,21 @@ logScope:
|
|||
|
||||
# `type Client` is defined in ./common to avoid circular dependency
|
||||
|
||||
const status = "status"
|
||||
|
||||
proc new*(T: type Client, clientConfig: ClientConfig): T =
|
||||
let statusArg = StatusArg(clientConfig: clientConfig)
|
||||
var taskRunner = TaskRunner.new()
|
||||
var
|
||||
statusArg = StatusArg(clientConfig: clientConfig)
|
||||
taskRunner = TaskRunner.new()
|
||||
|
||||
taskRunner.createWorker(thread, status, statusContext, statusArg)
|
||||
statusArg.chanSendToHost =
|
||||
taskRunner.workers[status].worker.chanRecvFromWorker
|
||||
|
||||
var topics: OrderedSet[string]
|
||||
let topicsStr = clientConfig.contentTopics.strip()
|
||||
if topicsStr != "":
|
||||
topics = topicsStr.split(" ").map(handleTopic).filter(t => t != "")
|
||||
.toOrderedSet()
|
||||
T(clientConfig: clientConfig, events: newEventChannel(), running: false,
|
||||
taskRunner: taskRunner)
|
||||
|
||||
T(clientConfig: clientConfig, events: newEventChannel(), loggedin: false,
|
||||
online: false, running: false, taskRunner: taskRunner, topics: topics)
|
||||
proc getTopics(self: Client): Future[seq[ContentTopic]] {.async, gcsafe.}
|
||||
|
||||
proc start*(self: Client) {.async.} =
|
||||
debug "client starting"
|
||||
|
@ -44,16 +47,29 @@ proc start*(self: Client) {.async.} =
|
|||
debug "client started"
|
||||
|
||||
asyncSpawn self.listen()
|
||||
self.topics = toOrderedSet(await self.getTopics())
|
||||
if self.topics.len > 0: self.currentTopic = self.topics.toSeq[^1]
|
||||
|
||||
proc stopContext(self: Client): Future[void] {.async, gcsafe.}
|
||||
|
||||
proc stop*(self: Client) {.async.} =
|
||||
debug "client stopping"
|
||||
|
||||
self.running = false
|
||||
await self.stopContext()
|
||||
await self.taskRunner.stop()
|
||||
self.events.close()
|
||||
|
||||
debug "client stopped"
|
||||
|
||||
# task invocation procs --------------------------------------------------------
|
||||
|
||||
proc addCustomToken*(self: Client, address: Address, name, symbol,
|
||||
color: string, decimals: uint) {.async.} =
|
||||
|
||||
asyncSpawn addCustomToken(self.taskRunner, status, address, name, symbol,
|
||||
color, decimals)
|
||||
|
||||
proc addWalletAccount*(self: Client, name, password: string) {.async.} =
|
||||
asyncSpawn addWalletAccount(self.taskRunner, status, name, password)
|
||||
|
||||
|
@ -72,8 +88,17 @@ proc addWalletSeed*(self: Client, name, mnemonic, password,
|
|||
proc addWalletWatchOnly*(self: Client, address, name: string) {.async.} =
|
||||
asyncSpawn addWalletWatchOnly(self.taskRunner, status, address, name)
|
||||
|
||||
proc connect*(self: Client, username: string) {.async.} =
|
||||
asyncSpawn startWakuChat(self.taskRunner, status, username)
|
||||
proc callRpc*(self: Client, rpcMethod: string, params: JsonNode) {.async.} =
|
||||
asyncSpawn callRpc(self.taskRunner, status, rpcMethod, params)
|
||||
|
||||
proc connect*(self: Client) {.async.} =
|
||||
asyncSpawn connect(self.taskRunner, status)
|
||||
|
||||
proc createAccount*(self: Client, password: string) {.async.} =
|
||||
asyncSpawn createAccount(self.taskRunner, status, password)
|
||||
|
||||
proc deleteCustomToken*(self: Client, index: int) {.async.} =
|
||||
asyncSpawn deleteCustomToken(self.taskRunner, status, index)
|
||||
|
||||
proc deleteWalletAccount*(self: Client, index: int,
|
||||
password: string) {.async.} =
|
||||
|
@ -81,10 +106,19 @@ proc deleteWalletAccount*(self: Client, index: int,
|
|||
asyncSpawn deleteWalletAccount(self.taskRunner, status, index, password)
|
||||
|
||||
proc disconnect*(self: Client) {.async.} =
|
||||
asyncSpawn stopWakuChat(self.taskRunner, status)
|
||||
asyncSpawn disconnect(self.taskRunner, status)
|
||||
|
||||
proc createAccount*(self: Client, password: string) {.async.} =
|
||||
asyncSpawn createAccount(self.taskRunner, status, password)
|
||||
proc getAssets*(self: Client, owner: Address) {.async.} =
|
||||
asyncSpawn getAssets(self.taskRunner, status, owner)
|
||||
|
||||
proc getCustomTokens*(self: Client) {.async.} =
|
||||
asyncSpawn getCustomTokens(self.taskRunner, status)
|
||||
|
||||
proc getTopics(self: Client): Future[seq[ContentTopic]] {.async, gcsafe.} =
|
||||
return await getTopics(self.taskRunner, status)
|
||||
|
||||
proc getPrice*(self: Client, tokenSymbol, fiatCurrency: string) {.async.} =
|
||||
asyncSpawn getPrice(self.taskRunner, status, tokenSymbol, fiatCurrency)
|
||||
|
||||
proc importMnemonic*(self: Client, mnemonic: string, passphrase: string,
|
||||
password: string) {.async.} =
|
||||
|
@ -92,10 +126,10 @@ proc importMnemonic*(self: Client, mnemonic: string, passphrase: string,
|
|||
asyncSpawn importMnemonic(self.taskRunner, status, mnemonic, passphrase,
|
||||
password)
|
||||
|
||||
proc joinTopic*(self: Client, topic: string) {.async.} =
|
||||
proc joinTopic*(self: Client, topic: ContentTopic) {.async.} =
|
||||
asyncSpawn joinTopic(self.taskRunner, status, topic)
|
||||
|
||||
proc leaveTopic*(self: Client, topic: string) {.async.} =
|
||||
proc leaveTopic*(self: Client, topic: ContentTopic) {.async.} =
|
||||
asyncSpawn leaveTopic(self.taskRunner, status, topic)
|
||||
|
||||
proc listAccounts*(self: Client) {.async.} =
|
||||
|
@ -110,29 +144,19 @@ proc login*(self: Client, account: int, password: string) {.async.} =
|
|||
proc logout*(self: Client) {.async.} =
|
||||
asyncSpawn logout(self.taskRunner, status)
|
||||
|
||||
proc sendMessage*(self: Client, message: string) {.async.} =
|
||||
asyncSpawn publishWakuChat(self.taskRunner, status, message)
|
||||
proc sendMessage*(self: Client, message: string, topic: ContentTopic)
|
||||
{.async.} =
|
||||
|
||||
proc getAssets*(self: Client, owner: Address) {.async.} =
|
||||
asyncSpawn getAssets(self.taskRunner, status, owner)
|
||||
asyncSpawn sendMessage(self.taskRunner, status, message, topic)
|
||||
|
||||
proc getCustomTokens*(self: Client) {.async.} =
|
||||
asyncSpawn getCustomTokens(self.taskRunner, status)
|
||||
proc sendTransaction*(self: Client, fromAddress: EthAddress,
|
||||
transaction: Transaction, password: string) {.async.} =
|
||||
|
||||
proc addCustomToken*(self: Client, address: Address, name, symbol, color: string, decimals: uint) {.async.} =
|
||||
asyncSpawn addCustomToken(self.taskRunner, status, address, name, symbol, color, decimals)
|
||||
|
||||
proc deleteCustomToken*(self: Client, index: int) {.async.} =
|
||||
asyncSpawn deleteCustomToken(self.taskRunner, status, index)
|
||||
|
||||
proc callRpc*(self: Client, rpcMethod: string, params: JsonNode) {.async.} =
|
||||
asyncSpawn callRpc(self.taskRunner, status, rpcMethod, params)
|
||||
|
||||
proc sendTransaction*(self: Client, fromAddress: EthAddress, transaction: Transaction, password: string) {.async.} =
|
||||
asyncSpawn sendTransaction(self.taskRunner, status, fromAddress, transaction, password)
|
||||
|
||||
proc getPrice*(self: Client, tokenSymbol, fiatCurrency: string) {.async.} =
|
||||
asyncSpawn getPrice(self.taskRunner, status, tokenSymbol, fiatCurrency)
|
||||
asyncSpawn sendTransaction(self.taskRunner, status, fromAddress, transaction,
|
||||
password)
|
||||
|
||||
proc setPriceTimeout*(self: Client, timeout: int) {.async.} =
|
||||
asyncSpawn setPriceTimeout(self.taskRunner, status, timeout)
|
||||
|
||||
proc stopContext(self: Client) {.async, gcsafe.} =
|
||||
await stopContext(self.taskRunner, status)
|
||||
|
|
|
@ -1,59 +1,16 @@
|
|||
import # std libs
|
||||
std/[sets, strformat, strutils, sugar, times]
|
||||
|
||||
import # vendor libs
|
||||
task_runner
|
||||
|
||||
import # status lib
|
||||
status/api/accounts
|
||||
|
||||
import # client modules
|
||||
../config
|
||||
../common
|
||||
|
||||
export accounts, config, sets, strformat, strutils, task_runner, times
|
||||
export common
|
||||
|
||||
logScope:
|
||||
topics = "client"
|
||||
|
||||
type
|
||||
Event* = ref object of RootObj
|
||||
|
||||
EventChannel* = AsyncChannel[ThreadSafeString]
|
||||
|
||||
# TODO: alphabetise Client above HelpText -- didn't want to interfere with
|
||||
# ongoing work
|
||||
Client* = ref object
|
||||
account*: PublicAccount
|
||||
clientConfig*: ClientConfig
|
||||
currentTopic*: ContentTopic
|
||||
events*: EventChannel
|
||||
loggedin*: bool
|
||||
online*: bool
|
||||
running*: bool
|
||||
taskRunner*: TaskRunner
|
||||
topics*: OrderedSet[string]
|
||||
|
||||
const
|
||||
hashCharSet* = {'#'}
|
||||
status* = "status"
|
||||
|
||||
proc handleTopic*(topic: string): string =
|
||||
var t = topic
|
||||
let topicSplit = topic.split('/')
|
||||
|
||||
# if event.topic is a properly formatted waku v2 content topic then the
|
||||
# whole string will be passed to joinTopic
|
||||
if topicSplit.len != 5 or topicSplit[0] != "":
|
||||
# otherwise convert it to a properly formatted content topic
|
||||
t = topic.strip(true, false, hashCharSet)
|
||||
# should end with `/rlp` for real encoding and decoding
|
||||
if t != "":
|
||||
# formatted topic should use hex encoded first four bytes of sha256
|
||||
# digest, but will need to e.g. return a tuple and come up with some
|
||||
# structure/s to keep track of hashed and human-friendly names
|
||||
# t = "0x" & ($sha256.digest(t))[0..7].toLowerAscii
|
||||
t = fmt"/waku/1/{t}/proto"
|
||||
|
||||
return t
|
||||
|
||||
proc newEventChannel*(): EventChannel =
|
||||
newAsyncChannel[ThreadSafeString](-1)
|
||||
topics*: OrderedSet[ContentTopic]
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
import # vendor libs
|
||||
web3/ethtypes
|
||||
|
||||
import # status lib
|
||||
status/api/[opensea, tokens, wallet]
|
||||
status/api/[accounts, opensea, tokens, wallet]
|
||||
|
||||
import # client modules
|
||||
./common
|
||||
|
||||
export common
|
||||
|
||||
logScope:
|
||||
topics = "client"
|
||||
|
||||
|
@ -17,79 +12,65 @@ type
|
|||
|
||||
AddCustomTokenEvent* = ref object of ClientEvent
|
||||
address*: string
|
||||
name*: string
|
||||
symbol*: string
|
||||
color*: string
|
||||
decimals*: uint
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
name*: string
|
||||
symbol*: string
|
||||
|
||||
AddWalletAccountEvent* = ref object of ClientEvent
|
||||
name*: string
|
||||
address*: Address
|
||||
name*: string
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
|
||||
CreateAccountEvent* = ref object of ClientEvent
|
||||
account*: PublicAccount
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
|
||||
DeleteCustomTokenEvent* = ref object of ClientEvent
|
||||
address*: string
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
|
||||
CallRpcEvent* = ref object of ClientEvent
|
||||
response*: string
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
response*: string
|
||||
|
||||
GetAssetsEvent* = ref object of ClientEvent
|
||||
assets*: seq[Asset]
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
|
||||
GetCustomTokensEvent* = ref object of ClientEvent
|
||||
tokens*: seq[Token]
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
tokens*: seq[Token]
|
||||
|
||||
DeleteWalletAccountEvent* = ref object of ClientEvent
|
||||
name*: string
|
||||
address*: string
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
name*: string
|
||||
|
||||
GetPriceEvent* = ref object of ClientEvent
|
||||
symbol*: string
|
||||
currency*: string
|
||||
price*: float
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
price*: float
|
||||
symbol*: string
|
||||
|
||||
ImportMnemonicEvent* = ref object of ClientEvent
|
||||
error*: string
|
||||
account*: PublicAccount
|
||||
timestamp*: int64
|
||||
error*: string
|
||||
|
||||
JoinTopicEvent* = ref object of ClientEvent
|
||||
timestamp*: int64
|
||||
topic*: string
|
||||
topic*: ContentTopic
|
||||
|
||||
LeaveTopicEvent* = ref object of ClientEvent
|
||||
timestamp*: int64
|
||||
topic*: string
|
||||
topic*: ContentTopic
|
||||
|
||||
ListAccountsEvent* = ref object of ClientEvent
|
||||
accounts*: seq[PublicAccount]
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
|
||||
ListWalletAccountsEvent* = ref object of ClientEvent
|
||||
accounts*: seq[WalletAccount]
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
|
||||
LoginEvent* = ref object of ClientEvent
|
||||
account*: PublicAccount
|
||||
|
@ -100,50 +81,56 @@ type
|
|||
error*: string
|
||||
loggedin*: bool
|
||||
|
||||
NetworkStatusEvent* = ref object of ClientEvent
|
||||
WakuConnectionEvent* = ref object of ClientEvent
|
||||
error*: string
|
||||
online*: bool
|
||||
|
||||
SendTransactionEvent* = ref object of ClientEvent
|
||||
response*: string
|
||||
SendMessageEvent* = ref object of ClientEvent
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
sent*: bool
|
||||
|
||||
SendTransactionEvent* = ref object of ClientEvent
|
||||
error*: string
|
||||
response*: string
|
||||
|
||||
SetPriceTimeoutEvent* = ref object of ClientEvent
|
||||
timeout*: int
|
||||
error*: string
|
||||
timestamp*: int64
|
||||
timeout*: int
|
||||
|
||||
UserMessageEvent* = ref object of ClientEvent
|
||||
message*: string
|
||||
timestamp*: int64
|
||||
topic*: string
|
||||
topic*: ContentTopic
|
||||
username*: string
|
||||
|
||||
const clientEvents* = [
|
||||
"AddCustomTokenEvent",
|
||||
"AddWalletAccountEvent",
|
||||
"CallRpcEvent",
|
||||
"CreateAccountEvent",
|
||||
"DeleteCustomTokenEvent",
|
||||
"DeleteWalletAccountEvent",
|
||||
"GetCustomTokensEvent",
|
||||
"GetAssetsEvent",
|
||||
"GetPriceEvent",
|
||||
"ImportMnemonicEvent",
|
||||
"JoinTopicEvent",
|
||||
"LeaveTopicEvent",
|
||||
"ListAccountsEvent",
|
||||
"ListWalletAccountsEvent",
|
||||
"LoginEvent",
|
||||
"LogoutEvent",
|
||||
"NetworkStatusEvent",
|
||||
"SendTransactionEvent",
|
||||
"SetPriceTimeoutEvent",
|
||||
"UserMessageEvent"
|
||||
]
|
||||
const
|
||||
clientEvents* = [
|
||||
"AddCustomTokenEvent",
|
||||
"AddWalletAccountEvent",
|
||||
"CallRpcEvent",
|
||||
"CreateAccountEvent",
|
||||
"DeleteCustomTokenEvent",
|
||||
"DeleteWalletAccountEvent",
|
||||
"GetCustomTokensEvent",
|
||||
"GetAssetsEvent",
|
||||
"GetPriceEvent",
|
||||
"ImportMnemonicEvent",
|
||||
"JoinTopicEvent",
|
||||
"LeaveTopicEvent",
|
||||
"ListAccountsEvent",
|
||||
"ListWalletAccountsEvent",
|
||||
"LoginEvent",
|
||||
"LogoutEvent",
|
||||
"SendMessageEvent",
|
||||
"SendTransactionEvent",
|
||||
"SetPriceTimeoutEvent",
|
||||
"UserMessageEvent",
|
||||
"WakuConnectionEvent"
|
||||
]
|
||||
|
||||
status = "status"
|
||||
|
||||
proc listenToStatus(self: Client) {.async.} =
|
||||
let worker = self.taskRunner.workers["status"].worker
|
||||
let worker = self.taskRunner.workers[status].worker
|
||||
while self.running and self.taskRunner.running.load():
|
||||
let event = await worker.chanRecvFromWorker.recv()
|
||||
asyncSpawn self.events.send(event)
|
||||
|
|
|
@ -5,4 +5,4 @@ proc writeValue*(writer: var JsonWriter, value: ChainId) =
|
|||
writeValue(writer, uint64 value)
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var ChainId) =
|
||||
value = ChainId reader.readValue(uint64)
|
||||
value = ChainId reader.readValue(uint64)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,74 +0,0 @@
|
|||
import # std libs
|
||||
std/[json, options, random, sequtils, strutils, tables, times, uri]
|
||||
|
||||
import # vendor libs
|
||||
bearssl, chronicles, chronos, chronos/apps/http/httpclient, eth/keys,
|
||||
libp2p/[crypto/crypto, crypto/secp, multiaddress, muxers/muxer, peerid,
|
||||
peerinfo, protobuf/minprotobuf, protocols/protocol, stream/connection,
|
||||
switch],
|
||||
nimcrypto/utils,
|
||||
stew/[byteutils, endians2, results],
|
||||
waku/common/utils/nat,
|
||||
waku/v2/node/[waku_payload, wakunode2],
|
||||
waku/v2/protocol/[waku_filter/waku_filter, waku_lightpush/waku_lightpush,
|
||||
waku_message, waku_store/waku_store],
|
||||
waku/v2/utils/peers
|
||||
|
||||
export
|
||||
byteutils, crypto, keys, minprotobuf, nat, peers, results, secp, utils,
|
||||
waku_filter, waku_lightpush, waku_message, waku_store, wakunode2
|
||||
|
||||
logScope:
|
||||
topics = "client"
|
||||
|
||||
type
|
||||
Chat2Message* = object
|
||||
nick*: string
|
||||
payload*: seq[byte]
|
||||
timestamp*: int64
|
||||
|
||||
PrivateKey* = crypto.PrivateKey
|
||||
|
||||
Topic* = wakunode2.Topic
|
||||
|
||||
const DefaultTopic* = "/waku/2/default-waku/proto"
|
||||
|
||||
# Initialize the default random number generator, only needs to be called once:
|
||||
# https://nim-lang.org/docs/random.html#randomize
|
||||
randomize()
|
||||
|
||||
proc encode*(message: Chat2Message): ProtoBuffer =
|
||||
result = initProtoBuffer()
|
||||
result.write(1, uint64(message.timestamp))
|
||||
result.write(2, message.nick)
|
||||
result.write(3, message.payload)
|
||||
|
||||
proc init*(T: type Chat2Message, buffer: seq[byte]): ProtoResult[T] =
|
||||
var msg = Chat2Message()
|
||||
let pb = initProtoBuffer(buffer)
|
||||
|
||||
var timestamp: uint64
|
||||
discard ? pb.getField(1, timestamp)
|
||||
msg.timestamp = int64(timestamp)
|
||||
|
||||
discard ? pb.getField(2, msg.nick)
|
||||
discard ? pb.getField(3, msg.payload)
|
||||
|
||||
ok(msg)
|
||||
|
||||
proc init*(T: type Chat2Message, nick: string, message: string): T =
|
||||
let
|
||||
payload = message.toBytes()
|
||||
timestamp = getTime().toUnix()
|
||||
|
||||
T(nick: nick, payload: payload, timestamp: timestamp)
|
||||
|
||||
proc selectRandomNode*(fleetStr: string): Future[string] {.async.} =
|
||||
let
|
||||
url = "https://fleets.status.im"
|
||||
response = await fetch(HttpSessionRef.new(), parseUri(url))
|
||||
fleet = string.fromBytes(response.data)
|
||||
nodes = toSeq(
|
||||
fleet.parseJson(){"fleets", "wakuv2." & fleetStr, "waku"}.pairs())
|
||||
|
||||
return nodes[rand(nodes.len - 1)].val.getStr()
|
|
@ -0,0 +1,28 @@
|
|||
import # std libs
|
||||
std/[sets, strformat, strutils]
|
||||
|
||||
# std libs
|
||||
from times import getTime, toUnix
|
||||
|
||||
import # vendor libs
|
||||
chronicles, chronos, task_runner
|
||||
|
||||
import # status lib
|
||||
status/api/common
|
||||
|
||||
import # client modules
|
||||
./config
|
||||
|
||||
export # modules
|
||||
chronicles, chronos, common, config, sets, strformat, strutils, task_runner
|
||||
|
||||
export # symbols
|
||||
getTime, toUnix
|
||||
|
||||
type
|
||||
Event* = ref object of RootObj
|
||||
timestamp*: int64
|
||||
|
||||
EventChannel* = AsyncChannel[ThreadSafeString]
|
||||
|
||||
proc newEventChannel*(): EventChannel = newAsyncChannel[ThreadSafeString](-1)
|
|
@ -4,10 +4,14 @@ import # std libs
|
|||
import # vendor libs
|
||||
chronicles, confutils, confutils/std/net
|
||||
|
||||
import # client modules
|
||||
./client/waku_chat2
|
||||
import # status lib
|
||||
status/api/waku
|
||||
|
||||
export confutils, net.ValidIpAddress, net.init
|
||||
export # modules
|
||||
confutils
|
||||
|
||||
export # symbols
|
||||
net.ValidIpAddress, net.init, waku.WakuFleet
|
||||
|
||||
logScope:
|
||||
topics = "client config"
|
||||
|
@ -19,8 +23,6 @@ type
|
|||
|
||||
VIP* = distinct string
|
||||
|
||||
WakuFleet* = enum none, prod, test
|
||||
|
||||
const
|
||||
ERROR = LogLevel.ERROR
|
||||
NONE = LogLevel.NONE
|
||||
|
@ -181,7 +183,7 @@ macro config(): untyped =
|
|||
|
||||
topics* {.
|
||||
defaultValue: "/waku/2/default-waku/proto"
|
||||
desc: "Default topics to subscribe to (space separated list)"
|
||||
desc: "PubSub topics to subscribe to (space separated list)"
|
||||
name: "waku-topics"
|
||||
.}: string
|
||||
|
||||
|
@ -302,17 +304,12 @@ macro config(): untyped =
|
|||
name: "waku-fleet"
|
||||
.}: WakuFleet
|
||||
|
||||
# in the help text for --waku-content-topics the formatting-indicator
|
||||
# should end with /rlp for real decoding; and when topic sha256 hashing
|
||||
# is implemented for /waku/1 chats should change {topic} to
|
||||
# {topic-digest}
|
||||
|
||||
contentTopics* {.
|
||||
defaultValue: "#test"
|
||||
desc: "Default content topics for chat messages " &
|
||||
"(space separated list). Topic names that do not conform to " &
|
||||
"23/WAKU2-TOPICS will formatted as /waku/1/{topic}/proto " &
|
||||
"with leading \"#\" removed",
|
||||
desc: "Content topics to subscribe to for chat messages (space " &
|
||||
"separated list). Topic names that do not conform to " &
|
||||
"23/WAKU2-TOPICS will be formatted as " &
|
||||
"/waku/1/{topic-digest}/rfc26 with leading \"#\" removed",
|
||||
name: "waku-content-topics"
|
||||
.}: string
|
||||
|
||||
|
@ -347,10 +344,9 @@ proc completeCmdArg*(T: type LLevel, val: TaintedString): seq[string] =
|
|||
|
||||
proc parseCmdArg*(T: type PK, p: TaintedString): T =
|
||||
try:
|
||||
discard waku_chat2.crypto.PrivateKey(scheme: Secp256k1,
|
||||
skkey: SkPrivateKey.init(waku_chat2.utils.fromHex(p)).tryGet())
|
||||
result = PK(p)
|
||||
except CatchableError:
|
||||
discard Nodekey.fromHex(p).tryGet()
|
||||
return PK(p)
|
||||
except CatchableError as e:
|
||||
raise newException(ConfigurationError, "Invalid private key")
|
||||
|
||||
proc completeCmdArg*(T: type PK, val: TaintedString): seq[string] =
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import # client modules
|
||||
./tui/events
|
||||
./tui/[common, events, screen, tasks]
|
||||
|
||||
export events
|
||||
export common
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
@ -11,11 +11,11 @@ logScope:
|
|||
# This module's purpose is to start the client and initiate listening for
|
||||
# events coming from the client and user
|
||||
|
||||
const input = "input"
|
||||
|
||||
# `type Tui` and `proc stop(self: Tui)` are defined in ./common to avoid
|
||||
# circular dependency
|
||||
|
||||
const input = "input"
|
||||
|
||||
proc new*(T: type Tui, clientConfig: ClientConfig): T =
|
||||
let
|
||||
(locale, mainWin, mouse) = initScreen()
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
import # std libs
|
||||
std/[strformat, strutils, tables]
|
||||
std/tables
|
||||
|
||||
import # vendor libs
|
||||
web3/conversions
|
||||
|
||||
import # status lib
|
||||
status/api/opensea
|
||||
|
||||
import # client modules
|
||||
./parser
|
||||
|
||||
export parser
|
||||
./commands, ./common, ./macros, ./parser, ./screen
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
@ -343,28 +338,66 @@ proc action*(self: Tui, event: ImportMnemonicEvent) {.async, gcsafe, nimcall.} =
|
|||
proc action*(self: Tui, event: JoinTopicEvent) {.async, gcsafe, nimcall.} =
|
||||
let
|
||||
timestamp = event.timestamp
|
||||
topic = event.topic
|
||||
topic = if event.topic.shortName != "": event.topic.shortName
|
||||
else: $event.topic
|
||||
|
||||
if not self.client.topics.contains(topic):
|
||||
self.client.topics.incl(topic)
|
||||
self.printResult(fmt"Joined topic: {topic}", timestamp)
|
||||
if not self.client.topics.contains(event.topic):
|
||||
self.client.topics.incl(event.topic)
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Joined topic: {topic}", timestamp)
|
||||
else:
|
||||
self.printResult(fmt"Topic already joined: {topic}", timestamp)
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Topic already joined: {topic}", timestamp)
|
||||
|
||||
if self.client.currentTopic != event.topic:
|
||||
self.client.currentTopic = event.topic
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Switched current topic: {topic}", timestamp)
|
||||
|
||||
# LeaveTopicEvent -------------------------------------------------------------
|
||||
|
||||
proc action*(self: Tui, event: LeaveTopicEvent) {.async, gcsafe,
|
||||
nimcall.} =
|
||||
let
|
||||
timestamp = event.timestamp
|
||||
topic = event.topic
|
||||
let timestamp = event.timestamp
|
||||
var topic = if event.topic.shortName != "": event.topic.shortName
|
||||
else: $event.topic
|
||||
|
||||
if self.client.topics.contains(topic):
|
||||
self.client.topics.excl(topic)
|
||||
self.printResult(fmt"Left topic: {topic}", timestamp)
|
||||
if self.client.topics.contains(event.topic):
|
||||
self.client.topics.excl(event.topic)
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Left topic: {topic}", timestamp)
|
||||
else:
|
||||
self.printResult(fmt"Topic not joined, no need to leave: {topic}",
|
||||
timestamp)
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Topic not joined, no need to leave: {topic}",
|
||||
timestamp)
|
||||
|
||||
if self.client.topics.len > 0:
|
||||
if self.client.currentTopic == event.topic or
|
||||
self.client.currentTopic == noTopic:
|
||||
|
||||
let currentTopic = self.client.topics.toSeq[^1]
|
||||
|
||||
topic = if currentTopic.shortName != "": currentTopic.shortName
|
||||
else: $currentTopic
|
||||
|
||||
self.client.currentTopic = currentTopic
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Switched current topic: {topic}", timestamp)
|
||||
|
||||
else:
|
||||
let currentTopic = self.client.currentTopic
|
||||
|
||||
topic = if currentTopic.shortName != "": currentTopic.shortName
|
||||
else: $currentTopic
|
||||
|
||||
if self.outputReady:
|
||||
self.printResult(fmt"Current topic: {topic}", timestamp)
|
||||
|
||||
else:
|
||||
self.client.currentTopic = noTopic
|
||||
if self.outputReady:
|
||||
self.printResult("No current topic set because no topics are joined",
|
||||
timestamp)
|
||||
|
||||
# ListAccountsEvent -----------------------------------------------------------
|
||||
|
||||
|
@ -391,6 +424,7 @@ proc action*(self: Tui, event: ListAccountsEvent) {.async, gcsafe,
|
|||
self.printResult(fmt"{2.indent()}{i}. {name} ({abbrev})",
|
||||
timestamp)
|
||||
i = i + 1
|
||||
|
||||
else:
|
||||
self.printResult(
|
||||
"No accounts. Create an account using `/create <password>`.",
|
||||
|
@ -432,46 +466,58 @@ proc action*(self: Tui, event: ListWalletAccountsEvent) {.async, gcsafe,
|
|||
# LoginEvent ------------------------------------------------------------------
|
||||
|
||||
proc action*(self: Tui, event: LoginEvent) {.async, gcsafe, nimcall.} =
|
||||
let
|
||||
error = event.error
|
||||
loggedin = event.loggedin
|
||||
# if TUI is not ready for output then ignore it
|
||||
if self.outputReady:
|
||||
let
|
||||
error = event.error
|
||||
timestamp = event.timestamp
|
||||
|
||||
if error != "":
|
||||
self.wprintFormatError(getTime().toUnix(), fmt"{error}")
|
||||
else:
|
||||
self.client.account = event.account
|
||||
self.printResult("Login successful.", getTime().toUnix())
|
||||
if not self.client.online:
|
||||
asyncSpawn self.client.connect(self.client.account.name)
|
||||
|
||||
self.client.loggedin = loggedin
|
||||
trace "TUI updated client state", loggedin
|
||||
if error != "":
|
||||
self.wprintFormatError(timestamp, fmt"{error}")
|
||||
else:
|
||||
self.printResult("Login successful.", timestamp)
|
||||
|
||||
# LogoutEvent -----------------------------------------------------------------
|
||||
|
||||
proc action*(self: Tui, event: LogoutEvent) {.async, gcsafe, nimcall.} =
|
||||
let
|
||||
error = event.error
|
||||
loggedin = event.loggedin
|
||||
# if TUI is not ready for output then ignore it
|
||||
if self.outputReady:
|
||||
let
|
||||
error = event.error
|
||||
timestamp = event.timestamp
|
||||
|
||||
if error != "":
|
||||
self.wprintFormatError(getTime().toUnix(), fmt"{error}")
|
||||
else:
|
||||
self.client.account = PublicAccount()
|
||||
self.printResult("Logout successful.", getTime().toUnix())
|
||||
if self.client.online:
|
||||
asyncSpawn self.client.disconnect()
|
||||
if error != "":
|
||||
self.wprintFormatError(timestamp, fmt"{error}")
|
||||
else:
|
||||
self.printResult("Logout successful.", timestamp)
|
||||
|
||||
self.client.loggedin = loggedin
|
||||
trace "TUI updated client state", loggedin
|
||||
# WakuConnectionEvent ----------------------------------------------------------
|
||||
|
||||
# NetworkStatusEvent -----------------------------------------------------------
|
||||
proc action*(self: Tui, event: WakuConnectionEvent) {.async, gcsafe, nimcall.} =
|
||||
# if TUI is not ready for output then ignore it
|
||||
if self.outputReady:
|
||||
let
|
||||
error = event.error
|
||||
online = event.online
|
||||
timestamp = event.timestamp
|
||||
|
||||
proc action*(self: Tui, event: NetworkStatusEvent) {.async, gcsafe, nimcall.} =
|
||||
let online = event.online
|
||||
if error != "":
|
||||
self.wprintFormatError(timestamp, fmt"{error}")
|
||||
elif online:
|
||||
self.printResult("Connected to network.", timestamp)
|
||||
else:
|
||||
self.printResult("Disconnected from network.", timestamp)
|
||||
|
||||
self.client.online = online
|
||||
trace "TUI updated client state", online
|
||||
# SendMessageEvent -------------------------------------------------------------
|
||||
|
||||
proc action*(self: Tui, event: SendMessageEvent) {.async, gcsafe, nimcall.} =
|
||||
# if TUI is not ready for output then ignore it
|
||||
if self.outputReady:
|
||||
let
|
||||
error = event.error
|
||||
timestamp = event.timestamp
|
||||
|
||||
if error != "": self.wprintFormatError(timestamp, fmt"{error}")
|
||||
|
||||
# UserMessageEvent -------------------------------------------------------------
|
||||
|
||||
|
@ -481,25 +527,23 @@ proc action*(self: Tui, event: UserMessageEvent) {.async, gcsafe, nimcall.} =
|
|||
let
|
||||
message = event.message
|
||||
timestamp = event.timestamp
|
||||
topic =
|
||||
if event.topic.shortName != "":
|
||||
event.topic.shortName
|
||||
else:
|
||||
if isChat2(event.topic):
|
||||
"chat2: #" & event.topic.topicName
|
||||
else:
|
||||
$event.topic
|
||||
|
||||
username = event.username
|
||||
|
||||
var topic = event.topic
|
||||
let topicSplit = topic.split('/')
|
||||
|
||||
# if event.topic is not a properly formatted waku v2 content topic then the
|
||||
# whole string will be passed to printMessage
|
||||
if topicSplit.len == 5 and topicSplit[0] == "":
|
||||
# for "/toy-chat/2/example/proto", topic would be "example"
|
||||
topic = topicSplit[3]
|
||||
|
||||
debug "TUI received user message", message, timestamp, username
|
||||
self.printMessage(message, timestamp, username, topic)
|
||||
|
||||
# CallRpcEvent -----------------------------------------------------------------
|
||||
|
||||
proc action*(self: Tui, event: CallRpcEvent) {.async, gcsafe,
|
||||
nimcall.} =
|
||||
|
||||
proc action*(self: Tui, event: CallRpcEvent) {.async, gcsafe, nimcall.} =
|
||||
# if TUI is not ready for output then ignore it
|
||||
if self.outputReady:
|
||||
if event.error != "":
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import # std libs
|
||||
std/[json, sequtils, strformat, strutils, sugar]
|
||||
|
||||
import # client modules
|
||||
./common, ./macros, ./screen, ./tasks
|
||||
std/sugar
|
||||
|
||||
import # vendor libs
|
||||
eth/common as eth_common, stew/byteutils
|
||||
|
||||
|
||||
export common, screen, strutils, tasks
|
||||
import # client modules
|
||||
./common, ./macros, ./screen
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
@ -61,7 +58,8 @@ proc new*(T: type AddCustomToken, args: varargs[string]): T =
|
|||
color = args[3]
|
||||
decimals = args[4]
|
||||
|
||||
T(address: address, name: name, symbol: symbol, color: color, decimals: decimals)
|
||||
T(address: address, name: name, symbol: symbol, color: color,
|
||||
decimals: decimals)
|
||||
|
||||
proc split*(T: type AddCustomToken, argsRaw: string): seq[string] =
|
||||
argsRaw.split(" ")
|
||||
|
@ -309,11 +307,7 @@ proc split*(T: type Connect, argsRaw: string): seq[string] =
|
|||
@[]
|
||||
|
||||
proc command*(self: Tui, command: Connect) {.async, gcsafe, nimcall.} =
|
||||
if self.client.loggedin:
|
||||
asyncSpawn self.client.connect(self.client.account.name)
|
||||
else:
|
||||
self.wprintFormatError(getTime().toUnix,
|
||||
"client is not logged in, cannot connect.")
|
||||
asyncSpawn self.client.connect()
|
||||
|
||||
# CreateAccount ----------------------------------------------------------------
|
||||
|
||||
|
@ -436,10 +430,7 @@ proc split*(T: type Disconnect, argsRaw: string): seq[string] =
|
|||
@[]
|
||||
|
||||
proc command*(self: Tui, command: Disconnect) {.async, gcsafe, nimcall.} =
|
||||
if self.client.online:
|
||||
asyncSpawn self.client.disconnect()
|
||||
else:
|
||||
self.wprintFormatError(getTime().toUnix, "client is not online.")
|
||||
asyncSpawn self.client.disconnect()
|
||||
|
||||
# GetAssets -----------------------------------------------------------------
|
||||
|
||||
|
@ -620,15 +611,21 @@ proc split*(T: type JoinTopic, argsRaw: string): seq[string] =
|
|||
@[argsRaw.strip().split(" ")[0]]
|
||||
|
||||
proc command*(self: Tui, command: JoinTopic) {.async, gcsafe, nimcall.} =
|
||||
var topic = handleTopic(command.topic)
|
||||
let timestamp = getTime().toUnix
|
||||
|
||||
var topic = command.topic
|
||||
|
||||
if topic == "":
|
||||
self.wprintFormatError(getTime().toUnix,
|
||||
self.wprintFormatError(timestamp,
|
||||
"topic cannot be blank, please provide a topic as the first argument.")
|
||||
elif self.client.topics.contains(topic):
|
||||
self.printResult(fmt"Topic already joined: {topic}", getTime().toUnix)
|
||||
|
||||
else:
|
||||
asyncSpawn self.client.joinTopic(topic)
|
||||
let topicResult = ContentTopic.init(topic)
|
||||
|
||||
if topicResult.isErr:
|
||||
self.wprintFormatError(timestamp, $topicResult.error)
|
||||
else:
|
||||
asyncSpawn self.client.joinTopic(topicResult.get)
|
||||
|
||||
# LeaveTopic -------------------------------------------------------------------
|
||||
|
||||
|
@ -646,16 +643,21 @@ proc split*(T: type LeaveTopic, argsRaw: string): seq[string] =
|
|||
@[argsRaw.strip().split(" ")[0]]
|
||||
|
||||
proc command*(self: Tui, command: LeaveTopic) {.async, gcsafe, nimcall.} =
|
||||
let topic = handleTopic(command.topic)
|
||||
let timestamp = getTime().toUnix
|
||||
|
||||
var topic = command.topic
|
||||
|
||||
if topic == "":
|
||||
self.wprintFormatError(getTime().toUnix,
|
||||
self.wprintFormatError(timestamp,
|
||||
"topic cannot be blank, please provide a topic as the first argument.")
|
||||
elif not self.client.topics.contains(topic):
|
||||
self.printResult(fmt"Topic not joined, no need to leave: {topic}",
|
||||
getTime().toUnix)
|
||||
|
||||
else:
|
||||
asyncSpawn self.client.leaveTopic(topic)
|
||||
let topicResult = ContentTopic.init(topic)
|
||||
|
||||
if topicResult.isErr:
|
||||
self.wprintFormatError(timestamp, $topicResult.error)
|
||||
else:
|
||||
asyncSpawn self.client.leaveTopic(topicResult.get)
|
||||
|
||||
# ListAccounts -----------------------------------------------------------------
|
||||
|
||||
|
@ -673,7 +675,7 @@ proc split*(T: type ListAccounts, argsRaw: string): seq[string] =
|
|||
proc command*(self: Tui, command: ListAccounts) {.async, gcsafe, nimcall.} =
|
||||
asyncSpawn self.client.listAccounts()
|
||||
|
||||
# ListTopics -----------------------------------------------------------------
|
||||
# ListTopics -------------------------------------------------------------------
|
||||
|
||||
proc help*(T: type ListTopics): HelpText =
|
||||
let command = "listtopics"
|
||||
|
@ -694,15 +696,34 @@ proc command*(self: Tui, command: ListTopics) {.async, gcsafe, nimcall.} =
|
|||
if topics.len > 0:
|
||||
var i = 1
|
||||
self.printResult("Joined topics:", timestamp)
|
||||
for topic in topics:
|
||||
self.printResult(fmt"{2.indent()}{i}. {topic}", timestamp)
|
||||
for topic in topics.items:
|
||||
let t =
|
||||
if topic.shortName != "":
|
||||
fmt("{topic.shortName} ({$topic})")
|
||||
else:
|
||||
$topic
|
||||
|
||||
self.printResult(fmt"{2.indent()}{i}. {t}", timestamp)
|
||||
i = i + 1
|
||||
|
||||
let currentTopic = self.client.currentTopic
|
||||
if currentTopic != noTopic:
|
||||
let topic = if currentTopic.shortName != "": currentTopic.shortName
|
||||
else: $currentTopic
|
||||
|
||||
self.printResult(fmt"Current topic: {topic}", timestamp)
|
||||
|
||||
else:
|
||||
# there shouldn't be a situation where:
|
||||
# `topics.len > 0 and currentTopic == noTopic`
|
||||
# but hand-written state machines are tricky, so just in case...
|
||||
self.printResult("No current topic set", timestamp)
|
||||
|
||||
else:
|
||||
self.printResult("No topics joined. Join a topic using `/join <topic>`.",
|
||||
timestamp)
|
||||
|
||||
# ListWalletAccounts -----------------------------------------------------------------
|
||||
# ListWalletAccounts -----------------------------------------------------------
|
||||
|
||||
proc help*(T: type ListWalletAccounts): HelpText =
|
||||
let command = "listwalletaccounts"
|
||||
|
@ -789,7 +810,7 @@ proc split*(T: type Quit, argsRaw: string): seq[string] =
|
|||
@[]
|
||||
|
||||
proc command*(self: Tui, command: Quit) {.async, gcsafe, nimcall.} =
|
||||
await self.stop()
|
||||
waitFor self.stop()
|
||||
|
||||
# SendMessage ------------------------------------------------------------------
|
||||
|
||||
|
@ -810,11 +831,14 @@ proc split*(T: type SendMessage, argsRaw: string): seq[string] =
|
|||
@[argsRaw]
|
||||
|
||||
proc command*(self: Tui, command: SendMessage) {.async, gcsafe, nimcall.} =
|
||||
if not self.client.online:
|
||||
self.wprintFormatError(getTime().toUnix,
|
||||
"client is not online, cannot send message.")
|
||||
let timestamp = getTime().toUnix
|
||||
|
||||
if self.client.currentTopic == noTopic:
|
||||
self.wprintFormatError(timestamp,
|
||||
"current topic is not set, cannot send message.")
|
||||
else:
|
||||
asyncSpawn self.client.sendMessage(command.message)
|
||||
asyncSpawn self.client.sendMessage(command.message,
|
||||
self.client.currentTopic)
|
||||
|
||||
# CallRpc ----------------------------------------------------------------------
|
||||
|
||||
|
@ -980,6 +1004,46 @@ proc command*(self: Tui, cmd: SendTransaction) {.async, gcsafe,
|
|||
except:
|
||||
self.wprintFormatError(getTime().toUnix, "invalid arguments.")
|
||||
|
||||
# Switchtopic ------------------------------------------------------------------
|
||||
|
||||
proc help*(T: type SwitchTopic): HelpText =
|
||||
let command = "switchtopic"
|
||||
HelpText(command: command, aliases: aliased.getOrDefault(command), parameters: @[
|
||||
CommandParameter(name: "topic",
|
||||
description: "Name of the topic to make the current topic.")
|
||||
], description: "Sets the current topic to which messages will be sent.")
|
||||
|
||||
proc new*(T: type Switchtopic, args: varargs[string]): T =
|
||||
T(topic: args[0])
|
||||
|
||||
proc split*(T: type Switchtopic, argsRaw: string): seq[string] =
|
||||
@[argsRaw.strip().split(" ")[0]]
|
||||
|
||||
proc command*(self: Tui, command: Switchtopic) {.async, gcsafe, nimcall.} =
|
||||
let timestamp = getTime().toUnix
|
||||
var topic = command.topic
|
||||
|
||||
if topic == "":
|
||||
self.wprintFormatError(timestamp,
|
||||
"topic cannot be blank, please provide a topic as the first argument.")
|
||||
|
||||
else:
|
||||
let topicResult = ContentTopic.init(topic)
|
||||
|
||||
if topicResult.isErr:
|
||||
self.wprintFormatError(timestamp, $topicResult.error)
|
||||
else:
|
||||
let contentTopic = topicResult.get
|
||||
topic = if contentTopic.shortName != "": contentTopic.shortName
|
||||
else: $contentTopic
|
||||
|
||||
if self.client.topics.contains(contentTopic):
|
||||
self.client.currentTopic = contentTopic
|
||||
self.printResult(fmt"Switched current topic: {topic}", timestamp)
|
||||
else:
|
||||
self.wprintFormatError(timestamp,
|
||||
fmt"Cannot set current topic to an unjoined topic: {topic}")
|
||||
|
||||
# Help -------------------------------------------------------------------------
|
||||
# Note: although "Help" is not alphabetically last, we need to keep this below
|
||||
# all other `help()` definitions so that they are available to the
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import # client modules
|
||||
../client, ./ncurses_helpers
|
||||
../client, ../common,
|
||||
./ncurses_helpers
|
||||
|
||||
import # vendor libs
|
||||
eth/common
|
||||
|
||||
export client, ncurses_helpers
|
||||
export client, common, ncurses_helpers
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
@ -46,10 +44,10 @@ type
|
|||
|
||||
AddCustomToken* = ref object of Command
|
||||
address*: string
|
||||
name*: string
|
||||
symbol*: string
|
||||
color*: string
|
||||
decimals*: string
|
||||
name*: string
|
||||
symbol*: string
|
||||
|
||||
AddWalletAccount* = ref object of Command
|
||||
name*: string
|
||||
|
@ -62,23 +60,23 @@ type
|
|||
|
||||
AddWalletSeed* = ref object of Command
|
||||
bip39Passphrase*: string
|
||||
name*: string
|
||||
mnemonic*: string
|
||||
name*: string
|
||||
password*: string
|
||||
|
||||
AddWalletWatchOnly* = ref object of Command
|
||||
name*: string
|
||||
address*: string
|
||||
name*: string
|
||||
|
||||
CallRpc* = ref object of Command
|
||||
rpcMethod*: string
|
||||
params*: string
|
||||
rpcMethod*: string
|
||||
|
||||
Connect* = ref object of Command
|
||||
|
||||
CommandParameter* = ref object of RootObj
|
||||
name*: string
|
||||
description*: string
|
||||
name*: string
|
||||
|
||||
CreateAccount* = ref object of Command
|
||||
password*: string
|
||||
|
@ -98,17 +96,17 @@ type
|
|||
GetCustomTokens* = ref object of Command
|
||||
|
||||
GetPrice* = ref object of Command
|
||||
tokenSymbol*: string
|
||||
fiatCurrency*: string
|
||||
tokenSymbol*: string
|
||||
|
||||
Help* = ref object of Command
|
||||
command*: string
|
||||
|
||||
HelpText* = ref object of RootObj
|
||||
command*: string
|
||||
parameters*: seq[CommandParameter]
|
||||
aliases*: seq[string]
|
||||
command*: string
|
||||
description*: string
|
||||
parameters*: seq[CommandParameter]
|
||||
|
||||
ImportMnemonic* = ref object of Command
|
||||
mnemonic*: string
|
||||
|
@ -140,19 +138,25 @@ type
|
|||
|
||||
SendTransaction* = ref object of Command
|
||||
fromAddress*: string
|
||||
toAddress*: string
|
||||
value*: string
|
||||
maxPriorityFee*: string
|
||||
maxFee*: string
|
||||
gasLimit*: string
|
||||
payload*: string
|
||||
maxFee*: string
|
||||
maxPriorityFee*: string
|
||||
nonce*: string
|
||||
password*: string
|
||||
|
||||
payload*: string
|
||||
toAddress*: string
|
||||
value*: string
|
||||
|
||||
SetPriceTimeout* = ref object of Command
|
||||
timeout*: string
|
||||
|
||||
SwitchTopic* = ref object of Command
|
||||
topic*: string
|
||||
|
||||
const
|
||||
ESCAPE* = "ESCAPE"
|
||||
RETURN* = "RETURN"
|
||||
|
||||
TuiEvents* = [
|
||||
"InputKey",
|
||||
"InputReady",
|
||||
|
@ -186,9 +190,11 @@ const
|
|||
"listtopics": "ListTopics",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"quit": "Quit",
|
||||
"sendtransaction": "SendTransaction",
|
||||
"setpricetimeout": "SetPriceTimeout",
|
||||
"quit": "Quit"
|
||||
"quit": "Quit",
|
||||
"switchtopic": "SwitchTopic"
|
||||
}.toTable
|
||||
|
||||
aliases* = {
|
||||
|
@ -205,7 +211,11 @@ const
|
|||
"gettokens": "getcustomtokens",
|
||||
"import": "importmnemonic",
|
||||
"join": "jointopic",
|
||||
"joinpublic": "jointopic",
|
||||
"joinpublicchat": "jointopic",
|
||||
"leave": "leavetopic",
|
||||
"leavepublic": "leavetopic",
|
||||
"leavepublicchat": "leavetopic",
|
||||
"list": "listaccounts",
|
||||
"listwallets": "listwalletaccounts",
|
||||
"part": "leavetopic",
|
||||
|
@ -213,6 +223,7 @@ const
|
|||
"send": DEFAULT_COMMAND,
|
||||
"sub": "jointopic",
|
||||
"subscribe": "jointopic",
|
||||
"switch": "switchtopic",
|
||||
"topics": "listtopics",
|
||||
"trx": "sendtransaction",
|
||||
"unjoin": "leavetopic",
|
||||
|
@ -236,12 +247,14 @@ const
|
|||
"getcustomtokens": @["gettokens"],
|
||||
"importmnemonic": @["import"],
|
||||
"help": @["?"],
|
||||
"jointopic": @["join", "sub", "subscribe"],
|
||||
"leavetopic": @["leave", "part", "unjoin", "unsub", "unsubscribe"],
|
||||
"jointopic": @["join", "joinpublic", "joinpublicchat", "sub", "subscribe"],
|
||||
"leavetopic": @["leave", "leavepublic", "leavepublicchat", "part", "unjoin",
|
||||
"unsub", "unsubscribe"],
|
||||
"listaccounts": @["list"],
|
||||
"listtopics": @["topics"],
|
||||
"listwalletaccounts": @["listwallets", "wallets"],
|
||||
"sendtransaction": @["trx"]
|
||||
"sendtransaction": @["trx"],
|
||||
"switchtopic": @["switch"]
|
||||
}.toTable
|
||||
|
||||
proc stop*(self: Tui) {.async.} =
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import # client modules
|
||||
./actions
|
||||
|
||||
export actions
|
||||
./actions, ./common, ./macros
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
|
|
@ -4,8 +4,6 @@ import # std libs
|
|||
import # client modules
|
||||
./common
|
||||
|
||||
export common
|
||||
|
||||
# Events -----------------------------------------------------------------------
|
||||
|
||||
macro `&`[T; A, B: static int](a: array[A, T], b: array[B, T]): untyped =
|
||||
|
|
|
@ -2,13 +2,10 @@ import # std libs
|
|||
std/bitops
|
||||
|
||||
import # vendor libs
|
||||
chronicles, ncurses
|
||||
ncurses
|
||||
|
||||
export ncurses
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
||||
# NOTE: depending on OS, terminal emulator, font, and related software, there
|
||||
# can be problems re: how ncurses displays some emojis and other characters,
|
||||
# e.g. those that make use of ZWJ or ZWNJ (more generally "extended grapheme
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import # client modules
|
||||
./commands as cmd, ./macros
|
||||
|
||||
export cmd, macros
|
||||
./commands as cmd, ./common, ./macros
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import # std libs
|
||||
std/[strformat, strutils]
|
||||
# std libs
|
||||
from times import fromUnix, inZone, local
|
||||
|
||||
import # client modules
|
||||
./common
|
||||
|
||||
export common
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
||||
|
|
|
@ -4,15 +4,9 @@ import # vendor libs
|
|||
import # client modules
|
||||
./common
|
||||
|
||||
export common
|
||||
|
||||
logScope:
|
||||
topics = "tui"
|
||||
|
||||
const
|
||||
ESCAPE* = "ESCAPE"
|
||||
RETURN* = "RETURN"
|
||||
|
||||
type ByteArray = array[0..3, byte]
|
||||
|
||||
proc readInput*() {.task(kind=no_rts, stoppable=false).} =
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import # status modules
|
||||
./api/[accounts, auth, common, opensea, provider, settings, tokens, wallet]
|
||||
./api/[accounts, auth, common, opensea, provider, settings, tokens, waku,
|
||||
wallet]
|
||||
|
||||
export accounts, auth, common, opensea, provider, settings, tokens, wallet
|
||||
export accounts, auth, common, opensea, provider, settings, tokens, waku, wallet
|
||||
|
|
|
@ -13,11 +13,11 @@ import # status modules
|
|||
../private/extkeys/[paths, types],
|
||||
./common
|
||||
|
||||
export
|
||||
common, public_accounts
|
||||
# TODO: are these exports needed?
|
||||
# accounts, alias, conversions, generator, identicon, paths,
|
||||
# public_accounts, secp256k1, settings, types, uuid
|
||||
export common except setLoginState, setNetworkState
|
||||
export public_accounts
|
||||
# TODO: are these exports needed?
|
||||
# accounts, alias, conversions, generator, identicon, paths,
|
||||
# public_accounts, secp256k1, settings, types, uuid
|
||||
|
||||
type
|
||||
AccountsError* = enum
|
||||
|
@ -113,10 +113,11 @@ proc storeDerivedAccounts(self: StatusObject, id: UUID, keyUid: string,
|
|||
# First, record if we are currently logged in, and then init the user db
|
||||
# if not. After we know the db has been inited, create the needed accounts.
|
||||
# Once finished, close the db if we were originally logged out.
|
||||
let wasLoggedIn = self.isLoggedIn
|
||||
let wasLoggedIn = self.loginState == LoginState.loggedin
|
||||
if not wasLoggedIn:
|
||||
?self.initUserDb(keyUid, password).mapErrTo(
|
||||
{DbError.KeyError: InvalidPassword}.toTable, InitUserDbError)
|
||||
self.setLoginState(LoginState.loggedin)
|
||||
|
||||
let userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
?userDb.createAccount(defaultWalletAccount).mapErrTo(CreateAcctError)
|
||||
|
@ -124,15 +125,15 @@ proc storeDerivedAccounts(self: StatusObject, id: UUID, keyUid: string,
|
|||
|
||||
if not wasLoggedIn:
|
||||
?self.closeUserDb.mapErrTo(CloseDbError)
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
|
||||
ok pubAccount
|
||||
|
||||
|
||||
proc createAccount*(self: StatusObject, mnemonicPhraseLength: int,
|
||||
bip39Passphrase, password: string, dir: string):
|
||||
AccountsResult[PublicAccount] =
|
||||
|
||||
if self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedout:
|
||||
return err MustBeLoggedOut
|
||||
|
||||
let
|
||||
|
@ -145,6 +146,8 @@ proc createAccount*(self: StatusObject, mnemonicPhraseLength: int,
|
|||
|
||||
?self.initUserDb(account.keyUid, password).mapErrTo(
|
||||
{DbError.KeyError: InvalidPassword}.toTable, InitUserDbError)
|
||||
self.setLoginState(LoginState.loggedin)
|
||||
|
||||
let
|
||||
pubAccount = ?self.storeDerivedAccounts(account.id, account.keyUid, paths,
|
||||
password, dir, AccountType.Generated).mapErrTo(StoreDerivedAcctsError)
|
||||
|
@ -195,6 +198,7 @@ proc createAccount*(self: StatusObject, mnemonicPhraseLength: int,
|
|||
let userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
?userDb.createSettings(settings, nodeConfig).mapErrTo(CreateSettingsError)
|
||||
?self.closeUserDb.mapErrTo(CloseDbError)
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
|
||||
ok pubAccount
|
||||
|
||||
|
@ -202,6 +206,7 @@ proc getChatAccount*(self: StatusObject): AccountsResult[accounts.Account] =
|
|||
let
|
||||
userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
acct = ?userDb.getChatAccount.mapErrTo(GetChatAcctError)
|
||||
|
||||
ok acct
|
||||
|
||||
proc getPublicAccounts*(self: StatusObject):
|
||||
|
@ -210,10 +215,8 @@ proc getPublicAccounts*(self: StatusObject):
|
|||
let accts = ?self.accountsDb.getPublicAccounts().mapErrTo(GetPublicAcctsError)
|
||||
ok accts
|
||||
|
||||
|
||||
proc importMnemonic*(self: StatusObject, mnemonic: Mnemonic,
|
||||
bip39Passphrase, password: string, dir: string):
|
||||
AccountsResult[PublicAccount] =
|
||||
bip39Passphrase, password, dir: string): AccountsResult[PublicAccount] =
|
||||
|
||||
let
|
||||
imported = ?self.accountsGenerator.importMnemonic(mnemonic,
|
||||
|
@ -222,6 +225,7 @@ proc importMnemonic*(self: StatusObject, mnemonic: Mnemonic,
|
|||
PATH_DEFAULT_WALLET]
|
||||
pubAccount = ?self.storeDerivedAccounts(imported.id, imported.keyUid,
|
||||
paths, password, dir, AccountType.Seed).mapErrTo(StoreDerivedAcctsError)
|
||||
|
||||
ok pubAccount
|
||||
|
||||
proc saveAccount*(self: StatusObject, account: PublicAccount):
|
||||
|
|
|
@ -7,8 +7,7 @@ import # status modules
|
|||
../private/[accounts/public_accounts, conversions, settings, util],
|
||||
./common
|
||||
|
||||
export
|
||||
common
|
||||
export common except setLoginState, setNetworkState
|
||||
# TODO: do we still need these exports?
|
||||
# conversions, public_accounts, settings
|
||||
|
||||
|
@ -21,6 +20,8 @@ type
|
|||
"keyUid"
|
||||
InitUserDbError = "auth: error initialising user db"
|
||||
InvalidPassword = "auth: invalid password"
|
||||
MustBeLoggedIn = "auth: operation not permitted, must be logged " &
|
||||
"in"
|
||||
MustBeLoggedOut = "auth: operation not permitted, must be logged " &
|
||||
"out"
|
||||
ParseAddressError = "auth: failed to parse address"
|
||||
|
@ -34,22 +35,42 @@ type
|
|||
proc login*(self: StatusObject, keyUid, password: string):
|
||||
AuthResult[PublicAccount] =
|
||||
|
||||
if self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedout:
|
||||
return err MustBeLoggedOut
|
||||
|
||||
let account = ?self.accountsDb.getPublicAccount(keyUid).mapErrTo(
|
||||
GetAccountError)
|
||||
self.setLoginState(LoginState.loggingin)
|
||||
|
||||
if account.isNone:
|
||||
let account = self.accountsDb.getPublicAccount(keyUid)
|
||||
if account.isErr:
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
return err GetAccountError
|
||||
|
||||
if account.get.isNone:
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
return err InvalidKeyUid
|
||||
|
||||
?self.initUserDb(keyUid, password).mapErrTo(
|
||||
[(DbError.KeyError, InvalidPassword)].toTable, InitUserDbError)
|
||||
let init = self.initUserDb(keyUid, password).mapErrTo(
|
||||
{DbError.KeyError: InvalidPassword}.toTable, InitUserDbError)
|
||||
|
||||
ok account.get
|
||||
if init.isErr:
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
return err init.error
|
||||
|
||||
proc logout*(self: StatusObject): AuthResult[void] {.raises: [].} =
|
||||
?self.closeUserDb().mapErrTo(CloseDbError)
|
||||
self.setLoginState(LoginState.loggedin)
|
||||
ok account.get.get
|
||||
|
||||
proc logout*(self: StatusObject): AuthResult[void] =
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
self.setLoginState(LoginState.loggingout)
|
||||
|
||||
let close = self.closeUserDb().mapErrTo(CloseDbError)
|
||||
if close.isErr:
|
||||
self.setLoginState(LoginState.loggedin)
|
||||
return close
|
||||
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
ok()
|
||||
|
||||
proc validatePassword*(self: StatusObject, password, dir: string):
|
||||
|
@ -59,10 +80,13 @@ proc validatePassword*(self: StatusObject, password, dir: string):
|
|||
userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
address = ?userDb.getSetting(string,
|
||||
SettingsCol.WalletRootAddress).mapErrTo(WalletRootAddressError)
|
||||
|
||||
if address.isNone:
|
||||
return ok false
|
||||
|
||||
let
|
||||
addressParsed = ?address.get.parseAddress.mapErrTo(ParseAddressError)
|
||||
loadAcctResult = self.accountsGenerator.loadAccount(addressParsed, password,
|
||||
dir)
|
||||
|
||||
return ok loadAcctResult.isOk
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # std libs
|
||||
std/[os, tables, typetraits]
|
||||
std/[os, sets, strformat, strutils, tables, typetraits]
|
||||
|
||||
import # vendor libs
|
||||
sqlcipher, web3, web3/ethtypes
|
||||
|
@ -10,43 +10,66 @@ from web3/conversions as web3_conversions import `$`
|
|||
|
||||
import # status modules
|
||||
../private/common,
|
||||
../private/[accounts/generator/generator, callrpc, database, settings,
|
||||
token_prices, util]
|
||||
../private/[accounts/generator/generator, callrpc, database, events, settings,
|
||||
token_prices, util, waku]
|
||||
|
||||
from ../private/conversions import parseAddress, readValue, writeValue
|
||||
from ../private/extkeys/types import Mnemonic
|
||||
from ../private/opensea import Asset, AssetContract, Collection
|
||||
|
||||
export
|
||||
`$`, common, database, ethtypes, generator, Mnemonic, parseAddress, readValue,
|
||||
sqlcipher, util, writeValue
|
||||
export # modules
|
||||
common, database, ethtypes, events, generator, sqlcipher, util
|
||||
|
||||
export # symbols
|
||||
`$`, Asset, AssetContract, Collection, Mnemonic, parseAddress, readValue,
|
||||
writeValue
|
||||
|
||||
type
|
||||
StatusObject* = ref object
|
||||
accountsGenerator*: Generator
|
||||
accountsDbConn: DbConn
|
||||
dataDir*: string
|
||||
userDbConn: DbConn
|
||||
web3Conn: Table[string, Web3]
|
||||
priceMap*: PriceMap
|
||||
LoginState* = enum loggedout, loggingin, loggedin, loggingout
|
||||
|
||||
NetworkState* = enum offline, connecting, online, disconnecting
|
||||
|
||||
StatusObject* = ref object
|
||||
accountsDbConn: DbConn
|
||||
accountsGenerator*: Generator
|
||||
dataDir*: string
|
||||
loginState: LoginState
|
||||
networkState: NetworkState
|
||||
priceMap*: PriceMap
|
||||
signalHandler*: StatusSignalHandler
|
||||
topics*: OrderedSet[common.ContentTopic]
|
||||
userDbConn: DbConn
|
||||
wakuFilter*: bool
|
||||
wakuFilterHandler*: ContentFilterHandler
|
||||
wakuFilternode*: string
|
||||
wakuHistoryHandler*: QueryHandlerFunc
|
||||
wakuLightpush*: bool
|
||||
wakuLightpushnode*: string
|
||||
wakuNode*: WakuNode
|
||||
wakuPubSubTopics*: seq[string]
|
||||
wakuRlnRelay*: bool
|
||||
wakuStore*: bool
|
||||
wakuStorenode*: string
|
||||
web3Conn: Table[string, Web3]
|
||||
|
||||
proc new*(T: type StatusObject, dataDir: string,
|
||||
accountsDbFileName: string = "accounts.sql"): DbResult[T] =
|
||||
accountsDbFileName = "accounts.sql",
|
||||
signalHandler = defaultStatusSignalHandler):
|
||||
DbResult[T] =
|
||||
|
||||
let
|
||||
accountsDb = ?initDb(dataDir / accountsDbFileName).mapErrTo(
|
||||
InitFailure)
|
||||
accountsDb = ?initDb(dataDir / accountsDbFileName).mapErrTo(InitFailure)
|
||||
generator = Generator.new()
|
||||
|
||||
ok T(accountsDbConn: accountsDb, dataDir: dataDir, accountsGenerator: generator,
|
||||
web3Conn: initTable[string, Web3](), priceMap: newTable[string, ToPriceMap]())
|
||||
ok T(accountsDbConn: accountsDb, accountsGenerator: generator,
|
||||
dataDir: dataDir, loginState: loggedout, networkState: offline,
|
||||
priceMap: newTable[string, ToPriceMap](), signalHandler: signalHandler,
|
||||
wakuPubSubTopics: @[waku.DefaultTopic], wakuStore: true,
|
||||
web3Conn: initTable[string, Web3]())
|
||||
|
||||
proc accountsDb*(self: StatusObject): DbConn {.raises: [].} =
|
||||
self.accountsDbConn
|
||||
|
||||
proc isLoggedIn*(self: StatusObject): bool {.raises: [].} =
|
||||
not distinctBase(self.userDbConn).isNil and self.userDbConn.isOpen
|
||||
|
||||
proc userDb*(self: StatusObject): DbResult[DbConn] {.raises: [].} =
|
||||
if distinctBase(self.userDbConn).isNil:
|
||||
return err NotInitialized
|
||||
|
@ -60,15 +83,6 @@ proc closeUserDb*(self: StatusObject): DbResult[void] {.raises: [].} =
|
|||
self.userDbConn = nil
|
||||
ok()
|
||||
|
||||
proc close*(self: StatusObject): DbResult[void] {.raises: [].} =
|
||||
if self.isLoggedIn:
|
||||
?self.closeUserDb()
|
||||
try:
|
||||
self.accountsDb.close()
|
||||
ok()
|
||||
except SqliteError, Exception:
|
||||
err CloseFailure
|
||||
|
||||
proc initUserDb*(self: StatusObject, keyUid, password: string): DbResult[void] =
|
||||
self.userDbConn = ?initDb(self.dataDir / keyUid & ".db", password)
|
||||
ok()
|
||||
|
@ -104,3 +118,27 @@ proc web3*(self: StatusObject): Web3Result[Web3] =
|
|||
.mapErrTo(Web3Error(kind: web3Internal, internalError: NotFound))
|
||||
|
||||
ok web3Conn
|
||||
|
||||
proc loginState*(self: StatusObject): LoginState =
|
||||
self.loginState
|
||||
|
||||
proc setLoginState*(self: StatusObject, state: LoginState) {.raises: [].} =
|
||||
self.loginState = state
|
||||
|
||||
proc networkState*(self: StatusObject): NetworkState =
|
||||
self.networkState
|
||||
|
||||
proc setNetworkState*(self: StatusObject, state: NetworkState) {.raises: [].} =
|
||||
self.networkState = state
|
||||
|
||||
# this and logic around login/out and dis/connect needs to be reconsidered
|
||||
proc close*(self: StatusObject): DbResult[void] {.raises: [].} =
|
||||
if self.loginState == LoginState.loggedin:
|
||||
?self.closeUserDb()
|
||||
self.setLoginState(LoginState.loggedout)
|
||||
|
||||
try:
|
||||
self.accountsDb.close()
|
||||
ok()
|
||||
except SqliteError, Exception:
|
||||
err CloseFailure
|
||||
|
|
|
@ -47,7 +47,7 @@ type
|
|||
proc callRpc*(self: StatusObject, rpcMethod: string, params: JsonNode):
|
||||
Future[ProviderResult[JsonNode]] {.async.} =
|
||||
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err ProviderError(kind: pApi, apiError: MustBeLoggedIn)
|
||||
|
||||
let web3Result = self.web3
|
||||
|
@ -72,7 +72,7 @@ proc sendTransaction*(self: StatusObject, fromAddress: EthAddress,
|
|||
transaction: Transaction, password: string, dir: string):
|
||||
Future[ProviderResult[JsonNode]] {.async.} =
|
||||
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err ProviderError(kind: pApi, apiError: MustBeLoggedIn)
|
||||
|
||||
let db = self.userDb()
|
||||
|
@ -129,5 +129,7 @@ proc sendTransaction*(self: StatusObject, fromAddress: EthAddress,
|
|||
let
|
||||
ogRpcError = error.rpcError
|
||||
rpcError = RpcError(code: ogRpcError.code, message: ogRpcError.message)
|
||||
|
||||
return err ProviderError(kind: pRpc, rpcError: rpcError)
|
||||
|
||||
return ok respResult.get
|
||||
|
|
|
@ -4,8 +4,7 @@ import # status modules
|
|||
../private/[settings, util],
|
||||
./common
|
||||
|
||||
export
|
||||
common
|
||||
export common except setLoginState, setNetworkState
|
||||
|
||||
type
|
||||
SettingsError* = enum
|
||||
|
@ -16,10 +15,11 @@ type
|
|||
SettingsResult*[T] = Result[T, SettingsError]
|
||||
|
||||
proc getSettings*(self: StatusObject): SettingsResult[Settings] =
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let
|
||||
userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
settings = ?userDb.getSettings.mapErrTo(GetSettingsError)
|
||||
ok settings
|
||||
|
||||
ok settings
|
||||
|
|
|
@ -10,8 +10,8 @@ import # status modules
|
|||
../private/[util, token_prices, tokens],
|
||||
./common
|
||||
|
||||
export
|
||||
common, tokens
|
||||
export common except setLoginState, setNetworkState
|
||||
export tokens
|
||||
|
||||
type
|
||||
CustomTokenError* = enum
|
||||
|
@ -29,7 +29,7 @@ type
|
|||
proc addCustomToken*(self: StatusObject, address: Address, name, symbol,
|
||||
color: string, decimals: uint): CustomTokenResult[Token] =
|
||||
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let token = Token(address: address, name: name, symbol: symbol, color: color,
|
||||
|
@ -43,7 +43,7 @@ proc addCustomToken*(self: StatusObject, address: Address, name, symbol,
|
|||
proc deleteCustomToken*(self: StatusObject, address: Address):
|
||||
CustomTokenResult[Address] =
|
||||
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
|
@ -52,16 +52,20 @@ proc deleteCustomToken*(self: StatusObject, address: Address):
|
|||
ok address
|
||||
|
||||
proc getCustomTokens*(self: StatusObject): CustomTokenResult[seq[Token]] =
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let
|
||||
userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
tokens = ?userDb.getCustomTokens().mapErrTo(GetFailure)
|
||||
|
||||
ok tokens
|
||||
|
||||
proc getPrice*(self: StatusObject, tokenSymbol: string, fiatCurrency: string):
|
||||
CustomTokenResult[float] {.raises: [ref KeyError].} =
|
||||
CustomTokenResult[float] {.raises: [Defect, ref KeyError].} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if not contains(self.priceMap, tokenSymbol) or
|
||||
not contains(self.priceMap[tokenSymbol], fiatCurrency):
|
||||
|
@ -72,6 +76,9 @@ proc getPrice*(self: StatusObject, tokenSymbol: string, fiatCurrency: string):
|
|||
proc updatePrices*(self: StatusObject): Future[CustomTokenResult[void]]
|
||||
{.async.} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let tokensResult = self.getCustomTokens()
|
||||
if tokensResult.isErr: return err tokensResult.error
|
||||
let tokens = tokensResult.get
|
||||
|
|
|
@ -0,0 +1,524 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # std libs
|
||||
std/[options, sequtils, sets, strutils, sugar]
|
||||
|
||||
from times import getTime, toUnix
|
||||
|
||||
import # vendor libs
|
||||
chronicles, chronos, eth/keys as eth_keys, nimcrypto/pbkdf2, stew/byteutils
|
||||
|
||||
# status libs
|
||||
import ../private/waku except ContentTopic
|
||||
import
|
||||
../private/[alias, protocol, util],
|
||||
./accounts, ./common
|
||||
|
||||
export common except setLoginState, setNetworkState
|
||||
export waku except ContentTopic
|
||||
|
||||
logScope:
|
||||
topics = "status_api"
|
||||
|
||||
type
|
||||
Nodekey* = waku.crypto.PrivateKey
|
||||
|
||||
WakuError* = enum
|
||||
InvalidKey = "waku: invalid private key"
|
||||
MustBeLoggedIn = "waku: operation not permitted, must be logged in"
|
||||
MustBeOffline = "waku: operation not permitted, must be offline"
|
||||
MustBeOnline = "waku: operation not permitted, must be online"
|
||||
NoChatAccount = "waku: could not retrieve chat account"
|
||||
NoChatAccountName = "waku: could not retrieve name of chat account"
|
||||
NoSendUnsuspportedApp = "waku: cannot send message to unsupported " &
|
||||
"content topic"
|
||||
SendNotSupportedWaku1 = "waku: sending messages to public chats is not " &
|
||||
"currently supported"
|
||||
|
||||
WakuResult*[T] = Result[T, WakuError]
|
||||
|
||||
proc init*(T: type Nodekey): T =
|
||||
Nodekey.random(Secp256k1, waku.keys.newRng()[]).expect(
|
||||
"random key generation should never fail")
|
||||
|
||||
proc fromHex*(T: type Nodekey, key: string): Result[T, WakuError] =
|
||||
if not (key.len == 64 or key.len == 66): return err InvalidKey
|
||||
|
||||
var hex: string
|
||||
|
||||
if key[0..1] == "0x":
|
||||
hex = key
|
||||
else:
|
||||
hex = "0x" & key
|
||||
|
||||
if hex.len == 66 and isHexString(hex).get(false):
|
||||
let skkey = ?SkPrivateKey.init(
|
||||
waku.utils.fromHex(hex[2..^1])).mapErrTo(InvalidKey)
|
||||
|
||||
ok Nodekey(scheme: Secp256k1, skkey: skkey)
|
||||
else:
|
||||
err InvalidKey
|
||||
|
||||
proc handleChat2Message(self: StatusObject, message: WakuMessage,
|
||||
contentTopic: ContentTopic, pubSubTopic: waku.Topic) {.async.} =
|
||||
|
||||
let
|
||||
chat2MessageResult = Chat2Message.decode(message.payload)
|
||||
timestamp = getTime().toUnix
|
||||
|
||||
if chat2MessageResult.isOk:
|
||||
let
|
||||
chat2Message = chat2MessageResult.get
|
||||
event = Chat2MessageEvent(data: chat2Message, timestamp: timestamp,
|
||||
topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.chat2Message))
|
||||
|
||||
else:
|
||||
let
|
||||
chat2MessageError = chat2MessageResult.error
|
||||
event = Chat2MessageErrorEvent(error: chat2MessageError,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.chat2MessageError))
|
||||
|
||||
proc handleWaku1Message(self: StatusObject, message: WakuMessage,
|
||||
contentTopic: ContentTopic, pubSubTopic: waku.Topic) {.async.} =
|
||||
|
||||
# currently we only support public chat messages
|
||||
|
||||
var
|
||||
ctx: HMAC[sha256]
|
||||
salt: seq[byte] = @[]
|
||||
shortName = contentTopic.shortName
|
||||
symKey: SymKey
|
||||
|
||||
let timestamp = getTime().toUnix
|
||||
|
||||
if shortName.startsWith('#'): shortName = shortName[1..^1]
|
||||
|
||||
if pbkdf2(ctx, shortName.toBytes(), salt, 65356, symKey) != sizeof(SymKey):
|
||||
let
|
||||
publicChatMessageError = PublicChatMessageError.BadKey
|
||||
event = PublicChatMessageErrorEvent(error: publicChatMessageError,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.publicChatMessageError))
|
||||
|
||||
else:
|
||||
let decryptedMessageOption = decode(message.payload,
|
||||
none[eth_keys.PrivateKey](), some(symKey))
|
||||
|
||||
if decryptedMessageOption.isNone:
|
||||
let
|
||||
publicChatMessageError = PublicChatMessageError.DecryptFailed
|
||||
event = PublicChatMessageErrorEvent(error: publicChatMessageError,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.publicChatMessageError))
|
||||
|
||||
else:
|
||||
let decryptedMessage = decryptedMessageOption.get
|
||||
|
||||
try:
|
||||
let
|
||||
protoMessage = protocol.ProtocolMessage.decode(
|
||||
decryptedMessage.payload)
|
||||
|
||||
appMetaMessage = protocol.ApplicationMetadataMessage.decode(
|
||||
protoMessage.public_message)
|
||||
|
||||
chatMessage = protocol.ChatMessage.decode(
|
||||
appMetaMessage.payload)
|
||||
|
||||
pubkeyOption = decryptedMessage.src
|
||||
|
||||
if pubkeyOption.isNone:
|
||||
let
|
||||
publicChatMessageError = PublicChatMessageError.NoPublicKey
|
||||
event = PublicChatMessageErrorEvent(error: publicChatMessageError,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.publicChatMessageError))
|
||||
|
||||
else:
|
||||
let
|
||||
pubkey = pubkeyOption.get
|
||||
aliasResult = generateAlias(
|
||||
"0x04" & byteutils.toHex(pubkey.toRaw()))
|
||||
|
||||
if aliasResult.isErr:
|
||||
let
|
||||
publicChatMessageError = PublicChatMessageError.NoAlias
|
||||
event = PublicChatMessageErrorEvent(error: publicChatMessageError,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.publicChatMessageError))
|
||||
|
||||
else:
|
||||
let
|
||||
alias = aliasResult.get
|
||||
timestamp = chatMessage.timestamp.int64 div 1000.int64
|
||||
publicChatMessage = PublicChatMessage(alias: alias,
|
||||
message: chatMessage, pubkey: pubkey, timestamp: timestamp)
|
||||
|
||||
event = PublicChatMessageEvent(data: publicChatMessage,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.publicChatMessage))
|
||||
|
||||
except ProtobufReadError as e:
|
||||
let
|
||||
publicChatMessageError = PublicChatMessageError.DecodeFailed
|
||||
event = PublicChatMessageErrorEvent(error: publicChatMessageError,
|
||||
timestamp: timestamp, topic: contentTopic)
|
||||
|
||||
asyncSpawn self.signalHandler((event: event,
|
||||
kind: StatusEventKind.publicChatMessageError))
|
||||
|
||||
proc handleAppMessage(self: StatusObject, message: WakuMessage,
|
||||
contentTopic: ContentTopic, pubSubTopic: waku.Topic) {.async.} =
|
||||
|
||||
# we know how to handle messages for only some content topics:
|
||||
# * `/toy-chat/2/{topic}/proto`
|
||||
# * `/waku/1/{topic-digest}/rfc26`
|
||||
|
||||
if contentTopic.isChat2:
|
||||
asyncSpawn self.handleChat2Message(message, contentTopic, pubSubTopic)
|
||||
|
||||
elif contentTopic.isWaku1:
|
||||
asyncSpawn self.handleWaku1Message(message, contentTopic, pubSubTopic)
|
||||
|
||||
else:
|
||||
trace "ignored message for unsupported app", contentTopic, pubSubTopic
|
||||
|
||||
proc handleWakuMessage(self: StatusObject, pubSubTopic: waku.Topic,
|
||||
message: WakuMessage) {.async.} =
|
||||
|
||||
let contentTopicResult = ContentTopic.init(message.contentTopic)
|
||||
|
||||
if contentTopicResult.isErr:
|
||||
error "received WakuMessage with invalid content topic",
|
||||
contentTopic=message.contentTopic, pubSubTopic
|
||||
|
||||
return
|
||||
|
||||
var contentTopic = contentTopicResult.get
|
||||
|
||||
# use our util.includes as a workaround for what seems to be a bug in
|
||||
# Nim's std/sets.contains
|
||||
if self.topics.includes(contentTopic):
|
||||
# is there a better way to do it? `[]` proc doesn't exist for OrderedSet
|
||||
let h = contentTopic.hash
|
||||
for t in self.topics:
|
||||
if t.hash == h: contentTopic.shortName = t.shortName
|
||||
|
||||
asyncSpawn self.handleAppMessage(message, contentTopic, pubSubTopic)
|
||||
|
||||
else:
|
||||
trace "ignored message for unjoined topic", contentTopic,
|
||||
joined=self.topics, pubSubTopic
|
||||
|
||||
proc getTopics*(self: StatusObject): seq[ContentTopic] =
|
||||
self.topics.toSeq
|
||||
|
||||
proc joinTopic*(self: StatusObject, topic: ContentTopic) =
|
||||
self.topics.incl(topic)
|
||||
|
||||
proc leaveTopic*(self: StatusObject, topic: ContentTopic) =
|
||||
self.topics.excl(topic)
|
||||
|
||||
proc lightpushHandler(response: PushResponse) {.gcsafe.} =
|
||||
trace "received lightpush response", response
|
||||
|
||||
proc sendChat2Message(self: StatusObject, message: string, topic: ContentTopic):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
var nick: string
|
||||
let chatAccountResult = self.getChatAccount()
|
||||
|
||||
if chatAccountResult.isOk:
|
||||
let chatAccount = chatAccountResult.get
|
||||
if chatAccount.name.isSome:
|
||||
nick = chatAccount.name.get
|
||||
else:
|
||||
return err NoChatAccountName
|
||||
else:
|
||||
return err NoChatAccount
|
||||
|
||||
let
|
||||
chat2protobuf = Chat2Message.init(nick, message).encode()
|
||||
payload = chat2protobuf.buffer
|
||||
wakuMessage = WakuMessage(payload: payload, contentTopic: $topic,
|
||||
version: 0)
|
||||
|
||||
if not self.wakuNode.wakuLightPush.isNil():
|
||||
for pTopic in self.wakuPubSubTopics:
|
||||
asyncSpawn self.wakuNode.lightpush(pTopic, wakuMessage, lightpushHandler)
|
||||
|
||||
else:
|
||||
for pTopic in self.wakuPubSubTopics:
|
||||
asyncSpawn self.wakuNode.publish(pTopic, wakuMessage, self.wakuRlnRelay)
|
||||
|
||||
return ok()
|
||||
|
||||
proc sendWaku1Message(self: StatusObject, message: string, topic: ContentTopic):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
return err SendNotSupportedWaku1
|
||||
|
||||
proc sendMessage*(self: StatusObject, message: string, topic: ContentTopic):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if self.networkState != NetworkState.online:
|
||||
return err MustBeOnline
|
||||
|
||||
# we know how to send messages for only some content topics:
|
||||
# * `/toy-chat/2/{topic}/proto`
|
||||
# * `/waku/1/{topic-digest}/rfc26`
|
||||
|
||||
if topic.isChat2:
|
||||
return await self.sendChat2Message(message, topic)
|
||||
|
||||
elif topic.isWaku1:
|
||||
return await self.sendWaku1Message(message, topic)
|
||||
|
||||
else:
|
||||
return err NoSendUnsuspportedApp
|
||||
|
||||
proc addFiltersImpl(self: StatusObject, topics: seq[ContentTopic]):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if not self.wakuNode.wakuFilter.isNil():
|
||||
let contentFilters = collect(newSeq):
|
||||
for topic in topics:
|
||||
ContentFilter(contentTopic: $topic)
|
||||
|
||||
for pTopic in self.wakuPubSubTopics:
|
||||
await self.wakuNode.subscribe(FilterRequest(
|
||||
contentFilters: contentFilters, pubSubTopic: pTopic, subscribe: true),
|
||||
self.wakuFilterHandler)
|
||||
|
||||
else:
|
||||
warn "cannot subscribe to filter requests when node's filter is nil"
|
||||
|
||||
return ok()
|
||||
|
||||
proc addFilters*(self: StatusObject, topics: seq[ContentTopic]):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if self.networkState != NetworkState.online:
|
||||
return err MustBeOnline
|
||||
|
||||
return await self.addFiltersImpl(topics)
|
||||
|
||||
proc removeFiltersImpl(self: StatusObject, topics: seq[ContentTopic]):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if not self.wakuNode.wakuFilter.isNil():
|
||||
let contentFilters = collect(newSeq):
|
||||
for topic in topics:
|
||||
ContentFilter(contentTopic: $topic)
|
||||
|
||||
for pTopic in self.wakuPubSubTopics:
|
||||
await self.wakuNode.unsubscribe(FilterRequest(
|
||||
contentFilters: contentFilters, pubSubTopic: pTopic, subscribe: false))
|
||||
|
||||
else:
|
||||
warn "cannot unsubscribe from filter requests when node's filter is nil"
|
||||
|
||||
return ok()
|
||||
|
||||
proc removeFilters*(self: StatusObject, topics: seq[ContentTopic]):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if self.networkState != NetworkState.online:
|
||||
return err MustBeOnline
|
||||
|
||||
return await self.removeFiltersImpl(topics)
|
||||
|
||||
proc queryHistory*(self: StatusObject, topics: seq[ContentTopic]):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if self.networkState != NetworkState.online:
|
||||
return err MustBeOnline
|
||||
|
||||
let contentFilters = collect(newSeq):
|
||||
for topic in topics:
|
||||
HistoryContentFilter(contentTopic: $topic)
|
||||
|
||||
await self.wakuNode.query(HistoryQuery(contentFilters: contentFilters),
|
||||
self.wakuHistoryHandler)
|
||||
|
||||
return ok()
|
||||
|
||||
proc connect*(self: StatusObject, nodekey = Nodekey.init(),
|
||||
extIp = none[ValidIpAddress](), extTcpPort = none[Port](),
|
||||
extUdpPort = none[Port](), bindIp = ValidIpAddress.init("0.0.0.0"),
|
||||
bindTcpPort = Port(60000), bindUdpPort = Port(60000), portsShift = 0.uint16,
|
||||
pubSubTopics = @[waku.DefaultTopic], rlnRelay = false, relay = true,
|
||||
fleet = WakuFleet.prod, staticnodes: seq[string] = @[], swapProtocol = true,
|
||||
filternode = "", lightpushnode = "", store = true, storenode = "",
|
||||
keepalive = false):
|
||||
Future[WakuResult[void]] {.async.} =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if self.networkState != NetworkState.offline:
|
||||
return err MustBeOffline
|
||||
|
||||
self.setNetworkState(NetworkState.connecting)
|
||||
|
||||
let
|
||||
filter = filternode != ""
|
||||
lightpush = lightpushnode != ""
|
||||
wakuNode = WakuNode.new(nodekey, bindIp,
|
||||
Port(bindTcpPort.uint16 + portsShift), extIp, extTcpPort)
|
||||
|
||||
self.wakuFilter = filter
|
||||
self.wakuFilternode = filternode
|
||||
self.wakuLightpush = lightpush
|
||||
self.wakuLightpushnode = lightpushnode
|
||||
self.wakuNode = wakuNode
|
||||
self.wakuPubSubTopics = pubSubTopics
|
||||
self.wakuRlnRelay = rlnRelay
|
||||
self.wakuStore = store
|
||||
self.wakuStorenode = storenode
|
||||
|
||||
await wakuNode.start()
|
||||
|
||||
wakuNode.mountRelay(pubSubTopics, rlnRelayEnabled = rlnRelay,
|
||||
relayMessages = relay)
|
||||
|
||||
wakuNode.mountLibp2pPing()
|
||||
|
||||
if staticnodes.len > 0:
|
||||
info "connecting to static peers", nodes=staticnodes
|
||||
await wakuNode.connectToNodes(staticnodes)
|
||||
|
||||
elif fleet != WakuFleet.none:
|
||||
info "static peers not configured, choosing one at random", fleet
|
||||
let node = await selectRandomNode($fleet)
|
||||
|
||||
info "connecting to peer", node
|
||||
await wakuNode.connectToNodes(@[node])
|
||||
|
||||
if swapProtocol: wakuNode.mountSwap()
|
||||
|
||||
if filter:
|
||||
proc filterHandler(message: WakuMessage) {.gcsafe, closure.} =
|
||||
try:
|
||||
discard self.handleWakuMessage(waku.DefaultTopic, message)
|
||||
except CatchableError as e:
|
||||
error "waku filter handler encountered an unknown error", error=e.msg
|
||||
|
||||
self.wakuFilterHandler = filterHandler
|
||||
|
||||
wakuNode.mountFilter()
|
||||
wakuNode.wakuFilter.setPeer(parsePeerInfo(filternode))
|
||||
|
||||
(await self.addFiltersImpl(self.getTopics)).expect(
|
||||
"addFilters is not expected to fail in this context")
|
||||
|
||||
if lightpush:
|
||||
wakuNode.mountLightPush()
|
||||
wakuNode.wakuLightPush.setPeer(parsePeerInfo(lightpushnode))
|
||||
|
||||
if store or storenode != "":
|
||||
proc historyHandler(response: HistoryResponse) {.gcsafe, closure.} =
|
||||
for message in response.messages:
|
||||
try:
|
||||
discard self.handleWakuMessage(waku.DefaultTopic, message)
|
||||
except CatchableError as e:
|
||||
error "waku history handler encountered an unknown error", error=e.msg
|
||||
|
||||
self.wakuHistoryHandler = historyHandler
|
||||
|
||||
wakuNode.mountStore(persistMessages = false)
|
||||
|
||||
var snode: Option[string]
|
||||
|
||||
if storenode != "":
|
||||
snode = some(storenode)
|
||||
|
||||
elif fleet != WakuFleet.none:
|
||||
info "store nodes not configured, choosing one at random", fleet
|
||||
snode = some(await selectRandomNode($fleet))
|
||||
|
||||
if snode.isNone:
|
||||
warn "unable to determine a storenode, no connection made"
|
||||
|
||||
else:
|
||||
info "connecting to storenode", storenode=snode
|
||||
wakuNode.wakuStore.setPeer(parsePeerInfo(snode.get()))
|
||||
|
||||
if relay:
|
||||
proc relayHandler(pubSubTopic: waku.Topic, data: seq[byte])
|
||||
{.async, gcsafe.} =
|
||||
let decoded = WakuMessage.init(data)
|
||||
if decoded.isOk():
|
||||
let message = decoded.get()
|
||||
asyncSpawn self.handleWakuMessage(pubSubTopic, message)
|
||||
else:
|
||||
error "received invalid WakuMessage", error=decoded.error, pubSubTopic
|
||||
|
||||
for pTopic in pubSubTopics:
|
||||
wakuNode.subscribe(pTopic, relayHandler)
|
||||
|
||||
if keepAlive: wakuNode.startKeepalive()
|
||||
|
||||
self.setNetworkState(NetworkState.online)
|
||||
|
||||
return ok()
|
||||
|
||||
proc disconnect*(self: StatusObject): Future[WakuResult[void]] {.async.} =
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
if self.networkState != NetworkState.online:
|
||||
return err MustBeOnline
|
||||
|
||||
self.setNetworkState(NetworkState.disconnecting)
|
||||
|
||||
if self.wakuFilter:
|
||||
(await self.removeFiltersImpl(self.getTopics)).expect(
|
||||
"removeFilters is not expected to fail in this context")
|
||||
|
||||
for pTopic in self.wakuPubSubTopics:
|
||||
self.wakuNode.unsubscribeAll(pTopic)
|
||||
|
||||
await self.wakuNode.stop()
|
||||
|
||||
self.wakuFilter = false
|
||||
self.wakuFilternode = ""
|
||||
self.wakuLightpush = false
|
||||
self.wakuLightpushnode = ""
|
||||
self.wakuNode = nil
|
||||
self.wakuPubSubTopics = @[waku.DefaultTopic]
|
||||
self.wakuRlnRelay = false
|
||||
self.wakuStore = true
|
||||
self.wakuStorenode = ""
|
||||
|
||||
self.setNetworkState(NetworkState.offline)
|
||||
|
||||
return ok()
|
|
@ -12,8 +12,8 @@ import # status modules
|
|||
../private/extkeys/[paths, types],
|
||||
./auth, ./common
|
||||
|
||||
export
|
||||
accounts, common
|
||||
export accounts
|
||||
export common except setLoginState, setNetworkState
|
||||
# TODO: do we still need these exports?
|
||||
# auth, conversions, paths, secp256k1, settings, types, uuid
|
||||
|
||||
|
@ -104,10 +104,10 @@ proc storeDerivedAccount(self: StatusObject, id: UUID, path: KeyPath, name,
|
|||
return self.storeWalletAccount(name, address, publicKey.some, accountType,
|
||||
path)
|
||||
|
||||
proc addWalletAccount*(self: StatusObject, name, password,
|
||||
dir: string): WalletResult[accounts.Account] =
|
||||
proc addWalletAccount*(self: StatusObject, name, password, dir: string):
|
||||
WalletResult[accounts.Account] =
|
||||
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let
|
||||
|
@ -159,6 +159,9 @@ proc storeImportedWalletAccount(self: StatusObject, privateKey: SkSecretKey,
|
|||
proc addWalletPrivateKey*(self: StatusObject, privateKeyHex: string,
|
||||
name, password, dir: string): WalletResult[accounts.Account] =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
var privateKeyStripped = privateKeyHex
|
||||
privateKeyStripped.removePrefix("0x")
|
||||
|
||||
|
@ -171,6 +174,9 @@ proc addWalletPrivateKey*(self: StatusObject, privateKeyHex: string,
|
|||
proc addWalletSeed*(self: StatusObject, mnemonic: Mnemonic, name, password,
|
||||
dir, bip39Passphrase: string): WalletResult[accounts.Account] =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let isPasswordValid = ?self.validatePassword(password, dir).mapErrTo(
|
||||
PasswordValidationError)
|
||||
if not isPasswordValid:
|
||||
|
@ -182,8 +188,11 @@ proc addWalletSeed*(self: StatusObject, mnemonic: Mnemonic, name, password,
|
|||
return self.storeDerivedAccount(imported.id, PATH_DEFAULT_WALLET, name,
|
||||
password, dir, AccountType.Seed)
|
||||
|
||||
proc addWalletWatchOnly*(self: StatusObject, address: Address,
|
||||
name: string): WalletResult[accounts.Account] =
|
||||
proc addWalletWatchOnly*(self: StatusObject, address: Address, name: string):
|
||||
WalletResult[accounts.Account] =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
return self.storeWalletAccount(name, address, SkPublicKey.none,
|
||||
AccountType.Watch, PATH_DEFAULT_WALLET)
|
||||
|
@ -191,6 +200,9 @@ proc addWalletWatchOnly*(self: StatusObject, address: Address,
|
|||
proc deleteWalletAccount*(self: StatusObject, address: Address,
|
||||
password, dir: string): WalletResult[accounts.Account] =
|
||||
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let isPasswordValid = ?self.validatePassword(password, dir).mapErrTo(
|
||||
PasswordValidationError)
|
||||
if not isPasswordValid:
|
||||
|
@ -207,18 +219,17 @@ proc deleteWalletAccount*(self: StatusObject, address: Address,
|
|||
|
||||
ok deleted.get
|
||||
|
||||
|
||||
proc toWalletAccount(account: accounts.Account): WalletAccount {.used.} =
|
||||
let name = if account.name.isNone: "" else: account.name.get
|
||||
WalletAccount(address: account.address, name: name)
|
||||
|
||||
proc getWalletAccounts*(self: StatusObject): WalletResult[seq[WalletAccount]] =
|
||||
|
||||
if not self.isLoggedIn:
|
||||
if self.loginState != LoginState.loggedin:
|
||||
return err MustBeLoggedIn
|
||||
|
||||
let
|
||||
userDb = ?self.userDb.mapErrTo(UserDbError)
|
||||
walletAccts = ?userDb.getWalletAccounts.mapErrTo(GetWalletError)
|
||||
accounts = walletAccts.map(a => a.toWalletAccount)
|
||||
|
||||
ok accounts
|
||||
|
|
|
@ -112,6 +112,9 @@ proc deleteWalletAccount*(db: DbConn, address: Address):
|
|||
var tblAccounts: Account
|
||||
let account = ?db.getWalletAccount(address)
|
||||
if account.isSome:
|
||||
if account.get.wallet.isSome and
|
||||
account.get.wallet.get: return ok none(Account)
|
||||
|
||||
let query = fmt"""DELETE FROM {tblAccounts.tableName}
|
||||
WHERE {tblAccounts.address.columnName} = ?
|
||||
AND {tblAccounts.wallet.columnName} = FALSE"""
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import
|
||||
import # std libs
|
||||
std/[hashes, strutils]
|
||||
|
||||
import # vendor libs
|
||||
stew/results
|
||||
|
||||
import # status modules
|
||||
./util
|
||||
|
||||
export results
|
||||
|
||||
type
|
||||
|
@ -8,6 +14,16 @@ type
|
|||
|
||||
StatusError* = object of CatchableError
|
||||
|
||||
ContentTopic* = object
|
||||
appName*: string
|
||||
appVersion*: string
|
||||
encoding*: string
|
||||
shortName*: string
|
||||
topicName*: string
|
||||
|
||||
ContentTopicError* = enum
|
||||
invalid = "invalid content topic"
|
||||
|
||||
DbError* = enum
|
||||
CloseFailure = "db: failed to close database"
|
||||
DataAndTypeMismatch = "db: failed to deserialise data to supplied type"
|
||||
|
@ -64,3 +80,63 @@ type
|
|||
rpcError*: RpcError
|
||||
|
||||
Web3Result*[T] = Result[T, Web3Error]
|
||||
|
||||
const noTopic* = ContentTopic()
|
||||
|
||||
proc `$`*(t: ContentTopic): string =
|
||||
"/" & t.appName & "/" & t.appVersion & "/" & t.topicName & "/" & t.encoding
|
||||
|
||||
proc hash*(t: ContentTopic): Hash =
|
||||
hash $t
|
||||
|
||||
proc init*(T: type ContentTopic, t: string, s: string = ""):
|
||||
Result[ContentTopic, ContentTopicError] =
|
||||
|
||||
var
|
||||
appName: string
|
||||
appVersion: string
|
||||
encoding: string
|
||||
shortName = s
|
||||
topicName: string
|
||||
|
||||
topic = t.strip()
|
||||
|
||||
if topic == "" or topic == "#":
|
||||
return err ContentTopicError.invalid
|
||||
|
||||
else:
|
||||
let topicSplit = topic.split('/')
|
||||
|
||||
if topic.startsWith('/') and topicSplit.len == 5:
|
||||
appName = topicSplit[1]
|
||||
appVersion = topicSplit[2]
|
||||
topicName = topicSplit[3]
|
||||
encoding = topicSplit[4]
|
||||
|
||||
else:
|
||||
if topic.startsWith('#'): topic = topic[1..^1]
|
||||
|
||||
appName = "waku"
|
||||
appVersion = "1"
|
||||
topicName = "0x" & ($keccak256.digest(topic))[0..7].toLowerAscii
|
||||
encoding = "rfc26"
|
||||
|
||||
if shortName == "":
|
||||
shortName = "#" & topic
|
||||
elif not shortName.startsWith('#'):
|
||||
shortName = "#" & shortName
|
||||
|
||||
ok(T(appName: appName, appVersion: appVersion, encoding: encoding,
|
||||
shortName: shortName, topicName: topicName))
|
||||
|
||||
proc isChat2*(t: ContentTopic): bool =
|
||||
t.appName == "toy-chat" and
|
||||
t.appVersion == "2" and
|
||||
t.encoding == "proto"
|
||||
|
||||
proc isWaku1*(t: ContentTopic): bool =
|
||||
t.appName == "waku" and
|
||||
t.appVersion == "1" and
|
||||
isHexString(t.topicName).get(false) and
|
||||
t.topicName.toLowerAscii == t.topicName and
|
||||
t.encoding == "rfc26"
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # vendor libs
|
||||
chronicles, json_serialization
|
||||
|
||||
import # status modules
|
||||
./common, ./protocol
|
||||
|
||||
logScope:
|
||||
topics = "status_private"
|
||||
|
||||
type
|
||||
StatusEvent* = ref object of RootObj
|
||||
timestamp*: int64
|
||||
|
||||
StatusDataEvent*[T] = ref object of StatusEvent
|
||||
data*: T
|
||||
|
||||
StatusErrorEvent*[T] = ref object of StatusEvent
|
||||
error*: T
|
||||
|
||||
StatusEventKind* = enum
|
||||
chat2Message
|
||||
chat2MessageError
|
||||
publicChatMessage
|
||||
publicChatMessageError
|
||||
|
||||
StatusSignal* = tuple[event: StatusEvent, kind: StatusEventKind]
|
||||
|
||||
StatusSignalHandler* = proc(signal: StatusSignal):
|
||||
Future[void] {.gcsafe, nimcall.}
|
||||
|
||||
StatusMessageEvent*[T] = ref object of StatusDataEvent[T]
|
||||
topic*: ContentTopic
|
||||
|
||||
StatusMessageErrorEvent*[T] = ref object of StatusErrorEvent[T]
|
||||
topic*: ContentTopic
|
||||
|
||||
Chat2MessageEvent* = StatusMessageEvent[Chat2Message]
|
||||
|
||||
Chat2MessageErrorEvent* = StatusMessageErrorEvent[Chat2MessageError]
|
||||
|
||||
PublicChatMessageEvent* = StatusMessageEvent[PublicChatMessage]
|
||||
|
||||
PublicChatMessageErrorEvent* = StatusMessageErrorEvent[PublicChatMessageError]
|
||||
|
||||
proc encode[T](arg: T): string {.raises: [Defect, IOError].} =
|
||||
arg.toJson(typeAnnotations = true)
|
||||
|
||||
const defaultStatusSignalHandler*: StatusSignalHandler =
|
||||
proc(signal: StatusSignal) {.async, gcsafe, nimcall.} =
|
||||
let kind = signal.kind
|
||||
|
||||
try:
|
||||
case kind:
|
||||
of chat2Message:
|
||||
let data = cast[Chat2MessageEvent](signal.event).data
|
||||
trace "received data signal", kind, data
|
||||
|
||||
of chat2MessageError:
|
||||
let error = cast[Chat2MessageErrorEvent](signal.event).error
|
||||
trace "received error signal", kind, error
|
||||
|
||||
of publicChatMessage:
|
||||
let data = cast[PublicChatMessageEvent](signal.event).data
|
||||
trace "received data signal", kind, data
|
||||
|
||||
of publicChatMessageError:
|
||||
let error = cast[PublicChatMessageErrorEvent](signal.event).error
|
||||
trace "received error signal", kind, error
|
||||
|
||||
except IOError as e:
|
||||
error "failed to encode signal.event for log", kind, error=e.msg
|
|
@ -1,5 +1,6 @@
|
|||
import # status modules
|
||||
./protocol/[application_metadata_message, chat_message,
|
||||
./protocol/[application_metadata_message, chat_message, chat2_message,
|
||||
encryption/protocol_message]
|
||||
|
||||
export application_metadata_message, chat_message, protocol_message
|
||||
export application_metadata_message, chat_message, chat2_message,
|
||||
protocol_message
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # vendor libs
|
||||
protobuf_serialization
|
||||
|
||||
|
@ -7,7 +9,9 @@ export protobuf_serialization
|
|||
# automatically exports `type ApplicationMetadataMessage`
|
||||
import_proto3 "protobuf/application_metadata_message.proto"
|
||||
|
||||
proc decode*(T: type ApplicationMetadataMessage, input: seq[byte]): T =
|
||||
proc decode*(T: type ApplicationMetadataMessage, input: seq[byte]):
|
||||
T {.raises: [Defect, ProtobufReadError].} =
|
||||
|
||||
Protobuf.decode(input, T)
|
||||
|
||||
proc encode*(input: ApplicationMetadataMessage): seq[byte] =
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # std libs
|
||||
std/times
|
||||
|
||||
import # vendor libs
|
||||
libp2p/protobuf/minprotobuf
|
||||
|
||||
import # status modules
|
||||
../util
|
||||
|
||||
export ProtoBuffer, ProtoResult
|
||||
|
||||
type
|
||||
Chat2Message* = object
|
||||
nick*: string
|
||||
payload*: seq[byte]
|
||||
timestamp*: int64
|
||||
|
||||
Chat2MessageError* = enum
|
||||
GetFieldError = "chat2: error getting field from protobuffer"
|
||||
|
||||
proc decode*(T: type Chat2Message, input: seq[byte]):
|
||||
Result[Chat2Message, Chat2MessageError] =
|
||||
|
||||
var
|
||||
msg = Chat2Message()
|
||||
timestamp: uint64
|
||||
|
||||
let pb = input.initProtoBuffer
|
||||
|
||||
discard ? pb.getField(1, timestamp).mapErrTo(GetFieldError)
|
||||
msg.timestamp = int64(timestamp)
|
||||
discard ? pb.getField(2, msg.nick).mapErrTo(GetFieldError)
|
||||
discard ? pb.getField(3, msg.payload).mapErrTo(GetFieldError)
|
||||
|
||||
ok(msg)
|
||||
|
||||
proc encode*(message: Chat2Message): ProtoBuffer =
|
||||
var pb = initProtoBuffer()
|
||||
|
||||
pb.write(1, uint64(message.timestamp))
|
||||
pb.write(2, message.nick)
|
||||
pb.write(3, message.payload)
|
||||
|
||||
return pb
|
||||
|
||||
proc init*(T: type Chat2Message, nick: string, message: string): T =
|
||||
let
|
||||
payload = message.toBytes
|
||||
timestamp = getTime().toUnix
|
||||
|
||||
T(nick: nick, payload: payload, timestamp: timestamp)
|
|
@ -1,5 +1,7 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # vendor libs
|
||||
protobuf_serialization
|
||||
eth/keys, protobuf_serialization
|
||||
|
||||
export protobuf_serialization
|
||||
|
||||
|
@ -7,7 +9,25 @@ export protobuf_serialization
|
|||
# automatically exports `type ChatMessage`
|
||||
import_proto3 "protobuf/chat_message.proto"
|
||||
|
||||
proc decode*(T: type ChatMessage, input: seq[byte]): T =
|
||||
type
|
||||
PublicChatMessage* = object
|
||||
alias*: string
|
||||
message*: ChatMessage
|
||||
pubkey*: PublicKey
|
||||
timestamp*: int64
|
||||
|
||||
PublicChatMessageError* = enum
|
||||
BadKey = "pubchat: symmetric key derived from content topic had " &
|
||||
"incorrect length"
|
||||
DecodeFailed = "pubchat: failed to decode message protobuf"
|
||||
DecryptFailed = "pubchat: failed to decrypt message payload"
|
||||
NoAlias = "pubchat: failed to generate alias from public key in " &
|
||||
"message"
|
||||
NoPublicKey = "pubchat: failed to get public key from message"
|
||||
|
||||
proc decode*(T: type ChatMessage, input: seq[byte]):
|
||||
T {.raises: [Defect, ProtobufReadError].} =
|
||||
|
||||
Protobuf.decode(input, T)
|
||||
|
||||
proc encode*(input: ChatMessage): seq[byte] =
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # vendor libs
|
||||
protobuf_serialization
|
||||
|
||||
|
@ -7,7 +9,9 @@ export protobuf_serialization
|
|||
# automatically exports `type ProtocolMessage`
|
||||
import_proto3 "protobuf/protocol_message.proto"
|
||||
|
||||
proc decode*(T: type ProtocolMessage, input: seq[byte]): T =
|
||||
proc decode*(T: type ProtocolMessage, input: seq[byte]):
|
||||
T {.raises: [Defect, ProtobufReadError].} =
|
||||
|
||||
Protobuf.decode(input, T)
|
||||
|
||||
proc encode*(input: ProtocolMessage): seq[byte] =
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # std libs
|
||||
std/[re, strutils, tables, unicode]
|
||||
std/[re, sets, strutils, tables, unicode]
|
||||
|
||||
import # vendor libs
|
||||
nimcrypto, stew/results, web3/ethtypes
|
||||
|
@ -88,3 +88,13 @@ proc mapErrTo*[T, E1, E2](r: Result[T, E1], t: Table[E1, E2], default: E2):
|
|||
return t[e]
|
||||
except KeyError:
|
||||
return default)
|
||||
|
||||
proc includes*[T](s: OrderedSet[T], key: T): bool =
|
||||
# There seems to be a bug in Nim's std/sets.contains where on a non-main
|
||||
# thread matching hashes do not result in a return value of true. Create a
|
||||
# substitute proc here as a workaround until the bug can be identified and
|
||||
# fixed.
|
||||
let k = key.hash
|
||||
for i in s:
|
||||
if i.hash == k: return true
|
||||
return false
|
||||
|
|
|
@ -1,28 +1,43 @@
|
|||
# NOTE: Including a top-level {.push raises: [Defect].} here interferes with
|
||||
# nim-confutils. The compiler will force nim-confutils to annotate its procs
|
||||
# with the needed `{.raises: [,,,].}` pragmas.
|
||||
{.push raises: [Defect].}
|
||||
|
||||
# imports and exports in this module need to be checked, some don't seem to be
|
||||
# necessary even though the compiler doesn't warn about unused imports
|
||||
|
||||
import # std libs
|
||||
std/[options, os]
|
||||
std/[json, options, random, sequtils, strutils, tables, uri]
|
||||
|
||||
import # vendor libs
|
||||
confutils, chronicles, chronos,
|
||||
libp2p/crypto/[crypto, secp],
|
||||
eth/keys,
|
||||
json_rpc/[rpcclient, rpcserver],
|
||||
stew/shims/net as stewNet,
|
||||
waku/v2/node/[config, wakunode2],
|
||||
waku/common/utils/nat
|
||||
bearssl, chronos, chronos/apps/http/httpclient, eth/keys,
|
||||
libp2p/[crypto/crypto, crypto/secp, multiaddress, muxers/muxer, peerid,
|
||||
peerinfo, protocols/protocol, stream/connection, switch],
|
||||
nimcrypto/utils,
|
||||
stew/[byteutils, endians2, results, shims/net],
|
||||
waku/common/utils/nat,
|
||||
waku/v2/node/wakunode2,
|
||||
waku/v2/protocol/[waku_filter/waku_filter, waku_lightpush/waku_lightpush,
|
||||
waku_message, waku_store/waku_store],
|
||||
waku/v2/utils/peers,
|
||||
waku/whisper/whisper_types
|
||||
|
||||
# The initial implementation of initNode is by intention a minimum viable usage
|
||||
# of nim-waku v2 from within nim-status
|
||||
export # modules
|
||||
byteutils, crypto, keys, nat, net, peers, results, secp, wakunode2,
|
||||
whisper_types
|
||||
|
||||
proc initNode*(config: WakuNodeConf = WakuNodeConf.load()): WakuNode =
|
||||
type
|
||||
PrivateKey* = crypto.PrivateKey
|
||||
|
||||
Topic* = wakunode2.Topic
|
||||
|
||||
WakuFleet* = enum none, prod, test
|
||||
|
||||
const DefaultTopic* = "/waku/2/default-waku/proto"
|
||||
|
||||
proc selectRandomNode*(fleetStr: string): Future[string] {.async.} =
|
||||
let
|
||||
(extIp, extTcpPort, extUdpPort) = setupNat(config.nat, clientId,
|
||||
Port(uint16(config.tcpPort) + config.portsShift),
|
||||
Port(uint16(config.udpPort) + config.portsShift))
|
||||
url = "https://fleets.status.im"
|
||||
response = await fetch(HttpSessionRef.new(), parseUri(url))
|
||||
fleet = string.fromBytes(response.data)
|
||||
nodes = toSeq(
|
||||
fleet.parseJson(){"fleets", "wakuv2." & fleetStr, "waku"}.pairs())
|
||||
|
||||
result = WakuNode.new(config.nodeKey, config.listenAddress,
|
||||
Port(uint16(config.tcpPort) + config.portsShift), extIp, extTcpPort)
|
||||
return nodes[rand(nodes.len - 1)].val.getStr()
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
{.push raises: [Defect].}
|
||||
|
||||
import # vendor libs
|
||||
chronos, confutils,
|
||||
eth/[keys, p2p]
|
||||
stew/results,
|
||||
waku/v1/[protocol/waku_protocol, node/waku_helpers],
|
||||
waku/common/utils/nat, stew/byteutils, stew/shims/net as stewNet
|
||||
|
||||
var connThread: Thread[void]
|
||||
|
||||
proc initWakuV1*() {.thread.} =
|
||||
# Test waku
|
||||
const clientId = "NimStatusWaku"
|
||||
let nodeKey = KeyPair.random(keys.newRng()[])
|
||||
let rng = keys.newRng()
|
||||
let (ipExt, tcpPortExt, udpPortExt) = setupNat("any", clientId, Port(30307), Port(30307))
|
||||
let address = if ipExt.isNone(): Address(ip: parseIpAddress("0.0.0.0"), tcpPort: Port(30307),udpPort: Port(30307)) else: Address(ip: ipExt.get(), tcpPort: Port(30307), udpPort: Port(30307))
|
||||
|
||||
# Create Ethereum Node
|
||||
var node = newEthereumNode(nodekey, # Node identifier
|
||||
address, # Address reachable for incoming requests
|
||||
1, # Network Id, only applicable for ETH protocol
|
||||
nil, # Database, not required for Waku
|
||||
clientId, # Client id string
|
||||
addAllCapabilities = false, # Disable default all RLPx capabilities
|
||||
rng = rng)
|
||||
|
||||
node.addCapability Waku # Enable only the Waku protocol.
|
||||
|
||||
|
||||
# Set up the Waku configuration.
|
||||
let wakuConfig = WakuConfig(powRequirement: 0.002,
|
||||
bloom: some(fullBloom()), # Full bloom filter
|
||||
isLightNode: false, # Full node
|
||||
maxMsgSize: waku_protocol.defaultMaxMsgSize,
|
||||
topics: none(seq[waku_protocol.Topic]) # empty topic interest
|
||||
)
|
||||
node.configureWaku(wakuConfig)
|
||||
|
||||
let staticNodes = @[
|
||||
"enode://6e6554fb3034b211398fcd0f0082cbb6bd13619e1a7e76ba66e1809aaa0c5f1ac53c9ae79cf2fd4a7bacb10d12010899b370c75fed19b991d9c0cdd02891abad@47.75.99.169:443",
|
||||
"enode://436cc6f674928fdc9a9f7990f2944002b685d1c37f025c1be425185b5b1f0900feaf1ccc2a6130268f9901be4a7d252f37302c8335a2c1a62736e9232691cc3a@178.128.138.128:443",
|
||||
"enode://32ff6d88760b0947a3dee54ceff4d8d7f0b4c023c6dad34568615fcae89e26cc2753f28f12485a4116c977be937a72665116596265aa0736b53d46b27446296a@34.70.75.208:443",
|
||||
"enode://23d0740b11919358625d79d4cac7d50a34d79e9c69e16831c5c70573757a1f5d7d884510bc595d7ee4da3c1508adf87bbc9e9260d804ef03f8c1e37f2fb2fc69@47.52.106.107:443",
|
||||
"enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@178.128.140.188:443",
|
||||
"enode://5405c509df683c962e7c9470b251bb679dd6978f82d5b469f1f6c64d11d50fbd5dd9f7801c6ad51f3b20a5f6c7ffe248cc9ab223f8bcbaeaf14bb1c0ef295fd0@35.223.215.156:443",
|
||||
"enode://b957e51f41e4abab8382e1ea7229e88c6e18f34672694c6eae389eac22dab8655622bbd4a08192c321416b9becffaab11c8e2b7a5d0813b922aa128b82990dab@47.75.222.178:443",
|
||||
"enode://66ba15600cda86009689354c3a77bdf1a97f4f4fb3ab50ffe34dbc904fac561040496828397be18d9744c75881ffc6ac53729ddbd2cdbdadc5f45c400e2622f7@178.128.141.87:443",
|
||||
"enode://182ed5d658d1a1a4382c9e9f7c9e5d8d9fec9db4c71ae346b9e23e1a589116aeffb3342299bdd00e0ab98dbf804f7b2d8ae564ed18da9f45650b444aed79d509@34.68.132.118:443",
|
||||
"enode://8bebe73ddf7cf09e77602c7d04c93a73f455b51f24ae0d572917a4792f1dec0bb4c562759b8830cc3615a658d38c1a4a38597a1d7ae3ba35111479fc42d65dec@47.75.85.212:443",
|
||||
"enode://4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b@134.209.136.79:443",
|
||||
"enode://fbeddac99d396b91d59f2c63a3cb5fc7e0f8a9f7ce6fe5f2eed5e787a0154161b7173a6a73124a4275ef338b8966dc70a611e9ae2192f0f2340395661fad81c0@34.67.230.193:443",
|
||||
"enode://ac3948b2c0786ada7d17b80cf869cf59b1909ea3accd45944aae35bf864cc069126da8b82dfef4ddf23f1d6d6b44b1565c4cf81c8b98022253c6aea1a89d3ce2@47.75.88.12:443",
|
||||
"enode://ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e@134.209.136.123:443",
|
||||
"enode://c07aa0deea3b7056c5d45a85bca42f0d8d3b1404eeb9577610f386e0a4744a0e7b2845ae328efc4aa4b28075af838b59b5b3985bffddeec0090b3b7669abc1f3@35.226.92.155:443",
|
||||
"enode://385579fc5b14e04d5b04af7eee835d426d3d40ccf11f99dbd95340405f37cf3bbbf830b3eb8f70924be0c2909790120682c9c3e791646e2d5413e7801545d353@47.244.221.249:443",
|
||||
"enode://4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c@134.209.136.101:443",
|
||||
"enode://0624b4a90063923c5cc27d12624b6a49a86dfb3623fcb106801217fdbab95f7617b83fa2468b9ae3de593ff6c1cf556ccf9bc705bfae9cb4625999765127b423@35.222.158.246:443",
|
||||
"enode://b77bffc29e2592f30180311dd81204ab845e5f78953b5ba0587c6631be9c0862963dea5eb64c90617cf0efd75308e22a42e30bc4eb3cd1bbddbd1da38ff6483e@47.75.10.177:443",
|
||||
"enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@178.128.141.249:443",
|
||||
"enode://a5fe9c82ad1ffb16ae60cb5d4ffe746b9de4c5fbf20911992b7dd651b1c08ba17dd2c0b27ee6b03162c52d92f219961cc3eb14286aca8a90b75cf425826c3bd8@104.154.230.58:443",
|
||||
"enode://cf5f7a7e64e3b306d1bc16073fba45be3344cb6695b0b616ccc2da66ea35b9f35b3b231c6cf335fdfaba523519659a440752fc2e061d1e5bc4ef33864aac2f19@47.75.221.196:443",
|
||||
"enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@178.128.141.0:443",
|
||||
"enode://282e009967f9f132a5c2dd366a76319f0d22d60d0c51f7e99795a1e40f213c2705a2c10e4cc6f3890319f59da1a535b8835ed9b9c4b57c3aad342bf312fd7379@35.223.240.17:443",
|
||||
"enode://13d63a1f85ccdcbd2fb6861b9bd9d03f94bdba973608951f7c36e5df5114c91de2b8194d71288f24bfd17908c48468e89dd8f0fb8ccc2b2dedae84acdf65f62a@47.244.210.80:443",
|
||||
"enode://2b01955d7e11e29dce07343b456e4e96c081760022d1652b1c4b641eaf320e3747871870fa682e9e9cfb85b819ce94ed2fee1ac458904d54fd0b97d33ba2c4a4@134.209.136.112:443",
|
||||
"enode://b706a60572634760f18a27dd407b2b3582f7e065110dae10e3998498f1ae3f29ba04db198460d83ed6d2bfb254bb06b29aab3c91415d75d3b869cd0037f3853c@35.239.5.162:443",
|
||||
"enode://32915c8841faaef21a6b75ab6ed7c2b6f0790eb177ad0f4ea6d731bacc19b938624d220d937ebd95e0f6596b7232bbb672905ee12601747a12ee71a15bfdf31c@47.75.59.11:443",
|
||||
"enode://0d9d65fcd5592df33ed4507ce862b9c748b6dbd1ea3a1deb94e3750052760b4850aa527265bbaf357021d64d5cc53c02b410458e732fafc5b53f257944247760@178.128.141.42:443",
|
||||
"enode://e87f1d8093d304c3a9d6f1165b85d6b374f1c0cc907d39c0879eb67f0a39d779be7a85cbd52920b6f53a94da43099c58837034afa6a7be4b099bfcd79ad13999@35.238.106.101:443"
|
||||
]
|
||||
|
||||
connectToNodes(node, staticNodes)
|
||||
|
||||
let connectedFut = node.connectToNetwork(@[],
|
||||
true, # Enable listening
|
||||
false # Disable discovery (only discovery v4 is currently supported)
|
||||
)
|
||||
connectedFut.callback = proc(data: pointer) {.gcsafe.} =
|
||||
{.gcsafe.}:
|
||||
if connectedFut.failed:
|
||||
fatal "connectToNetwork failed", msg = connectedFut.readError.msg
|
||||
quit(1)
|
||||
|
||||
# Code to be executed on receival of a message on filter.
|
||||
proc handler(msg: ReceivedMessage) =
|
||||
echo "MSG RECEIVED!"
|
||||
if msg.decoded.src.isSome():
|
||||
echo "Received message from ", $msg.decoded.src.get(), ": ",
|
||||
string.fromBytes(msg.decoded.payload)
|
||||
|
||||
|
||||
let
|
||||
symKey: SymKey = hexToByteArray[32]("0xa82a520aff70f7a989098376e48ec128f25f767085e84d7fb995a9815eebff0a")
|
||||
topic = hexToByteArray[4]("0x9c22ff5f") # test
|
||||
filter = initFilter(symKey = some(symKey), topics = @[topic])
|
||||
|
||||
discard node.subscribeFilter(filter, handler)
|
||||
|
||||
|
||||
proc initAsyncThread() =
|
||||
initWakuV1()
|
||||
runForever()
|
||||
|
||||
|
||||
proc startWakuV1*() =
|
||||
connThread.createThread(initAsyncThread)
|
||||
debug "Async thread created"
|
|
@ -19,7 +19,7 @@ procSuite "api":
|
|||
check statusObjResult.isOk
|
||||
let statusObj = statusObjResult.get
|
||||
check:
|
||||
statusObj.isLoggedIn == false
|
||||
statusObj.loginState == LoginState.loggedout
|
||||
statusObj.accountsGenerator != nil
|
||||
statusObj.dataDir == dataDir
|
||||
|
||||
|
@ -69,7 +69,7 @@ procSuite "api":
|
|||
check:
|
||||
# should not be able to log out when not logged in
|
||||
logoutResult.isErr
|
||||
statusObj.isLoggedIn == false
|
||||
statusObj.loginState == LoginState.loggedout
|
||||
|
||||
# var getSettingResult =
|
||||
# statusObj.getSetting(int, SettingsCol.LatestDerivedPath, 0)
|
||||
|
@ -108,7 +108,7 @@ procSuite "api":
|
|||
logoutResult = statusObj.logout()
|
||||
check:
|
||||
logoutResult.isOk
|
||||
statusObj.isLoggedIn == false
|
||||
statusObj.loginState == LoginState.loggedout
|
||||
|
||||
|
||||
check statusObj.close.isOk
|
||||
|
|
|
@ -17,5 +17,5 @@ import # test modules
|
|||
./permissions,
|
||||
./settings,
|
||||
./tokens,
|
||||
./tx_history,
|
||||
./waku_smoke
|
||||
./tx_history
|
||||
# ./waku_smoke
|
||||
|
|
Loading…
Reference in New Issue