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:
emizzle 2020-09-07 19:39:17 +10:00 committed by Iuri Matias
parent 4f0cdad8c7
commit 2c7dd929ad
20 changed files with 325 additions and 171 deletions

View File

@ -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/mailservers
import ../../status/stickers
@ -105,17 +105,18 @@ QtObject:
QtProperty[QVariant] stickerMarketAddress:
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:
result = self.status.stickers.buyPackGasEstimate(packId, address, price)
result = self.status.stickers.estimateGas(packId, address, price)
except:
result = "400000"
result = 325000
proc buyStickerPack*(self: ChatsView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} =
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:
result = fmt"""{{ "error": {{ "message": "{e.msg}" }} }}"""
result = $(%* { "error": %* { "message": %e.msg }})
proc obtainAvailableStickerPacks*(self: ChatsView) =
spawnAndSend(self, "setAvailableStickerPacks") do:

View File

@ -1,6 +1,7 @@
import NimQml, Tables, strformat, strutils, chronicles, json, std/wrapnils, parseUtils, stint, tables
import ../../status/[status, wallet, threads]
import ../../status/wallet/collectibles as status_collectibles
import ../../status/libstatus/accounts/constants
import ../../status/libstatus/wallet as status_wallet
import ../../status/libstatus/tokens
import ../../status/libstatus/types
@ -91,7 +92,7 @@ QtObject:
read = getSigningPhrase
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])
@ -262,12 +263,28 @@ QtObject:
QtProperty[QVariant] accounts:
read = getAccountList
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.} =
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:
result = fmt"""{{ "error": {{ "message": "{e.msg}" }} }}"""
result = $(%* { "error": %* { "message": %e.msg }})
proc getDefaultAccount*(self: WalletView): string {.slot.} =
self.currentAccount.address

View File

@ -1,11 +1,10 @@
import eventemitter, json, strutils, sequtils, tables, chronicles, sugar, times
import libstatus/contracts as status_contracts
import eventemitter, json, strutils, sequtils, tables, chronicles, times
import libstatus/chat as status_chat
import libstatus/mailservers as status_mailservers
import libstatus/chatCommands as status_chat_commands
import libstatus/accounts/constants as constants
import libstatus/types
import mailservers, stickers
import stickers
import profile/profile
import chat/[chat, message]
import signals/messages

View File

@ -12,7 +12,7 @@ import stew/byteutils
import unicode
import algorithm
import eth/common/eth_types, stew/byteutils
import libstatus/contracts
import libstatus/eth/contracts
const domain* = ".stateofus.eth"
proc userName*(ensName: string, removeSuffix: bool = false): string =

View File

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

View File

@ -1,15 +1,17 @@
import sequtils, strformat, sugar, macros, tables, strutils
import eth/common/eth_types, stew/byteutils, nimcrypto
import
sequtils, sugar, macros, tables, strutils
import
eth/common/eth_types, stew/byteutils, nimcrypto
from eth/common/utils import parseAddress
import ./types, ./settings, ./coder
import
../types, ../settings, ../coder, transactions, methods
export
GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf, Register, SetPubkey,
TokenOfOwnerByIndex, TokenPackId, TokenUri, FixedBytes, DynamicBytes, toHex, fromHex
type Method* = object
name*: string
signature*: string
TokenOfOwnerByIndex, TokenPackId, TokenUri, FixedBytes, DynamicBytes, toHex, fromHex,
decodeContractResponse, encodeAbi, estimateGas, send, call
type Contract* = ref object
name*: string
@ -121,32 +123,3 @@ proc getContract(network: Network, name: string): Contract =
proc getContract*(name: string): Contract =
let network = settings.getCurrentNetwork()
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)

View 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

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

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

View File

@ -1,4 +1,4 @@
import ./core as status, ./types, ./contracts, ./settings, ./edn_helpers
import ./core as status, ./types, ./eth/contracts, ./settings, ./edn_helpers
import
json, json_serialization, tables, chronicles, strutils, sequtils, httpclient,
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.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 =
let
contract = contracts.getContract("sticker-pack")

View File

@ -1,6 +1,6 @@
import json, chronicles, strformat, stint, strutils
import core, wallet
import contracts
import ./eth/contracts
import eth/common/eth_types, eth/common/utils
import json_serialization
import settings

View File

@ -1,5 +1,5 @@
import json, random, strutils, strformat, tables, chronicles
import stint, nim_status
import json, random, strutils, strformat, tables, chronicles, unicode
import stint
from times import getTime, toUnix, nanosecond
import accounts/signing_phrases
@ -82,7 +82,7 @@ proc first*(jArray: JsonNode, fieldName, id: string): JsonNode =
if jArray.kind != JArray:
raise newException(ValueError, "Parameter 'jArray' is a " & $jArray.kind & ", but must be a JArray")
for child in jArray.getElems:
if child{fieldName}.getStr == id:
if child{fieldName}.getStr.toLower == id.toLower:
return child
proc any*(jArray: JsonNode, fieldName, id: string): bool =
@ -90,7 +90,7 @@ proc any*(jArray: JsonNode, fieldName, id: string): bool =
return false
result = false
for child in jArray.getElems:
if child{fieldName}.getStr == id:
if child{fieldName}.getStr.toLower == id.toLower:
return true
proc isEmpty*(a: JsonNode): bool =

View File

@ -2,7 +2,7 @@ import json, json, options, json_serialization, stint, chronicles
import core, types, utils, strutils, strformat
from nim_status import validateMnemonic, startWallet
import ../wallet/account
import ./contracts as contractMethods
import ./eth/contracts as contractMethods
import eth/common/eth_types
import ./types
import ../signals/types as signal_types
@ -62,14 +62,6 @@ proc getTransfersByAddress*(address: string): seq[types.Transaction] =
let msg = getCurrentExceptionMsg()
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 =
let payload = %* [address, "latest"]
let response = parseJson(callPrivateRPC("eth_getBalance", payload))

View File

@ -1,16 +1,14 @@
import
import # global deps
tables, strutils, sequtils, sugar
import
import # project deps
chronicles, eth/common/eth_types, eventemitter
from eth/common/utils import parseAddress
import
libstatus/types, libstatus/stickers as status_stickers,
libstatus/contracts as status_contracts
from libstatus/utils as libstatus_utils import eth2Wei, gwei2Wei, toUInt64
import # local deps
libstatus/types, libstatus/eth/contracts as status_contracts,
libstatus/stickers as status_stickers, transactions
from libstatus/utils as libstatus_utils import eth2Wei
logScope:
topics = "stickers-model"
@ -43,27 +41,53 @@ proc init*(self: StickersModel) =
var evArgs = StickerArgs(e)
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 buyPackGasEstimate*(self: StickersModel, packId: int, address: string, price: string): string =
proc buildTransaction(self: StickersModel, packId: Uint256, address: EthAddress, price: Uint256, approveAndCall: var ApproveAndCall, sntContract: var Contract, gas = "", gasPrice = ""): EthSend =
sntContract = status_contracts.getContract("snt")
let
priceTyped = eth2Wei(parseFloat(price), 18) # SNT
hexGas = status_stickers.buyPackGasEstimate(packId.u256, parseAddress(address), priceTyped)
result = $fromHex[int](hexGas)
stickerMktContract = status_contracts.getContract("sticker-market")
buyToken = BuyToken(packId: packId, address: address, price: price)
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 =
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] =
if self.purchasedStickerPacks != @[]:
return self.purchasedStickerPacks

View 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

View File

@ -1,17 +1,17 @@
import eventemitter, json, strformat, strutils, chronicles, sequtils, httpclient, tables
import json_serialization, stint
from eth/common/utils import parseAddress
from eth/common/eth_types import EthAddress
import libstatus/accounts as status_accounts
import libstatus/tokens as status_tokens
import libstatus/settings as status_settings
import libstatus/wallet as status_wallet
import libstatus/accounts/constants as constants
import libstatus/contracts as contracts
from libstatus/types import GeneratedAccount, DerivedAccount, Transaction, Setting, GasPricePrediction, EthSend, Quantity, `%`, StatusGoException, Network, RpcResponse, RpcException
import libstatus/eth/[eth, contracts]
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
import wallet/balance_manager
import wallet/account
import wallet/collectibles
import wallet/[balance_manager, account, collectibles]
import transactions
export account, collectibles
export Transaction
@ -50,34 +50,70 @@ proc initEvents*(self: WalletModel) =
proc delete*(self: WalletModel) =
discard
proc sendTransaction*(self: WalletModel, source, to, assetAddress, value, gas, gasPrice, password: string): RpcResponse =
var
weiValue = eth2Wei(parseFloat(value), 18) # ETH
data = ""
toAddr = parseAddress(to)
let gasPriceInWei = gwei2Wei(parseFloat(gasPrice))
proc buildTokenTransaction(self: WalletModel, source, to, assetAddress: EthAddress, value: float, transfer: var Transfer, contract: var Contract, gas = "", gasPrice = ""): EthSend =
let token = self.tokens.first("address", $assetAddress)
contract = getContract("snt")
transfer = Transfer(to: to, value: eth2Wei(value, token["decimals"].getInt))
transactions.buildTokenTransaction(source, assetAddress, gas, gasPrice)
# TODO: this code needs to be tested with testnet assets (to be implemented in
# a future PR
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
let
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
proc estimateGas*(self: WalletModel, source, to, value: string): int =
var tx = transactions.buildTransaction(
parseAddress(source),
eth2Wei(parseFloat(value), 18)
)
tx.to = parseAddress(to).some
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:
raise
@ -88,10 +124,15 @@ proc getDefaultCurrency*(self: WalletModel): string =
# 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.
proc getStatusTokenSymbol*(self: WalletModel): string =
proc getStatusToken*(self: WalletModel): string =
var token = Asset(address: $getContract("snt").address)
if status_settings.getCurrentNetwork() == Network.Testnet:
return "STT"
"SNT"
token.name = "Status Test Token"
token.symbol = "STT"
else:
token.name = "Status Network Token"
token.symbol = "SNT"
result = $(%token)
proc setDefaultCurrency*(self: WalletModel, currency: string) =
discard status_settings.saveSetting(Setting.Currency, currency)

View File

@ -1,7 +1,7 @@
import strformat, httpclient, json, chronicles, sequtils, strutils, tables, sugar
from eth/common/utils import parseAddress
import ../libstatus/core as status
import ../libstatus/contracts as contracts
import ../libstatus/eth/contracts as contracts
import ../libstatus/stickers as status_stickers
import ../chat as status_chat
import ../libstatus/types

View File

@ -7,7 +7,7 @@ import "../../../../shared"
ModalPopup {
id: root
property var asset: { "name": "Status", "symbol": walletModel.getStatusTokenSymbol() }
readonly property var asset: JSON.parse(walletModel.getStatusToken())
property int stickerPackId: -1
property string packPrice
property bool showBackBtn: false
@ -90,6 +90,7 @@ ModalPopup {
showBalanceForAssetSymbol = Qt.binding(function() { return root.asset.symbol })
minRequiredAssetBalance = Qt.binding(function() { return root.packPrice })
}
onSelectedAccountChanged: gasSelector.estimateGas()
}
RecipientSelector {
id: selectRecipient
@ -98,6 +99,7 @@ ModalPopup {
contacts: profileModel.addedContacts
selectedRecipient: { "address": chatsModel.stickerMarketAddress, "type": RecipientSelector.Type.Address }
readOnly: true
onSelectedRecipientChanged: gasSelector.estimateGas()
}
GasSelector {
id: gasSelector
@ -107,19 +109,17 @@ ModalPopup {
getGasEthValue: walletModel.getGasEthValue
getFiatValue: walletModel.getFiatValue
defaultCurrency: walletModel.defaultCurrency
selectedGasLimit: { return getDefaultGasLimit() }
reset: function() {
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
selectedGasLimit = Qt.binding(getDefaultGasLimit)
}
function getDefaultGasLimit() {
if (root.stickerPackId > -1 && selectFromAccount.selectedAccount && root.packPrice && parseFloat(root.packPrice) > 0) {
return chatsModel.getStickerBuyPackGasEstimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice)
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(root.stickerPackId > -1 && selectFromAccount.selectedAccount && root.packPrice && parseFloat(root.packPrice) > 0)) {
selectedGasLimit = 325000
return
}
return 200000
}
selectedGasLimit = chatsModel.buyPackGasEstimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice)
})
}
GasValidator {
id: gasValidator

View File

@ -83,6 +83,7 @@ ModalPopup {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
}
onSelectedAccountChanged: gasSelector.estimateGas()
}
SeparatorWithIcon {
id: separator
@ -102,6 +103,7 @@ ModalPopup {
contacts = Qt.binding(function() { return profileModel.addedContacts })
selectedRecipient = {}
}
onSelectedRecipientChanged: gasSelector.estimateGas()
}
}
TransactionFormGroup {
@ -119,6 +121,8 @@ ModalPopup {
reset: function() {
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
}
onSelectedAssetChanged: gasSelector.estimateGas()
onSelectedAmountChanged: gasSelector.estimateGas()
}
GasSelector {
id: gasSelector
@ -134,6 +138,24 @@ ModalPopup {
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
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 {
id: gasValidator

View File

@ -37,7 +37,7 @@ Item {
}
txtValidationError.text = ""
let gasTotal = selectedGasEthValue
if (selectedAsset && selectedAsset.symbol.toUpperCase() === "ETH") {
if (selectedAsset && selectedAsset.symbol && selectedAsset.symbol.toUpperCase() === "ETH") {
gasTotal += selectedAmount
}
const currAcctGasAsset = Utils.findAssetBySymbol(selectedAccount.assets, "ETH")