feat: introduce Task Manager threadpool

The `TaskManager` threadpool is a memory-safe replacement for the `spawnAndSend` operations that are currently causing memory issues in status-desktop.

From a fundamental memory management point of view, `libstatus/settings`, `libstatus/contracts`, and `libstatus/tokens` (custom tokens) have all been converted to `{.threadvar.}`s and `Atomic[bool]`s to maintain the cache and `dirty` flag across threads, respectively, eliminating the need for thread locks and incorrect `{.gcsafe.}` compiler overrides.

The successful [recyclable threadpool experiment from `nim-task-runner`](https://github.com/status-im/nim-task-runner/blob/test/use-cases/test/use_cases/test_sync.nim) using `AsyncChannel[ThreadSafeString]`s was brought over to `status-desktop` and implemented in somewhat of a hardcoded manner, as we knew this would save some time instead of trying to create a fully fleshed out `nim-task-runner` API and build a miraculous macro that may or may not be able to generate the needed API.

The threadpool is started by the `TaskManager` and both the `TaskManager` and the `TaskManager`'s threadpool are started as early as possible in the application lifecycle (in `nim_status_client.nim`). The `TaskManager` creates a thread to run the threadpool. During its initialization, the threadpool then spools up all the threads it will manage and puts them in an idle thread sequence. This is to prevent expensive thread creation and teardown happening during the app's lifetime as it is quite expensive and blocks the main thread. When tasks comes in to the pool, the task is sent to an idle thread, or put in a queue if all threads are busy. The idle thread is moved to the busy thread sequence. When a task is completed, the thread is taken out of the busy threads sequence and moved back in to the sequence of idle threads, effectively recycling it.

The first `spawnAndSend` we were able to change over to the new threadpool was `estimate`, which estimates the gas of a sticker purchase transaction.

From the consumer point of view, the existing `spawnAndSend` to achieve this looks like:
```nim
  proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
    let status_stickers = self.status.stickers
    spawnAndSend(self, "setGasEstimate") do:
      var success: bool
      var estimate = status_stickers.estimateGas(packId, address, price, success)
      if not success:
        estimate = 325000
      let result: tuple[estimate: int, uuid: string] = (estimate, uuid)
      Json.encode(result)
```
And the new syntax looks like this:
```nim
  proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
    self.status.taskManager.threadPool.stickers.stickerPackPurchaseGasEstimate(cast[pointer](self.vptr), "setGasEstimate", packId, address, price, uuid)
```
The logic inside the `spawnAndSend` body was moved to [src/status/tasks/stickers.nim](https://github.com/status-im/status-desktop/compare/experiment/tasks-3?expand=1#diff-09e57eef00b0cee5c4abdb9039f948d8372e7003e09e934a9b4c7e9167d47658).

This is just the first migration of `spawnAndSend`, however moving the majority of the remaining `spawnAndSend`s will likely just be an exercise in copy/pasta. There will be one or two that may require a bit more thinking, depending how they rely on data from the model.

Once the `spawnAndSend`s have been converted to the threadpool, we can start implementing the [long-running process from the task runner use case experiments](https://github.com/status-im/nim-task-runner/blob/test/use-cases/test/use_cases/test_long_running.nim).

And finally, we can then implement the [async tasks](https://github.com/status-im/nim-task-runner/blob/test/use-cases/test/use_cases/test_async.nim) if needed.

@michaelsbradleyjr and I spent many hours digging in to the depths of nim's memory management in an attempt to understand it. We have created [a presentation with our task runner experiment findings](https://docs.google.com/presentation/d/1ItCxAfsVTcIoH_E4bgvmHljhbU-tC3T6K2A6ahwAedk/edit?usp=sharing), and @michaelsbradleyjr has spent time [answering questions off the back of that presentation.](https://gist.github.com/michaelsbradleyjr/1eaa9937b3fbb4ffff3fb814f0dd82a9).

We have created a fork of `edn.nim` at `status-im/edn.nim` and we need the PR to be merged and the commit hash updated before we can merge this PR in to `status-desktop`.
This commit is contained in:
Eric Mastro 2021-03-17 17:25:41 +11:00 committed by Iuri Matias
parent 6b6a318a8c
commit 66912fd811
20 changed files with 681 additions and 330 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ noBackup/
*.pro.user *.pro.user
*.pro.autosave *.pro.autosave
*.qml.autosave *.qml.autosave
.update.timestamp
.vscode .vscode
bin/ bin/
/bottles/ /bottles/

6
.gitmodules vendored
View File

@ -91,3 +91,9 @@
[submodule "vendor/nim-status-go"] [submodule "vendor/nim-status-go"]
path = vendor/nim-status-go path = vendor/nim-status-go
url = https://github.com/status-im/nim-status-go.git url = https://github.com/status-im/nim-status-go.git
[submodule "vendor/nim-task-runner"]
path = vendor/nim-task-runner
url = https://github.com/status-im/nim-task-runner.git
[submodule "vendor/edn.nim"]
path = vendor/edn.nim
url = https://github.com/status-im/edn.nim.git

View File

@ -5,6 +5,7 @@ import ../../../status/libstatus/stickers as status_stickers
import ../../../status/libstatus/wallet as status_wallet import ../../../status/libstatus/wallet as status_wallet
import sticker_pack_list, sticker_list, chat_item import sticker_pack_list, sticker_list, chat_item
import json_serialization import json_serialization
import ../../../status/tasks/task_manager
logScope: logScope:
topics = "stickers-view" topics = "stickers-view"
@ -30,10 +31,10 @@ QtObject:
result.recentStickers = newStickerList() result.recentStickers = newStickerList()
result.activeChannel = activeChannel result.activeChannel = activeChannel
result.setup result.setup
proc addStickerPackToList*(self: StickersView, stickerPack: StickerPack, isInstalled, isBought, isPending: bool) = proc addStickerPackToList*(self: StickersView, stickerPack: StickerPack, isInstalled, isBought, isPending: bool) =
self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought, isPending) self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought, isPending)
proc getStickerPackList(self: StickersView): QVariant {.slot.} = proc getStickerPackList(self: StickersView): QVariant {.slot.} =
newQVariant(self.stickerPacks) newQVariant(self.stickerPacks)
@ -41,19 +42,12 @@ QtObject:
read = getStickerPackList read = getStickerPackList
proc transactionWasSent*(self: StickersView, txResult: string) {.signal.} proc transactionWasSent*(self: StickersView, txResult: string) {.signal.}
proc transactionCompleted*(self: StickersView, success: bool, txHash: string, revertReason: string = "") {.signal.} proc transactionCompleted*(self: StickersView, success: bool, txHash: string, revertReason: string = "") {.signal.}
proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} = proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
let status_stickers = self.status.stickers self.status.taskManager.threadPool.stickers.stickerPackPurchaseGasEstimate(cast[pointer](self.vptr), "setGasEstimate", packId, address, price, uuid)
spawnAndSend(self, "setGasEstimate") do:
var success: bool
var estimate = status_stickers.estimateGas(packId, address, price, success)
if not success:
estimate = 325000
let result: tuple[estimate: int, uuid: string] = (estimate, uuid)
Json.encode(result)
proc gasEstimateReturned*(self: StickersView, estimate: int, uuid: string) {.signal.} proc gasEstimateReturned*(self: StickersView, estimate: int, uuid: string) {.signal.}
proc setGasEstimate*(self: StickersView, estimateJson: string) {.slot.} = proc setGasEstimate*(self: StickersView, estimateJson: string) {.slot.} =
@ -63,7 +57,7 @@ QtObject:
proc buy*(self: StickersView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} = proc buy*(self: StickersView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} =
var success: bool var success: bool
let response = self.status.stickers.buyPack(packId, address, price, gas, gasPrice, password, success) let response = self.status.stickers.buyPack(packId, address, price, gas, gasPrice, password, success)
# TODO: # TODO:
# check if response["error"] is not null and handle the error # check if response["error"] is not null and handle the error
result = $(%* { "result": %response, "success": %success }) result = $(%* { "result": %response, "success": %success })
if success: if success:
@ -133,7 +127,7 @@ QtObject:
proc resetBuyAttempt*(self: StickersView, packId: int) {.slot.} = proc resetBuyAttempt*(self: StickersView, packId: int) {.slot.} =
self.stickerPacks.updateStickerPackInList(packId, false, false) self.stickerPacks.updateStickerPackInList(packId, false, false)
proc uninstall*(self: StickersView, packId: int) {.slot.} = proc uninstall*(self: StickersView, packId: int) {.slot.} =
self.status.stickers.uninstallStickerPack(packId) self.status.stickers.uninstallStickerPack(packId)
self.status.stickers.removeRecentStickers(packId) self.status.stickers.removeRecentStickers(packId)
@ -156,4 +150,4 @@ QtObject:
proc send*(self: StickersView, hash: string, pack: int) {.slot.} = proc send*(self: StickersView, hash: string, pack: int) {.slot.} =
let sticker = Sticker(hash: hash, packId: pack) let sticker = Sticker(hash: hash, packId: pack)
self.addRecentStickerToList(sticker) self.addRecentStickerToList(sticker)
self.status.chat.sendSticker(self.activeChannel.id, sticker) self.status.chat.sendSticker(self.activeChannel.id, sticker)

View File

@ -491,9 +491,8 @@ QtObject:
proc gasPricePredictionsChanged*(self: WalletView) {.signal.} proc gasPricePredictionsChanged*(self: WalletView) {.signal.}
proc getGasPricePredictions*(self: WalletView) {.slot.} = proc getGasPricePredictions*(self: WalletView) {.slot.} =
let walletModel = self.status.wallet
spawnAndSend(self, "getGasPricePredictionsResult") do: spawnAndSend(self, "getGasPricePredictionsResult") do:
$ %walletModel.getGasPricePredictions() $ %getGasPricePredictions2()
proc getGasPricePredictionsResult(self: WalletView, gasPricePredictionsJson: string) {.slot.} = proc getGasPricePredictionsResult(self: WalletView, gasPricePredictionsJson: string) {.slot.} =
let prediction = Json.decode(gasPricePredictionsJson, GasPricePrediction) let prediction = Json.decode(gasPricePredictionsJson, GasPricePrediction)

View File

@ -10,11 +10,13 @@ import app/onboarding/core as onboarding
import app/login/core as login import app/login/core as login
import app/provider/core as provider import app/provider/core as provider
import status/signals/core as signals import status/signals/core as signals
import status/tasks/task_manager
import status/libstatus/types import status/libstatus/types
import status/libstatus/accounts/constants import status/libstatus/accounts/constants
import status_go import status_go
import status/status as statuslib import status/status as statuslib
import ./eventemitter import ./eventemitter
import chronos, task_runner
var signalsQObjPointer: pointer var signalsQObjPointer: pointer
@ -28,7 +30,9 @@ proc mainProc() =
else: else:
"/../fleets.json" "/../fleets.json"
let status = statuslib.newStatusInstance(readFile(joinPath(getAppDir(), fleets))) let taskManager = newTaskManager()
taskManager.init()
let status = statuslib.newStatusInstance(taskManager, readFile(joinPath(getAppDir(), fleets)))
status.initNode() status.initNode()
enableHDPI() enableHDPI()
@ -132,6 +136,7 @@ proc mainProc() =
wallet.checkPendingTransactions() wallet.checkPendingTransactions()
wallet.start() wallet.start()
engine.setRootContextProperty("loginModel", login.variant) engine.setRootContextProperty("loginModel", login.variant)
engine.setRootContextProperty("onboardingModel", onboarding.variant) engine.setRootContextProperty("onboardingModel", onboarding.variant)
@ -152,6 +157,7 @@ proc mainProc() =
profile.delete() profile.delete()
utilsController.delete() utilsController.delete()
browserController.delete() browserController.delete()
taskManager.teardown()
# Initialize only controllers whose init functions # Initialize only controllers whose init functions
@ -188,9 +194,7 @@ proc mainProc() =
# it will be passed as a regular C function to libstatus. This means that # it will be passed as a regular C function to libstatus. This means that
# we cannot capture any local variables here (we must rely on globals) # we cannot capture any local variables here (we must rely on globals)
var callback: SignalCallback = proc(p0: cstring) {.cdecl.} = var callback: SignalCallback = proc(p0: cstring) {.cdecl.} =
setupForeignThreadGc()
signal_handler(signalsQObjPointer, p0, "receiveSignal") signal_handler(signalsQObjPointer, p0, "receiveSignal")
tearDownForeignThreadGc()
status_go.setSignalEventCallback(callback) status_go.setSignalEventCallback(callback)

View File

@ -21,12 +21,12 @@ const domain* = ".stateofus.eth"
proc userName*(ensName: string, removeSuffix: bool = false): string = proc userName*(ensName: string, removeSuffix: bool = false): string =
if ensName != "" and ensName.endsWith(domain): if ensName != "" and ensName.endsWith(domain):
if removeSuffix: if removeSuffix:
result = ensName.split(".")[0] result = ensName.split(".")[0]
else: else:
result = ensName result = ensName
else: else:
if ensName.endsWith(".eth") and removeSuffix: if ensName.endsWith(".eth") and removeSuffix:
return ensName.split(".")[0] return ensName.split(".")[0]
result = ensName result = ensName
@ -63,7 +63,7 @@ proc namehash*(ensName:string): string =
concatArrays[0..31] = node concatArrays[0..31] = node
concatArrays[32..63] = elem concatArrays[32..63] = elem
node = keccak_256.digest(concatArrays).data node = keccak_256.digest(concatArrays).data
result = "0x" & node.toHex() result = "0x" & node.toHex()
const registry* = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" const registry* = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
@ -81,7 +81,7 @@ proc resolver*(usernameHash: string): string =
result = "0x" & resolverAddr result = "0x" & resolverAddr
const owner_signature = "0x02571be3" # owner(bytes32 node) const owner_signature = "0x02571be3" # owner(bytes32 node)
proc owner*(username: string): string = proc owner*(username: string): string =
var userNameHash = namehash(addDomain(username)) var userNameHash = namehash(addDomain(username))
userNameHash.removePrefix("0x") userNameHash.removePrefix("0x")
let payload = %* [{ let payload = %* [{
@ -97,7 +97,7 @@ proc owner*(username: string): string =
result = "0x" & ownerAddr.substr(26) result = "0x" & ownerAddr.substr(26)
const pubkey_signature = "0xc8690233" # pubkey(bytes32 node) const pubkey_signature = "0xc8690233" # pubkey(bytes32 node)
proc pubkey*(username: string): string = proc pubkey*(username: string): string =
var userNameHash = namehash(addDomain(username)) var userNameHash = namehash(addDomain(username))
userNameHash.removePrefix("0x") userNameHash.removePrefix("0x")
let ensResolver = resolver(userNameHash) let ensResolver = resolver(userNameHash)
@ -116,7 +116,7 @@ proc pubkey*(username: string): string =
result = "0x04" & pubkey result = "0x04" & pubkey
const address_signature = "0x3b3b57de" # addr(bytes32 node) const address_signature = "0x3b3b57de" # addr(bytes32 node)
proc address*(username: string): string = proc address*(username: string): string =
var userNameHash = namehash(addDomain(username)) var userNameHash = namehash(addDomain(username))
userNameHash.removePrefix("0x") userNameHash.removePrefix("0x")
let ensResolver = resolver(userNameHash) let ensResolver = resolver(userNameHash)
@ -133,7 +133,7 @@ proc address*(username: string): string =
result = "0x" & address.substr(26) result = "0x" & address.substr(26)
const contenthash_signature = "0xbc1c58d1" # contenthash(bytes32) const contenthash_signature = "0xbc1c58d1" # contenthash(bytes32)
proc contenthash*(ensAddr: string): string = proc contenthash*(ensAddr: string): string =
var ensHash = namehash(ensAddr) var ensHash = namehash(ensAddr)
ensHash.removePrefix("0x") ensHash.removePrefix("0x")
let ensResolver = resolver(ensHash) let ensResolver = resolver(ensHash)
@ -147,7 +147,7 @@ proc contenthash*(ensAddr: string): string =
let bytesResponse = response.parseJson["result"].getStr; let bytesResponse = response.parseJson["result"].getStr;
if bytesResponse == "0x": if bytesResponse == "0x":
return "" return ""
let size = fromHex(Stuint[256], bytesResponse[66..129]).truncate(int) let size = fromHex(Stuint[256], bytesResponse[66..129]).truncate(int)
result = bytesResponse[130..129+size*2] result = bytesResponse[130..129+size*2]
@ -159,7 +159,7 @@ proc getPrice*(): Stuint[256] =
"to": $contract.address, "to": $contract.address,
"data": contract.methods["getPrice"].encodeAbi() "data": contract.methods["getPrice"].encodeAbi()
}, "latest"] }, "latest"]
let responseStr = callPrivateRPC("eth_call", payload) let responseStr = callPrivateRPC("eth_call", payload)
let response = Json.decode(responseStr, RpcResponse) let response = Json.decode(responseStr, RpcResponse)
if not response.error.isNil: if not response.error.isNil:
@ -180,7 +180,7 @@ proc registerUsernameEstimateGas*(username: string, address: string, pubKey: str
ensUsernamesContract = contracts.getContract("ens-usernames") ensUsernamesContract = contracts.getContract("ens-usernames")
sntContract = contracts.getSntContract() sntContract = contracts.getSntContract()
price = getPrice() price = getPrice()
let let
register = Register(label: label, account: parseAddress(address), x: x, y: y) register = Register(label: label, account: parseAddress(address), x: x, y: y)
registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register) registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register)
@ -188,7 +188,7 @@ proc registerUsernameEstimateGas*(username: string, address: string, pubKey: str
approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(approveAndCallObj) approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(approveAndCallObj)
var tx = transactions.buildTokenTransaction(parseAddress(address), sntContract.address, "", "") var tx = transactions.buildTokenTransaction(parseAddress(address), sntContract.address, "", "")
let response = sntContract.methods["approveAndCall"].estimateGas(tx, approveAndCallObj, success) let response = sntContract.methods["approveAndCall"].estimateGas(tx, approveAndCallObj, success)
if success: if success:
result = fromHex[int](response) result = fromHex[int](response)
@ -202,11 +202,11 @@ proc registerUsername*(username, pubKey, address, gas, gasPrice, password: stri
ensUsernamesContract = contracts.getContract("ens-usernames") ensUsernamesContract = contracts.getContract("ens-usernames")
sntContract = contracts.getSntContract() sntContract = contracts.getSntContract()
price = getPrice() price = getPrice()
let let
register = Register(label: label, account: parseAddress(address), x: x, y: y) register = Register(label: label, account: parseAddress(address), x: x, y: y)
registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register) registerAbiEncoded = ensUsernamesContract.methods["register"].encodeAbi(register)
approveAndCallObj = ApproveAndCall[132](to: ensUsernamesContract.address, value: price, data: DynamicBytes[132].fromHex(registerAbiEncoded)) approveAndCallObj = ApproveAndCall[132](to: ensUsernamesContract.address, value: price, data: DynamicBytes[132].fromHex(registerAbiEncoded))
var tx = transactions.buildTokenTransaction(parseAddress(address), sntContract.address, gas, gasPrice) var tx = transactions.buildTokenTransaction(parseAddress(address), sntContract.address, gas, gasPrice)
@ -227,7 +227,7 @@ proc setPubKeyEstimateGas*(username: string, address: string, pubKey: string, su
resolverAddress = resolver(hash) resolverAddress = resolver(hash)
var tx = transactions.buildTokenTransaction(parseAddress(address), parseAddress(resolverAddress), "", "") var tx = transactions.buildTokenTransaction(parseAddress(address), parseAddress(resolverAddress), "", "")
try: try:
let response = resolverContract.methods["setPubkey"].estimateGas(tx, setPubkey, success) let response = resolverContract.methods["setPubkey"].estimateGas(tx, setPubkey, success)
if success: if success:
@ -295,8 +295,10 @@ proc decodeENSContentHash*(value: string): tuple[ensType: ENSType, output: strin
# 12 = identifies sha2-256 hash # 12 = identifies sha2-256 hash
# 20 = multihash length = 32 # 20 = multihash length = 32
# ...rest = multihash digest # ...rest = multihash digest
let multiHash = MultiHash.init(nimcrypto.fromHex(multiHashStr)).get() let
return (ENSType.IPFS, $Cid.init(CIDv0, MultiCodec.codec(codec), multiHash)) multiHash = MultiHash.init(nimcrypto.fromHex(multiHashStr)).get()
decoded = Cid.init(CIDv0, MultiCodec.codec(codec), multiHash).get()
return (ENSType.IPFS, $decoded)
except Exception as e: except Exception as e:
error "Error decoding ENS contenthash", hash=value, exception=e.msg error "Error decoding ENS contenthash", hash=value, exception=e.msg
raise raise

View File

@ -1,5 +1,5 @@
import import
sequtils, sugar, macros, tables, strutils, locks sequtils, sugar, macros, tables, strutils
import import
web3/ethtypes, stew/byteutils, nimcrypto, json_serialization, chronicles web3/ethtypes, stew/byteutils, nimcrypto, json_serialization, chronicles
@ -16,9 +16,6 @@ export
logScope: logScope:
topics = "contracts" topics = "contracts"
var contractsLock: Lock
initLock(contractsLock)
const ERC20_METHODS = @[ const ERC20_METHODS = @[
("name", Method(signature: "name()")), ("name", Method(signature: "name()")),
("symbol", Method(signature: "symbol()")), ("symbol", Method(signature: "symbol()")),
@ -80,186 +77,193 @@ proc newErc721Contract(name: string, network: Network, address: Address, symbol:
Erc721Contract(name: name, network: network, address: address, symbol: symbol, hasIcon: hasIcon, methods: ERC721_ENUMERABLE_METHODS.concat(addlMethods).toTable) Erc721Contract(name: name, network: network, address: address, symbol: symbol, hasIcon: hasIcon, methods: ERC721_ENUMERABLE_METHODS.concat(addlMethods).toTable)
var ALL_CONTRACTS {.guard: contractsLock.}: seq[Contract] = @[ var
# Mainnet contracts contracts {.threadvar.}: seq[Contract]
newErc20Contract("Status Network Token", Network.Mainnet, parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"), "SNT", 18, true), contractsInited {.threadvar.}: bool
newErc20Contract("Dai Stablecoin", Network.Mainnet, parseAddress("0x6b175474e89094c44da98b954eedeac495271d0f"), "DAI", 18, true), proc allContracts(): seq[Contract] =
newErc20Contract("Sai Stablecoin v1.0", Network.Mainnet, parseAddress("0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"), "SAI", 18, true), if contractsInited:
newErc20Contract("MKR", Network.Mainnet, parseAddress("0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2"), "MKR", 18, true), result = contracts
newErc20Contract("EOS", Network.Mainnet, parseAddress("0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"), "EOS", 18, true), else:
newErc20Contract("OMGToken", Network.Mainnet, parseAddress("0xd26114cd6ee289accf82350c8d8487fedb8a0c07"), "OMG", 18, true), contracts = @[
newErc20Contract("Populous Platform", Network.Mainnet, parseAddress("0xd4fa1460f537bb9085d22c7bccb5dd450ef28e3a"), "PPT", 8, true), # Mainnet contracts
newErc20Contract("Reputation", Network.Mainnet, parseAddress("0x1985365e9f78359a9b6ad760e32412f4a445e862"), "REP", 18, true), newErc20Contract("Status Network Token", Network.Mainnet, parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"), "SNT", 18, true),
newErc20Contract("PowerLedger", Network.Mainnet, parseAddress("0x595832f8fc6bf59c85c527fec3740a1b7a361269"), "POWR", 6, true), newErc20Contract("Dai Stablecoin", Network.Mainnet, parseAddress("0x6b175474e89094c44da98b954eedeac495271d0f"), "DAI", 18, true),
newErc20Contract("TenX Pay Token", Network.Mainnet, parseAddress("0xb97048628db6b661d4c2aa833e95dbe1a905b280"), "PAY", 18, true), newErc20Contract("Sai Stablecoin v1.0", Network.Mainnet, parseAddress("0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"), "SAI", 18, true),
newErc20Contract("Veros", Network.Mainnet, parseAddress("0x92e78dae1315067a8819efd6dca432de9dcde2e9"), "VRS", 6, false), newErc20Contract("MKR", Network.Mainnet, parseAddress("0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2"), "MKR", 18, true),
newErc20Contract("Golem Network Token", Network.Mainnet, parseAddress("0xa74476443119a942de498590fe1f2454d7d4ac0d"), "GNT", 18, true), newErc20Contract("EOS", Network.Mainnet, parseAddress("0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"), "EOS", 18, true),
newErc20Contract("Salt", Network.Mainnet, parseAddress("0x4156d3342d5c385a87d264f90653733592000581"), "SALT", 8, true), newErc20Contract("OMGToken", Network.Mainnet, parseAddress("0xd26114cd6ee289accf82350c8d8487fedb8a0c07"), "OMG", 18, true),
newErc20Contract("BNB", Network.Mainnet, parseAddress("0xb8c77482e45f1f44de1745f52c74426c631bdd52"), "BNB", 18, true), newErc20Contract("Populous Platform", Network.Mainnet, parseAddress("0xd4fa1460f537bb9085d22c7bccb5dd450ef28e3a"), "PPT", 8, true),
newErc20Contract("Basic Attention Token", Network.Mainnet, parseAddress("0x0d8775f648430679a709e98d2b0cb6250d2887ef"), "BAT", 18, true), newErc20Contract("Reputation", Network.Mainnet, parseAddress("0x1985365e9f78359a9b6ad760e32412f4a445e862"), "REP", 18, true),
newErc20Contract("Kyber Network Crystal", Network.Mainnet, parseAddress("0xdd974d5c2e2928dea5f71b9825b8b646686bd200"), "KNC", 18, true), newErc20Contract("PowerLedger", Network.Mainnet, parseAddress("0x595832f8fc6bf59c85c527fec3740a1b7a361269"), "POWR", 6, true),
newErc20Contract("BTU Protocol", Network.Mainnet, parseAddress("0xb683D83a532e2Cb7DFa5275eED3698436371cc9f"), "BTU", 18, true), newErc20Contract("TenX Pay Token", Network.Mainnet, parseAddress("0xb97048628db6b661d4c2aa833e95dbe1a905b280"), "PAY", 18, true),
newErc20Contract("Digix DAO", Network.Mainnet, parseAddress("0xe0b7927c4af23765cb51314a0e0521a9645f0e2a"), "DGD", 9, true), newErc20Contract("Veros", Network.Mainnet, parseAddress("0x92e78dae1315067a8819efd6dca432de9dcde2e9"), "VRS", 6, false),
newErc20Contract("Aeternity", Network.Mainnet, parseAddress("0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d"), "AE", 18, true), newErc20Contract("Golem Network Token", Network.Mainnet, parseAddress("0xa74476443119a942de498590fe1f2454d7d4ac0d"), "GNT", 18, true),
newErc20Contract("Tronix", Network.Mainnet, parseAddress("0xf230b790e05390fc8295f4d3f60332c93bed42e2"), "TRX", 6, true), newErc20Contract("Salt", Network.Mainnet, parseAddress("0x4156d3342d5c385a87d264f90653733592000581"), "SALT", 8, true),
newErc20Contract("Ethos", Network.Mainnet, parseAddress("0x5af2be193a6abca9c8817001f45744777db30756"), "ETHOS", 8, true), newErc20Contract("BNB", Network.Mainnet, parseAddress("0xb8c77482e45f1f44de1745f52c74426c631bdd52"), "BNB", 18, true),
newErc20Contract("Raiden Token", Network.Mainnet, parseAddress("0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6"), "RDN", 18, true), newErc20Contract("Basic Attention Token", Network.Mainnet, parseAddress("0x0d8775f648430679a709e98d2b0cb6250d2887ef"), "BAT", 18, true),
newErc20Contract("SingularDTV", Network.Mainnet, parseAddress("0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009"), "SNGLS", 0, true), newErc20Contract("Kyber Network Crystal", Network.Mainnet, parseAddress("0xdd974d5c2e2928dea5f71b9825b8b646686bd200"), "KNC", 18, true),
newErc20Contract("Gnosis Token", Network.Mainnet, parseAddress("0x6810e776880c02933d47db1b9fc05908e5386b96"), "GNO", 18, true), newErc20Contract("BTU Protocol", Network.Mainnet, parseAddress("0xb683D83a532e2Cb7DFa5275eED3698436371cc9f"), "BTU", 18, true),
newErc20Contract("StorjToken", Network.Mainnet, parseAddress("0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac"), "STORJ", 8, true), newErc20Contract("Digix DAO", Network.Mainnet, parseAddress("0xe0b7927c4af23765cb51314a0e0521a9645f0e2a"), "DGD", 9, true),
newErc20Contract("AdEx", Network.Mainnet, parseAddress("0x4470bb87d77b963a013db939be332f927f2b992e"), "ADX", 4, false), newErc20Contract("Aeternity", Network.Mainnet, parseAddress("0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d"), "AE", 18, true),
newErc20Contract("FunFair", Network.Mainnet, parseAddress("0x419d0d8bdd9af5e606ae2232ed285aff190e711b"), "FUN", 8, true), newErc20Contract("Tronix", Network.Mainnet, parseAddress("0xf230b790e05390fc8295f4d3f60332c93bed42e2"), "TRX", 6, true),
newErc20Contract("Civic", Network.Mainnet, parseAddress("0x41e5560054824ea6b0732e656e3ad64e20e94e45"), "CVC", 8, true), newErc20Contract("Ethos", Network.Mainnet, parseAddress("0x5af2be193a6abca9c8817001f45744777db30756"), "ETHOS", 8, true),
newErc20Contract("ICONOMI", Network.Mainnet, parseAddress("0x888666ca69e0f178ded6d75b5726cee99a87d698"), "ICN", 18, true), newErc20Contract("Raiden Token", Network.Mainnet, parseAddress("0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6"), "RDN", 18, true),
newErc20Contract("Walton Token", Network.Mainnet, parseAddress("0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74"), "WTC", 18, true), newErc20Contract("SingularDTV", Network.Mainnet, parseAddress("0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009"), "SNGLS", 0, true),
newErc20Contract("Bytom", Network.Mainnet, parseAddress("0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750"), "BTM", 8, true), newErc20Contract("Gnosis Token", Network.Mainnet, parseAddress("0x6810e776880c02933d47db1b9fc05908e5386b96"), "GNO", 18, true),
newErc20Contract("0x Protocol Token", Network.Mainnet, parseAddress("0xe41d2489571d322189246dafa5ebde1f4699f498"), "ZRX", 18, true), newErc20Contract("StorjToken", Network.Mainnet, parseAddress("0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac"), "STORJ", 8, true),
newErc20Contract("Bancor Network Token", Network.Mainnet, parseAddress("0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c"), "BNT", 18, true), newErc20Contract("AdEx", Network.Mainnet, parseAddress("0x4470bb87d77b963a013db939be332f927f2b992e"), "ADX", 4, false),
newErc20Contract("Metal", Network.Mainnet, parseAddress("0xf433089366899d83a9f26a773d59ec7ecf30355e"), "MTL", 8, false), newErc20Contract("FunFair", Network.Mainnet, parseAddress("0x419d0d8bdd9af5e606ae2232ed285aff190e711b"), "FUN", 8, true),
newErc20Contract("PayPie", Network.Mainnet, parseAddress("0xc42209accc14029c1012fb5680d95fbd6036e2a0"), "PPP", 18, true), newErc20Contract("Civic", Network.Mainnet, parseAddress("0x41e5560054824ea6b0732e656e3ad64e20e94e45"), "CVC", 8, true),
newErc20Contract("ChainLink Token", Network.Mainnet, parseAddress("0x514910771af9ca656af840dff83e8264ecf986ca"), "LINK", 18, true), newErc20Contract("ICONOMI", Network.Mainnet, parseAddress("0x888666ca69e0f178ded6d75b5726cee99a87d698"), "ICN", 18, true),
newErc20Contract("Kin", Network.Mainnet, parseAddress("0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), "KIN", 18, true), newErc20Contract("Walton Token", Network.Mainnet, parseAddress("0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74"), "WTC", 18, true),
newErc20Contract("Aragon Network Token", Network.Mainnet, parseAddress("0x960b236a07cf122663c4303350609a66a7b288c0"), "ANT", 18, true), newErc20Contract("Bytom", Network.Mainnet, parseAddress("0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750"), "BTM", 8, true),
newErc20Contract("MobileGo Token", Network.Mainnet, parseAddress("0x40395044ac3c0c57051906da938b54bd6557f212"), "MGO", 8, true), newErc20Contract("0x Protocol Token", Network.Mainnet, parseAddress("0xe41d2489571d322189246dafa5ebde1f4699f498"), "ZRX", 18, true),
newErc20Contract("Monaco", Network.Mainnet, parseAddress("0xb63b606ac810a52cca15e44bb630fd42d8d1d83d"), "MCO", 8, true), newErc20Contract("Bancor Network Token", Network.Mainnet, parseAddress("0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c"), "BNT", 18, true),
newErc20Contract("loopring", Network.Mainnet, parseAddress("0xef68e7c694f40c8202821edf525de3782458639f"), "LRC", 18, true), newErc20Contract("Metal", Network.Mainnet, parseAddress("0xf433089366899d83a9f26a773d59ec7ecf30355e"), "MTL", 8, false),
newErc20Contract("Zeus Shield Coin", Network.Mainnet, parseAddress("0x7a41e0517a5eca4fdbc7fbeba4d4c47b9ff6dc63"), "ZSC", 18, true), newErc20Contract("PayPie", Network.Mainnet, parseAddress("0xc42209accc14029c1012fb5680d95fbd6036e2a0"), "PPP", 18, true),
newErc20Contract("Streamr DATAcoin", Network.Mainnet, parseAddress("0x0cf0ee63788a0849fe5297f3407f701e122cc023"), "DATA", 18, true), newErc20Contract("ChainLink Token", Network.Mainnet, parseAddress("0x514910771af9ca656af840dff83e8264ecf986ca"), "LINK", 18, true),
newErc20Contract("Ripio Credit Network Token", Network.Mainnet, parseAddress("0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6"), "RCN", 18, true), newErc20Contract("Kin", Network.Mainnet, parseAddress("0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), "KIN", 18, true),
newErc20Contract("WINGS", Network.Mainnet, parseAddress("0x667088b212ce3d06a1b553a7221e1fd19000d9af"), "WINGS", 18, true), newErc20Contract("Aragon Network Token", Network.Mainnet, parseAddress("0x960b236a07cf122663c4303350609a66a7b288c0"), "ANT", 18, true),
newErc20Contract("Edgeless", Network.Mainnet, parseAddress("0x08711d3b02c8758f2fb3ab4e80228418a7f8e39c"), "EDG", 0, true), newErc20Contract("MobileGo Token", Network.Mainnet, parseAddress("0x40395044ac3c0c57051906da938b54bd6557f212"), "MGO", 8, true),
newErc20Contract("Melon Token", Network.Mainnet, parseAddress("0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1"), "MLN", 18, true), newErc20Contract("Monaco", Network.Mainnet, parseAddress("0xb63b606ac810a52cca15e44bb630fd42d8d1d83d"), "MCO", 8, true),
newErc20Contract("Moeda Loyalty Points", Network.Mainnet, parseAddress("0x51db5ad35c671a87207d88fc11d593ac0c8415bd"), "MDA", 18, true), newErc20Contract("loopring", Network.Mainnet, parseAddress("0xef68e7c694f40c8202821edf525de3782458639f"), "LRC", 18, true),
newErc20Contract("PILLAR", Network.Mainnet, parseAddress("0xe3818504c1b32bf1557b16c238b2e01fd3149c17"), "PLR", 18, true), newErc20Contract("Zeus Shield Coin", Network.Mainnet, parseAddress("0x7a41e0517a5eca4fdbc7fbeba4d4c47b9ff6dc63"), "ZSC", 18, true),
newErc20Contract("QRL", Network.Mainnet, parseAddress("0x697beac28b09e122c4332d163985e8a73121b97f"), "QRL", 8, true), newErc20Contract("Streamr DATAcoin", Network.Mainnet, parseAddress("0x0cf0ee63788a0849fe5297f3407f701e122cc023"), "DATA", 18, true),
newErc20Contract("Modum Token", Network.Mainnet, parseAddress("0x957c30ab0426e0c93cd8241e2c60392d08c6ac8e"), "MOD", 0, true), newErc20Contract("Ripio Credit Network Token", Network.Mainnet, parseAddress("0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6"), "RCN", 18, true),
newErc20Contract("Token-as-a-Service", Network.Mainnet, parseAddress("0xe7775a6e9bcf904eb39da2b68c5efb4f9360e08c"), "TAAS", 6, true), newErc20Contract("WINGS", Network.Mainnet, parseAddress("0x667088b212ce3d06a1b553a7221e1fd19000d9af"), "WINGS", 18, true),
newErc20Contract("GRID Token", Network.Mainnet, parseAddress("0x12b19d3e2ccc14da04fae33e63652ce469b3f2fd"), "GRID", 12, true), newErc20Contract("Edgeless", Network.Mainnet, parseAddress("0x08711d3b02c8758f2fb3ab4e80228418a7f8e39c"), "EDG", 0, true),
newErc20Contract("SANtiment network token", Network.Mainnet, parseAddress("0x7c5a0ce9267ed19b22f8cae653f198e3e8daf098"), "SAN", 18, true), newErc20Contract("Melon Token", Network.Mainnet, parseAddress("0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1"), "MLN", 18, true),
newErc20Contract("SONM Token", Network.Mainnet, parseAddress("0x983f6d60db79ea8ca4eb9968c6aff8cfa04b3c63"), "SNM", 18, true), newErc20Contract("Moeda Loyalty Points", Network.Mainnet, parseAddress("0x51db5ad35c671a87207d88fc11d593ac0c8415bd"), "MDA", 18, true),
newErc20Contract("Request Token", Network.Mainnet, parseAddress("0x8f8221afbb33998d8584a2b05749ba73c37a938a"), "REQ", 18, true), newErc20Contract("PILLAR", Network.Mainnet, parseAddress("0xe3818504c1b32bf1557b16c238b2e01fd3149c17"), "PLR", 18, true),
newErc20Contract("Substratum", Network.Mainnet, parseAddress("0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a"), "SUB", 2, true), newErc20Contract("QRL", Network.Mainnet, parseAddress("0x697beac28b09e122c4332d163985e8a73121b97f"), "QRL", 8, true),
newErc20Contract("Decentraland MANA", Network.Mainnet, parseAddress("0x0f5d2fb29fb7d3cfee444a200298f468908cc942"), "MANA", 18, true), newErc20Contract("Modum Token", Network.Mainnet, parseAddress("0x957c30ab0426e0c93cd8241e2c60392d08c6ac8e"), "MOD", 0, true),
newErc20Contract("AirSwap Token", Network.Mainnet, parseAddress("0x27054b13b1b798b345b591a4d22e6562d47ea75a"), "AST", 4, true), newErc20Contract("Token-as-a-Service", Network.Mainnet, parseAddress("0xe7775a6e9bcf904eb39da2b68c5efb4f9360e08c"), "TAAS", 6, true),
newErc20Contract("R token", Network.Mainnet, parseAddress("0x48f775efbe4f5ece6e0df2f7b5932df56823b990"), "R", 0, true), newErc20Contract("GRID Token", Network.Mainnet, parseAddress("0x12b19d3e2ccc14da04fae33e63652ce469b3f2fd"), "GRID", 12, true),
newErc20Contract("FirstBlood Token", Network.Mainnet, parseAddress("0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7"), "1ST", 18, true), newErc20Contract("SANtiment network token", Network.Mainnet, parseAddress("0x7c5a0ce9267ed19b22f8cae653f198e3e8daf098"), "SAN", 18, true),
newErc20Contract("Cofoundit", Network.Mainnet, parseAddress("0x12fef5e57bf45873cd9b62e9dbd7bfb99e32d73e"), "CFI", 18, true), newErc20Contract("SONM Token", Network.Mainnet, parseAddress("0x983f6d60db79ea8ca4eb9968c6aff8cfa04b3c63"), "SNM", 18, true),
newErc20Contract("Enigma", Network.Mainnet, parseAddress("0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4"), "ENG", 8, true), newErc20Contract("Request Token", Network.Mainnet, parseAddress("0x8f8221afbb33998d8584a2b05749ba73c37a938a"), "REQ", 18, true),
newErc20Contract("Amber Token", Network.Mainnet, parseAddress("0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce"), "AMB", 18, true), newErc20Contract("Substratum", Network.Mainnet, parseAddress("0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a"), "SUB", 2, true),
newErc20Contract("XPlay Token", Network.Mainnet, parseAddress("0x90528aeb3a2b736b780fd1b6c478bb7e1d643170"), "XPA", 18, true), newErc20Contract("Decentraland MANA", Network.Mainnet, parseAddress("0x0f5d2fb29fb7d3cfee444a200298f468908cc942"), "MANA", 18, true),
newErc20Contract("Open Trading Network", Network.Mainnet, parseAddress("0x881ef48211982d01e2cb7092c915e647cd40d85c"), "OTN", 18, true), newErc20Contract("AirSwap Token", Network.Mainnet, parseAddress("0x27054b13b1b798b345b591a4d22e6562d47ea75a"), "AST", 4, true),
newErc20Contract("Trustcoin", Network.Mainnet, parseAddress("0xcb94be6f13a1182e4a4b6140cb7bf2025d28e41b"), "TRST", 6, true), newErc20Contract("R token", Network.Mainnet, parseAddress("0x48f775efbe4f5ece6e0df2f7b5932df56823b990"), "R", 0, true),
newErc20Contract("Monolith TKN", Network.Mainnet, parseAddress("0xaaaf91d9b90df800df4f55c205fd6989c977e73a"), "TKN", 8, true), newErc20Contract("FirstBlood Token", Network.Mainnet, parseAddress("0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7"), "1ST", 18, true),
newErc20Contract("RHOC", Network.Mainnet, parseAddress("0x168296bb09e24a88805cb9c33356536b980d3fc5"), "RHOC", 8, true), newErc20Contract("Cofoundit", Network.Mainnet, parseAddress("0x12fef5e57bf45873cd9b62e9dbd7bfb99e32d73e"), "CFI", 18, true),
newErc20Contract("Target Coin", Network.Mainnet, parseAddress("0xac3da587eac229c9896d919abc235ca4fd7f72c1"), "TGT", 1, false), newErc20Contract("Enigma", Network.Mainnet, parseAddress("0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4"), "ENG", 8, true),
newErc20Contract("Everex", Network.Mainnet, parseAddress("0xf3db5fa2c66b7af3eb0c0b782510816cbe4813b8"), "EVX", 4, true), newErc20Contract("Amber Token", Network.Mainnet, parseAddress("0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce"), "AMB", 18, true),
newErc20Contract("ICOS", Network.Mainnet, parseAddress("0x014b50466590340d41307cc54dcee990c8d58aa8"), "ICOS", 6, true), newErc20Contract("XPlay Token", Network.Mainnet, parseAddress("0x90528aeb3a2b736b780fd1b6c478bb7e1d643170"), "XPA", 18, true),
newErc20Contract("district0x Network Token", Network.Mainnet, parseAddress("0x0abdace70d3790235af448c88547603b945604ea"), "DNT", 18, true), newErc20Contract("Open Trading Network", Network.Mainnet, parseAddress("0x881ef48211982d01e2cb7092c915e647cd40d85c"), "OTN", 18, true),
newErc20Contract("Dentacoin", Network.Mainnet, parseAddress("0x08d32b0da63e2c3bcf8019c9c5d849d7a9d791e6"), "٨", 0, false), newErc20Contract("Trustcoin", Network.Mainnet, parseAddress("0xcb94be6f13a1182e4a4b6140cb7bf2025d28e41b"), "TRST", 6, true),
newErc20Contract("Eidoo Token", Network.Mainnet, parseAddress("0xced4e93198734ddaff8492d525bd258d49eb388e"), "EDO", 18, true), newErc20Contract("Monolith TKN", Network.Mainnet, parseAddress("0xaaaf91d9b90df800df4f55c205fd6989c977e73a"), "TKN", 8, true),
newErc20Contract("BitDice", Network.Mainnet, parseAddress("0x29d75277ac7f0335b2165d0895e8725cbf658d73"), "CSNO", 8, false), newErc20Contract("RHOC", Network.Mainnet, parseAddress("0x168296bb09e24a88805cb9c33356536b980d3fc5"), "RHOC", 8, true),
newErc20Contract("Cobinhood Token", Network.Mainnet, parseAddress("0xb2f7eb1f2c37645be61d73953035360e768d81e6"), "COB", 18, true), newErc20Contract("Target Coin", Network.Mainnet, parseAddress("0xac3da587eac229c9896d919abc235ca4fd7f72c1"), "TGT", 1, false),
newErc20Contract("Enjin Coin", Network.Mainnet, parseAddress("0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c"), "ENJ", 18, false), newErc20Contract("Everex", Network.Mainnet, parseAddress("0xf3db5fa2c66b7af3eb0c0b782510816cbe4813b8"), "EVX", 4, true),
newErc20Contract("AVENTUS", Network.Mainnet, parseAddress("0x0d88ed6e74bbfd96b831231638b66c05571e824f"), "AVT", 18, false), newErc20Contract("ICOS", Network.Mainnet, parseAddress("0x014b50466590340d41307cc54dcee990c8d58aa8"), "ICOS", 6, true),
newErc20Contract("Chronobank TIME", Network.Mainnet, parseAddress("0x6531f133e6deebe7f2dce5a0441aa7ef330b4e53"), "TIME", 8, false), newErc20Contract("district0x Network Token", Network.Mainnet, parseAddress("0x0abdace70d3790235af448c88547603b945604ea"), "DNT", 18, true),
newErc20Contract("Cindicator Token", Network.Mainnet, parseAddress("0xd4c435f5b09f855c3317c8524cb1f586e42795fa"), "CND", 18, true), newErc20Contract("Dentacoin", Network.Mainnet, parseAddress("0x08d32b0da63e2c3bcf8019c9c5d849d7a9d791e6"), "٨", 0, false),
newErc20Contract("Stox", Network.Mainnet, parseAddress("0x006bea43baa3f7a6f765f14f10a1a1b08334ef45"), "STX", 18, true), newErc20Contract("Eidoo Token", Network.Mainnet, parseAddress("0xced4e93198734ddaff8492d525bd258d49eb388e"), "EDO", 18, true),
newErc20Contract("Xaurum", Network.Mainnet, parseAddress("0x4df812f6064def1e5e029f1ca858777cc98d2d81"), "XAUR", 8, true), newErc20Contract("BitDice", Network.Mainnet, parseAddress("0x29d75277ac7f0335b2165d0895e8725cbf658d73"), "CSNO", 8, false),
newErc20Contract("Vibe", Network.Mainnet, parseAddress("0x2c974b2d0ba1716e644c1fc59982a89ddd2ff724"), "VIB", 18, true), newErc20Contract("Cobinhood Token", Network.Mainnet, parseAddress("0xb2f7eb1f2c37645be61d73953035360e768d81e6"), "COB", 18, true),
newErc20Contract("PRG", Network.Mainnet, parseAddress("0x7728dfef5abd468669eb7f9b48a7f70a501ed29d"), "PRG", 6, false), newErc20Contract("Enjin Coin", Network.Mainnet, parseAddress("0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c"), "ENJ", 18, false),
newErc20Contract("Delphy Token", Network.Mainnet, parseAddress("0x6c2adc2073994fb2ccc5032cc2906fa221e9b391"), "DPY", 18, true), newErc20Contract("AVENTUS", Network.Mainnet, parseAddress("0x0d88ed6e74bbfd96b831231638b66c05571e824f"), "AVT", 18, false),
newErc20Contract("CoinDash Token", Network.Mainnet, parseAddress("0x2fe6ab85ebbf7776fee46d191ee4cea322cecf51"), "CDT", 18, true), newErc20Contract("Chronobank TIME", Network.Mainnet, parseAddress("0x6531f133e6deebe7f2dce5a0441aa7ef330b4e53"), "TIME", 8, false),
newErc20Contract("Tierion Network Token", Network.Mainnet, parseAddress("0x08f5a9235b08173b7569f83645d2c7fb55e8ccd8"), "TNT", 8, true), newErc20Contract("Cindicator Token", Network.Mainnet, parseAddress("0xd4c435f5b09f855c3317c8524cb1f586e42795fa"), "CND", 18, true),
newErc20Contract("DomRaiderToken", Network.Mainnet, parseAddress("0x9af4f26941677c706cfecf6d3379ff01bb85d5ab"), "DRT", 8, true), newErc20Contract("Stox", Network.Mainnet, parseAddress("0x006bea43baa3f7a6f765f14f10a1a1b08334ef45"), "STX", 18, true),
newErc20Contract("SPANK", Network.Mainnet, parseAddress("0x42d6622dece394b54999fbd73d108123806f6a18"), "SPANK", 18, true), newErc20Contract("Xaurum", Network.Mainnet, parseAddress("0x4df812f6064def1e5e029f1ca858777cc98d2d81"), "XAUR", 8, true),
newErc20Contract("Berlin Coin", Network.Mainnet, parseAddress("0x80046305aaab08f6033b56a360c184391165dc2d"), "BRLN", 18, true), newErc20Contract("Vibe", Network.Mainnet, parseAddress("0x2c974b2d0ba1716e644c1fc59982a89ddd2ff724"), "VIB", 18, true),
newErc20Contract("USD//C", Network.Mainnet, parseAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), "USDC", 6, true), newErc20Contract("PRG", Network.Mainnet, parseAddress("0x7728dfef5abd468669eb7f9b48a7f70a501ed29d"), "PRG", 6, false),
newErc20Contract("Livepeer Token", Network.Mainnet, parseAddress("0x58b6a8a3302369daec383334672404ee733ab239"), "LPT", 18, true), newErc20Contract("Delphy Token", Network.Mainnet, parseAddress("0x6c2adc2073994fb2ccc5032cc2906fa221e9b391"), "DPY", 18, true),
newErc20Contract("Simple Token", Network.Mainnet, parseAddress("0x2c4e8f2d746113d0696ce89b35f0d8bf88e0aeca"), "ST", 18, true), newErc20Contract("CoinDash Token", Network.Mainnet, parseAddress("0x2fe6ab85ebbf7776fee46d191ee4cea322cecf51"), "CDT", 18, true),
newErc20Contract("Wrapped BTC", Network.Mainnet, parseAddress("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"), "WBTC", 8, true), newErc20Contract("Tierion Network Token", Network.Mainnet, parseAddress("0x08f5a9235b08173b7569f83645d2c7fb55e8ccd8"), "TNT", 8, true),
newErc20Contract("Bloom Token", Network.Mainnet, parseAddress("0x107c4504cd79c5d2696ea0030a8dd4e92601b82e"), "BLT", 18, true), newErc20Contract("DomRaiderToken", Network.Mainnet, parseAddress("0x9af4f26941677c706cfecf6d3379ff01bb85d5ab"), "DRT", 8, true),
Contract(name: "stickers", network: Network.Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"), newErc20Contract("SPANK", Network.Mainnet, parseAddress("0x42d6622dece394b54999fbd73d108123806f6a18"), "SPANK", 18, true),
methods: [ newErc20Contract("Berlin Coin", Network.Mainnet, parseAddress("0x80046305aaab08f6033b56a360c184391165dc2d"), "BRLN", 18, true),
("packCount", Method(signature: "packCount()")), newErc20Contract("USD//C", Network.Mainnet, parseAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), "USDC", 6, true),
("getPackData", Method(signature: "getPackData(uint256)")) newErc20Contract("Livepeer Token", Network.Mainnet, parseAddress("0x58b6a8a3302369daec383334672404ee733ab239"), "LPT", 18, true),
].toTable newErc20Contract("Simple Token", Network.Mainnet, parseAddress("0x2c4e8f2d746113d0696ce89b35f0d8bf88e0aeca"), "ST", 18, true),
), newErc20Contract("Wrapped BTC", Network.Mainnet, parseAddress("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"), "WBTC", 8, true),
Contract(name: "sticker-market", network: Network.Mainnet, address: parseAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"), newErc20Contract("Bloom Token", Network.Mainnet, parseAddress("0x107c4504cd79c5d2696ea0030a8dd4e92601b82e"), "BLT", 18, true),
methods: [ Contract(name: "stickers", network: Network.Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"),
("buyToken", Method(signature: "buyToken(uint256,address,uint256)")) methods: [
].toTable ("packCount", Method(signature: "packCount()")),
), ("getPackData", Method(signature: "getPackData(uint256)"))
newErc721Contract("sticker-pack", Network.Mainnet, parseAddress("0x110101156e8F0743948B2A61aFcf3994A8Fb172e"), "PACK", false, @[("tokenPackId", Method(signature: "tokenPackId(uint256)"))]), ].toTable
# Strikers seems dead. Their website doesn't work anymore ),
newErc721Contract("strikers", Network.Mainnet, parseAddress("0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"), "STRK", true), Contract(name: "sticker-market", network: Network.Mainnet, address: parseAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"),
newErc721Contract("ethermon", Network.Mainnet, parseAddress("0xb2c0782ae4a299f7358758b2d15da9bf29e1dd99"), "EMONA", true), methods: [
newErc721Contract("kudos", Network.Mainnet, parseAddress("0x2aea4add166ebf38b63d09a75de1a7b94aa24163"), "KDO", true), ("buyToken", Method(signature: "buyToken(uint256,address,uint256)"))
newErc721Contract("crypto-kitties", Network.Mainnet, parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d"), "CK", true), ].toTable
Contract(name: "ens-usernames", network: Network.Mainnet, address: parseAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"), ),
methods: [ newErc721Contract("sticker-pack", Network.Mainnet, parseAddress("0x110101156e8F0743948B2A61aFcf3994A8Fb172e"), "PACK", false, @[("tokenPackId", Method(signature: "tokenPackId(uint256)"))]),
("register", Method(signature: "register(bytes32,address,bytes32,bytes32)")), # Strikers seems dead. Their website doesn't work anymore
("getPrice", Method(signature: "getPrice()")) newErc721Contract("strikers", Network.Mainnet, parseAddress("0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"), "STRK", true),
].toTable newErc721Contract("ethermon", Network.Mainnet, parseAddress("0xb2c0782ae4a299f7358758b2d15da9bf29e1dd99"), "EMONA", true),
), newErc721Contract("kudos", Network.Mainnet, parseAddress("0x2aea4add166ebf38b63d09a75de1a7b94aa24163"), "KDO", true),
Contract(name: "ens-resolver", network: Network.Mainnet, address: parseAddress("0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"), newErc721Contract("crypto-kitties", Network.Mainnet, parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d"), "CK", true),
methods: [ Contract(name: "ens-usernames", network: Network.Mainnet, address: parseAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"),
("setPubkey", Method(signature: "setPubkey(bytes32,bytes32,bytes32)")) methods: [
].toTable ("register", Method(signature: "register(bytes32,address,bytes32,bytes32)")),
), ("getPrice", Method(signature: "getPrice()"))
].toTable
),
Contract(name: "ens-resolver", network: Network.Mainnet, address: parseAddress("0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"),
methods: [
("setPubkey", Method(signature: "setPubkey(bytes32,bytes32,bytes32)"))
].toTable
),
# Testnet (Ropsten) contracts # Testnet (Ropsten) contracts
newErc20Contract("Status Test Token", Network.Testnet, parseAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162"), "STT", 18, true), newErc20Contract("Status Test Token", Network.Testnet, parseAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162"), "STT", 18, true),
newErc20Contract("Handy Test Token", Network.Testnet, parseAddress("0xdee43a267e8726efd60c2e7d5b81552dcd4fa35c"), "HND", 0, false), newErc20Contract("Handy Test Token", Network.Testnet, parseAddress("0xdee43a267e8726efd60c2e7d5b81552dcd4fa35c"), "HND", 0, false),
newErc20Contract("Lucky Test Token", Network.Testnet, parseAddress("0x703d7dc0bc8e314d65436adf985dda51e09ad43b"), "LXS", 2, false), newErc20Contract("Lucky Test Token", Network.Testnet, parseAddress("0x703d7dc0bc8e314d65436adf985dda51e09ad43b"), "LXS", 2, false),
newErc20Contract("Adi Test Token", Network.Testnet, parseAddress("0xe639e24346d646e927f323558e6e0031bfc93581"), "ADI", 7, false), newErc20Contract("Adi Test Token", Network.Testnet, parseAddress("0xe639e24346d646e927f323558e6e0031bfc93581"), "ADI", 7, false),
newErc20Contract("Wagner Test Token", Network.Testnet, parseAddress("0x2e7cd05f437eb256f363417fd8f920e2efa77540"), "WGN", 10, false), newErc20Contract("Wagner Test Token", Network.Testnet, parseAddress("0x2e7cd05f437eb256f363417fd8f920e2efa77540"), "WGN", 10, false),
newErc20Contract("Modest Test Token", Network.Testnet, parseAddress("0x57cc9b83730e6d22b224e9dc3e370967b44a2de0"), "MDS", 18, false), newErc20Contract("Modest Test Token", Network.Testnet, parseAddress("0x57cc9b83730e6d22b224e9dc3e370967b44a2de0"), "MDS", 18, false),
Contract(name: "tribute-to-talk", network: Network.Testnet, address: parseAddress("0xC61aa0287247a0398589a66fCD6146EC0F295432")), Contract(name: "tribute-to-talk", network: Network.Testnet, address: parseAddress("0xC61aa0287247a0398589a66fCD6146EC0F295432")),
Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"), Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"),
methods: [ methods: [
("packCount", Method(signature: "packCount()")), ("packCount", Method(signature: "packCount()")),
("getPackData", Method(signature: "getPackData(uint256)")) ("getPackData", Method(signature: "getPackData(uint256)"))
].toTable ].toTable
), ),
Contract(name: "sticker-market", network: Network.Testnet, address: parseAddress("0x6CC7274aF9cE9572d22DFD8545Fb8c9C9Bcb48AD"), Contract(name: "sticker-market", network: Network.Testnet, address: parseAddress("0x6CC7274aF9cE9572d22DFD8545Fb8c9C9Bcb48AD"),
methods: [ methods: [
("buyToken", Method(signature: "buyToken(uint256,address,uint256)")) ("buyToken", Method(signature: "buyToken(uint256,address,uint256)"))
].toTable ].toTable
), ),
newErc721Contract("sticker-pack", Network.Testnet, parseAddress("0xf852198d0385c4b871e0b91804ecd47c6ba97351"), "PACK", false, @[("tokenPackId", Method(signature: "tokenPackId(uint256)"))]), newErc721Contract("sticker-pack", Network.Testnet, parseAddress("0xf852198d0385c4b871e0b91804ecd47c6ba97351"), "PACK", false, @[("tokenPackId", Method(signature: "tokenPackId(uint256)"))]),
newErc721Contract("kudos", Network.Testnet, parseAddress("0xcd520707fc68d153283d518b29ada466f9091ea8"), "KDO", true), newErc721Contract("kudos", Network.Testnet, parseAddress("0xcd520707fc68d153283d518b29ada466f9091ea8"), "KDO", true),
Contract(name: "ens-usernames", network: Network.Testnet, address: parseAddress("0x11d9F481effd20D76cEE832559bd9Aca25405841"), Contract(name: "ens-usernames", network: Network.Testnet, address: parseAddress("0x11d9F481effd20D76cEE832559bd9Aca25405841"),
methods: [ methods: [
("register", Method(signature: "register(bytes32,address,bytes32,bytes32)")), ("register", Method(signature: "register(bytes32,address,bytes32,bytes32)")),
("getPrice", Method(signature: "getPrice()")) ("getPrice", Method(signature: "getPrice()"))
].toTable ].toTable
), ),
Contract(name: "ens-resolver", network: Network.Testnet, address: parseAddress("0x42D63ae25990889E35F215bC95884039Ba354115"), Contract(name: "ens-resolver", network: Network.Testnet, address: parseAddress("0x42D63ae25990889E35F215bC95884039Ba354115"),
methods: [ methods: [
("setPubkey", Method(signature: "setPubkey(bytes32,bytes32,bytes32)")) ("setPubkey", Method(signature: "setPubkey(bytes32,bytes32,bytes32)"))
].toTable ].toTable
), ),
# Rinkeby contracts # Rinkeby contracts
newErc20Contract("Moksha Coin", Network.Rinkeby, parseAddress("0x6ba7dc8dd10880ab83041e60c4ede52bb607864b"), "MOKSHA", 18, false), newErc20Contract("Moksha Coin", Network.Rinkeby, parseAddress("0x6ba7dc8dd10880ab83041e60c4ede52bb607864b"), "MOKSHA", 18, false),
newErc20Contract("WIBB", Network.Rinkeby, parseAddress("0x7d4ccf6af2f0fdad48ee7958bcc28bdef7b732c7"), "WIBB", 18, false), newErc20Contract("WIBB", Network.Rinkeby, parseAddress("0x7d4ccf6af2f0fdad48ee7958bcc28bdef7b732c7"), "WIBB", 18, false),
newErc20Contract("Status Test Token", Network.Rinkeby, parseAddress("0x43d5adc3b49130a575ae6e4b00dfa4bc55c71621"), "STT", 18, false), newErc20Contract("Status Test Token", Network.Rinkeby, parseAddress("0x43d5adc3b49130a575ae6e4b00dfa4bc55c71621"), "STT", 18, false),
# xDai contracts # xDai contracts
newErc20Contract("buffiDai", Network.XDai, parseAddress("0x3e50bf6703fc132a94e4baff068db2055655f11b"), "BUFF", 18, false), newErc20Contract("buffiDai", Network.XDai, parseAddress("0x3e50bf6703fc132a94e4baff068db2055655f11b"), "BUFF", 18, false),
newErc20Contract("Uniswap", Network.Mainnet, parseAddress("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"), "UNI", 18, true), newErc20Contract("Uniswap", Network.Mainnet, parseAddress("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"), "UNI", 18, true),
newErc20Contract("Compound", Network.Mainnet, parseAddress("0xc00e94cb662c3520282e6f5717214004a7f26888"), "COMP", 18, true), newErc20Contract("Compound", Network.Mainnet, parseAddress("0xc00e94cb662c3520282e6f5717214004a7f26888"), "COMP", 18, true),
newErc20Contract("Balancer", Network.Mainnet, parseAddress("0xba100000625a3754423978a60c9317c58a424e3d"), "BAL", 18, true), newErc20Contract("Balancer", Network.Mainnet, parseAddress("0xba100000625a3754423978a60c9317c58a424e3d"), "BAL", 18, true),
newErc20Contract("Akropolis", Network.Mainnet, parseAddress("0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7"), "AKRO", 18, true), newErc20Contract("Akropolis", Network.Mainnet, parseAddress("0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7"), "AKRO", 18, true),
newErc20Contract("Orchid", Network.Mainnet, parseAddress("0x4575f41308EC1483f3d399aa9a2826d74Da13Deb"), "OXT", 18, false), newErc20Contract("Orchid", Network.Mainnet, parseAddress("0x4575f41308EC1483f3d399aa9a2826d74Da13Deb"), "OXT", 18, false),
] ]
contractsInited = true
result = contracts
proc getContract(network: Network, name: string): Contract = proc getContract(network: Network, name: string): Contract =
{.gcsafe.}: let found = allContracts().filter(contract => contract.name == name and contract.network == network)
withLock contractsLock: result = if found.len > 0: found[0] else: nil
let found = ALL_CONTRACTS.filter(contract => contract.name == name and contract.network == network)
result = if found.len > 0: found[0] else: nil
proc getContract*(name: string): Contract = proc getContract*(name: string): Contract =
let network = settings.getCurrentNetwork() let network = settings.getCurrentNetwork()
@ -275,27 +279,19 @@ proc getErc20ContractByAddress*(contracts: seq[Erc20Contract], address: Address)
proc getErc20Contract*(symbol: string): Erc20Contract = proc getErc20Contract*(symbol: string): Erc20Contract =
let network = settings.getCurrentNetwork() let network = settings.getCurrentNetwork()
{.gcsafe.}: result = allContracts().filter(contract => contract.network == network and contract of Erc20Contract).map(contract => Erc20Contract(contract)).getErc20ContractBySymbol(symbol)
withLock contractsLock:
result = ALL_CONTRACTS.filter(contract => contract.network == network and contract of Erc20Contract).map(contract => Erc20Contract(contract)).getErc20ContractBySymbol(symbol)
proc getErc20Contract*(address: Address): Erc20Contract = proc getErc20Contract*(address: Address): Erc20Contract =
let network = settings.getCurrentNetwork() let network = settings.getCurrentNetwork()
{.gcsafe.}: result = allContracts().filter(contract => contract.network == network and contract of Erc20Contract).map(contract => Erc20Contract(contract)).getErc20ContractByAddress(address)
withLock contractsLock:
result = ALL_CONTRACTS.filter(contract => contract.network == network and contract of Erc20Contract).map(contract => Erc20Contract(contract)).getErc20ContractByAddress(address)
proc getErc20Contracts*(): seq[Erc20Contract] = proc getErc20Contracts*(): seq[Erc20Contract] =
let network = settings.getCurrentNetwork() let network = settings.getCurrentNetwork()
{.gcsafe.}: result = allContracts().filter(contract => contract of Erc20Contract and contract.network == network).map(contract => Erc20Contract(contract))
withLock contractsLock:
result = ALL_CONTRACTS.filter(contract => contract of Erc20Contract and contract.network == network).map(contract => Erc20Contract(contract))
proc getErc721Contract(network: Network, name: string): Erc721Contract = proc getErc721Contract(network: Network, name: string): Erc721Contract =
{.gcsafe.}: let found = allContracts().filter(contract => contract of Erc721Contract and Erc721Contract(contract).name.toLower == name.toLower and contract.network == network)
withLock contractsLock: result = if found.len > 0: Erc721Contract(found[0]) else: nil
let found = ALL_CONTRACTS.filter(contract => contract of Erc721Contract and Erc721Contract(contract).name.toLower == name.toLower and contract.network == network)
result = if found.len > 0: Erc721Contract(found[0]) else: nil
proc getErc721Contract*(name: string): Erc721Contract = proc getErc721Contract*(name: string): Erc721Contract =
let network = settings.getCurrentNetwork() let network = settings.getCurrentNetwork()
@ -303,9 +299,7 @@ proc getErc721Contract*(name: string): Erc721Contract =
proc getErc721Contracts*(): seq[Erc721Contract] = proc getErc721Contracts*(): seq[Erc721Contract] =
let network = settings.getCurrentNetwork() let network = settings.getCurrentNetwork()
{.gcsafe.}: result = allContracts().filter(contract => contract of Erc721Contract and contract.network == network).map(contract => Erc721Contract(contract))
withLock contractsLock:
result = ALL_CONTRACTS.filter(contract => contract of Erc721Contract and contract.network == network).map(contract => Erc721Contract(contract))
proc getSntContract*(): Erc20Contract = proc getSntContract*(): Erc20Contract =
if settings.getCurrentNetwork() == Network.Mainnet: if settings.getCurrentNetwork() == Network.Mainnet:

View File

@ -1,37 +1,46 @@
import core, ./types, ../signals/types as statusgo_types, ./accounts/constants, ./utils import
import json, tables, sugar, sequtils, strutils json, tables, sugar, sequtils, strutils, atomics
import json_serialization
import locks
import uuids
var settingsLock {.global.}: Lock import
initLock(settingsLock) json_serialization, chronicles, uuids
var settings = %*{} import
var dirty = true ./core, ./types, ../signals/types as statusgo_types, ./accounts/constants,
./utils
var
settings {.threadvar.}: JsonNode
settingsInited {.threadvar.}: bool
dirty: Atomic[bool]
dirty.store(true)
settings = %* {}
proc saveSetting*(key: Setting, value: string | JsonNode): StatusGoError = proc saveSetting*(key: Setting, value: string | JsonNode): StatusGoError =
withLock settingsLock: try:
try: let response = callPrivateRPC("settings_saveSetting", %* [key, value])
let response = callPrivateRPC("settings_saveSetting", %* [key, value]) let responseResult = $(response.parseJSON(){"result"})
result = Json.decode($response, StatusGoError) if responseResult == "null":
except: result.error = ""
dirty = true else: result = Json.decode(response, StatusGoError)
except Exception as e:
error "Error saving setting", key=key, value=value, msg=e.msg
dirty.store(true)
proc getWeb3ClientVersion*(): string = proc getWeb3ClientVersion*(): string =
parseJson(callPrivateRPC("web3_clientVersion"))["result"].getStr parseJson(callPrivateRPC("web3_clientVersion"))["result"].getStr
proc getSettings*(useCached: bool = true, keepSensitiveData: bool = false): JsonNode = proc getSettings*(useCached: bool = true, keepSensitiveData: bool = false): JsonNode =
withLock settingsLock: let cacheIsDirty = (not settingsInited) or dirty.load
{.gcsafe.}: if useCached and (not cacheIsDirty) and (not keepSensitiveData):
if useCached and not dirty and not keepSensitiveData: result = settings
result = settings else:
else: result = callPrivateRPC("settings_getSettings").parseJSON()["result"]
result = callPrivateRPC("settings_getSettings").parseJSON()["result"] if not keepSensitiveData:
if not keepSensitiveData: dirty.store(false)
dirty = false delete(result, "mnemonic")
delete(result, "mnemonic") settings = result
settings = result settingsInited = true
proc getSetting*[T](name: Setting, defaultValue: T, useCached: bool = true): T = proc getSetting*[T](name: Setting, defaultValue: T, useCached: bool = true): T =
let settings: JsonNode = getSettings(useCached, $name == "mnemonic") let settings: JsonNode = getSettings(useCached, $name == "mnemonic")

View File

@ -47,7 +47,8 @@ proc decodeContentHash*(value: string): string =
# 20 = multihash length = 32 # 20 = multihash length = 32
# ...rest = multihash digest # ...rest = multihash digest
let multiHash = MultiHash.init(nimcrypto.fromHex(multiHashStr)).get() let multiHash = MultiHash.init(nimcrypto.fromHex(multiHashStr)).get()
result = $Cid.init(CIDv0, MultiCodec.codec(codec), multiHash) let resultTyped = Cid.init(CIDv0, MultiCodec.codec(codec), multiHash).get()
result = $resultTyped
trace "Decoded sticker hash", cid=result trace "Decoded sticker hash", cid=result
except Exception as e: except Exception as e:
error "Error decoding sticker", hash=value, exception=e.msg error "Error decoding sticker", hash=value, exception=e.msg
@ -59,14 +60,14 @@ proc decodeContentHash*(value: string): string =
proc getBalance*(address: Address): int = proc getBalance*(address: Address): int =
let contract = contracts.getContract("sticker-pack") let contract = contracts.getContract("sticker-pack")
if contract == nil: return 0 if contract == nil: return 0
let let
balanceOf = BalanceOf(address: address) balanceOf = BalanceOf(address: address)
payload = %* [{ payload = %* [{
"to": $contract.address, "to": $contract.address,
"data": contract.methods["balanceOf"].encodeAbi(balanceOf) "data": contract.methods["balanceOf"].encodeAbi(balanceOf)
}, "latest"] }, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload) let responseStr = status.callPrivateRPC("eth_call", payload)
let response = Json.decode(responseStr, RpcResponse) let response = Json.decode(responseStr, RpcResponse)
if not response.error.isNil: if not response.error.isNil:
@ -84,7 +85,7 @@ proc getPackCount*(): int =
"to": $contract.address, "to": $contract.address,
"data": contract.methods["packCount"].encodeAbi() "data": contract.methods["packCount"].encodeAbi()
}, "latest"] }, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload) let responseStr = status.callPrivateRPC("eth_call", payload)
let response = Json.decode(responseStr, RpcResponse) let response = Json.decode(responseStr, RpcResponse)
if not response.error.isNil: if not response.error.isNil:
@ -95,39 +96,38 @@ proc getPackCount*(): int =
# Gets sticker pack data # Gets sticker pack data
proc getPackData*(id: Stuint[256]): StickerPack = proc getPackData*(id: Stuint[256]): StickerPack =
{.gcsafe.}: let
let contract = contracts.getContract("stickers")
contract = contracts.getContract("stickers") contractMethod = contract.methods["getPackData"]
contractMethod = contract.methods["getPackData"] getPackData = GetPackData(packId: id)
getPackData = GetPackData(packId: id) payload = %* [{
payload = %* [{ "to": $contract.address,
"to": $contract.address, "data": contractMethod.encodeAbi(getPackData)
"data": contractMethod.encodeAbi(getPackData) }, "latest"]
}, "latest"] let responseStr = status.callPrivateRPC("eth_call", payload)
let responseStr = status.callPrivateRPC("eth_call", payload) let response = Json.decode(responseStr, RpcResponse)
let response = Json.decode(responseStr, RpcResponse) if not response.error.isNil:
if not response.error.isNil: raise newException(RpcException, "Error getting sticker pack data: " & response.error.message)
raise newException(RpcException, "Error getting sticker pack data: " & response.error.message)
let packData = contracts.decodeContractResponse[PackData](response.result) let packData = contracts.decodeContractResponse[PackData](response.result)
# contract response includes a contenthash, which needs to be decoded to reveal # contract response includes a contenthash, which needs to be decoded to reveal
# an IPFS identifier. Once decoded, download the content from IPFS. This content # an IPFS identifier. Once decoded, download the content from IPFS. This content
# is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM # is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM
# and it also needs to be decoded in to a nim type # and it also needs to be decoded in to a nim type
let secureSSLContext = newContext() let secureSSLContext = newContext()
let client = newHttpClient(sslContext = secureSSLContext) let client = newHttpClient(sslContext = secureSSLContext)
let contentHash = contracts.toHex(packData.contentHash) let contentHash = contracts.toHex(packData.contentHash)
let url = "https://ipfs.infura.io/ipfs/" & decodeContentHash(contentHash) let url = "https://ipfs.infura.io/ipfs/" & decodeContentHash(contentHash)
var ednMeta = client.getContent(url) var ednMeta = client.getContent(url)
# decode the EDN content in to a StickerPack # decode the EDN content in to a StickerPack
result = edn_helpers.decode[StickerPack](ednMeta) result = edn_helpers.decode[StickerPack](ednMeta)
# EDN doesn't include a packId for each sticker, so add it here # EDN doesn't include a packId for each sticker, so add it here
result.stickers.apply(proc(sticker: var Sticker) = result.stickers.apply(proc(sticker: var Sticker) =
sticker.packId = truncate(id, int)) sticker.packId = truncate(id, int))
result.id = truncate(id, int) result.id = truncate(id, int)
result.price = packData.price result.price = packData.price
proc tokenOfOwnerByIndex*(address: Address, idx: Stuint[256]): int = proc tokenOfOwnerByIndex*(address: Address, idx: Stuint[256]): int =
let let
@ -137,7 +137,7 @@ proc tokenOfOwnerByIndex*(address: Address, idx: Stuint[256]): int =
"to": $contract.address, "to": $contract.address,
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex) "data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex)
}, "latest"] }, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload) let responseStr = status.callPrivateRPC("eth_call", payload)
let response = Json.decode(responseStr, RpcResponse) let response = Json.decode(responseStr, RpcResponse)
if not response.error.isNil: if not response.error.isNil:
@ -154,7 +154,7 @@ proc getPackIdFromTokenId*(tokenId: Stuint[256]): int =
"to": $contract.address, "to": $contract.address,
"data": contract.methods["tokenPackId"].encodeAbi(tokenPackId) "data": contract.methods["tokenPackId"].encodeAbi(tokenPackId)
}, "latest"] }, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload) let responseStr = status.callPrivateRPC("eth_call", payload)
let response = Json.decode(responseStr, RpcResponse) let response = Json.decode(responseStr, RpcResponse)
if not response.error.isNil: if not response.error.isNil:
@ -202,7 +202,7 @@ proc getRecentStickers*(): seq[Sticker] =
proc getAvailableStickerPacks*(): Table[int, StickerPack] = proc getAvailableStickerPacks*(): Table[int, StickerPack] =
var availableStickerPacks = initTable[int, StickerPack]() var availableStickerPacks = initTable[int, StickerPack]()
try: try:
let numPacks = getPackCount() let numPacks = getPackCount()
for i in 0..<numPacks: for i in 0..<numPacks:
try: try:

View File

@ -1,38 +1,40 @@
import json, chronicles, strformat, stint, strutils, sequtils, tables import
import core, wallet json, chronicles, strformat, stint, strutils, sequtils, tables, atomics
import ./eth/contracts
import web3/[ethtypes, conversions] import
import json_serialization web3/[ethtypes, conversions], json_serialization
import settings
import
./settings, ./core, ./wallet, ./eth/contracts
from types import Setting, Network, RpcResponse, RpcException from types import Setting, Network, RpcResponse, RpcException
from utils import parseAddress from utils import parseAddress
import locks
logScope: logScope:
topics = "wallet" topics = "wallet"
var customTokensLock: Lock var
initLock(customTokensLock) customTokens {.threadvar.}: seq[Erc20Contract]
customTokensInited {.threadvar.}: bool
dirty: Atomic[bool]
var customTokens {.guard: customTokensLock.}: seq[Erc20Contract] = @[] dirty.store(true)
var dirty {.guard: customTokensLock.} = true
proc getCustomTokens*(useCached: bool = true): seq[Erc20Contract] = proc getCustomTokens*(useCached: bool = true): seq[Erc20Contract] =
{.gcsafe.}: let cacheIsDirty = not customTokensInited or dirty.load
withLock customTokensLock: if useCached and not cacheIsDirty:
if useCached and not dirty: result = customTokens
result = customTokens else:
else: let payload = %* []
let payload = %* [] let responseStr = callPrivateRPC("wallet_getCustomTokens", payload)
let responseStr = callPrivateRPC("wallet_getCustomTokens", payload) # TODO: this should be handled in the deserialisation of RpcResponse,
# TODO: this should be handled in the deserialisation of RpcResponse, # question has been posed: https://discordapp.com/channels/613988663034118151/616299964242460682/762828178624217109
# question has been posed: https://discordapp.com/channels/613988663034118151/616299964242460682/762828178624217109 let response = RpcResponse(result: $(responseStr.parseJSON()["result"]))
let response = RpcResponse(result: $(responseStr.parseJSON()["result"])) if not response.error.isNil:
if not response.error.isNil: raise newException(RpcException, "Error getting custom tokens: " & response.error.message)
raise newException(RpcException, "Error getting custom tokens: " & response.error.message) result = if response.result == "null": @[] else: Json.decode(response.result, seq[Erc20Contract])
result = if response.result == "null": @[] else: Json.decode(response.result, seq[Erc20Contract]) dirty.store(false)
dirty = false customTokens = result
customTokens = result customTokensInited = true
proc visibleTokensSNTDefault(): JsonNode = proc visibleTokensSNTDefault(): JsonNode =
let currentNetwork = getCurrentNetwork() let currentNetwork = getCurrentNetwork()
@ -85,14 +87,12 @@ proc getVisibleTokens*(): seq[Erc20Contract] =
proc addCustomToken*(address: string, name: string, symbol: string, decimals: int, color: string) = proc addCustomToken*(address: string, name: string, symbol: string, decimals: int, color: string) =
let payload = %* [{"address": address, "name": name, "symbol": symbol, "decimals": decimals, "color": color}] let payload = %* [{"address": address, "name": name, "symbol": symbol, "decimals": decimals, "color": color}]
discard callPrivateRPC("wallet_addCustomToken", payload) discard callPrivateRPC("wallet_addCustomToken", payload)
withLock customTokensLock: dirty.store(true)
dirty = true
proc removeCustomToken*(address: string) = proc removeCustomToken*(address: string) =
let payload = %* [address] let payload = %* [address]
echo callPrivateRPC("wallet_deleteCustomToken", payload) echo callPrivateRPC("wallet_deleteCustomToken", payload)
withLock customTokensLock: dirty.store(true)
dirty = true
proc getTokensBalances*(accounts: openArray[string], tokens: openArray[string]): JsonNode = proc getTokensBalances*(accounts: openArray[string], tokens: openArray[string]): JsonNode =
let payload = %* [accounts, tokens] let payload = %* [accounts, tokens]

View File

@ -4,6 +4,7 @@ import libstatus/settings as libstatus_settings
import libstatus/types as libstatus_types import libstatus/types as libstatus_types
import chat, accounts, wallet, node, network, mailservers, messages, contacts, profile, stickers, permissions, fleet import chat, accounts, wallet, node, network, mailservers, messages, contacts, profile, stickers, permissions, fleet
import ../eventemitter import ../eventemitter
import tasks/task_manager
export chat, accounts, node, mailservers, messages, contacts, profile, network, permissions, fleet export chat, accounts, node, mailservers, messages, contacts, profile, network, permissions, fleet
@ -21,9 +22,11 @@ type Status* = ref object
network*: NetworkModel network*: NetworkModel
stickers*: StickersModel stickers*: StickersModel
permissions*: PermissionsModel permissions*: PermissionsModel
taskManager*: TaskManager
proc newStatusInstance*(fleetConfig: string): Status = proc newStatusInstance*(taskManager: TaskManager, fleetConfig: string): Status =
result = Status() result = Status()
result.taskManager = taskManager
result.events = createEventEmitter() result.events = createEventEmitter()
result.fleet = fleet.newFleetModel(result.events, fleetConfig) result.fleet = fleet.newFleetModel(result.events, fleetConfig)
result.chat = chat.newChatModel(result.events) result.chat = chat.newChatModel(result.events)

View File

@ -42,7 +42,7 @@ proc init*(self: StickersModel) =
var evArgs = StickerArgs(e) var evArgs = StickerArgs(e)
self.addStickerToRecent(evArgs.sticker, evArgs.save) self.addStickerToRecent(evArgs.sticker, evArgs.save)
proc buildTransaction(self: StickersModel, packId: Uint256, address: Address, price: Uint256, approveAndCall: var ApproveAndCall[100], sntContract: var Erc20Contract, gas = "", gasPrice = ""): EthSend = proc buildTransaction(packId: Uint256, address: Address, price: Uint256, approveAndCall: var ApproveAndCall[100], sntContract: var Erc20Contract, gas = "", gasPrice = ""): EthSend =
sntContract = status_contracts.getSntContract() sntContract = status_contracts.getSntContract()
let let
stickerMktContract = status_contracts.getContract("sticker-market") stickerMktContract = status_contracts.getContract("sticker-market")
@ -51,11 +51,11 @@ proc buildTransaction(self: StickersModel, packId: Uint256, address: Address, pr
approveAndCall = ApproveAndCall[100](to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded)) approveAndCall = ApproveAndCall[100](to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
transactions.buildTokenTransaction(address, sntContract.address, gas, gasPrice) transactions.buildTokenTransaction(address, sntContract.address, gas, gasPrice)
proc estimateGas*(self: StickersModel, packId: int, address: string, price: string, success: var bool): int = proc estimateGas*(packId: int, address: string, price: string, success: var bool): int =
var var
approveAndCall: ApproveAndCall[100] approveAndCall: ApproveAndCall[100]
sntContract = status_contracts.getSntContract() sntContract = status_contracts.getSntContract()
tx = self.buildTransaction( tx = buildTransaction(
packId.u256, packId.u256,
parseAddress(address), parseAddress(address),
eth2Wei(parseFloat(price), sntContract.decimals), eth2Wei(parseFloat(price), sntContract.decimals),
@ -71,7 +71,7 @@ proc buyPack*(self: StickersModel, packId: int, address, price, gas, gasPrice, p
var var
sntContract: Erc20Contract sntContract: Erc20Contract
approveAndCall: ApproveAndCall[100] approveAndCall: ApproveAndCall[100]
tx = self.buildTransaction( tx = buildTransaction(
packId.u256, packId.u256,
parseAddress(address), parseAddress(address),
eth2Wei(parseFloat(price), 18), # SNT eth2Wei(parseFloat(price), 18), # SNT

View File

@ -0,0 +1,40 @@
import
chronos, NimQml, json_serialization, task_runner
import
../stickers
type
StickerPackPurchaseGasEstimate* = object
vptr*: ByteAddress
slot*: string
packId*: int
address*: string
price*: string
uuid*: string
StickersTasks* = ref object
chanSendToPool: AsyncChannel[ThreadSafeString]
proc newStickersTasks*(chanSendToPool: AsyncChannel[ThreadSafeString]): StickersTasks =
new(result)
result.chanSendToPool = chanSendToPool
proc runTask*(stickerPackPurchaseGasEstimate: StickerPackPurchaseGasEstimate) =
var success: bool
var estimate = estimateGas(
stickerPackPurchaseGasEstimate.packId,
stickerPackPurchaseGasEstimate.address,
stickerPackPurchaseGasEstimate.price,
success
)
if not success:
estimate = 325000
let result: tuple[estimate: int, uuid: string] = (estimate, stickerPackPurchaseGasEstimate.uuid)
let resultPayload = Json.encode(result)
signal_handler(cast[pointer](stickerPackPurchaseGasEstimate.vptr), resultPayload, stickerPackPurchaseGasEstimate.slot)
proc stickerPackPurchaseGasEstimate*(self: StickersTasks, vptr: pointer, slot: string, packId: int, address: string, price: string, uuid: string) =
let task = StickerPackPurchaseGasEstimate(vptr: cast[ByteAddress](vptr), slot: slot, packId: packId, address: address, price: price, uuid: uuid)
let payload = task.toJson(typeAnnotations = true)
self.chanSendToPool.sendSync(payload.safe)

View File

@ -0,0 +1,27 @@
import # vendor libs
chronicles, task_runner
import # status-desktop libs
./threadpool
export threadpool
logScope:
topics = "task-manager"
type
TaskManager* = ref object
threadPool*: ThreadPool
proc newTaskManager*(): TaskManager =
new(result)
result.threadPool = newThreadPool()
proc init*(self: TaskManager) =
self.threadPool.init()
proc teardown*(self: TaskManager) =
self.threadPool.teardown()

View File

@ -0,0 +1,256 @@
import
chronicles, chronos, json, json_serialization, NimQml, sequtils, tables,
task_runner
import
./stickers
export
stickers
logScope:
topics = "task-threadpool"
type
ThreadPool* = ref object
chanRecvFromPool*: AsyncChannel[ThreadSafeString]
chanSendToPool*: AsyncChannel[ThreadSafeString]
thread: Thread[PoolThreadArg]
size: int
stickers*: StickersTasks
PoolThreadArg* = object
chanSendToMain*: AsyncChannel[ThreadSafeString]
chanRecvFromMain*: AsyncChannel[ThreadSafeString]
size*: int
TaskThreadArg = object
id: int
chanRecvFromPool: AsyncChannel[ThreadSafeString]
chanSendToPool: AsyncChannel[ThreadSafeString]
ThreadNotification = object
id: int
notice: string
# forward declarations
proc poolThread(arg: PoolThreadArg) {.thread.}
const MaxThreadPoolSize = 16
proc newThreadPool*(size: int = MaxThreadPoolSize): ThreadPool =
new(result)
result.chanRecvFromPool = newAsyncChannel[ThreadSafeString](-1)
result.chanSendToPool = newAsyncChannel[ThreadSafeString](-1)
result.thread = Thread[PoolThreadArg]()
result.size = size
result.stickers = newStickersTasks(result.chanSendToPool)
proc init*(self: ThreadPool) =
self.chanRecvFromPool.open()
self.chanSendToPool.open()
let arg = PoolThreadArg(
chanSendToMain: self.chanRecvFromPool,
chanRecvFromMain: self.chanSendToPool,
size: self.size
)
createThread(self.thread, poolThread, arg)
# block until we receive "ready"
let received = $(self.chanRecvFromPool.recvSync())
proc teardown*(self: ThreadPool) =
self.chanSendToPool.sendSync("shutdown".safe)
self.chanRecvFromPool.close()
self.chanSendToPool.close()
joinThread(self.thread)
proc task(arg: TaskThreadArg) {.async.} =
arg.chanRecvFromPool.open()
arg.chanSendToPool.open()
let noticeToPool = ThreadNotification(id: arg.id, notice: "ready")
info "[threadpool task thread] sending 'ready'", threadid=arg.id
await arg.chanSendToPool.send(noticeToPool.toJson(typeAnnotations = true).safe)
while true:
info "[threadpool task thread] waiting for message"
let received = $(await arg.chanRecvFromPool.recv())
if received == "shutdown":
info "[threadpool task thread] received 'shutdown'"
info "[threadpool task thread] breaking while loop"
break
let
jsonNode = parseJson(received)
messageType = jsonNode{"$type"}.getStr
info "[threadpool task thread] received task", messageType=messageType
info "[threadpool task thread] initiating task", messageType=messageType,
threadid=arg.id
try:
case messageType
of "StickerPackPurchaseGasEstimate":
let decoded = Json.decode(received, StickerPackPurchaseGasEstimate, allowUnknownFields = true)
decoded.runTask()
else:
error "[threadpool task thread] unknown message", message=received
except Exception as e:
error "[threadpool task thread] exception", error=e.msg
let noticeToPool = ThreadNotification(id: arg.id, notice: "done")
info "[threadpool task thread] sending 'done' notice to pool",
threadid=arg.id
await arg.chanSendToPool.send(noticeToPool.toJson(typeAnnotations = true).safe)
arg.chanRecvFromPool.close()
arg.chanSendToPool.close()
proc taskThread(arg: TaskThreadArg) {.thread.} =
waitFor task(arg)
proc pool(arg: PoolThreadArg) {.async.} =
let
chanSendToMain = arg.chanSendToMain
chanRecvFromMainOrTask = arg.chanRecvFromMain
var threadsBusy = newTable[int, tuple[thr: Thread[TaskThreadArg],
chanSendToTask: AsyncChannel[ThreadSafeString]]]()
var threadsIdle = newSeq[tuple[id: int, thr: Thread[TaskThreadArg],
chanSendToTask: AsyncChannel[ThreadSafeString]]](arg.size)
var taskQueue: seq[string] = @[] # FIFO queue
var allReady = 0
chanSendToMain.open()
chanRecvFromMainOrTask.open()
info "[threadpool] sending 'ready' to main thread"
await chanSendToMain.send("ready".safe)
for i in 0..<arg.size:
let id = i + 1
let chanSendToTask = newAsyncChannel[ThreadSafeString](-1)
chanSendToTask.open()
info "[threadpool] adding to threadsIdle", threadid=id
threadsIdle[i].id = id
createThread(
threadsIdle[i].thr,
taskThread,
TaskThreadArg(id: id, chanRecvFromPool: chanSendToTask,
chanSendToPool: chanRecvFromMainOrTask
)
)
threadsIdle[i].chanSendToTask = chanSendToTask
# when task received and number of busy threads == MaxThreadPoolSize,
# then put the task in a queue
# when task received and number of busy threads < MaxThreadPoolSize, pop
# a thread from threadsIdle, track that thread in threadsBusy, and run
# task in that thread
# if "done" received from a thread, remove thread from threadsBusy, and
# push thread into threadsIdle
while true:
info "[threadpool] waiting for message"
var task = $(await chanRecvFromMainOrTask.recv())
info "[threadpool] received message", msg=task
if task == "shutdown":
info "[threadpool] sending 'shutdown' to all task threads"
for tpl in threadsIdle:
await tpl.chanSendToTask.send("shutdown".safe)
for tpl in threadsBusy.values:
await tpl.chanSendToTask.send("shutdown".safe)
info "[threadpool] breaking while loop"
break
let
jsonNode = parseJson(task)
messageType = jsonNode{"$type"}.getStr
info "[threadpool] determined message type", messageType=messageType
case messageType
of "ThreadNotification":
try:
let notification = Json.decode(task, ThreadNotification, allowUnknownFields = true)
info "[threadpool] received notification",
notice=notification.notice, threadid=notification.id
if notification.notice == "ready":
info "[threadpool] received 'ready' from a task thread"
allReady = allReady + 1
elif notification.notice == "done":
let tpl = threadsBusy[notification.id]
info "[threadpool] adding to threadsIdle",
newlength=(threadsIdle.len + 1)
threadsIdle.add (notification.id, tpl.thr, tpl.chanSendToTask)
info "[threadpool] removing from threadsBusy",
newlength=(threadsBusy.len - 1), threadid=notification.id
threadsBusy.del notification.id
if taskQueue.len > 0:
info "[threadpool] removing from taskQueue",
newlength=(taskQueue.len - 1)
task = taskQueue[0]
taskQueue.delete 0, 0
info "[threadpool] removing from threadsIdle",
newlength=(threadsIdle.len - 1)
let tpl = threadsIdle[0]
threadsIdle.delete 0, 0
info "[threadpool] adding to threadsBusy",
newlength=(threadsBusy.len + 1), threadid=tpl.id
threadsBusy.add tpl.id, (tpl.thr, tpl.chanSendToTask)
await tpl.chanSendToTask.send(task.safe)
else:
error "[threadpool] unknown notification", notice=notification.notice
except Exception as e:
warn "[threadpool] unknown error in thread notification", message=task, error=e.msg
else: # must be a request to do task work
if allReady < arg.size or threadsBusy.len == arg.size:
# add to queue
info "[threadpool] adding to taskQueue",
newlength=(taskQueue.len + 1)
taskQueue.add task
# do we have available threads in the threadpool?
elif threadsBusy.len < arg.size:
# check if we have tasks waiting on queue
if taskQueue.len > 0:
# remove first element from the task queue
info "[threadpool] adding to taskQueue",
newlength=(taskQueue.len + 1)
taskQueue.add task
info "[threadpool] removing from taskQueue",
newlength=(taskQueue.len - 1)
task = taskQueue[0]
taskQueue.delete 0, 0
info "[threadpool] removing from threadsIdle",
newlength=(threadsIdle.len - 1)
let tpl = threadsIdle[0]
threadsIdle.delete 0, 0
info "[threadpool] adding to threadsBusy",
newlength=(threadsBusy.len + 1), threadid=tpl.id
threadsBusy.add tpl.id, (tpl.thr, tpl.chanSendToTask)
await tpl.chanSendToTask.send(task.safe)
var allTaskThreads: seq[Thread[TaskThreadArg]] = @[]
for tpl in threadsIdle:
tpl.chanSendToTask.close()
allTaskThreads.add tpl.thr
for tpl in threadsBusy.values:
tpl.chanSendToTask.close()
allTaskThreads.add tpl.thr
chanSendToMain.close()
chanRecvFromMainOrTask.close()
joinThreads(allTaskThreads)
proc poolThread(arg: PoolThreadArg) {.thread.} =
waitFor pool(arg)

View File

@ -349,3 +349,18 @@ proc getGasPricePredictions*(self: WalletModel): GasPricePrediction =
except Exception as e: except Exception as e:
echo "error getting gas price predictions" echo "error getting gas price predictions"
echo e.msg echo e.msg
proc getGasPricePredictions2*(): GasPricePrediction =
if status_settings.getCurrentNetwork() != Network.Mainnet:
# TODO: what about other chains like xdai?
return GasPricePrediction(safeLow: 1.0, standard: 2.0, fast: 3.0, fastest: 4.0)
try:
let url: string = fmt"https://etherchain.org/api/gasPriceOracle"
let secureSSLContext = newContext()
let client = newHttpClient(sslContext = secureSSLContext)
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
let response = client.request(url)
result = Json.decode(response.body, GasPricePrediction)
except Exception as e:
echo "error getting gas price predictions"
echo e.msg

2
vendor/DOtherSide vendored

@ -1 +1 @@
Subproject commit 1cc16aaa5c643d0d33c31f9953fbe4a9f6bde151 Subproject commit 5177a129d45985bb61471d7afecd397a82ed9d42

2
vendor/edn.nim vendored

@ -1 +1 @@
Subproject commit 4cda60880e108f0cb7efe1c209308f2db03267d9 Subproject commit 3305e41f9da3f2f21c56bd23b74b0a3589f3bf3e

2
vendor/nim-libp2p vendored

@ -1 +1 @@
Subproject commit 8d9e231a74c1afc76b6745e05020f8d4e33501e7 Subproject commit 70deac9e0d16f7d00a0d4404ed191042dbf079be

1
vendor/nim-task-runner vendored Submodule

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