diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 3090f62c5b..82f110beb8 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -220,7 +220,8 @@ proc newModule*[T]( devicesService, mailserversService, chatService, ensService, walletAccountService, generalService, communityService, networkService, keycardService, keychainService, tokenService ) - result.stickersModule = stickers_module.newModule(result, events, stickersService, settingsService, walletAccountService, networkService, tokenService) + result.stickersModule = stickers_module.newModule(result, events, stickersService, settingsService, walletAccountService, + networkService, tokenService, keycardService) result.activityCenterModule = activity_center_module.newModule(result, events, activityCenterService, contactsService, messageService, chatService, communityService) result.communitiesModule = communities_module.newModule(result, events, communityService, contactsService, communityTokensService, diff --git a/src/app/modules/main/stickers/controller.nim b/src/app/modules/main/stickers/controller.nim index be1770853b..f57ff7a898 100644 --- a/src/app/modules/main/stickers/controller.nim +++ b/src/app/modules/main/stickers/controller.nim @@ -1,16 +1,18 @@ -import Tables, stint, json +import Tables, os, uuids, stint, json import ./io_interface -import ../../../core/eventemitter -import ../../../../app_service/service/stickers/service as stickers_service -import ../../../../app_service/service/token/service -import ../../../../app_service/service/settings/service as settings_service -import ../../../../app_service/service/network/service as network_service -import ../../../../app_service/service/eth/utils as eth_utils -import ../../../../app_service/service/wallet_account/service as wallet_account_service -import ../../../../app_service/service/token/service as token_service -import ../../shared_modules/keycard_popup/io_interface as keycard_shared_module +import app/core/eventemitter +import app_service/service/node/service as node_service +import app_service/service/stickers/service as stickers_service +import app_service/service/token/service +import app_service/service/settings/service as settings_service +import app_service/service/network/service as network_service +import app_service/service/eth/utils as eth_utils +import app_service/service/wallet_account/service as wallet_account_service +import app_service/service/token/service as token_service +import app_service/service/keycard/service as keycard_service +import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module const UNIQUE_BUY_STICKER_TRANSACTION_MODULE_IDENTIFIER* = "StickersSection-TransactionModule" @@ -23,6 +25,8 @@ type networkService: network_service.Service walletAccountService: wallet_account_service.Service tokenService: token_service.Service + keycardService: keycard_service.Service + connectionKeycardResponse: UUID disconnected: bool proc newController*( @@ -33,6 +37,7 @@ proc newController*( walletAccountService: wallet_account_service.Service, networkService: network_service.Service, tokenService: token_service.Service, + keycardService: keycard_service.Service ): Controller = result = Controller() result.delegate = delegate @@ -42,6 +47,7 @@ proc newController*( result.networkService = networkService result.walletAccountService = walletAccountService result.tokenService = tokenService + result.keycardService = keycardService result.disconnected = false proc delete*(self: Controller) = @@ -90,16 +96,25 @@ proc init*(self: Controller) = let args = SharedKeycarModuleArgs(e) if args.uniqueIdentifier != UNIQUE_BUY_STICKER_TRANSACTION_MODULE_IDENTIFIER: return - self.delegate.onUserAuthenticated(args.password) + self.delegate.onKeypairAuthenticated(args.password, args.pin) self.events.on(SIGNAL_STICKER_PACK_INSTALLED) do(e: Args): let args = StickerPackInstalledArgs(e) self.delegate.onStickerPackInstalled(args.packId) -proc buy*(self: Controller, packId: string, address: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, eip1559Enabled: bool): StickerBuyResultArgs = - self.stickerService.buy(packId, address, gas, gasPrice, maxPriorityFeePerGas, maxFeePerGas, password, eip1559Enabled) +proc prepareTxForBuyingStickers*(self: Controller, chainId: int, packId: string, address: string, gas: string, gasPrice: string, + maxPriorityFeePerGas: string, maxFeePerGas: string, eip1559Enabled: bool): JsonNode = + return self.stickerService.prepareTxForBuyingStickers(chainId, packId, address, gas, gasPrice, maxPriorityFeePerGas, + maxFeePerGas, eip1559Enabled) -proc getRecentStickers*(self: Controller): seq[StickerDto] = +proc signBuyingStickersTxLocally*(self: Controller, data, account, hashedPasssword: string): string = + return self.stickerService.signBuyingStickersTxLocally(data, account, hashedPasssword) + +proc sendBuyingStickersTxWithSignatureAndWatch*(self: Controller, chainId: int, txData: JsonNode, packId: string, + signature: string): StickerBuyResultArgs = + return self.stickerService.sendBuyingStickersTxWithSignatureAndWatch(chainId, txData, packId, signature) + +proc getRecentStickers*(self: Controller): seq[StickerDto] = return self.stickerService.getRecentStickers() proc loadRecentStickers*(self: Controller) = @@ -152,6 +167,9 @@ proc getSNTBalance*(self: Controller): string = proc getWalletDefaultAddress*(self: Controller): string = return self.walletAccountService.getWalletAccount(0).address +proc getKeypairByAccountAddress*(self: Controller, address: string): KeypairDto = + return self.walletAccountService.getKeypairByAccountAddress(address) + proc getCurrentCurrency*(self: Controller): string = return self.settingsService.getCurrency() @@ -174,7 +192,31 @@ proc getStatusToken*(self: Controller): string = } return $jsonObj -proc authenticateUser*(self: Controller, keyUid = "") = +proc authenticate*(self: Controller, keyUid = "") = let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_BUY_STICKER_TRANSACTION_MODULE_IDENTIFIER, keyUid: keyUid) self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) + +proc disconnectKeycardReponseSignal(self: Controller) = + self.events.disconnect(self.connectionKeycardResponse) + +proc connectKeycardReponseSignal(self: Controller) = + self.connectionKeycardResponse = self.events.onWithUUID(SIGNAL_KEYCARD_RESPONSE) do(e: Args): + let args = KeycardLibArgs(e) + self.disconnectKeycardReponseSignal() + let currentFlow = self.keycardService.getCurrentFlow() + if currentFlow != KCSFlowType.Sign: + self.delegate.onTransactionSigned("", KeycardEvent()) + return + self.delegate.onTransactionSigned(args.flowType, args.flowEvent) + +proc cancelCurrentFlow*(self: Controller) = + self.keycardService.cancelCurrentFlow() + # in most cases we're running another flow after canceling the current one, + # this way we're giving to the keycard some time to cancel the current flow + sleep(200) + +proc runSignFlow*(self: Controller, pin, bip44Path, txHash: string) = + self.cancelCurrentFlow() + self.connectKeycardReponseSignal() + self.keycardService.startSignFlow(bip44Path, txHash, pin) \ No newline at end of file diff --git a/src/app/modules/main/stickers/io_interface.nim b/src/app/modules/main/stickers/io_interface.nim index 8cef814554..279c0312ef 100644 --- a/src/app/modules/main/stickers/io_interface.nim +++ b/src/app/modules/main/stickers/io_interface.nim @@ -1,6 +1,9 @@ import Tables, stint import ./item -import ../../../../app_service/service/stickers/service as stickers_service + +import app_service/service/wallet_account/service as wallet_account_service +import app_service/service/stickers/service as stickers_service +from app_service/service/keycard/service import KeycardEvent type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -109,5 +112,8 @@ method stickerTransactionConfirmed*(self: AccessInterface, trxType: string, pack method stickerTransactionReverted*(self: AccessInterface, trxType: string, packID: string, transactionHash: string) {.base.} = raise newException(ValueError, "No implementation available") -method onUserAuthenticated*(self: AccessInterface, password: string) {.base.} = +method onKeypairAuthenticated*(self: AccessInterface, password: string, pin: string) {.base.} = raise newException(ValueError, "No implementation available") + +method onTransactionSigned*(self: AccessInterface, keycardFlowType: string, keycardEvent: KeycardEvent) {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/stickers/module.nim b/src/app/modules/main/stickers/module.nim index eaa7ad9ebc..de58abb850 100644 --- a/src/app/modules/main/stickers/module.nim +++ b/src/app/modules/main/stickers/module.nim @@ -1,14 +1,18 @@ -import NimQml, Tables, stint, sugar, sequtils, json, strutils, strformat, parseutils +import NimQml, Tables, stint, sugar, sequtils, json, strutils, strformat, parseutils, chronicles import ./io_interface, ./view, ./controller, ./item, ./models/sticker_pack_list import ../io_interface as delegate_interface -import ../../../global/global_singleton -import ../../../core/eventemitter -import ../../../../app_service/service/stickers/service as stickers_service -import ../../../../app_service/service/settings/service as settings_service -import ../../../../app_service/service/network/service as network_service -import ../../../../app_service/common/conversion as service_conversion -import ../../../../app_service/service/wallet_account/service as wallet_account_service -import ../../../../app_service/service/token/service as token_service +import app/global/global_singleton +import app/core/eventemitter +import app_service/service/stickers/service as stickers_service +import app_service/service/settings/service as settings_service +import app_service/service/network/service as network_service +import app_service/common/conversion as service_conversion +import app_service/common/utils as common_utils +import app_service/common/wallet_constants as common_wallet_constants +import app_service/service/wallet_account/service as wallet_account_service +import app_service/service/token/service as token_service +import app_service/service/keycard/service as keycard_service +import app_service/service/keycard/constants as keycard_constants export io_interface @@ -18,12 +22,13 @@ const cancelledRequest* = "cancelled" type TmpBuyStickersTransactionDetails = object packId: string address: string + addressPath: string gas: string gasPrice: string maxPriorityFeePerGas: string maxFeePerGas: string eip1559Enabled: bool - + txData: JsonNode type Module* = ref object of io_interface.AccessInterface @@ -42,12 +47,14 @@ proc newModule*( walletAccountService: wallet_account_service.Service, networkService: network_service.Service, tokenService: token_service.Service, + keycardService: keycard_service.Service ): Module = result = Module() result.delegate = delegate result.view = newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, events, stickersService, settingsService, walletAccountService, networkService, tokenService) + result.controller = controller.newController(result, events, stickersService, settingsService, walletAccountService, + networkService, tokenService, keycardService) result.moduleLoaded = false singletonInstance.engine.setRootContextProperty("stickersModule", result.viewVariant) @@ -55,6 +62,13 @@ proc newModule*( method delete*(self: Module) = self.view.delete +proc clear(self: Module) = + self.tmpBuyStickersTransactionDetails = TmpBuyStickersTransactionDetails() + +proc finish(self: Module, chainId: int, txHash: string, error: string) = + self.clear() + self.view.transactionWasSent(chainId, txHash, error) + method load*(self: Module) = self.controller.init() let signingPhrase = self.controller.getSigningPhrase() @@ -76,53 +90,100 @@ method authenticateAndBuy*(self: Module, packId: string, address: string, gas: s self.tmpBuyStickersTransactionDetails.maxPriorityFeePerGas = maxPriorityFeePerGas self.tmpBuyStickersTransactionDetails.maxFeePerGas = maxFeePerGas self.tmpBuyStickersTransactionDetails.eip1559Enabled = eip1559Enabled + self.tmpBuyStickersTransactionDetails.txData = nil - if singletonInstance.userProfile.getIsKeycardUser(): - let keyUid = singletonInstance.userProfile.getKeyUid() - self.controller.authenticateUser(keyUid) + let kp = self.controller.getKeypairByAccountAddress(address) + if kp.migratedToKeycard(): + let accounts = kp.accounts.filter(acc => cmpIgnoreCase(acc.address, address) == 0) + if accounts.len != 1: + error "cannot resolve selected account to send from among known keypair accounts" + return + self.tmpBuyStickersTransactionDetails.addressPath = accounts[0].path + self.controller.authenticate(kp.keyUid) else: - self.controller.authenticateUser() + self.controller.authenticate() - ################################## - ## Do Not Delete - ## - ## Once we start with signing a transactions we shold check if the address we want to send a transaction from is migrated - ## or not. In case it's not we should just authenticate logged in user, otherwise we should use one of the keycards that - ## address (key pair) is migrated to and sign the transaction using it. - ## - ## The code bellow is an example how we can achieve that in future, when we start with signing transactions. - ## - ## let acc = self.controller.getAccountByAddress(from_addr) - ## if acc.isNil: - ## echo "error: selected account to send a transaction from is not known" - ## return - ## let keyPair = self.controller.getKeycardsWithSameKeyUid(acc.keyUid) - ## if keyPair.len == 0: - ## self.controller.authenticateUser() - ## else: - ## self.controller.authenticateUser(acc.keyUid, acc.path) - ## - ################################## +proc sendBuyingStickersTxWithSignatureAndWatch(self: Module, signature: string) = + if self.tmpBuyStickersTransactionDetails.txData.isNil: + let errMsg = "unexpected error while sending buying stickers tx" + error "error", msg=errMsg, methodName="sendBuyingStickersTxWithSignatureAndWatch" + self.finish(chainId = 0, txHash = "", error = errMsg) + return -method onUserAuthenticated*(self: Module, password: string) = + let response = self.controller.sendBuyingStickersTxWithSignatureAndWatch( + self.getChainIdForStickers(), + self.tmpBuyStickersTransactionDetails.txData, + self.tmpBuyStickersTransactionDetails.packId, + signature + ) + + if not response.error.isEmptyOrWhitespace(): + error "sending buying stickers tx failed", errMsg=response.error, methodName="sendBuyingStickersTxWithSignatureAndWatch" + self.finish(chainId = 0, txHash = "", error = response.error) + return + + self.view.stickerPacks.updateStickerPackInList(self.tmpBuyStickersTransactionDetails.packId, installed = false, pending = true) + self.finish(response.chainId, response.txHash, response.error) + +method onKeypairAuthenticated*(self: Module, password: string, pin: string) = if password.len == 0: - let response = %* {"success": false, "error": cancelledRequest} - self.view.transactionWasSent(chainId = 0, txHash = "", error = cancelledRequest) - else: - let response = self.controller.buy( - self.tmpBuyStickersTransactionDetails.packId, - self.tmpBuyStickersTransactionDetails.address, - self.tmpBuyStickersTransactionDetails.gas, - self.tmpBuyStickersTransactionDetails.gasPrice, - self.tmpBuyStickersTransactionDetails.maxPriorityFeePerGas, - self.tmpBuyStickersTransactionDetails.maxFeePerGas, - password, - self.tmpBuyStickersTransactionDetails.eip1559Enabled - ) - if response.error.isEmptyOrWhitespace(): - self.view.stickerPacks.updateStickerPackInList(self.tmpBuyStickersTransactionDetails.packId, installed = false, - pending = true) - self.view.transactionWasSent(chainId = response.chainId, txHash = response.txHash, error = response.error) + self.finish(chainId = 0, txHash = "", error = cancelledRequest) + return + + let chainId = self.getChainIdForStickers() + let txDataJson = self.controller.prepareTxForBuyingStickers( + chainId, + self.tmpBuyStickersTransactionDetails.packId, + self.tmpBuyStickersTransactionDetails.address, + self.tmpBuyStickersTransactionDetails.gas, + self.tmpBuyStickersTransactionDetails.gasPrice, + self.tmpBuyStickersTransactionDetails.maxPriorityFeePerGas, + self.tmpBuyStickersTransactionDetails.maxFeePerGas, + self.tmpBuyStickersTransactionDetails.eip1559Enabled + ) + + if txDataJson.isNil or + txDataJson.kind != JsonNodeKind.JObject or + not txDataJson.hasKey("txArgs") or + not txDataJson.hasKey("messageToSign"): + let errMsg = "unexpected response format preparing tx for buying stickers" + error "error", msg=errMsg, methodName="onKeypairAuthenticated" + self.finish(chainId = 0, txHash = "", error = errMsg) + return + + var txToBeSigned = txDataJson["messageToSign"].getStr + if txToBeSigned.len != common_wallet_constants.TX_HASH_LEN_WITH_PREFIX: + let errMsg = "unexpected tx hash length" + error "error", msg=errMsg, methodName="onKeypairAuthenticated" + self.finish(chainId = 0, txHash = "", error = errMsg) + return + + self.tmpBuyStickersTransactionDetails.txData = txDataJson["txArgs"] + + if txDataJson.hasKey("signOnKeycard") and txDataJson["signOnKeycard"].getBool: + if pin.len != PINLengthForStatusApp: + let errMsg = "cannot proceed with keycard signing, unexpected pin" + error "error", msg=errMsg, methodName="onKeypairAuthenticated" + self.finish(chainId = 0, txHash = "", error = errMsg) + return + var txForKcFlow = txToBeSigned + if txForKcFlow.startsWith("0x"): + txForKcFlow = txForKcFlow[2..^1] + self.controller.runSignFlow(pin, self.tmpBuyStickersTransactionDetails.addressPath, txForKcFlow) + return + + var finalPassword = password + if pin.len == 0: + finalPassword = common_utils.hashPassword(password) + + let signature = self.controller.signBuyingStickersTxLocally(txToBeSigned, self.tmpBuyStickersTransactionDetails.address, finalPassword) + if signature.len == 0: + let errMsg = "couldn't sign tx locally" + error "error", msg=errMsg, methodName="onKeypairAuthenticated" + self.finish(chainId = 0, txHash = "", error = errMsg) + return + + self.sendBuyingStickersTxWithSignatureAndWatch(signature) method obtainMarketStickerPacks*(self: Module) = self.controller.obtainMarketStickerPacks() @@ -258,3 +319,12 @@ method stickerTransactionConfirmed*(self: Module, trxType: string, packID: strin method stickerTransactionReverted*(self: Module, trxType: string, packID: string, transactionHash: string) = self.view.stickerPacks.updateStickerPackInList(packID, installed = false, pending = false) self.view.emitTransactionCompletedSignal(false, transactionHash, packID, trxType) + +method onTransactionSigned*(self: Module, keycardFlowType: string, keycardEvent: KeycardEvent) = + if keycardFlowType != keycard_constants.ResponseTypeValueKeycardFlowResult: + let errMsg = "unexpected error while keycard signing transaction" + error "error", msg=errMsg, methodName="onTransactionSigned" + self.finish(chainId = 0, txHash = "", error = errMsg) + return + let signature = "0x" & keycardEvent.txSignature.r & keycardEvent.txSignature.s & keycardEvent.txSignature.v + self.sendBuyingStickersTxWithSignatureAndWatch(signature) \ No newline at end of file diff --git a/src/app_service/common/wallet_constants.nim b/src/app_service/common/wallet_constants.nim index f804ed3042..d686692c4b 100644 --- a/src/app_service/common/wallet_constants.nim +++ b/src/app_service/common/wallet_constants.nim @@ -1,3 +1,7 @@ -const ETH_TRANSACTION_TYPE* = "eth" -const ERC20_TRANSACTION_TYPE* = "erc20" -const ERC721_TRANSACTION_TYPE* = "erc721" +const + ETH_TRANSACTION_TYPE* = "eth" + ERC20_TRANSACTION_TYPE* = "erc20" + ERC721_TRANSACTION_TYPE* = "erc721" + + TX_HASH_LEN* = 32 * 2 + TX_HASH_LEN_WITH_PREFIX* = TX_HASH_LEN + 2 \ No newline at end of file diff --git a/src/app_service/service/stickers/service.nim b/src/app_service/service/stickers/service.nim index 8ceb17cd87..ae431dc269 100644 --- a/src/app_service/service/stickers/service.nim +++ b/src/app_service/service/stickers/service.nim @@ -13,6 +13,8 @@ import ../../../backend/chat as status_chat import ../../../backend/response_type import ../../../backend/eth as status_eth import ../../../backend/backend as status_go_backend +import ../../../backend/wallet_connect as status_wallet_connect +import ../../../backend/wallet as status_wallet import ./dto/stickers import ../ens/utils as ens_utils import ../token/service as token_service @@ -44,7 +46,7 @@ type estimate*: int uuid*: string GasPriceArgs* = ref object of Args - gasPrice*: string + gasPrice*: string StickerTransactionArgs* = ref object of Args transactionHash*: string packID*: string @@ -121,7 +123,7 @@ QtObject: proc getInstalledStickerPacks*(self: Service): Table[string, StickerPackDto] = return self.installedStickerPacks - + proc getStickerMarketAddress*(self: Service): string = try: let chainId = self.networkService.getNetworkForStickers().chainId @@ -133,7 +135,7 @@ QtObject: proc confirmTransaction(self: Service, trxType: string, packID: string, transactionHash: string) = try: if not self.marketStickerPacks.contains(packID): - let pendingStickerPacksResponse = status_stickers.pending() + let pendingStickerPacksResponse = status_stickers.pending() for (pID, stickerPackJson) in pendingStickerPacksResponse.result.pairs(): if packID != pID: continue self.marketStickerPacks[packID] = stickerPackJson.toStickerPackDto() @@ -155,7 +157,7 @@ QtObject: proc revertTransaction(self: Service, trxType: string, packID: string, transactionHash: string) = try: if not self.marketStickerPacks.contains(packID): - let pendingStickerPacksResponse = status_stickers.pending() + let pendingStickerPacksResponse = status_stickers.pending() for (pID, stickerPackJson) in pendingStickerPacksResponse.result.pairs(): if packID != pID: continue self.marketStickerPacks[packID] = stickerPackJson.toStickerPackDto() @@ -181,68 +183,10 @@ QtObject: else: self.revertTransaction($PendingTransactionTypeDto.BuyStickerPack, receivedData.data, receivedData.transactionHash) - proc buildTransaction*( - source: Address, - gas = "", - gasPrice = "", - isEIP1559Enabled = false, - maxPriorityFeePerGas = "", - maxFeePerGas = "", - ): TransactionDataDto = - result = TransactionDataDto( - source: source, - value: (0.u256).some, - gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some) - ) - if isEIP1559Enabled: - result.maxPriorityFeePerGas = if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxPriorityFeePerGas)).some - result.maxFeePerGas = (if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxFeePerGas)).some) - else: - result.gasPrice = (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some) - proc getStatusToken*(self: Service): TokenDto = let networkDto = self.networkService.getNetworkForStickers() - return self.tokenService.findTokenBySymbol(networkDto.chainId, networkDto.sntSymbol()) - proc buyPack*(self: Service, packId: string, address, gas, gasPrice: string, eip1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): tuple[txHash: string, error: string] = - let - chainId = self.networkService.getNetworkForStickers().chainId - txData = buildTransaction(parseAddress(address), gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas) - try: - let transactionResponse = status_stickers.buy(chainId, %txData, packId, common_utils.hashPassword(password)) - let transactionHash = transactionResponse.result.getStr() - let sntContract = self.getStatusToken() - self.transactionService.watchTransaction( - transactionHash, - address, - $sntContract.address, - $PendingTransactionTypeDto.BuyStickerPack, - packId, - chainId, - ) - return (txHash: transactionHash, error: "") - except ValueError: - let message = getCurrentExceptionMsg() - var error = message - if message.contains("could not decrypt key with given password"): - error = "could not decrypt key with given password" - error "Error sending transaction", message - return (txHash: "", error: error) - except RpcException: - error "Error sending transaction", message = getCurrentExceptionMsg() - - proc buy*(self: Service, packId: string, address: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, eip1559Enabled: bool): StickerBuyResultArgs = - try: - status_utils.validateTransactionInput(address, address, "", "0", gas, gasPrice, "", eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, "ok") - except Exception as e: - error "Error buying sticker pack", msg = e.msg - return StickerBuyResultArgs(chainId: 0, txHash: "", error: e.msg) - - var (txHash, err) = self.buyPack(packId, address, gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, password) - - return StickerBuyResultArgs(chainId: self.networkService.getNetworkForStickers().chainId, txHash: txHash, error: err) - proc setMarketStickerPacks*(self: Service, strickersJSON: string) {.slot.} = let stickersResult = Json.decode(strickersJSON, tuple[packs: seq[StickerPackDto], error: string]) @@ -263,8 +207,7 @@ QtObject: isPending: false )) - # TODO move this to be async - let pendingStickerPacksResponse = status_stickers.pending() + let pendingStickerPacksResponse = status_stickers.pending() for (packID, stickerPackJson) in pendingStickerPacksResponse.result.pairs(): if self.marketStickerPacks.contains(packID): continue self.marketStickerPacks[packID] = stickerPackJson.toStickerPackDto() @@ -411,7 +354,7 @@ QtObject: )) else: error "Sticker pack did not get installed", packId = installedPack.packId - + proc uninstallStickerPack*(self: Service, packId: string) = try: discard status_stickers.uninstall(packId) @@ -453,3 +396,98 @@ QtObject: let balances = status_go_backend.getTokensBalancesForChainIDs(@[network.chainId], @[account], @[token.address]).result return ens_utils.hex2Token(balances{account}{token.address}.getStr, token.decimals) + + # proc prepareTxForBuyingStickers*(self: Service, chainId: int, packId: string, address: string): JsonNode = + proc prepareTxForBuyingStickers*(self: Service, chainId: int, packId: string, address: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, + maxFeePerGas: string, eip1559Enabled: bool): JsonNode = + try: + var prepareTxResponse = status_stickers.prepareTxForBuyingStickers(chainId, address, packId) + if not prepareTxResponse.error.isNil: + error "error occurred", procName="prepareTxForBuyingStickers", msg = prepareTxResponse.error.message + return + + prepareTxResponse.result["gas"] = %* (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some) + if eip1559Enabled: + let maxPriorityFeePerGasFinal = if maxPriorityFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxPriorityFeePerGas)).some + let maxFeePerGasFinal = if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxFeePerGas)).some + prepareTxResponse.result["maxPriorityFeePerGas"] = %* ("0x" & maxPriorityFeePerGasFinal.unsafeGet.toHex) + prepareTxResponse.result["maxFeePerGas"] = %* ("0x" & maxFeePerGasFinal.unsafeGet.toHex) + else: + let gasPriceFinal = if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some + prepareTxResponse.result["gasPrice"] = %* ("0x" & gasPriceFinal.unsafeGet.toHex.stripLeadingZeros) + + var buildTxResponse: JsonNode + let err = status_wallet.buildTransaction(buildTxResponse, chainId, $prepareTxResponse.result) + if err.len > 0: + error "error occurred", procName="prepareTxForBuyingStickers", msg = err + return + + return buildTxResponse + except Exception as e: + error "error occurred", procName="prepareTxForBuyingStickers", msg = e.msg + + proc signBuyingStickersTxLocally*(self: Service, data, account, hashedPasssword: string): string = + try: + var response: JsonNode + let err = status_wallet.signMessage(response, data, account, hashedPasssword) + if err.len > 0 or response.isNil: + error "error occurred", procName="signBuyingStickersTxLocally", msg = err + return + return response.getStr() + except Exception as e: + error "error occurred", procName="signBuyingStickersTxLocally", msg = e.msg + + proc sendBuyingStickersTxWithSignatureAndWatch*(self: Service, chainId: int, txData: JsonNode, packId: string, + signature: string): StickerBuyResultArgs = + result = StickerBuyResultArgs(chainId: chainId) + try: + if txData.isNil: + result.error = "txData is nil" + error "error occurred", procName="sendBuyingStickersTxWithSignatureAndWatch", msg = result.error + return + if not txData.hasKey("from") or txData["from"].getStr().len == 0: + result.error = "from address is empty" + error "error occurred", procName="sendBuyingStickersTxWithSignatureAndWatch", msg = result.error + return + if not txData.hasKey("to") or txData["to"].getStr().len == 0: + result.error = "to address is empty" + error "error occurred", procName="sendBuyingStickersTxWithSignatureAndWatch", msg = result.error + return + + var finalSignature = signature + if finalSignature.startsWith("0x"): + finalSignature = finalSignature[2..^1] + + var txResponse: JsonNode + let err = status_wallet.sendTransactionWithSignature(txResponse, chainId, $PendingTransactionTypeDto.BuyStickerPack, + $txData, finalSignature) + if err.len > 0 or txResponse.isNil: + result.error = err + error "error occurred", procName="sendBuyingStickersTxWithSignatureAndWatch", msg = result.error + return + + let + transactionHash = txResponse.getStr() + fromAddress = txData["from"].getStr() + toAddress = txData["to"].getStr() + + let addPendingResponse = status_stickers.addPending(chainId, packId) + if not addPendingResponse.error.isNil: + result.error = addPendingResponse.error.message + error "error occurred", procName="sendBuyingStickersTxWithSignatureAndWatch", msg = result.error + return + + let sntContract = self.getStatusToken() + self.transactionService.watchTransaction( + transactionHash, + fromAddress, + toAddress, + $PendingTransactionTypeDto.BuyStickerPack, + packId, + chainId, + ) + + result.txHash = transactionHash + except Exception as e: + result.error = e.msg + error "error occurred", procName="sendBuyingStickersTxWithSignatureAndWatch", msg = result.error \ No newline at end of file diff --git a/src/backend/core.nim b/src/backend/core.nim index c16820001c..2fb254522a 100644 --- a/src/backend/core.nim +++ b/src/backend/core.nim @@ -7,7 +7,7 @@ export response_type logScope: topics = "rpc" -## we guard majority db calls which may occure during Profile KeyPair migration +## we guard majority db calls which may occure during Profile KeyPair migration ## (if there is a need we can guard other non rpc calls as well in the same way) var DB_BLOCKED_DUE_TO_PROFILE_MIGRATION* = false @@ -61,23 +61,6 @@ proc callPrivateRPC*( } return makePrivateRpcCall(methodName, inputJSON) -proc signMessage*(rpcParams: string): string = - return $status_go.signMessage(rpcParams) - -proc signTypedData*(data: string, address: string, password: string): string = - return $status_go.signTypedData(data, address, password) - -proc sendTransaction*(chainId: int, inputJSON: string, password: string): RpcResponse[JsonNode] - {.raises: [RpcException, ValueError, Defect, SerializationError].} = - try: - var hashed_password = "0x" & $keccak_256.digest(password) - let rpcResponseRaw = status_go.sendTransactionWithChainId(chainId, inputJSON, hashed_password) - result = Json.decode(rpcResponseRaw, RpcResponse[JsonNode]) - except Exception as e: - error "error sending tx", inputJSON, exception=e.msg - raise newException(RpcException, e.msg) - - proc migrateKeyStoreDir*(account: string, hashedPassword: string, oldKeystoreDir: string, multiaccountKeystoreDir: string) {.raises: [RpcException, ValueError, Defect, SerializationError].} = try: @@ -85,5 +68,3 @@ proc migrateKeyStoreDir*(account: string, hashedPassword: string, oldKeystoreDir except Exception as e: error "error migrating keystore dir", account, exception=e.msg raise newException(RpcException, e.msg) - - \ No newline at end of file diff --git a/src/backend/eth.nim b/src/backend/eth.nim index afa530c97b..9a0777c74d 100644 --- a/src/backend/eth.nim +++ b/src/backend/eth.nim @@ -15,9 +15,6 @@ proc getNativeChainBalance*(chainId: int, address: string): RpcResponse[JsonNode let payload = %* [address, "latest"] return core.callPrivateRPCWithChainId("eth_getBalance", chainId, payload) -proc sendTransaction*(chainId: int, transactionData: string, password: string): RpcResponse[JsonNode] {.raises: [Exception].} = - core.sendTransaction(chainId, transactionData, password) - # This is the replacement of the `call` function proc doEthCall*(payload = %* []): RpcResponse[JsonNode] {.raises: [Exception].} = core.callPrivateRPC("eth_call", payload) diff --git a/src/backend/stickers.nim b/src/backend/stickers.nim index 1e3e232975..04aecd3f97 100644 --- a/src/backend/stickers.nim +++ b/src/backend/stickers.nim @@ -11,6 +11,10 @@ proc pending*(): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [] return core.callPrivateRPC("stickers_pending", payload) +proc addPending*(chainId: int, packId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, packId] + result = core.callPrivateRPC("stickers_addPending", payload) + proc installed*(): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [] return core.callPrivateRPC("stickers_installed", payload) @@ -50,3 +54,7 @@ proc clearRecentStickers*(): RpcResponse[JsonNode] {.raises: [Exception].} = proc removePending*(packId: string): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [packId] return core.callPrivateRPC("stickers_removePending", payload) + +proc prepareTxForBuyingStickers*(chainId: int, address: string, packId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, address, packId] + result = core.callPrivateRPC("stickers_buyPrepareTx", payload) diff --git a/src/backend/wallet.nim b/src/backend/wallet.nim new file mode 100644 index 0000000000..94bb6a4763 --- /dev/null +++ b/src/backend/wallet.nim @@ -0,0 +1,76 @@ +import json, json_serialization, logging +import core, response_type +from ./gen import rpc +import status_go + +rpc(signMessage, "wallet"): + message: string + address: string + hashedPassword: string + +rpc(buildTransaction, "wallet"): + chainId: int + sendTxArgsJson: string + +rpc(sendTransactionWithSignature, "wallet"): + chainId: int + txType: string + sendTxArgsJson: string + signature: string + +proc isErrorResponse(rpcResponse: RpcResponse[JsonNode]): bool = + if not rpcResponse.error.isNil: + return true + return false + +proc prepareResponse(resultOut: var JsonNode, rpcResponse: RpcResponse[JsonNode]): string = + if isErrorResponse(rpcResponse): + return rpcResponse.error.message + if rpcResponse.result.isNil: + return "no result" + resultOut = rpcResponse.result + +## Signs the provided message with the provided account using the provided hashed password, performs `crypto.Sign` +## `resultOut` represents a json object that contains the signature if the call was successful, or `nil` +## `message` is the message to sign +## `address` is the address to sign with +## `hashedPassword` is the hashed password to sign with +## returns the error message if any, or an empty string +proc signMessage*(resultOut: var JsonNode, message: string, address: string, hashedPassword: string): string = + try: + let response = signMessage(message, address, hashedPassword) + return prepareResponse(resultOut, response) + except Exception as e: + warn e.msg + return e.msg + + +## Builds the tx with the provided tx args and chain id +## `resultOut` represents a json object that corresponds to the status go `transfer.TxResponse` type, or `nil` if the call was unsuccessful +## `chainId` is the chain id of the network +## `txArgsJSON` is the json string of the tx, corresponds to the status go `transactions.SendTxArgs` type +## returns the error message if any, or an empty string +proc buildTransaction*(resultOut: var JsonNode, chainId: int, sendTxArgsJson: string): string = + try: + let response = buildTransaction(chainId, sendTxArgsJson) + return prepareResponse(resultOut, response) + except Exception as e: + warn e.msg + return e.msg + + +## Sends the tx with signature on provided chain, setting tx type +## `resultOut` represents a json object that contains the tx hash if the call was successful, or `nil` +## `chainId` is the chain id of the network +## `txType` is the type of the tx, corresponds to the status go `transactions.PendingTrxType` type +## `txArgsJSON` is the json string of the tx, corresponding to the status go `transactions.SendTxArgs` type +## `signature` is the signature of the tx +## returns the error message if any, or an empty string +proc sendTransactionWithSignature*(resultOut: var JsonNode, chainId: int, txType: string, sendTxArgsJson: string, + signature: string): string = + try: + let response = sendTransactionWithSignature(chainId, txType, sendTxArgsJson, signature) + return prepareResponse(resultOut, response) + except Exception as e: + warn e.msg + return e.msg \ No newline at end of file