mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-09 13:56:10 +00:00
feat: enable token transactions
Fixes #788. Fixes #853. Fixes #856. refactor: gas estimation and transaction sends have been abstracted to allow calling `estimateGas`, `send`, and `call` on the contract method (similar to the web3 API). Moved sticker pack gas estimation and purchase tx over to the new API *Sticker purchase:* - gas estimate is done using new API and debounced using a timer *Wallet send transaction:* - tokens can now be sent - gas is estimated correctly for a token tx, and debounced using a timer ***NOTE*** 1. If attempting to send tokens on testnet, you must use a custom token as the token addresses in the pre-built list are for mainnet and will not work on testnet. 2. The new API should support all existing gas estimates, send txs, and calls. The loading of sticker pack data, balance, count, purchased sticker packs, etc, can be moved over to the new API. Almost all of the `eth_sendTransaction`, `eth_gasEstimate`, and `eth_call` could be move over as well (that's the idea at least).
This commit is contained in:
parent
4f0cdad8c7
commit
2c7dd929ad
@ -1,4 +1,4 @@
|
|||||||
import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os, strformat
|
import NimQml, Tables, json, sequtils, chronicles, times, re, sugar, strutils, os
|
||||||
import ../../status/status
|
import ../../status/status
|
||||||
import ../../status/mailservers
|
import ../../status/mailservers
|
||||||
import ../../status/stickers
|
import ../../status/stickers
|
||||||
@ -105,17 +105,18 @@ QtObject:
|
|||||||
QtProperty[QVariant] stickerMarketAddress:
|
QtProperty[QVariant] stickerMarketAddress:
|
||||||
read = getStickerMarketAddress
|
read = getStickerMarketAddress
|
||||||
|
|
||||||
proc getStickerBuyPackGasEstimate*(self: ChatsView, packId: int, address: string, price: string): string {.slot.} =
|
proc buyPackGasEstimate*(self: ChatsView, packId: int, address: string, price: string): int {.slot.} =
|
||||||
try:
|
try:
|
||||||
result = self.status.stickers.buyPackGasEstimate(packId, address, price)
|
result = self.status.stickers.estimateGas(packId, address, price)
|
||||||
except:
|
except:
|
||||||
result = "400000"
|
result = 325000
|
||||||
|
|
||||||
proc buyStickerPack*(self: ChatsView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} =
|
proc buyStickerPack*(self: ChatsView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} =
|
||||||
try:
|
try:
|
||||||
result = $(%self.status.stickers.buyStickerPack(packId, address, price, gas, gasPrice, password))
|
let response = self.status.stickers.buyPack(packId, address, price, gas, gasPrice, password)
|
||||||
|
result = $(%* { "result": %response })
|
||||||
except RpcException as e:
|
except RpcException as e:
|
||||||
result = fmt"""{{ "error": {{ "message": "{e.msg}" }} }}"""
|
result = $(%* { "error": %* { "message": %e.msg }})
|
||||||
|
|
||||||
proc obtainAvailableStickerPacks*(self: ChatsView) =
|
proc obtainAvailableStickerPacks*(self: ChatsView) =
|
||||||
spawnAndSend(self, "setAvailableStickerPacks") do:
|
spawnAndSend(self, "setAvailableStickerPacks") do:
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import NimQml, Tables, strformat, strutils, chronicles, json, std/wrapnils, parseUtils, stint, tables
|
import NimQml, Tables, strformat, strutils, chronicles, json, std/wrapnils, parseUtils, stint, tables
|
||||||
import ../../status/[status, wallet, threads]
|
import ../../status/[status, wallet, threads]
|
||||||
import ../../status/wallet/collectibles as status_collectibles
|
import ../../status/wallet/collectibles as status_collectibles
|
||||||
|
import ../../status/libstatus/accounts/constants
|
||||||
import ../../status/libstatus/wallet as status_wallet
|
import ../../status/libstatus/wallet as status_wallet
|
||||||
import ../../status/libstatus/tokens
|
import ../../status/libstatus/tokens
|
||||||
import ../../status/libstatus/types
|
import ../../status/libstatus/types
|
||||||
@ -91,7 +92,7 @@ QtObject:
|
|||||||
read = getSigningPhrase
|
read = getSigningPhrase
|
||||||
notify = signingPhraseChanged
|
notify = signingPhraseChanged
|
||||||
|
|
||||||
proc getStatusTokenSymbol*(self: WalletView): string {.slot.} = self.status.wallet.getStatusTokenSymbol
|
proc getStatusToken*(self: WalletView): string {.slot.} = self.status.wallet.getStatusToken
|
||||||
|
|
||||||
proc setCurrentAssetList*(self: WalletView, assetList: seq[Asset])
|
proc setCurrentAssetList*(self: WalletView, assetList: seq[Asset])
|
||||||
|
|
||||||
@ -262,12 +263,28 @@ QtObject:
|
|||||||
QtProperty[QVariant] accounts:
|
QtProperty[QVariant] accounts:
|
||||||
read = getAccountList
|
read = getAccountList
|
||||||
notify = accountListChanged
|
notify = accountListChanged
|
||||||
|
|
||||||
|
proc estimateGas*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string): string {.slot.} =
|
||||||
|
try:
|
||||||
|
var response: int
|
||||||
|
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
|
||||||
|
response = self.status.wallet.estimateTokenGas(from_addr, to, assetAddress, value)
|
||||||
|
else:
|
||||||
|
response = self.status.wallet.estimateGas(from_addr, to, value)
|
||||||
|
result = $(%* { "result": %response })
|
||||||
|
except RpcException as e:
|
||||||
|
result = $(%* { "error": %* { "message": %e.msg }})
|
||||||
|
|
||||||
proc sendTransaction*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, password: string): string {.slot.} =
|
proc sendTransaction*(self: WalletView, from_addr: string, to: string, assetAddress: string, value: string, gas: string, gasPrice: string, password: string): string {.slot.} =
|
||||||
try:
|
try:
|
||||||
result = $(%self.status.wallet.sendTransaction(from_addr, to, assetAddress, value, gas, gasPrice, password))
|
var response = ""
|
||||||
|
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
|
||||||
|
response = self.status.wallet.sendTokenTransaction(from_addr, to, assetAddress, value, gas, gasPrice, password)
|
||||||
|
else:
|
||||||
|
response = self.status.wallet.sendTransaction(from_addr, to, value, gas, gasPrice, password)
|
||||||
|
result = $(%* { "result": %response })
|
||||||
except RpcException as e:
|
except RpcException as e:
|
||||||
result = fmt"""{{ "error": {{ "message": "{e.msg}" }} }}"""
|
result = $(%* { "error": %* { "message": %e.msg }})
|
||||||
|
|
||||||
proc getDefaultAccount*(self: WalletView): string {.slot.} =
|
proc getDefaultAccount*(self: WalletView): string {.slot.} =
|
||||||
self.currentAccount.address
|
self.currentAccount.address
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import eventemitter, json, strutils, sequtils, tables, chronicles, sugar, times
|
import eventemitter, json, strutils, sequtils, tables, chronicles, times
|
||||||
import libstatus/contracts as status_contracts
|
|
||||||
import libstatus/chat as status_chat
|
import libstatus/chat as status_chat
|
||||||
import libstatus/mailservers as status_mailservers
|
import libstatus/mailservers as status_mailservers
|
||||||
import libstatus/chatCommands as status_chat_commands
|
import libstatus/chatCommands as status_chat_commands
|
||||||
import libstatus/accounts/constants as constants
|
import libstatus/accounts/constants as constants
|
||||||
import libstatus/types
|
import libstatus/types
|
||||||
import mailservers, stickers
|
import stickers
|
||||||
import profile/profile
|
import profile/profile
|
||||||
import chat/[chat, message]
|
import chat/[chat, message]
|
||||||
import signals/messages
|
import signals/messages
|
||||||
|
@ -12,7 +12,7 @@ import stew/byteutils
|
|||||||
import unicode
|
import unicode
|
||||||
import algorithm
|
import algorithm
|
||||||
import eth/common/eth_types, stew/byteutils
|
import eth/common/eth_types, stew/byteutils
|
||||||
import libstatus/contracts
|
import libstatus/eth/contracts
|
||||||
const domain* = ".stateofus.eth"
|
const domain* = ".stateofus.eth"
|
||||||
|
|
||||||
proc userName*(ensName: string, removeSuffix: bool = false): string =
|
proc userName*(ensName: string, removeSuffix: bool = false): string =
|
||||||
|
@ -193,4 +193,8 @@ func decode*[T](input: string, to: seq[T]): seq[T] =
|
|||||||
|
|
||||||
func decode*[T; I: static int](input: string, to: array[0..I, T]): array[0..I, T] =
|
func decode*[T; I: static int](input: string, to: array[0..I, T]): array[0..I, T] =
|
||||||
for i in 0..I:
|
for i in 0..I:
|
||||||
result[i] = input[i*64 .. (i+1)*64].decode(T)
|
result[i] = input[i*64 .. (i+1)*64].decode(T)
|
||||||
|
|
||||||
|
func decodeContractResponse*[T](input: string): T =
|
||||||
|
result = T()
|
||||||
|
discard decode(input.strip0xPrefix, 0, result)
|
@ -1,15 +1,17 @@
|
|||||||
import sequtils, strformat, sugar, macros, tables, strutils
|
import
|
||||||
import eth/common/eth_types, stew/byteutils, nimcrypto
|
sequtils, sugar, macros, tables, strutils
|
||||||
|
|
||||||
|
import
|
||||||
|
eth/common/eth_types, stew/byteutils, nimcrypto
|
||||||
from eth/common/utils import parseAddress
|
from eth/common/utils import parseAddress
|
||||||
import ./types, ./settings, ./coder
|
|
||||||
|
import
|
||||||
|
../types, ../settings, ../coder, transactions, methods
|
||||||
|
|
||||||
export
|
export
|
||||||
GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf, Register, SetPubkey,
|
GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf, Register, SetPubkey,
|
||||||
TokenOfOwnerByIndex, TokenPackId, TokenUri, FixedBytes, DynamicBytes, toHex, fromHex
|
TokenOfOwnerByIndex, TokenPackId, TokenUri, FixedBytes, DynamicBytes, toHex, fromHex,
|
||||||
|
decodeContractResponse, encodeAbi, estimateGas, send, call
|
||||||
type Method* = object
|
|
||||||
name*: string
|
|
||||||
signature*: string
|
|
||||||
|
|
||||||
type Contract* = ref object
|
type Contract* = ref object
|
||||||
name*: string
|
name*: string
|
||||||
@ -121,32 +123,3 @@ proc getContract(network: Network, name: string): Contract =
|
|||||||
proc getContract*(name: string): Contract =
|
proc getContract*(name: string): Contract =
|
||||||
let network = settings.getCurrentNetwork()
|
let network = settings.getCurrentNetwork()
|
||||||
getContract(network, name)
|
getContract(network, name)
|
||||||
|
|
||||||
proc encodeMethod(self: Method): string =
|
|
||||||
($nimcrypto.keccak256.digest(self.signature))[0..<8].toLower
|
|
||||||
|
|
||||||
proc encodeAbi*(self: Method, obj: object = RootObj()): string =
|
|
||||||
result = "0x" & self.encodeMethod()
|
|
||||||
|
|
||||||
# .fields is an iterator, and there's no way to get a count of an iterator
|
|
||||||
# in nim, so we have to loop and increment a counter
|
|
||||||
var fieldCount = 0
|
|
||||||
for i in obj.fields:
|
|
||||||
fieldCount += 1
|
|
||||||
var
|
|
||||||
offset = 32*fieldCount
|
|
||||||
data = ""
|
|
||||||
|
|
||||||
for field in obj.fields:
|
|
||||||
let encoded = encode(field)
|
|
||||||
if encoded.dynamic:
|
|
||||||
result &= offset.toHex(64).toLower
|
|
||||||
data &= encoded.data
|
|
||||||
offset += encoded.data.len
|
|
||||||
else:
|
|
||||||
result &= encoded.data
|
|
||||||
result &= data
|
|
||||||
|
|
||||||
func decodeContractResponse*[T](input: string): T =
|
|
||||||
result = T()
|
|
||||||
discard decode(input.strip0xPrefix, 0, result)
|
|
10
src/status/libstatus/eth/eth.nim
Normal file
10
src/status/libstatus/eth/eth.nim
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import
|
||||||
|
transactions, ../types
|
||||||
|
|
||||||
|
proc sendTransaction*(tx: var EthSend, password: string): string =
|
||||||
|
let response = transactions.sendTransaction(tx, password)
|
||||||
|
result = response.result
|
||||||
|
|
||||||
|
proc estimateGas*(tx: var EthSend): string =
|
||||||
|
let response = transactions.estimateGas(tx)
|
||||||
|
result = response.result
|
55
src/status/libstatus/eth/methods.nim
Normal file
55
src/status/libstatus/eth/methods.nim
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import
|
||||||
|
strutils, options
|
||||||
|
|
||||||
|
import
|
||||||
|
nimcrypto, eth/common/eth_types
|
||||||
|
|
||||||
|
import
|
||||||
|
../coder, eth, transactions, ../types
|
||||||
|
|
||||||
|
export sendTransaction
|
||||||
|
|
||||||
|
type Method* = object
|
||||||
|
name*: string
|
||||||
|
signature*: string
|
||||||
|
|
||||||
|
proc encodeMethod(self: Method): string =
|
||||||
|
($nimcrypto.keccak256.digest(self.signature))[0..<8].toLower
|
||||||
|
|
||||||
|
proc encodeAbi*(self: Method, obj: object = RootObj()): string =
|
||||||
|
result = "0x" & self.encodeMethod()
|
||||||
|
|
||||||
|
# .fields is an iterator, and there's no way to get a count of an iterator
|
||||||
|
# in nim, so we have to loop and increment a counter
|
||||||
|
var fieldCount = 0
|
||||||
|
for i in obj.fields:
|
||||||
|
fieldCount += 1
|
||||||
|
var
|
||||||
|
offset = 32*fieldCount
|
||||||
|
data = ""
|
||||||
|
|
||||||
|
for field in obj.fields:
|
||||||
|
let encoded = encode(field)
|
||||||
|
if encoded.dynamic:
|
||||||
|
result &= offset.toHex(64).toLower
|
||||||
|
data &= encoded.data
|
||||||
|
offset += encoded.data.len
|
||||||
|
else:
|
||||||
|
result &= encoded.data
|
||||||
|
result &= data
|
||||||
|
|
||||||
|
proc estimateGas*(self: Method, tx: var EthSend, methodDescriptor: object): string =
|
||||||
|
tx.data = self.encodeAbi(methodDescriptor)
|
||||||
|
let response = transactions.estimateGas(tx)
|
||||||
|
result = response.result # gas estimate in hex
|
||||||
|
|
||||||
|
proc send*(self: Method, tx: var EthSend, methodDescriptor: object, password: string): string =
|
||||||
|
tx.data = self.encodeAbi(methodDescriptor)
|
||||||
|
result = eth.sendTransaction(tx, password)
|
||||||
|
# result = coder.decodeContractResponse[string](response.result)
|
||||||
|
# result = response.result
|
||||||
|
|
||||||
|
proc call*[T](self: Method, tx: var EthSend, methodDescriptor: object): T =
|
||||||
|
tx.data = self.encodeAbi(methodDescriptor)
|
||||||
|
let response = transactions.call(tx)
|
||||||
|
result = coder.decodeContractResponse[T](response.result)
|
30
src/status/libstatus/eth/transactions.nim
Normal file
30
src/status/libstatus/eth/transactions.nim
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import
|
||||||
|
json
|
||||||
|
|
||||||
|
import
|
||||||
|
json_serialization, chronicles
|
||||||
|
|
||||||
|
import
|
||||||
|
../core, ../types
|
||||||
|
|
||||||
|
proc estimateGas*(tx: EthSend): RpcResponse =
|
||||||
|
let response = core.callPrivateRPC("eth_estimateGas", %*[%tx])
|
||||||
|
result = Json.decode(response, RpcResponse)
|
||||||
|
if not result.error.isNil:
|
||||||
|
raise newException(RpcException, "Error getting gas estimate: " & result.error.message)
|
||||||
|
|
||||||
|
trace "Gas estimated succesfully", estimate=result.result
|
||||||
|
|
||||||
|
proc sendTransaction*(tx: EthSend, password: string): RpcResponse =
|
||||||
|
let responseStr = core.sendTransaction($(%tx), password)
|
||||||
|
result = Json.decode(responseStr, RpcResponse)
|
||||||
|
if not result.error.isNil:
|
||||||
|
raise newException(RpcException, "Error sending transaction: " & result.error.message)
|
||||||
|
|
||||||
|
trace "Transaction sent succesfully", hash=result.result
|
||||||
|
|
||||||
|
proc call*(tx: EthSend): RpcResponse =
|
||||||
|
let responseStr = core.callPrivateRPC("eth_call", %*[%tx])
|
||||||
|
result = Json.decode(responseStr, RpcResponse)
|
||||||
|
if not result.error.isNil:
|
||||||
|
raise newException(RpcException, "Error calling method: " & result.error.message)
|
@ -1,4 +1,4 @@
|
|||||||
import ./core as status, ./types, ./contracts, ./settings, ./edn_helpers
|
import ./core as status, ./types, ./eth/contracts, ./settings, ./edn_helpers
|
||||||
import
|
import
|
||||||
json, json_serialization, tables, chronicles, strutils, sequtils, httpclient,
|
json, json_serialization, tables, chronicles, strutils, sequtils, httpclient,
|
||||||
stint, libp2p/[multihash, multicodec, cid], eth/common/eth_types
|
stint, libp2p/[multihash, multicodec, cid], eth/common/eth_types
|
||||||
@ -124,43 +124,6 @@ proc getPackData*(id: Stuint[256]): StickerPack =
|
|||||||
result.id = truncate(id, int)
|
result.id = truncate(id, int)
|
||||||
result.price = packData.price
|
result.price = packData.price
|
||||||
|
|
||||||
proc buyPackPayload(packId: Stuint[256], address: EthAddress, price: Stuint[256]): JsonNode =
|
|
||||||
let
|
|
||||||
stickerMktContract = contracts.getContract("sticker-market")
|
|
||||||
sntContract = contracts.getContract("snt")
|
|
||||||
buyToken = BuyToken(packId: packId, address: address, price: price)
|
|
||||||
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
|
|
||||||
let
|
|
||||||
approveAndCallObj = ApproveAndCall(to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
|
|
||||||
approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(approveAndCallObj)
|
|
||||||
result = %* {
|
|
||||||
"from": $address,
|
|
||||||
"to": $sntContract.address,
|
|
||||||
"data": approveAndCallAbiEncoded
|
|
||||||
}
|
|
||||||
|
|
||||||
proc buyPackGasEstimate*(packId: Stuint[256], address: EthAddress, price: Stuint[256]): string =
|
|
||||||
# TODO: pass in an EthSend object instead
|
|
||||||
let payload = buyPackPayload(packId, address, price)
|
|
||||||
let responseStr = status.callPrivateRPC("eth_estimateGas", %*[payload])
|
|
||||||
let response = Json.decode(responseStr, RpcResponse)
|
|
||||||
if not response.error.isNil:
|
|
||||||
raise newException(RpcException, "Error getting stickers buy pack gas estimate: " & response.error.message)
|
|
||||||
result = response.result # should be a tx receipt
|
|
||||||
|
|
||||||
# Buys a sticker pack for user
|
|
||||||
# See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Buy-a-Sticker-Pack for more
|
|
||||||
# details
|
|
||||||
proc buyPack*(packId: Stuint[256], address: EthAddress, price: Stuint[256], gas: uint64, gasPrice: int, password: string): RpcResponse =
|
|
||||||
# TODO: pass in an EthSend object instead
|
|
||||||
let payload = buyPackPayload(packId, address, price)
|
|
||||||
payload{"gas"} = %gas.encodeQuantity
|
|
||||||
payload{"gasPrice"} = %("0x" & gasPrice.toHex.stripLeadingZeros)
|
|
||||||
let responseStr = status.sendTransaction($payload, password)
|
|
||||||
result = Json.decode(responseStr, RpcResponse)
|
|
||||||
if not result.error.isNil:
|
|
||||||
raise newException(RpcException, "Error buying sticker pack: " & result.error.message)
|
|
||||||
|
|
||||||
proc tokenOfOwnerByIndex*(address: EthAddress, idx: Stuint[256]): int =
|
proc tokenOfOwnerByIndex*(address: EthAddress, idx: Stuint[256]): int =
|
||||||
let
|
let
|
||||||
contract = contracts.getContract("sticker-pack")
|
contract = contracts.getContract("sticker-pack")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import json, chronicles, strformat, stint, strutils
|
import json, chronicles, strformat, stint, strutils
|
||||||
import core, wallet
|
import core, wallet
|
||||||
import contracts
|
import ./eth/contracts
|
||||||
import eth/common/eth_types, eth/common/utils
|
import eth/common/eth_types, eth/common/utils
|
||||||
import json_serialization
|
import json_serialization
|
||||||
import settings
|
import settings
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import json, random, strutils, strformat, tables, chronicles
|
import json, random, strutils, strformat, tables, chronicles, unicode
|
||||||
import stint, nim_status
|
import stint
|
||||||
from times import getTime, toUnix, nanosecond
|
from times import getTime, toUnix, nanosecond
|
||||||
import accounts/signing_phrases
|
import accounts/signing_phrases
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ proc first*(jArray: JsonNode, fieldName, id: string): JsonNode =
|
|||||||
if jArray.kind != JArray:
|
if jArray.kind != JArray:
|
||||||
raise newException(ValueError, "Parameter 'jArray' is a " & $jArray.kind & ", but must be a JArray")
|
raise newException(ValueError, "Parameter 'jArray' is a " & $jArray.kind & ", but must be a JArray")
|
||||||
for child in jArray.getElems:
|
for child in jArray.getElems:
|
||||||
if child{fieldName}.getStr == id:
|
if child{fieldName}.getStr.toLower == id.toLower:
|
||||||
return child
|
return child
|
||||||
|
|
||||||
proc any*(jArray: JsonNode, fieldName, id: string): bool =
|
proc any*(jArray: JsonNode, fieldName, id: string): bool =
|
||||||
@ -90,7 +90,7 @@ proc any*(jArray: JsonNode, fieldName, id: string): bool =
|
|||||||
return false
|
return false
|
||||||
result = false
|
result = false
|
||||||
for child in jArray.getElems:
|
for child in jArray.getElems:
|
||||||
if child{fieldName}.getStr == id:
|
if child{fieldName}.getStr.toLower == id.toLower:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
proc isEmpty*(a: JsonNode): bool =
|
proc isEmpty*(a: JsonNode): bool =
|
||||||
|
@ -2,7 +2,7 @@ import json, json, options, json_serialization, stint, chronicles
|
|||||||
import core, types, utils, strutils, strformat
|
import core, types, utils, strutils, strformat
|
||||||
from nim_status import validateMnemonic, startWallet
|
from nim_status import validateMnemonic, startWallet
|
||||||
import ../wallet/account
|
import ../wallet/account
|
||||||
import ./contracts as contractMethods
|
import ./eth/contracts as contractMethods
|
||||||
import eth/common/eth_types
|
import eth/common/eth_types
|
||||||
import ./types
|
import ./types
|
||||||
import ../signals/types as signal_types
|
import ../signals/types as signal_types
|
||||||
@ -62,14 +62,6 @@ proc getTransfersByAddress*(address: string): seq[types.Transaction] =
|
|||||||
let msg = getCurrentExceptionMsg()
|
let msg = getCurrentExceptionMsg()
|
||||||
error "Failed getting wallet account transactions", msg
|
error "Failed getting wallet account transactions", msg
|
||||||
|
|
||||||
proc sendTransaction*(tx: EthSend, password: string): RpcResponse =
|
|
||||||
let responseStr = core.sendTransaction($(%tx), password)
|
|
||||||
result = Json.decode(responseStr, RpcResponse)
|
|
||||||
if not result.error.isNil:
|
|
||||||
raise newException(RpcException, "Error sending transaction: " & result.error.message)
|
|
||||||
|
|
||||||
trace "Transaction sent succesfully", hash=result
|
|
||||||
|
|
||||||
proc getBalance*(address: string): string =
|
proc getBalance*(address: string): string =
|
||||||
let payload = %* [address, "latest"]
|
let payload = %* [address, "latest"]
|
||||||
let response = parseJson(callPrivateRPC("eth_getBalance", payload))
|
let response = parseJson(callPrivateRPC("eth_getBalance", payload))
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import
|
import # global deps
|
||||||
tables, strutils, sequtils, sugar
|
tables, strutils, sequtils, sugar
|
||||||
|
|
||||||
import
|
import # project deps
|
||||||
chronicles, eth/common/eth_types, eventemitter
|
chronicles, eth/common/eth_types, eventemitter
|
||||||
|
|
||||||
from eth/common/utils import parseAddress
|
from eth/common/utils import parseAddress
|
||||||
|
|
||||||
import
|
import # local deps
|
||||||
libstatus/types, libstatus/stickers as status_stickers,
|
libstatus/types, libstatus/eth/contracts as status_contracts,
|
||||||
libstatus/contracts as status_contracts
|
libstatus/stickers as status_stickers, transactions
|
||||||
|
from libstatus/utils as libstatus_utils import eth2Wei
|
||||||
from libstatus/utils as libstatus_utils import eth2Wei, gwei2Wei, toUInt64
|
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
topics = "stickers-model"
|
topics = "stickers-model"
|
||||||
@ -43,27 +41,53 @@ proc init*(self: StickersModel) =
|
|||||||
var evArgs = StickerArgs(e)
|
var evArgs = StickerArgs(e)
|
||||||
self.addStickerToRecent(evArgs.sticker, evArgs.save)
|
self.addStickerToRecent(evArgs.sticker, evArgs.save)
|
||||||
|
|
||||||
# TODO: Replace this with a more generalised way of estimating gas so can be used for token transfers
|
proc buildTransaction(self: StickersModel, packId: Uint256, address: EthAddress, price: Uint256, approveAndCall: var ApproveAndCall, sntContract: var Contract, gas = "", gasPrice = ""): EthSend =
|
||||||
proc buyPackGasEstimate*(self: StickersModel, packId: int, address: string, price: string): string =
|
sntContract = status_contracts.getContract("snt")
|
||||||
let
|
let
|
||||||
priceTyped = eth2Wei(parseFloat(price), 18) # SNT
|
stickerMktContract = status_contracts.getContract("sticker-market")
|
||||||
hexGas = status_stickers.buyPackGasEstimate(packId.u256, parseAddress(address), priceTyped)
|
buyToken = BuyToken(packId: packId, address: address, price: price)
|
||||||
result = $fromHex[int](hexGas)
|
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
|
||||||
|
approveAndCall = ApproveAndCall(to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
|
||||||
|
transactions.buildTokenTransaction(address, sntContract.address, gas, gasPrice)
|
||||||
|
|
||||||
|
proc estimateGas*(self: StickersModel, packId: int, address: string, price: string): int =
|
||||||
|
var
|
||||||
|
approveAndCall: ApproveAndCall
|
||||||
|
sntContract = status_contracts.getContract("snt")
|
||||||
|
tx = self.buildTransaction(
|
||||||
|
packId.u256,
|
||||||
|
parseAddress(address),
|
||||||
|
eth2Wei(parseFloat(price), 18), # SNT
|
||||||
|
approveAndCall,
|
||||||
|
sntContract
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = sntContract.methods["approveAndCall"].estimateGas(tx, approveAndCall)
|
||||||
|
result = fromHex[int](response)
|
||||||
|
except RpcException as e:
|
||||||
|
raise
|
||||||
|
|
||||||
|
proc buyPack*(self: StickersModel, packId: int, address, price, gas, gasPrice, password: string): string =
|
||||||
|
var
|
||||||
|
sntContract: Contract
|
||||||
|
approveAndCall: ApproveAndCall
|
||||||
|
tx = self.buildTransaction(
|
||||||
|
packId.u256,
|
||||||
|
parseAddress(address),
|
||||||
|
eth2Wei(parseFloat(price), 18), # SNT
|
||||||
|
approveAndCall,
|
||||||
|
sntContract,
|
||||||
|
gas,
|
||||||
|
gasPrice
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = sntContract.methods["approveAndCall"].send(tx, approveAndCall, password)
|
||||||
|
except RpcException as e:
|
||||||
|
raise
|
||||||
|
|
||||||
proc getStickerMarketAddress*(self: StickersModel): EthAddress =
|
proc getStickerMarketAddress*(self: StickersModel): EthAddress =
|
||||||
result = status_contracts.getContract("sticker-market").address
|
result = status_contracts.getContract("sticker-market").address
|
||||||
|
|
||||||
proc buyStickerPack*(self: StickersModel, packId: int, address, price, gas, gasPrice, password: string): RpcResponse =
|
|
||||||
try:
|
|
||||||
let
|
|
||||||
addressTyped = parseAddress(address)
|
|
||||||
priceTyped = eth2Wei(parseFloat(price), 18) # SNT
|
|
||||||
gasTyped = cast[uint64](parseFloat(gas).toUInt64)
|
|
||||||
gasPriceTyped = gwei2Wei(parseFloat(gasPrice)).truncate(int)
|
|
||||||
result = status_stickers.buyPack(packId.u256, addressTyped, priceTyped, gasTyped, gasPriceTyped, password)
|
|
||||||
except RpcException as e:
|
|
||||||
raise
|
|
||||||
|
|
||||||
proc getPurchasedStickerPacks*(self: StickersModel, address: EthAddress): seq[int] =
|
proc getPurchasedStickerPacks*(self: StickersModel, address: EthAddress): seq[int] =
|
||||||
if self.purchasedStickerPacks != @[]:
|
if self.purchasedStickerPacks != @[]:
|
||||||
return self.purchasedStickerPacks
|
return self.purchasedStickerPacks
|
||||||
|
23
src/status/transactions.nim
Normal file
23
src/status/transactions.nim
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import
|
||||||
|
options, strutils
|
||||||
|
|
||||||
|
import
|
||||||
|
stint
|
||||||
|
from eth/common/eth_types import EthAddress
|
||||||
|
from eth/common/utils import parseAddress
|
||||||
|
|
||||||
|
import
|
||||||
|
libstatus/types
|
||||||
|
from libstatus/utils as status_utils import toUInt64, gwei2Wei
|
||||||
|
|
||||||
|
proc buildTransaction*(source: EthAddress, value: Uint256, gas = "", gasPrice = ""): EthSend =
|
||||||
|
result = EthSend(
|
||||||
|
source: source,
|
||||||
|
value: value.some,
|
||||||
|
gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some),
|
||||||
|
gasPrice: (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some)
|
||||||
|
)
|
||||||
|
|
||||||
|
proc buildTokenTransaction*(source, contractAddress: EthAddress, gas = "", gasPrice = ""): EthSend =
|
||||||
|
result = buildTransaction(source, 0.u256, gas, gasPrice)
|
||||||
|
result.to = contractAddress.some
|
@ -1,17 +1,17 @@
|
|||||||
import eventemitter, json, strformat, strutils, chronicles, sequtils, httpclient, tables
|
import eventemitter, json, strformat, strutils, chronicles, sequtils, httpclient, tables
|
||||||
import json_serialization, stint
|
import json_serialization, stint
|
||||||
from eth/common/utils import parseAddress
|
from eth/common/utils import parseAddress
|
||||||
|
from eth/common/eth_types import EthAddress
|
||||||
import libstatus/accounts as status_accounts
|
import libstatus/accounts as status_accounts
|
||||||
import libstatus/tokens as status_tokens
|
import libstatus/tokens as status_tokens
|
||||||
import libstatus/settings as status_settings
|
import libstatus/settings as status_settings
|
||||||
import libstatus/wallet as status_wallet
|
import libstatus/wallet as status_wallet
|
||||||
import libstatus/accounts/constants as constants
|
import libstatus/accounts/constants as constants
|
||||||
import libstatus/contracts as contracts
|
import libstatus/eth/[eth, contracts]
|
||||||
from libstatus/types import GeneratedAccount, DerivedAccount, Transaction, Setting, GasPricePrediction, EthSend, Quantity, `%`, StatusGoException, Network, RpcResponse, RpcException
|
from libstatus/types import GeneratedAccount, DerivedAccount, Transaction, Setting, GasPricePrediction, EthSend, Quantity, `%`, StatusGoException, Network, RpcResponse, RpcException, `$`
|
||||||
from libstatus/utils as libstatus_utils import eth2Wei, gwei2Wei, first, toUInt64
|
from libstatus/utils as libstatus_utils import eth2Wei, gwei2Wei, first, toUInt64
|
||||||
import wallet/balance_manager
|
import wallet/[balance_manager, account, collectibles]
|
||||||
import wallet/account
|
import transactions
|
||||||
import wallet/collectibles
|
|
||||||
export account, collectibles
|
export account, collectibles
|
||||||
export Transaction
|
export Transaction
|
||||||
|
|
||||||
@ -50,34 +50,70 @@ proc initEvents*(self: WalletModel) =
|
|||||||
proc delete*(self: WalletModel) =
|
proc delete*(self: WalletModel) =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc sendTransaction*(self: WalletModel, source, to, assetAddress, value, gas, gasPrice, password: string): RpcResponse =
|
proc buildTokenTransaction(self: WalletModel, source, to, assetAddress: EthAddress, value: float, transfer: var Transfer, contract: var Contract, gas = "", gasPrice = ""): EthSend =
|
||||||
var
|
let token = self.tokens.first("address", $assetAddress)
|
||||||
weiValue = eth2Wei(parseFloat(value), 18) # ETH
|
contract = getContract("snt")
|
||||||
data = ""
|
transfer = Transfer(to: to, value: eth2Wei(value, token["decimals"].getInt))
|
||||||
toAddr = parseAddress(to)
|
transactions.buildTokenTransaction(source, assetAddress, gas, gasPrice)
|
||||||
let gasPriceInWei = gwei2Wei(parseFloat(gasPrice))
|
|
||||||
|
|
||||||
# TODO: this code needs to be tested with testnet assets (to be implemented in
|
proc estimateGas*(self: WalletModel, source, to, value: string): int =
|
||||||
# a future PR
|
var tx = transactions.buildTransaction(
|
||||||
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
|
parseAddress(source),
|
||||||
let
|
eth2Wei(parseFloat(value), 18)
|
||||||
token = self.tokens.first("address", assetAddress)
|
|
||||||
contract = getContract("snt")
|
|
||||||
transfer = Transfer(to: toAddr, value: eth2Wei(parseFloat(value), token["decimals"].getInt))
|
|
||||||
weiValue = 0.u256
|
|
||||||
data = contract.methods["transfer"].encodeAbi(transfer)
|
|
||||||
toAddr = parseAddress(assetAddress)
|
|
||||||
|
|
||||||
let tx = EthSend(
|
|
||||||
source: parseAddress(source),
|
|
||||||
to: toAddr.some,
|
|
||||||
gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some),
|
|
||||||
gasPrice: (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some),
|
|
||||||
value: weiValue.some,
|
|
||||||
data: data
|
|
||||||
)
|
)
|
||||||
|
tx.to = parseAddress(to).some
|
||||||
try:
|
try:
|
||||||
result = status_wallet.sendTransaction(tx, password)
|
let response = eth.estimateGas(tx)
|
||||||
|
result = fromHex[int](response)
|
||||||
|
except RpcException as e:
|
||||||
|
raise
|
||||||
|
|
||||||
|
proc estimateTokenGas*(self: WalletModel, source, to, assetAddress, value: string): int =
|
||||||
|
var
|
||||||
|
transfer: Transfer
|
||||||
|
contract: Contract
|
||||||
|
tx = self.buildTokenTransaction(
|
||||||
|
parseAddress(source),
|
||||||
|
parseAddress(to),
|
||||||
|
parseAddress(assetAddress),
|
||||||
|
parseFloat(value),
|
||||||
|
transfer,
|
||||||
|
contract
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = contract.methods["transfer"].estimateGas(tx, transfer)
|
||||||
|
result = fromHex[int](response)
|
||||||
|
except RpcException as e:
|
||||||
|
raise
|
||||||
|
|
||||||
|
proc sendTransaction*(self: WalletModel, source, to, value, gas, gasPrice, password: string): string =
|
||||||
|
var tx = transactions.buildTransaction(
|
||||||
|
parseAddress(source),
|
||||||
|
eth2Wei(parseFloat(value), 18), gas, gasPrice
|
||||||
|
)
|
||||||
|
tx.to = parseAddress(to).some
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = eth.sendTransaction(tx, password)
|
||||||
|
except RpcException as e:
|
||||||
|
raise
|
||||||
|
|
||||||
|
proc sendTokenTransaction*(self: WalletModel, source, to, assetAddress, value, gas, gasPrice, password: string): string =
|
||||||
|
var
|
||||||
|
transfer: Transfer
|
||||||
|
contract: Contract
|
||||||
|
tx = self.buildTokenTransaction(
|
||||||
|
parseAddress(source),
|
||||||
|
parseAddress(to),
|
||||||
|
parseAddress(assetAddress),
|
||||||
|
parseFloat(value),
|
||||||
|
transfer,
|
||||||
|
contract,
|
||||||
|
gas,
|
||||||
|
gasPrice
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
result = contract.methods["transfer"].send(tx, transfer, password)
|
||||||
except RpcException as e:
|
except RpcException as e:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -88,10 +124,15 @@ proc getDefaultCurrency*(self: WalletModel): string =
|
|||||||
|
|
||||||
# TODO: This needs to be removed or refactored so that test tokens are shown
|
# TODO: This needs to be removed or refactored so that test tokens are shown
|
||||||
# when on testnet https://github.com/status-im/nim-status-client/issues/613.
|
# when on testnet https://github.com/status-im/nim-status-client/issues/613.
|
||||||
proc getStatusTokenSymbol*(self: WalletModel): string =
|
proc getStatusToken*(self: WalletModel): string =
|
||||||
|
var token = Asset(address: $getContract("snt").address)
|
||||||
if status_settings.getCurrentNetwork() == Network.Testnet:
|
if status_settings.getCurrentNetwork() == Network.Testnet:
|
||||||
return "STT"
|
token.name = "Status Test Token"
|
||||||
"SNT"
|
token.symbol = "STT"
|
||||||
|
else:
|
||||||
|
token.name = "Status Network Token"
|
||||||
|
token.symbol = "SNT"
|
||||||
|
result = $(%token)
|
||||||
|
|
||||||
proc setDefaultCurrency*(self: WalletModel, currency: string) =
|
proc setDefaultCurrency*(self: WalletModel, currency: string) =
|
||||||
discard status_settings.saveSetting(Setting.Currency, currency)
|
discard status_settings.saveSetting(Setting.Currency, currency)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import strformat, httpclient, json, chronicles, sequtils, strutils, tables, sugar
|
import strformat, httpclient, json, chronicles, sequtils, strutils, tables, sugar
|
||||||
from eth/common/utils import parseAddress
|
from eth/common/utils import parseAddress
|
||||||
import ../libstatus/core as status
|
import ../libstatus/core as status
|
||||||
import ../libstatus/contracts as contracts
|
import ../libstatus/eth/contracts as contracts
|
||||||
import ../libstatus/stickers as status_stickers
|
import ../libstatus/stickers as status_stickers
|
||||||
import ../chat as status_chat
|
import ../chat as status_chat
|
||||||
import ../libstatus/types
|
import ../libstatus/types
|
||||||
|
@ -7,7 +7,7 @@ import "../../../../shared"
|
|||||||
|
|
||||||
ModalPopup {
|
ModalPopup {
|
||||||
id: root
|
id: root
|
||||||
property var asset: { "name": "Status", "symbol": walletModel.getStatusTokenSymbol() }
|
readonly property var asset: JSON.parse(walletModel.getStatusToken())
|
||||||
property int stickerPackId: -1
|
property int stickerPackId: -1
|
||||||
property string packPrice
|
property string packPrice
|
||||||
property bool showBackBtn: false
|
property bool showBackBtn: false
|
||||||
@ -90,6 +90,7 @@ ModalPopup {
|
|||||||
showBalanceForAssetSymbol = Qt.binding(function() { return root.asset.symbol })
|
showBalanceForAssetSymbol = Qt.binding(function() { return root.asset.symbol })
|
||||||
minRequiredAssetBalance = Qt.binding(function() { return root.packPrice })
|
minRequiredAssetBalance = Qt.binding(function() { return root.packPrice })
|
||||||
}
|
}
|
||||||
|
onSelectedAccountChanged: gasSelector.estimateGas()
|
||||||
}
|
}
|
||||||
RecipientSelector {
|
RecipientSelector {
|
||||||
id: selectRecipient
|
id: selectRecipient
|
||||||
@ -98,6 +99,7 @@ ModalPopup {
|
|||||||
contacts: profileModel.addedContacts
|
contacts: profileModel.addedContacts
|
||||||
selectedRecipient: { "address": chatsModel.stickerMarketAddress, "type": RecipientSelector.Type.Address }
|
selectedRecipient: { "address": chatsModel.stickerMarketAddress, "type": RecipientSelector.Type.Address }
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
onSelectedRecipientChanged: gasSelector.estimateGas()
|
||||||
}
|
}
|
||||||
GasSelector {
|
GasSelector {
|
||||||
id: gasSelector
|
id: gasSelector
|
||||||
@ -107,19 +109,17 @@ ModalPopup {
|
|||||||
getGasEthValue: walletModel.getGasEthValue
|
getGasEthValue: walletModel.getGasEthValue
|
||||||
getFiatValue: walletModel.getFiatValue
|
getFiatValue: walletModel.getFiatValue
|
||||||
defaultCurrency: walletModel.defaultCurrency
|
defaultCurrency: walletModel.defaultCurrency
|
||||||
selectedGasLimit: { return getDefaultGasLimit() }
|
|
||||||
reset: function() {
|
reset: function() {
|
||||||
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
|
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
|
||||||
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
|
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
|
||||||
selectedGasLimit = Qt.binding(getDefaultGasLimit)
|
|
||||||
}
|
}
|
||||||
|
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
|
||||||
function getDefaultGasLimit() {
|
if (!(root.stickerPackId > -1 && selectFromAccount.selectedAccount && root.packPrice && parseFloat(root.packPrice) > 0)) {
|
||||||
if (root.stickerPackId > -1 && selectFromAccount.selectedAccount && root.packPrice && parseFloat(root.packPrice) > 0) {
|
selectedGasLimit = 325000
|
||||||
return chatsModel.getStickerBuyPackGasEstimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice)
|
return
|
||||||
}
|
}
|
||||||
return 200000
|
selectedGasLimit = chatsModel.buyPackGasEstimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
GasValidator {
|
GasValidator {
|
||||||
id: gasValidator
|
id: gasValidator
|
||||||
|
@ -83,6 +83,7 @@ ModalPopup {
|
|||||||
accounts = Qt.binding(function() { return walletModel.accounts })
|
accounts = Qt.binding(function() { return walletModel.accounts })
|
||||||
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
|
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
|
||||||
}
|
}
|
||||||
|
onSelectedAccountChanged: gasSelector.estimateGas()
|
||||||
}
|
}
|
||||||
SeparatorWithIcon {
|
SeparatorWithIcon {
|
||||||
id: separator
|
id: separator
|
||||||
@ -102,6 +103,7 @@ ModalPopup {
|
|||||||
contacts = Qt.binding(function() { return profileModel.addedContacts })
|
contacts = Qt.binding(function() { return profileModel.addedContacts })
|
||||||
selectedRecipient = {}
|
selectedRecipient = {}
|
||||||
}
|
}
|
||||||
|
onSelectedRecipientChanged: gasSelector.estimateGas()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransactionFormGroup {
|
TransactionFormGroup {
|
||||||
@ -119,6 +121,8 @@ ModalPopup {
|
|||||||
reset: function() {
|
reset: function() {
|
||||||
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
|
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
|
||||||
}
|
}
|
||||||
|
onSelectedAssetChanged: gasSelector.estimateGas()
|
||||||
|
onSelectedAmountChanged: gasSelector.estimateGas()
|
||||||
}
|
}
|
||||||
GasSelector {
|
GasSelector {
|
||||||
id: gasSelector
|
id: gasSelector
|
||||||
@ -134,6 +138,24 @@ ModalPopup {
|
|||||||
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
|
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
|
||||||
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
|
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
|
||||||
}
|
}
|
||||||
|
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
|
||||||
|
if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
|
||||||
|
selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
|
||||||
|
txtAmount.selectedAsset && txtAmount.selectedAsset.address &&
|
||||||
|
txtAmount.selectedAmount)) return
|
||||||
|
|
||||||
|
let gasEstimate = JSON.parse(walletModel.estimateGas(
|
||||||
|
selectFromAccount.selectedAccount.address,
|
||||||
|
selectRecipient.selectedRecipient.address,
|
||||||
|
txtAmount.selectedAsset.address,
|
||||||
|
txtAmount.selectedAmount))
|
||||||
|
|
||||||
|
if (gasEstimate.error) {
|
||||||
|
console.warn(qsTr("Error estimating gas: %1").arg(gasEstimate.error.message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedGasLimit = gasEstimate.result
|
||||||
|
})
|
||||||
}
|
}
|
||||||
GasValidator {
|
GasValidator {
|
||||||
id: gasValidator
|
id: gasValidator
|
||||||
|
@ -37,7 +37,7 @@ Item {
|
|||||||
}
|
}
|
||||||
txtValidationError.text = ""
|
txtValidationError.text = ""
|
||||||
let gasTotal = selectedGasEthValue
|
let gasTotal = selectedGasEthValue
|
||||||
if (selectedAsset && selectedAsset.symbol.toUpperCase() === "ETH") {
|
if (selectedAsset && selectedAsset.symbol && selectedAsset.symbol.toUpperCase() === "ETH") {
|
||||||
gasTotal += selectedAmount
|
gasTotal += selectedAmount
|
||||||
}
|
}
|
||||||
const currAcctGasAsset = Utils.findAssetBySymbol(selectedAccount.assets, "ETH")
|
const currAcctGasAsset = Utils.findAssetBySymbol(selectedAccount.assets, "ETH")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user