diff --git a/src/app/wallet/view.nim b/src/app/wallet/view.nim index f4f7a388be..de5f97d6f2 100644 --- a/src/app/wallet/view.nim +++ b/src/app/wallet/view.nim @@ -157,8 +157,8 @@ QtObject: read = getAccountList notify = accountListChanged - proc onSendTransaction*(self: WalletView, from_value: string, to: string, value: string, password: string): string {.slot.} = - result = self.status.wallet.sendTransaction(from_value, to, value, password) + proc onSendTransaction*(self: WalletView, from_value: string, to: string, assetAddress: string, value: string, password: string): string {.slot.} = + return self.status.wallet.sendTransaction(from_value, to, assetAddress, value, password) proc getDefaultAccount*(self: WalletView): string {.slot.} = self.currentAccount.address diff --git a/src/app/wallet/views/asset_list.nim b/src/app/wallet/views/asset_list.nim index 2c90ae6696..f627285be1 100644 --- a/src/app/wallet/views/asset_list.nim +++ b/src/app/wallet/views/asset_list.nim @@ -7,6 +7,7 @@ type Symbol = UserRole + 2, Value = UserRole + 3, FiatValue = UserRole + 4 + Address = UserRole + 5 QtObject: type AssetList* = ref object of QAbstractListModel @@ -32,6 +33,7 @@ QtObject: of "symbol": result = asset.symbol of "value": result = asset.value of "fiatValue": result = asset.fiatValue + of "address": result = asset.address method rowCount(self: AssetList, index: QModelIndex = nil): int = return self.assets.len @@ -48,12 +50,14 @@ QtObject: of AssetRoles.Symbol: result = newQVariant(asset.symbol) of AssetRoles.Value: result = newQVariant(asset.value) of AssetRoles.FiatValue: result = newQVariant(asset.fiatValue) + of AssetRoles.Address: result = newQVariant(asset.address) method roleNames(self: AssetList): Table[int, string] = { AssetRoles.Name.int:"name", AssetRoles.Symbol.int:"symbol", AssetRoles.Value.int:"value", - AssetRoles.FiatValue.int:"fiatValue" }.toTable + AssetRoles.FiatValue.int:"fiatValue", + AssetRoles.Address.int:"address" }.toTable proc addAssetToList*(self: AssetList, asset: Asset) = self.beginInsertRows(newQModelIndex(), self.assets.len, self.assets.len) diff --git a/src/status/libstatus/accounts/constants.nim b/src/status/libstatus/accounts/constants.nim index 0ecc65d99d..832f18ef0f 100644 --- a/src/status/libstatus/accounts/constants.nim +++ b/src/status/libstatus/accounts/constants.nim @@ -5,6 +5,8 @@ const SEED* = "seed" const KEY* = "key" const WATCH* = "watch" +const ZERO_ADDRESS* = "0x0000000000000000000000000000000000000000" + const PATH_WALLET_ROOT* = "m/44'/60'/0'/0" # EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived const PATH_EIP_1581* = "m/43'/60'/1581'" diff --git a/src/status/libstatus/contracts.nim b/src/status/libstatus/contracts.nim index 43400bbc51..7e74da607f 100644 --- a/src/status/libstatus/contracts.nim +++ b/src/status/libstatus/contracts.nim @@ -7,10 +7,10 @@ type Mainnet, Testnet -type Method = object - name: string - signature: string - noPadding: bool +type Method* = object + name*: string + signature*: string + noPadding*: bool type Contract* = ref object name*: string @@ -21,7 +21,8 @@ type Contract* = ref object let CONTRACTS: seq[Contract] = @[ Contract(name: "snt", network: Network.Mainnet, address: parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"), methods: [ - ("approveAndCall", Method(signature: "approveAndCall(address,uint256,bytes)")) + ("approveAndCall", Method(signature: "approveAndCall(address,uint256,bytes)")), + ("transfer", Method(signature: "transfer(address,uint256)")) ].toTable ), Contract(name: "snt", network: Network.Testnet, address: parseAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162")), @@ -77,6 +78,8 @@ proc encodeParam[T](value: T): string = result = toHex(value, 64) elif T is EthAddress: result = value.toHex() + else: + result = align(value, 64, '0') macro encodeAbi*(self: Method, params: varargs[untyped]): untyped = result = quote do: diff --git a/src/status/libstatus/wallet.nim b/src/status/libstatus/wallet.nim index f6d2e100ac..fa5f17aece 100644 --- a/src/status/libstatus/wallet.nim +++ b/src/status/libstatus/wallet.nim @@ -1,6 +1,9 @@ -import json, json, strformat, stint, strutils, chronicles +import json, httpclient, json, strformat, stint, strutils, sequtils, chronicles, parseutils, tables import libstatus, core, types import ../wallet/account +from ./accounts/constants import ZERO_ADDRESS +import ./contracts as contractMethods +from eth/common/utils import parseAddress proc getWalletAccounts*(): seq[WalletAccount] = try: @@ -26,17 +29,17 @@ proc getWalletAccounts*(): seq[WalletAccount] = let msg = getCurrentExceptionMsg() error "Failed getting wallet accounts", msg -proc getTransfersByAddress*(address: string): seq[Transaction] = +proc getTransfersByAddress*(address: string): seq[types.Transaction] = try: let response = getBlockByNumber("latest") let latestBlock = parseJson(response)["result"] let transactionsResponse = getTransfersByAddress(address, latestBlock["number"].getStr, "0x14") let transactions = parseJson(transactionsResponse)["result"] - var accountTransactions: seq[Transaction] = @[] + var accountTransactions: seq[types.Transaction] = @[] for transaction in transactions: - accountTransactions.add(Transaction( + accountTransactions.add(types.Transaction( typeValue: transaction["type"].getStr, address: transaction["address"].getStr, contract: transaction["contract"].getStr, @@ -58,14 +61,42 @@ proc getTransfersByAddress*(address: string): seq[Transaction] = error "Failed getting wallet account transactions", msg -proc sendTransaction*(from_address: string, to: string, value: string, password: string): string = - var args = %* { - "value": fmt"0x{toHex(value)}", - "from": from_address, - "to": to - } - var response = sendTransaction($args, password) - result = response +proc sendTransaction*(from_address: string, to: string, assetAddress: string, value: string, password: string): string = + try: + var args: JsonNode + if (assetAddress == ZERO_ADDRESS or assetAddress == ""): + var weiValue = value.parseFloat() * float(1000000000000000000) + var stringValue = fmt"{weiValue:<.0f}" + stringValue.removeSuffix('.') + var hexValue = parseBiggestInt(stringValue).toHex() + hexValue.removePrefix('0') + args = %* { + "value": fmt"0x{hexValue}", + "from": from_address, + "to": to + } + else: + var bigIntValue = u256(value) + # TODO get decimals from the token + bigIntValue = bigIntValue * u256(1000000000000000000) + # Just using mainnet SNT to get the method ABI + let contract = getContract(Network.Mainnet, "snt") + let transferEncoded = contract.methods["transfer"].encodeAbi(parseAddress(to), bigIntValue.toHex()) + + args = %* { + "from": from_address, + "to": assetAddress, + "data": transferEncoded + } + + let response = sendTransaction($args, password) + let parsedResponse = parseJson(response) + + result = parsedResponse["result"].getStr + trace "Transaction sent succesfully", hash=result + except Exception as e: + error "Error submitting transaction", msg=e.msg + result = e.msg proc getBalance*(address: string): string = let payload = %* [address, "latest"] diff --git a/src/status/wallet.nim b/src/status/wallet.nim index 9e09ba8b2b..f5a82bdc30 100644 --- a/src/status/wallet.nim +++ b/src/status/wallet.nim @@ -44,8 +44,8 @@ proc initEvents*(self: WalletModel) = proc delete*(self: WalletModel) = discard -proc sendTransaction*(self: WalletModel, from_value: string, to: string, value: string, password: string): string = - status_wallet.sendTransaction(from_value, to, value, password) +proc sendTransaction*(self: WalletModel, from_value: string, to: string, assetAddress: string, value: string, password: string): string = + status_wallet.sendTransaction(from_value, to, assetAddress, value, password) proc getDefaultCurrency*(self: WalletModel): string = # TODO: this should come from a model? It is going to be used too in the diff --git a/ui/app/AppLayouts/Wallet/SendModal.qml b/ui/app/AppLayouts/Wallet/SendModal.qml index f079762822..89729a8d06 100644 --- a/ui/app/AppLayouts/Wallet/SendModal.qml +++ b/ui/app/AppLayouts/Wallet/SendModal.qml @@ -15,7 +15,6 @@ ModalPopup { onOpened: { sendModalContent.amountInput.text = "" sendModalContent.amountInput.forceActiveFocus(Qt.MouseFocusReason) - sendModalContent.defaultAccount = walletModel.getDefaultAccount() const accounts = walletModel.accounts const numAccounts = accounts.rowCount() const accountsData = [] @@ -35,7 +34,8 @@ ModalPopup { assetsData.push({ name: assets.rowData(f, 'name'), symbol: assets.rowData(f, 'symbol'), - value: assets.rowData(f, 'value') + value: assets.rowData(f, 'value'), + address: assets.rowData(f, 'address') }) } sendModalContent.assets = assetsData diff --git a/ui/app/AppLayouts/Wallet/components/SendModalContent.qml b/ui/app/AppLayouts/Wallet/components/SendModalContent.qml index b11dd3ddc2..4b29fa68d2 100644 --- a/ui/app/AppLayouts/Wallet/components/SendModalContent.qml +++ b/ui/app/AppLayouts/Wallet/components/SendModalContent.qml @@ -7,7 +7,7 @@ Item { property alias amountInput: txtAmount property var accounts: [] property var assets: [] - property string defaultAccount: "0x1234" + property string defaultAccount: "0x8558BFe81d00333379Af1Cd4c07F3dc50081FEC4" property int selectedAccountIndex: 0 property string selectedAccountAddress: accounts && accounts.length ? accounts[selectedAccountIndex].address : "" @@ -16,6 +16,7 @@ Item { property int selectedAssetIndex: 0 property string selectedAssetName: assets && assets.length ? assets[selectedAssetIndex].name : "" + property string selectedAssetAddress: assets && assets.length ? assets[selectedAssetIndex].address : "" property string selectedAssetSymbol: assets && assets.length ? assets[selectedAssetIndex].symbol : "" property string selectedAccountValue: assets && assets.length ? assets[selectedAssetIndex].value : "" @@ -27,12 +28,22 @@ Item { if (!validate()) { return; } + // Convert to Wei + // TODO use the decimal value fo the token, it's not always 18 + // TODO add big number support +// const weiValue = parseFloat(txtAmount.text, 10) * 1000000000000000000 + console.log('SENDING', selectedAccountAddress, + txtTo.text, + selectedAssetAddress, + txtAmount.text, + txtPassword.text) let result = walletModel.onSendTransaction(selectedAccountAddress, txtTo.text, + selectedAssetAddress, txtAmount.text, txtPassword.text) - console.log(result) + console.log('hash', result) } function validate() { @@ -56,7 +67,7 @@ Item { amountValidationError = qsTr("You need to enter an amount") } else if (isNaN(txtAmount.text)) { amountValidationError = qsTr("This needs to be a number") - } else if (parseInt(txtAmount.text, 10) > parseInt(selectedAccountValue, 10)) { + } else if (parseFloat(txtAmount.text) > parseFloat(selectedAccountValue)) { amountValidationError = qsTr("Amount needs to be lower than your balance (%1)").arg(selectedAccountValue) } else { amountValidationError = ""