refactor(@desktop/browser): move provider logic to `status-go`
Fixes #4693
This commit is contained in:
parent
9a9d671014
commit
4f65286ead
|
@ -178,8 +178,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
|
|||
result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool,
|
||||
result.settingsService, result.walletAccountService, result.transactionService, result.ethService,
|
||||
result.networkService, result.tokenService)
|
||||
result.providerService = provider_service.newService(result.dappPermissionsService,
|
||||
result.settingsService, result.ensService)
|
||||
result.providerService = provider_service.newService(result.ensService)
|
||||
|
||||
# Modules
|
||||
result.startupModule = startup_module.newModule[AppController](
|
||||
|
|
|
@ -3,7 +3,7 @@ import stew/byteutils
|
|||
import ./utils/qrcodegen
|
||||
|
||||
# Services as instances shouldn't be used in this class, just some general/global procs
|
||||
import ../../app_service/service/eth/utils as procs_from_utils
|
||||
import ../../app_service/common/conversion
|
||||
import ../../app_service/service/accounts/service as procs_from_accounts
|
||||
|
||||
|
||||
|
@ -30,14 +30,17 @@ QtObject:
|
|||
result = url_fromUserInput(input)
|
||||
|
||||
proc eth2Wei*(self: Utils, eth: string, decimals: int): string {.slot.} =
|
||||
let uintValue = procs_from_utils.eth2Wei(parseFloat(eth), decimals)
|
||||
let uintValue = conversion.eth2Wei(parseFloat(eth), decimals)
|
||||
return uintValue.toString()
|
||||
|
||||
proc eth2Hex*(self: Utils, eth: float): string {.slot.} =
|
||||
return "0x" & conversion.eth2Wei(eth, 18).toHex()
|
||||
|
||||
proc wei2Eth*(self: Utils, wei: string, decimals: int): string {.slot.} =
|
||||
var weiValue = wei
|
||||
if(weiValue.startsWith("0x")):
|
||||
weiValue = fromHex(Stuint[256], weiValue).toString()
|
||||
return procs_from_utils.wei2Eth(weiValue, decimals)
|
||||
return conversion.wei2Eth(weiValue, decimals)
|
||||
|
||||
proc hex2Ascii*(self: Utils, value: string): string {.slot.} =
|
||||
result = string.fromBytes(hexToSeqByte(value))
|
||||
|
@ -52,7 +55,10 @@ QtObject:
|
|||
return str
|
||||
|
||||
proc hex2Eth*(self: Utils, value: string): string {.slot.} =
|
||||
return stripTrailingZeroes(procs_from_utils.wei2Eth(stint.fromHex(StUint[256], value)))
|
||||
return stripTrailingZeroes(conversion.wei2Eth(stint.fromHex(StUint[256], value)))
|
||||
|
||||
proc gwei2Hex*(self: Utils, gwei: float): string {.slot.} =
|
||||
return "0x" & conversion.gwei2Wei(gwei).toHex()
|
||||
|
||||
proc hex2Dec*(self: Utils, value: string): string {.slot.} =
|
||||
# somehow this value crashes the app
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
method hasPermission*(self: AccessInterface, hostname: string, permission: string): bool =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method addPermission*(self: AccessInterface, hostname: string, permission: string) =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method clearPermissions*(self: AccessInterface, dapp: string) =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method addPermission*(self: AccessInterface, hostname: string, permission: string) =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -2,9 +2,9 @@ import strutils
|
|||
import controller_interface
|
||||
import io_interface
|
||||
|
||||
import ../../../../../app_service/service/settings/service as settings_service
|
||||
import ../../../../../app_service/service/settings/service_interface as settings_service
|
||||
import ../../../../../app_service/service/dapp_permissions/service as dapp_permissions_service
|
||||
import ../../../../../app_service/service/provider/service as provider_service
|
||||
import ../../../../../app_service/service/provider/service_interface as provider_service
|
||||
export controller_interface
|
||||
|
||||
type
|
||||
|
@ -45,7 +45,7 @@ method disconnect*(self: Controller) =
|
|||
discard self.dappPermissionsService.revoke("web3".toPermission())
|
||||
|
||||
method postMessage*(self: Controller, requestType: string, message: string): string =
|
||||
return self.providerService.postMessage(parseEnum[RequestTypes](requestType), message)
|
||||
return self.providerService.postMessage(requestType, message)
|
||||
|
||||
method hasPermission*(self: Controller, hostname: string, permission: string): bool =
|
||||
return self.dappPermissionsService.hasPermission(hostname, permission.toPermission())
|
||||
|
|
|
@ -3,9 +3,9 @@ import io_interface
|
|||
import view
|
||||
import controller
|
||||
import ../io_interface as delegate_interface
|
||||
import ../../../../../app_service/service/settings/service as settings_service
|
||||
import ../../../../../app_service/service/settings/service_interface as settings_service
|
||||
import ../../../../../app_service/service/dapp_permissions/service as dapp_permissions_service
|
||||
import ../../../../../app_service/service/provider/service as provider_service
|
||||
import ../../../../../app_service/service/provider/service_interface as provider_service
|
||||
import ../../../../global/global_singleton
|
||||
export io_interface
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ var NODE_CONFIG* = %* {
|
|||
"VerifyTransactionURL": "https://mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED
|
||||
},
|
||||
"Web3ProviderConfig": {
|
||||
"Enabled": true
|
||||
"Enabled": true
|
||||
},
|
||||
"EnsConfig": {
|
||||
"Enabled": true
|
||||
|
|
|
@ -1,275 +1,39 @@
|
|||
import Tables, json, sequtils, chronicles
|
||||
import sets
|
||||
import options
|
||||
import strutils
|
||||
include ../../common/json_utils
|
||||
import ../dapp_permissions/service as dapp_permissions_service
|
||||
import ../ens/service as ens_service
|
||||
import ../settings/service_interface as settings_service
|
||||
import ../ens/utils as ens_utils
|
||||
import json, chronicles
|
||||
|
||||
import service_interface
|
||||
import ../../../backend/permissions as status_go_permissions
|
||||
import ../../../backend/core as status_go_core
|
||||
import ../../../backend/eth as status_eth
|
||||
import ../../common/utils as status_utils
|
||||
import ../eth/utils as eth_utils
|
||||
import ../eth/dto/transaction as transaction_data_dto
|
||||
import stew/byteutils
|
||||
import ../../../backend/provider as status_go_provider
|
||||
import ../ens/service as ens_service
|
||||
|
||||
export service_interface
|
||||
|
||||
logScope:
|
||||
topics = "provider-service"
|
||||
|
||||
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"])
|
||||
|
||||
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()
|
||||
)
|
||||
const HTTPS_SCHEME* = "https"
|
||||
|
||||
type
|
||||
Service* = ref object of service_interface.ServiceInterface
|
||||
dappPermissionsService: dapp_permissions_service.ServiceInterface
|
||||
settingsService: settings_service.ServiceInterface
|
||||
ensService: ens_service.Service
|
||||
|
||||
method delete*(self: Service) =
|
||||
discard
|
||||
|
||||
proc newService*(
|
||||
dappPermissionsService: dapp_permissions_service.ServiceInterface,
|
||||
settingsService: settings_service.ServiceInterface,
|
||||
ensService: ens_service.Service
|
||||
): Service =
|
||||
proc newService*(ensService: ens_service.Service): Service =
|
||||
result = Service()
|
||||
result.dappPermissionsService = dappPermissionsService
|
||||
result.settingsService = settingsService
|
||||
result.ensService = ensService
|
||||
|
||||
method init*(self: Service) =
|
||||
discard
|
||||
|
||||
proc process(self: Service, data: Web3SendAsyncReadOnly): string =
|
||||
if AUTH_METHODS.contains(data.payload.rpcMethod) and not self.dappPermissionsService.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 selectedTipLimit = request{"selectedTipLimit"}.getStr()
|
||||
let selectedOverallLimit = request{"selectedOverallLimit"}.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
|
||||
var errorMessage = ""
|
||||
var response: RpcResponse[JsonNode]
|
||||
var validInput: bool = true
|
||||
|
||||
let eip1559Enabled: bool = self.settingsService.isEIP1559Enabled()
|
||||
|
||||
try:
|
||||
eth_utils.validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy")
|
||||
except Exception as e:
|
||||
validInput = false
|
||||
success = false
|
||||
errorMessage = e.msg
|
||||
|
||||
if validInput:
|
||||
# TODO make this async
|
||||
var tx = ens_utils.buildTransaction(
|
||||
parseAddress(fromAddress),
|
||||
eth2Wei(parseFloat(value), 18),
|
||||
selectedGasLimit,
|
||||
selectedGasPrice,
|
||||
eip1559Enabled,
|
||||
selectedTipLimit,
|
||||
selectedOverallLimit,
|
||||
txData
|
||||
)
|
||||
tx.to = parseAddress(to).some
|
||||
|
||||
try:
|
||||
# TODO: use the transaction service to send the trx
|
||||
let json: JsonNode = %tx
|
||||
response = status_eth.sendTransaction($json, password)
|
||||
if response.error != nil:
|
||||
success = false
|
||||
errorMessage = response.error.message
|
||||
else:
|
||||
success = true
|
||||
errorMessage = ""
|
||||
except Exception as e:
|
||||
error "Error sending transaction", msg = e.msg
|
||||
errorMessage = e.msg
|
||||
success = false
|
||||
|
||||
return $ %* {
|
||||
"type": ResponseTypes.Web3SendAsyncCallback,
|
||||
"messageId": data.messageId,
|
||||
"error": errorMessage,
|
||||
"result": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": data.payload.id,
|
||||
"result": if (success): response.result.getStr 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 = status_utils.hashPassword(request["password"].getStr())
|
||||
let dappAddress = self.settingsService.getDappsAddress()
|
||||
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 = self.settingsService.getDappsAddress()
|
||||
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: Service, data: APIRequest): string =
|
||||
var value:JsonNode = case data.permission
|
||||
of Permission.Web3: %* [self.settingsService.getDappsAddress()]
|
||||
of Permission.ContactCode: %* self.settingsService.getPublicKey()
|
||||
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:
|
||||
discard self.dappPermissionsService.addPermission(data.hostname, data.permission)
|
||||
|
||||
return $ %* {
|
||||
"type": ResponseTypes.APIResponse,
|
||||
"isAllowed": isAllowed,
|
||||
"permission": data.permission,
|
||||
"messageId": data.messageId,
|
||||
"data": value
|
||||
}
|
||||
|
||||
method postMessage*(self: Service, requestType: RequestTypes, message: string): string =
|
||||
case 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:
|
||||
|
||||
method ensResourceURL*(self: Service, username: string, url: string): (string, string, string, string, bool) =
|
||||
let (scheme, host, path) = self.ensService.resourceUrl(username)
|
||||
if host == "":
|
||||
return (url, url, HTTPS_SCHEME, "", false)
|
||||
return (url, host, scheme, path, true)
|
||||
|
||||
return (url, host, scheme, path, true)
|
||||
method postMessage*(self: Service, requestType: string, message: string): string =
|
||||
try:
|
||||
return $status_go_provider.providerRequest(requestType, message).result
|
||||
except Exception as e:
|
||||
let errDescription = e.msg
|
||||
error "error: ", errDescription
|
|
@ -1,7 +1,4 @@
|
|||
import dto
|
||||
export dto
|
||||
|
||||
type
|
||||
type
|
||||
ServiceInterface* {.pure inheritable.} = ref object of RootObj
|
||||
## Abstract class for this service access.
|
||||
|
||||
|
@ -11,7 +8,7 @@ method delete*(self: ServiceInterface) {.base.} =
|
|||
method init*(self: ServiceInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method postMessage*(self: ServiceInterface, requestType: RequestTypes, message: string): string {.base.} =
|
||||
method postMessage*(self: ServiceInterface, requestType: string, message: string): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method ensResourceURL*(self: ServiceInterface, ens: string, url: string): (string, string, string, string, bool) =
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import json, json_serialization, strformat, chronicles
|
||||
import status_go
|
||||
import json, json_serialization, chronicles
|
||||
import ./utils
|
||||
import ./core
|
||||
|
||||
|
|
|
@ -495,7 +495,7 @@ Rectangle {
|
|||
}
|
||||
onDisconnect: {
|
||||
Web3ProviderStore.web3ProviderInst.disconnect()
|
||||
provider.postMessage(`{"type":"web3-disconnect-account"}`)
|
||||
provider.postMessage("web3-disconnect-account", "{}");
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,11 @@ StatusModal {
|
|||
|
||||
function postMessage(isAllowed){
|
||||
interactedWith = true
|
||||
request.isAllowed = isAllowed;
|
||||
RootStore.currentTabConnected = isAllowed
|
||||
web3Response(Web3ProviderStore.web3ProviderInst.postMessage(Constants.api_request, JSON.stringify(request)))
|
||||
if(isAllowed){
|
||||
Web3ProviderStore.addPermission(request.hostname, request.permission)
|
||||
}
|
||||
browserConnectionModal.web3Response(Web3ProviderStore.web3ProviderInst.postMessage(Constants.api_request, JSON.stringify(request)))
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
|
|
|
@ -31,6 +31,14 @@ QtObject {
|
|||
return globalUtils.wei2Eth(wei,decimals)
|
||||
}
|
||||
|
||||
function getEth2Hex(eth) {
|
||||
return globalUtils.eth2Hex(eth)
|
||||
}
|
||||
|
||||
function getGwei2Hex(gwei){
|
||||
return globalUtils.gwei2Hex(gwei)
|
||||
}
|
||||
|
||||
function generateIdenticon(pk) {
|
||||
return globalUtils.generateIdenticon(pk)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,10 @@ QtObject {
|
|||
request.hostname = ensAddr;
|
||||
}
|
||||
|
||||
if (requestType === Constants.api_request) {
|
||||
if (requestType === Constants.web3DisconnectAccount) {
|
||||
RootStore.currentTabConnected = true
|
||||
web3Response(JSON.stringify({type: Constants.web3DisconnectAccount}));
|
||||
} else if (requestType === Constants.api_request) {
|
||||
if (!Web3ProviderStore.web3ProviderInst.hasPermission(request.hostname, request.permission)) {
|
||||
RootStore.currentTabConnected = false
|
||||
var dialog = createAccessDialogComponent()
|
||||
|
@ -49,7 +52,6 @@ QtObject {
|
|||
dialog.open();
|
||||
} else {
|
||||
RootStore.currentTabConnected = true
|
||||
request.isAllowed = true
|
||||
web3Response(Web3ProviderStore.web3ProviderInst.postMessage(requestType, JSON.stringify(request)));
|
||||
}
|
||||
} else if (requestType === Constants.web3SendAsyncReadOnly &&
|
||||
|
@ -59,12 +61,19 @@ QtObject {
|
|||
const sendDialog = createSendTransactionModalComponent(request)
|
||||
|
||||
sendDialog.sendTransaction = function (selectedGasLimit, selectedGasPrice, selectedTipLimit, selectedOverallLimit, enteredPassword) {
|
||||
request.payload.selectedGasLimit = selectedGasLimit
|
||||
request.payload.selectedGasPrice = selectedGasPrice
|
||||
request.payload.selectedTipLimit = selectedTipLimit
|
||||
request.payload.selectedOverallLimit = selectedOverallLimit
|
||||
let trx = request.payload.params[0]
|
||||
// TODO: use bignumber instead of floats
|
||||
trx.value = RootStore.getEth2Hex(parseFloat(value))
|
||||
trx.gas = "0x" + parseInt(selectedGasLimit, 10).toString(16)
|
||||
if (walletModel.transactionsView.isEIP1559Enabled) {
|
||||
trx.maxPriorityFeePerGas = RootStore.getGwei2Hex(parseFloat(selectedTipLimit))
|
||||
trx.maxFeePerGas = RootStore.getGwei2Hex(parseFloat(selectedOverallLimit))
|
||||
} else {
|
||||
trx.gasPrice = RootStore.getGwei2Hex(parseFloat(selectedGasPrice))
|
||||
}
|
||||
|
||||
request.payload.password = enteredPassword
|
||||
request.payload.params[0].value = value
|
||||
request.payload.params[0] = trx
|
||||
|
||||
const response = Web3ProviderStore.web3ProviderInst.postMessage(requestType, JSON.stringify(request))
|
||||
provider.web3Response(response)
|
||||
|
|
Loading…
Reference in New Issue