diff --git a/devuser_guide/content/api/architecture_development/common_errors.md b/devuser_guide/content/api/architecture_development/common_errors.md index d87a1535f3..2220aebbfd 100644 --- a/devuser_guide/content/api/architecture_development/common_errors.md +++ b/devuser_guide/content/api/architecture_development/common_errors.md @@ -96,6 +96,10 @@ Typically this means the method has no return type and so `result =` isn't neces This usually means a method that has no return type is being discarded +### required type for : but expression '' is of type: + +This tpyically means there is an import missing + ## Warnings ### QML anchor warnings diff --git a/src/app/provider/view.nim b/src/app/provider/view.nim index a737906062..ca21e267e5 100644 --- a/src/app/provider/view.nim +++ b/src/app/provider/view.nim @@ -1,19 +1,12 @@ import NimQml -import ../../status/[status, ens, chat/stickers, wallet, settings] +import ../../status/[status, ens, chat/stickers, wallet, settings, provider] import ../../status/types -import ../../status/libstatus/accounts -import ../../status/libstatus/core -import ../../status/libstatus/settings as status_settings import json, json_serialization, sets, strutils import chronicles import nbaser import stew/byteutils from base32 import nil -const AUTH_METHODS = toHashSet(["eth_accounts", "eth_coinbase", "eth_sendTransaction", "eth_sign", "keycard_signTypedData", "eth_signTypedData", "eth_signTypedData_v3", "personal_sign", "personal_ecRecover"]) -const SIGN_METHODS = toHashSet(["eth_sign", "personal_sign", "eth_signTypedData", "eth_signTypedData_v3"]) -const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"]) - const HTTPS_SCHEME = "https" const IPFS_GATEWAY = ".infura.status.im" const SWARM_GATEWAY = "swarm-gateways.net" @@ -23,65 +16,6 @@ const base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" logScope: topics = "provider-view" -type - RequestTypes {.pure.} = enum - Web3SendAsyncReadOnly = "web3-send-async-read-only", - HistoryStateChanged = "history-state-changed", - APIRequest = "api-request" - Unknown = "unknown" - - ResponseTypes {.pure.} = enum - Web3SendAsyncCallback = "web3-send-async-callback", - APIResponse = "api-response", - Web3ResponseError = "web3-response-error" - -type - Payload = ref object - id: JsonNode - rpcMethod: string - - Web3SendAsyncReadOnly = ref object - messageId: JsonNode - payload: Payload - request: string - hostname: string - - APIRequest = ref object - isAllowed: bool - messageId: JsonNode - permission: Permission - hostname: string - -proc requestType(message: string): RequestTypes = - let data = message.parseJson - result = RequestTypes.Unknown - try: - result = parseEnum[RequestTypes](data["type"].getStr()) - except: - warn "Unknown request type received", value=data["permission"].getStr() - -proc toWeb3SendAsyncReadOnly(message: string): Web3SendAsyncReadOnly = - let data = message.parseJson - result = Web3SendAsyncReadOnly( - messageId: data["messageId"], - request: $data["payload"], - hostname: data{"hostname"}.getStr(), - payload: Payload( - id: data["payload"]{"id"}, - rpcMethod: data["payload"]["method"].getStr() - ) - ) - -proc toAPIRequest(message: string): APIRequest = - let data = message.parseJson - - result = APIRequest( - messageId: data["messageId"], - isAllowed: data{"isAllowed"}.getBool(), - permission: data["permission"].getStr().toPermission(), - hostname: data{"hostname"}.getStr() - ) - QtObject: type Web3ProviderView* = ref object of QObject status*: Status @@ -100,153 +34,6 @@ QtObject: result.dappsAddress = "" result.setup - proc process(data: Web3SendAsyncReadOnly, status: Status): string = - if AUTH_METHODS.contains(data.payload.rpcMethod) and not status.permissions.hasPermission(data.hostname, Permission.Web3): - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": { - "code": 4100 - } - } - - if data.payload.rpcMethod == "eth_sendTransaction": - try: - let request = data.request.parseJson - let fromAddress = request["params"][0]["from"].getStr() - let to = request["params"][0]{"to"}.getStr() - let value = if (request["params"][0]["value"] != nil): - request["params"][0]["value"].getStr() - else: - "0" - let password = request["password"].getStr() - let selectedGasLimit = request["selectedGasLimit"].getStr() - let selectedGasPrice = request["selectedGasPrice"].getStr() - let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull): - request["params"][0]["data"].getStr() - else: - "" - - var success: bool - # TODO make this async - let response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, password, success, txData) - let errorMessage = if not success: - if response == "": - "web3-response-error" - else: - response - else: - "" - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": errorMessage, - "result": { - "jsonrpc": "2.0", - "id": data.payload.id, - "result": if (success): response else: "" - } - } - except Exception as e: - error "Error sending the transaction", msg = e.msg - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": { - "code": 4100, - "message": e.msg - } - } - - if SIGN_METHODS.contains(data.payload.rpcMethod): - try: - let request = data.request.parseJson - var params = request["params"] - let password = hashPassword(request["password"].getStr()) - let dappAddress = status_settings.getSetting[string](Setting.DappsAddress) - var rpcResult = "{}" - - case data.payload.rpcMethod: - of "eth_signTypedData", "eth_signTypedData_v3": - rpcResult = signTypedData(params[1].getStr(), dappAddress, password) - else: - rpcResult = signMessage($ %* { - "data": params[0].getStr(), - "password": password, - "account": dappAddress - }) - - let jsonRpcResult = rpcResult.parseJson - let success: bool = not jsonRpcResult.hasKey("error") - let errorMessage = if success: "" else: jsonRpcResult["error"]{"message"}.getStr() - let response = if success: jsonRpcResult["result"].getStr() else: "" - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": errorMessage, - "result": { - "jsonrpc": "2.0", - "id": if data.payload.id == nil: newJNull() else: data.payload.id, - "result": if (success): response else: "" - } - } - - except Exception as e: - error "Error signing message", msg = e.msg - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": { - "code": 4100, - "message": e.msg - } - } - - - - if ACC_METHODS.contains(data.payload.rpcMethod): - let dappAddress = status_settings.getSetting[string](Setting.DappsAddress) - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "result": { - "jsonrpc": "2.0", - "id": data.payload.id, - "result": if data.payload.rpcMethod == "eth_coinbase": newJString(dappAddress) else: %*[dappAddress] - } - } - - let rpcResult = callRPC(data.request) - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": (if rpcResult == "": newJString("web3-response-error") else: newJNull()), - "result": rpcResult.parseJson - } - - proc process*(data: APIRequest, status: Status): string = - var value:JsonNode = case data.permission - of Permission.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")] - of Permission.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0") - of Permission.Unknown: newJNull() - - let isAllowed = data.isAllowed and data.permission != Permission.Unknown - - info "API request received", host=data.hostname, value=data.permission, isAllowed - - if isAllowed: status.permissions.addPermission(data.hostname, data.permission) - - return $ %* { - "type": ResponseTypes.APIResponse, - "isAllowed": isAllowed, - "permission": data.permission, - "messageId": data.messageId, - "data": value - } - proc hasPermission*(self: Web3ProviderView, hostname: string, permission: string): bool {.slot.} = result = self.status.permissions.hasPermission(hostname, permission.toPermission()) @@ -254,13 +41,10 @@ QtObject: self.status.permissions.revoke("web3".toPermission()) proc postMessage*(self: Web3ProviderView, message: string): string {.slot.} = - case message.requestType(): - of RequestTypes.Web3SendAsyncReadOnly: message.toWeb3SendAsyncReadOnly().process(self.status) - of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO: - of RequestTypes.APIRequest: message.toAPIRequest().process(self.status) - else: """{"type":"TODO-IMPLEMENT-THIS"}""" ##################### TODO: + result = self.status.provider.postMessage(message) - proc getNetworkId*(self: Web3ProviderView): int {.slot.} = getCurrentNetworkDetails().config.networkId + proc getNetworkId*(self: Web3ProviderView): int {.slot.} = + self.status.settings.getCurrentNetworkDetails().config.networkId QtProperty[int] networkId: read = getNetworkId diff --git a/src/status/provider.nim b/src/status/provider.nim new file mode 100644 index 0000000000..b025cf51cf --- /dev/null +++ b/src/status/provider.nim @@ -0,0 +1,240 @@ +import ens, chat/stickers, wallet, settings, permissions +import ../eventemitter +import types +import libstatus/accounts +import libstatus/core +import libstatus/settings as status_settings +import json, json_serialization, sets, strutils +import chronicles +import nbaser +import stew/byteutils +from base32 import nil + +logScope: + topics = "provider-model" + +type + RequestTypes {.pure.} = enum + Web3SendAsyncReadOnly = "web3-send-async-read-only", + HistoryStateChanged = "history-state-changed", + APIRequest = "api-request" + Unknown = "unknown" + + ResponseTypes {.pure.} = enum + Web3SendAsyncCallback = "web3-send-async-callback", + APIResponse = "api-response", + Web3ResponseError = "web3-response-error" + +type + Payload = ref object + id: JsonNode + rpcMethod: string + + Web3SendAsyncReadOnly = ref object + messageId: JsonNode + payload: Payload + request: string + hostname: string + + APIRequest = ref object + isAllowed: bool + messageId: JsonNode + permission: Permission + hostname: string + +const AUTH_METHODS = toHashSet(["eth_accounts", "eth_coinbase", "eth_sendTransaction", "eth_sign", "keycard_signTypedData", "eth_signTypedData", "eth_signTypedData_v3", "personal_sign", "personal_ecRecover"]) +const SIGN_METHODS = toHashSet(["eth_sign", "personal_sign", "eth_signTypedData", "eth_signTypedData_v3"]) +const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"]) + +type ProviderModel* = ref object + events*: EventEmitter + permissions*: PermissionsModel + +proc newProviderModel*(events: EventEmitter, permissions: PermissionsModel): ProviderModel = + result = ProviderModel() + result.events = events + result.permissions = permissions + +proc requestType(message: string): RequestTypes = + let data = message.parseJson + result = RequestTypes.Unknown + try: + result = parseEnum[RequestTypes](data["type"].getStr()) + except: + warn "Unknown request type received", value=data["permission"].getStr() + +proc toWeb3SendAsyncReadOnly(message: string): Web3SendAsyncReadOnly = + let data = message.parseJson + result = Web3SendAsyncReadOnly( + messageId: data["messageId"], + request: $data["payload"], + hostname: data{"hostname"}.getStr(), + payload: Payload( + id: data["payload"]{"id"}, + rpcMethod: data["payload"]["method"].getStr() + ) + ) + +proc toAPIRequest(message: string): APIRequest = + let data = message.parseJson + + result = APIRequest( + messageId: data["messageId"], + isAllowed: data{"isAllowed"}.getBool(), + permission: data["permission"].getStr().toPermission(), + hostname: data{"hostname"}.getStr() + ) + +proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string = + if AUTH_METHODS.contains(data.payload.rpcMethod) and not self.permissions.hasPermission(data.hostname, Permission.Web3): + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": { + "code": 4100 + } + } + + if data.payload.rpcMethod == "eth_sendTransaction": + try: + let request = data.request.parseJson + let fromAddress = request["params"][0]["from"].getStr() + let to = request["params"][0]{"to"}.getStr() + let value = if (request["params"][0]["value"] != nil): + request["params"][0]["value"].getStr() + else: + "0" + let password = request["password"].getStr() + let selectedGasLimit = request["selectedGasLimit"].getStr() + let selectedGasPrice = request["selectedGasPrice"].getStr() + let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull): + request["params"][0]["data"].getStr() + else: + "" + + var success: bool + # TODO make this async + let response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, password, success, txData) + let errorMessage = if not success: + if response == "": + "web3-response-error" + else: + response + else: + "" + + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": errorMessage, + "result": { + "jsonrpc": "2.0", + "id": data.payload.id, + "result": if (success): response else: "" + } + } + except Exception as e: + error "Error sending the transaction", msg = e.msg + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": { + "code": 4100, + "message": e.msg + } + } + + if SIGN_METHODS.contains(data.payload.rpcMethod): + try: + let request = data.request.parseJson + var params = request["params"] + let password = hashPassword(request["password"].getStr()) + let dappAddress = status_settings.getSetting[string](Setting.DappsAddress) + var rpcResult = "{}" + + case data.payload.rpcMethod: + of "eth_signTypedData", "eth_signTypedData_v3": + rpcResult = signTypedData(params[1].getStr(), dappAddress, password) + else: + rpcResult = signMessage($ %* { + "data": params[0].getStr(), + "password": password, + "account": dappAddress + }) + + let jsonRpcResult = rpcResult.parseJson + let success: bool = not jsonRpcResult.hasKey("error") + let errorMessage = if success: "" else: jsonRpcResult["error"]{"message"}.getStr() + let response = if success: jsonRpcResult["result"].getStr() else: "" + + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": errorMessage, + "result": { + "jsonrpc": "2.0", + "id": if data.payload.id == nil: newJNull() else: data.payload.id, + "result": if (success): response else: "" + } + } + + except Exception as e: + error "Error signing message", msg = e.msg + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": { + "code": 4100, + "message": e.msg + } + } + + + + if ACC_METHODS.contains(data.payload.rpcMethod): + let dappAddress = status_settings.getSetting[string](Setting.DappsAddress) + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "result": { + "jsonrpc": "2.0", + "id": data.payload.id, + "result": if data.payload.rpcMethod == "eth_coinbase": newJString(dappAddress) else: %*[dappAddress] + } + } + + let rpcResult = callRPC(data.request) + + return $ %* { + "type": ResponseTypes.Web3SendAsyncCallback, + "messageId": data.messageId, + "error": (if rpcResult == "": newJString("web3-response-error") else: newJNull()), + "result": rpcResult.parseJson + } + +proc process*(self: ProviderModel, data: APIRequest): string = + var value:JsonNode = case data.permission + of Permission.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")] + of Permission.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0") + of Permission.Unknown: newJNull() + + let isAllowed = data.isAllowed and data.permission != Permission.Unknown + + info "API request received", host=data.hostname, value=data.permission, isAllowed + + if isAllowed: self.permissions.addPermission(data.hostname, data.permission) + + return $ %* { + "type": ResponseTypes.APIResponse, + "isAllowed": isAllowed, + "permission": data.permission, + "messageId": data.messageId, + "data": value + } + +proc postMessage*(self: ProviderModel, message: string): string = + case message.requestType(): + of RequestTypes.Web3SendAsyncReadOnly: self.process(message.toWeb3SendAsyncReadOnly()) + of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO: + of RequestTypes.APIRequest: self.process(message.toAPIRequest()) + else: """{"type":"TODO-IMPLEMENT-THIS"}""" ##################### TODO: diff --git a/src/status/status.nim b/src/status/status.nim index 634573a510..3b4e767dc0 100644 --- a/src/status/status.nim +++ b/src/status/status.nim @@ -2,7 +2,7 @@ import libstatus/accounts as libstatus_accounts import libstatus/core as libstatus_core import libstatus/settings as libstatus_settings import types as libstatus_types -import chat, accounts, wallet, node, network, messages, contacts, profile, stickers, permissions, fleet, settings, mailservers, browser, tokens +import chat, accounts, wallet, node, network, messages, contacts, profile, stickers, permissions, fleet, settings, mailservers, browser, tokens, provider import ../eventemitter import ./tasks/task_runner_impl @@ -26,6 +26,7 @@ type Status* = ref object mailservers*: MailserversModel browser*: BrowserModel tokens*: TokensModel + provider*: ProviderModel proc newStatusInstance*(fleetConfig: string): Status = result = Status() @@ -47,6 +48,7 @@ proc newStatusInstance*(fleetConfig: string): Status = result.mailservers = mailservers.newMailserversModel(result.events) result.browser = browser.newBrowserModel(result.events) result.tokens = tokens.newTokensModel(result.events) + result.provider = provider.newProviderModel(result.events, result.permissions) proc initNode*(self: Status) = self.tasks.init()