feat(@desktop/stickers): support buying stickers if profile keypair is migrated to a keycard

Closes part 1 of #12556
This commit is contained in:
Sale Djenic 2023-11-28 19:10:42 +01:00 committed by saledjenic
parent 82a829de0c
commit a057e93901
10 changed files with 385 additions and 162 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

76
src/backend/wallet.nim Normal file
View File

@ -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