fix: loading of wallet history, display of tx datetime
Fixes: #2539. Transaction history is now correctly being fetched from status-go as per mobile. Firstly, when accounts are added (ie as watch accounts), `wallet_checkRecentHistory` must be called first so that the status-go db is populated. After that, `wallet_getTransfersByAddress` can be called. On app load, when we run the `initBalance` logic, we are calling `wallet_getTransfersByAddress`, asking for the last 20 transactions with the `loadMore` parameter set to false. When the user navigates to the Wallet > History tab, they can then click “Load More” to fetch more transactions from status-go. Once the number of transactions returns false below the expected amount, the remaining transactions to fetch have been exhausted and the “Load More” button is disabled. feat: add non-archival node warning to the UI to indicate to the user that they may not have complete results feat: set active account to the added account Once an account is added to the wallet, that newly added account is selected as the active account. 1. The “load more” button is active when new transactions that aren’t already displayed are returned from `wallet_getTransfersByAddress`. This is the only way to enable or disable the “Load more” button as status-go is not able to return information regarding whether or not there are more transactions to be fetched. The downside to this is that lets say the last page of transactions is returned, but there are no more pages left. These returned txs are not currently displayed, so the “load more” button will still be enabled. However, the next click of the button will return 0 results, thus disabling it. It’s effectively an extra click to get to the disabled state. 2. For more information on how the `toBlock` parameter operates for the `wallet_getTransfersForAddress` RPC call, see https://notes.status.im/XmENTrVRRaqhwE4gK0m8Mg?view.
This commit is contained in:
parent
21e6affa98
commit
0b0a542828
|
@ -288,12 +288,6 @@ returns `true` if the specified address is in the list of default or custom (use
|
||||||
|
|
||||||
Returns stringified JSON result of the decoding. The JSON will contain only an `error` field if there was an error during decoding. Otherwise, it will contain a `symbol` (the token symbol) and an `amount` (amount approved to spend) field.
|
Returns stringified JSON result of the decoding. The JSON will contain only an `error` field if there was an error during decoding. Otherwise, it will contain a `symbol` (the token symbol) and an `amount` (amount approved to spend) field.
|
||||||
|
|
||||||
#### `isHistoryFetched(address: string)` : `bool`
|
|
||||||
|
|
||||||
* `address` (`string`): address of the account to check
|
|
||||||
|
|
||||||
returns `true` if `status-go` has returned transfer history for the specified account (result of `wallet_getTransfersByAddress`)
|
|
||||||
|
|
||||||
#### `loadTransactionsForAccount(address: string)` : `void`
|
#### `loadTransactionsForAccount(address: string)` : `void`
|
||||||
|
|
||||||
* `address` (`string`): address of the account to load transactions for
|
* `address` (`string`): address of the account to load transactions for
|
||||||
|
@ -343,7 +337,6 @@ The `walletModel` exposes the following signals, which can be consumed in QML us
|
||||||
| `transactionWasSent` | `txResult` (`string`): JSON stringified result of sending a transaction | fired when accounts on the node have chagned |
|
| `transactionWasSent` | `txResult` (`string`): JSON stringified result of sending a transaction | fired when accounts on the node have chagned |
|
||||||
| `defaultCurrencyChanged` | none | fired when the user's default currency has chagned |
|
| `defaultCurrencyChanged` | none | fired when the user's default currency has chagned |
|
||||||
| `gasPricePredictionsChanged` | none | fired when the gas price predictions have changed, typically after getting a gas price prediction response |
|
| `gasPricePredictionsChanged` | none | fired when the gas price predictions have changed, typically after getting a gas price prediction response |
|
||||||
| `historyWasFetched` | none | fired when `status-go` completes fetching of transaction history |
|
|
||||||
| `loadingTrxHistoryChanged` | `isLoading` (`bool`): `true` if the transaction history is loading | fired when the loading of transfer history starts and completes |
|
| `loadingTrxHistoryChanged` | `isLoading` (`bool`): `true` if the transaction history is loading | fired when the loading of transfer history starts and completes |
|
||||||
| `ensWasResolved` | `resolvedAddress` (`string`): address resolved from the ENS name<br>`uuid` (`string`): unique identifier that was used to identify the request in QML so that only specific components can respond when needed | fired when an ENS name was resolved |
|
| `ensWasResolved` | `resolvedAddress` (`string`): address resolved from the ENS name<br>`uuid` (`string`): unique identifier that was used to identify the request in QML so that only specific components can respond when needed | fired when an ENS name was resolved |
|
||||||
| `transactionCompleted` | `success` (`bool`): `true` if the transaction was successful<br>`txHash` (`string`): has of the transaction<br>`revertReason` (`string`): reason transaction was reverted (if provided and if the transaction was reverted) | fired when a tracked transction (from the wallet or ENS) was completed |
|
| `transactionCompleted` | `success` (`bool`): `true` if the transaction was successful<br>`txHash` (`string`): has of the transaction<br>`revertReason` (`string`): reason transaction was reverted (if provided and if the transaction was reverted) | fired when a tracked transction (from the wallet or ENS) was completed |
|
||||||
|
|
|
@ -19,7 +19,6 @@ The `walletModel` exposes the following signals, which can be consumed in QML us
|
||||||
| `transactionWasSent` | `txResult` (`string`): JSON stringified result of sending a transaction | fired when accounts on the node have chagned |
|
| `transactionWasSent` | `txResult` (`string`): JSON stringified result of sending a transaction | fired when accounts on the node have chagned |
|
||||||
| `defaultCurrencyChanged` | none | fired when the user's default currency has chagned |
|
| `defaultCurrencyChanged` | none | fired when the user's default currency has chagned |
|
||||||
| `gasPricePredictionsChanged` | none | fired when the gas price predictions have changed, typically after getting a gas price prediction response |
|
| `gasPricePredictionsChanged` | none | fired when the gas price predictions have changed, typically after getting a gas price prediction response |
|
||||||
| `historyWasFetched` | none | fired when `status-go` completes fetching of transaction history |
|
|
||||||
| `loadingTrxHistoryChanged` | `isLoading` (`bool`): `true` if the transaction history is loading | fired when the loading of transfer history starts and completes |
|
| `loadingTrxHistoryChanged` | `isLoading` (`bool`): `true` if the transaction history is loading | fired when the loading of transfer history starts and completes |
|
||||||
| `ensWasResolved` | `resolvedAddress` (`string`): address resolved from the ENS name<br>`uuid` (`string`): unique identifier that was used to identify the request in QML so that only specific components can respond when needed | fired when an ENS name was resolved |
|
| `ensWasResolved` | `resolvedAddress` (`string`): address resolved from the ENS name<br>`uuid` (`string`): unique identifier that was used to identify the request in QML so that only specific components can respond when needed | fired when an ENS name was resolved |
|
||||||
| `transactionCompleted` | `success` (`bool`): `true` if the transaction was successful<br>`txHash` (`string`): has of the transaction<br>`revertReason` (`string`): reason transaction was reverted (if provided and if the transaction was reverted) | fired when a tracked transction (from the wallet or ENS) was completed |
|
| `transactionCompleted` | `success` (`bool`): `true` if the transaction was successful<br>`txHash` (`string`): has of the transaction<br>`revertReason` (`string`): reason transaction was reverted (if provided and if the transaction was reverted) | fired when a tracked transction (from the wallet or ENS) was completed |
|
||||||
|
@ -100,7 +99,6 @@ Methods can be invoked by calling them directly on the `walletModel`, ie `wallet
|
||||||
| `isFetchingHistory` | `address` (`string`): address of the account to check | `bool` | returns `true` if `status-go` is currently fetching the transaction history for the specified account |
|
| `isFetchingHistory` | `address` (`string`): address of the account to check | `bool` | returns `true` if `status-go` is currently fetching the transaction history for the specified account |
|
||||||
| `isKnownTokenContract` | `address` (`string`): contract address | `bool` | returns `true` if the specified address is in the list of default or custom (user-added) contracts |
|
| `isKnownTokenContract` | `address` (`string`): contract address | `bool` | returns `true` if the specified address is in the list of default or custom (user-added) contracts |
|
||||||
| `decodeTokenApproval` | `tokenAddress` (`string`): contract address<br>`data` (`string`): response received from the ERC-20 token `Approve` function call | `string` | Returns stringified JSON result of the decoding. The JSON will contain only an `error` field if there was an error during decoding. Otherwise, it will contain a `symbol` (the token symbol) and an `amount` (amount approved to spend) field. |
|
| `decodeTokenApproval` | `tokenAddress` (`string`): contract address<br>`data` (`string`): response received from the ERC-20 token `Approve` function call | `string` | Returns stringified JSON result of the decoding. The JSON will contain only an `error` field if there was an error during decoding. Otherwise, it will contain a `symbol` (the token symbol) and an `amount` (amount approved to spend) field. |
|
||||||
| `isHistoryFetched` | `address` (`string`): address of the account to check | `bool` | returns `true` if `status-go` has returned transfer history for the specified account (result of `wallet_getTransfersByAddress`) |
|
|
||||||
| `loadTransactionsForAccount` | `address` (`string`): address of the account to load transactions for | `void` | loads the transfer history for the specified account (result of `wallet_getTransfersByAddress`) in a separate thread |
|
| `loadTransactionsForAccount` | `address` (`string`): address of the account to load transactions for | `void` | loads the transfer history for the specified account (result of `wallet_getTransfersByAddress`) in a separate thread |
|
||||||
| `setTrxHistoryResult` | `historyJSON` (`string`): stringified JSON result from `status-go`'s response to `wallet_getTransfersByAddress` | `void` | sets the transaction history for the account requested. If the requested account was tracked by the `walletModel`, it will have its transactions updated (including `currentAccount`). The `loadingTrxHistoryChanged` signal is also fired with `false` as a parameter. |
|
| `setTrxHistoryResult` | `historyJSON` (`string`): stringified JSON result from `status-go`'s response to `wallet_getTransfersByAddress` | `void` | sets the transaction history for the account requested. If the requested account was tracked by the `walletModel`, it will have its transactions updated (including `currentAccount`). The `loadingTrxHistoryChanged` signal is also fired with `false` as a parameter. |
|
||||||
| `resolveENS` | `ens` (`string`): the ENS name to resolve | `void` | resolves an ENS name in a separate thread |
|
| `resolveENS` | `ens` (`string`): the ENS name to resolve | `void` | resolves an ENS name in a separate thread |
|
||||||
|
|
|
@ -87,7 +87,7 @@ QtObject:
|
||||||
# somehow this value crashes the app
|
# somehow this value crashes the app
|
||||||
if value == "0x0":
|
if value == "0x0":
|
||||||
return "0"
|
return "0"
|
||||||
return stripTrailingZeroes(stint.toString(stint.fromHex(StUint[256], value)))
|
return $stint.fromHex(StUint[256], value)
|
||||||
|
|
||||||
proc urlFromUserInput*(self: UtilsView, input: string): string {.slot.} =
|
proc urlFromUserInput*(self: UtilsView, input: string): string {.slot.} =
|
||||||
result = url_fromUserInput(input)
|
result = url_fromUserInput(input)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, strformat, strutils, chronicles
|
import NimQml, strformat, strutils, chronicles, sugar, sequtils
|
||||||
|
|
||||||
import view
|
import view
|
||||||
import views/[asset_list, account_list, account_item]
|
import views/[asset_list, account_list, account_item]
|
||||||
|
@ -34,7 +34,7 @@ proc init*(self: WalletController) =
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
self.view.addAccountToList(account)
|
self.view.addAccountToList(account)
|
||||||
|
|
||||||
self.view.initBalances()
|
self.view.checkRecentHistory()
|
||||||
self.view.setDappBrowserAddress()
|
self.view.setDappBrowserAddress()
|
||||||
|
|
||||||
self.status.events.on("accountsUpdated") do(e: Args):
|
self.status.events.on("accountsUpdated") do(e: Args):
|
||||||
|
@ -63,14 +63,16 @@ proc init*(self: WalletController) =
|
||||||
# TODO: show notification
|
# TODO: show notification
|
||||||
|
|
||||||
of "new-transfers":
|
of "new-transfers":
|
||||||
for acc in data.accounts:
|
self.view.initBalances(data.accounts)
|
||||||
self.view.loadTransactionsForAccount(acc)
|
|
||||||
self.view.initBalances(false)
|
|
||||||
of "recent-history-fetching":
|
of "recent-history-fetching":
|
||||||
self.view.setHistoryFetchState(data.accounts, true)
|
self.view.setHistoryFetchState(data.accounts, true)
|
||||||
of "recent-history-ready":
|
of "recent-history-ready":
|
||||||
|
self.view.initBalances(data.accounts)
|
||||||
self.view.setHistoryFetchState(data.accounts, false)
|
self.view.setHistoryFetchState(data.accounts, false)
|
||||||
self.view.initBalances(false)
|
of "non-archival-node-detected":
|
||||||
|
self.view.setHistoryFetchState(self.status.wallet.accounts.map(account => account.address), false)
|
||||||
|
self.view.setNonArchivalNode()
|
||||||
|
error "Non-archival node detected, please check your Infura key or your connected node"
|
||||||
else:
|
else:
|
||||||
error "Unhandled wallet signal", eventType=data.eventType
|
error "Unhandled wallet signal", eventType=data.eventType
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import # std libs
|
import # std libs
|
||||||
atomics, strformat, strutils, sequtils, json, std/wrapnils, parseUtils, tables
|
algorithm, atomics, sequtils, strformat, strutils, sugar, sequtils, json,
|
||||||
|
parseUtils, std/wrapnils, tables
|
||||||
|
|
||||||
import # vendor libs
|
import # vendor libs
|
||||||
NimQml, chronicles, stint
|
NimQml, chronicles, stint
|
||||||
|
@ -36,7 +37,9 @@ type
|
||||||
GasPredictionsTaskArg = ref object of QObjectTaskArg
|
GasPredictionsTaskArg = ref object of QObjectTaskArg
|
||||||
LoadTransactionsTaskArg = ref object of QObjectTaskArg
|
LoadTransactionsTaskArg = ref object of QObjectTaskArg
|
||||||
address: string
|
address: string
|
||||||
blockNumber: string
|
toBlock: Uint256
|
||||||
|
limit: int
|
||||||
|
loadMore: bool
|
||||||
ResolveEnsTaskArg = ref object of QObjectTaskArg
|
ResolveEnsTaskArg = ref object of QObjectTaskArg
|
||||||
ens: string
|
ens: string
|
||||||
uuid: string
|
uuid: string
|
||||||
|
@ -144,16 +147,20 @@ const loadTransactionsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.}
|
||||||
arg = decode[LoadTransactionsTaskArg](argEncoded)
|
arg = decode[LoadTransactionsTaskArg](argEncoded)
|
||||||
output = %*{
|
output = %*{
|
||||||
"address": arg.address,
|
"address": arg.address,
|
||||||
"history": getTransfersByAddress(arg.address)
|
"history": getTransfersByAddress(arg.address, arg.toBlock, arg.limit, arg.loadMore),
|
||||||
|
"loadMore": arg.loadMore
|
||||||
}
|
}
|
||||||
arg.finish(output)
|
arg.finish(output)
|
||||||
|
|
||||||
proc loadTransactions[T](self: T, slot: string, address: string) =
|
proc loadTransactions[T](self: T, slot: string, address: string, toBlock: Uint256, limit: int, loadMore: bool) =
|
||||||
let arg = LoadTransactionsTaskArg(
|
let arg = LoadTransactionsTaskArg(
|
||||||
tptr: cast[ByteAddress](loadTransactionsTask),
|
tptr: cast[ByteAddress](loadTransactionsTask),
|
||||||
vptr: cast[ByteAddress](self.vptr),
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
slot: slot,
|
slot: slot,
|
||||||
address: address
|
address: address,
|
||||||
|
toBlock: toBlock,
|
||||||
|
limit: limit,
|
||||||
|
loadMore: loadMore
|
||||||
)
|
)
|
||||||
self.status.tasks.threadpool.start(arg)
|
self.status.tasks.threadpool.start(arg)
|
||||||
|
|
||||||
|
@ -211,6 +218,7 @@ QtObject:
|
||||||
defaultGasLimit: string
|
defaultGasLimit: string
|
||||||
signingPhrase: string
|
signingPhrase: string
|
||||||
fetchingHistoryState: Table[string, bool]
|
fetchingHistoryState: Table[string, bool]
|
||||||
|
isNonArchivalNode: bool
|
||||||
|
|
||||||
proc delete(self: WalletView) =
|
proc delete(self: WalletView) =
|
||||||
self.accounts.delete
|
self.accounts.delete
|
||||||
|
@ -249,6 +257,7 @@ QtObject:
|
||||||
result.defaultGasLimit = "21000"
|
result.defaultGasLimit = "21000"
|
||||||
result.signingPhrase = ""
|
result.signingPhrase = ""
|
||||||
result.fetchingHistoryState = initTable[string, bool]()
|
result.fetchingHistoryState = initTable[string, bool]()
|
||||||
|
result.isNonArchivalNode = false
|
||||||
result.setup
|
result.setup
|
||||||
|
|
||||||
proc etherscanLinkChanged*(self: WalletView) {.signal.}
|
proc etherscanLinkChanged*(self: WalletView) {.signal.}
|
||||||
|
@ -325,7 +334,8 @@ QtObject:
|
||||||
self.setCurrentCollectiblesLists(selectedAccount.collectiblesLists)
|
self.setCurrentCollectiblesLists(selectedAccount.collectiblesLists)
|
||||||
self.loadCollectiblesForAccount(selectedAccount.address, selectedAccount.collectiblesLists)
|
self.loadCollectiblesForAccount(selectedAccount.address, selectedAccount.collectiblesLists)
|
||||||
|
|
||||||
self.setCurrentTransactions(selectedAccount.transactions)
|
self.currentTransactions.setHasMore(selectedAccount.transactions.hasMore)
|
||||||
|
self.setCurrentTransactions(selectedAccount.transactions.data)
|
||||||
|
|
||||||
proc getCurrentAccount*(self: WalletView): QVariant {.slot.} =
|
proc getCurrentAccount*(self: WalletView): QVariant {.slot.} =
|
||||||
result = newQVariant(self.currentAccount)
|
result = newQVariant(self.currentAccount)
|
||||||
|
@ -616,6 +626,18 @@ QtObject:
|
||||||
self.loadCollectibles("setCollectiblesResult", address, collectibleType)
|
self.loadCollectibles("setCollectiblesResult", address, collectibleType)
|
||||||
self.currentCollectiblesLists.setLoadingByType(collectibleType, 1)
|
self.currentCollectiblesLists.setLoadingByType(collectibleType, 1)
|
||||||
|
|
||||||
|
proc isNonArchivalNodeChanged*(self: WalletView) {.signal.}
|
||||||
|
|
||||||
|
proc setNonArchivalNode*(self: WalletView, isNonArchivalNode: bool = true) {.slot.} =
|
||||||
|
self.isNonArchivalNode = isNonArchivalNode
|
||||||
|
self.isNonArchivalNodeChanged()
|
||||||
|
|
||||||
|
proc isNonArchivalNode*(self: WalletView): bool {.slot.} = result = ?.self.isNonArchivalNode
|
||||||
|
QtProperty[bool] isNonArchivalNode:
|
||||||
|
read = isNonArchivalNode
|
||||||
|
write = setNonArchivalNode
|
||||||
|
notify = isNonArchivalNodeChanged
|
||||||
|
|
||||||
proc gasPricePredictionsChanged*(self: WalletView) {.signal.}
|
proc gasPricePredictionsChanged*(self: WalletView) {.signal.}
|
||||||
|
|
||||||
proc getGasPricePredictions*(self: WalletView) {.slot.} =
|
proc getGasPricePredictions*(self: WalletView) {.slot.} =
|
||||||
|
@ -685,50 +707,78 @@ QtObject:
|
||||||
|
|
||||||
return """{"error":"Unknown token address"}""";
|
return """{"error":"Unknown token address"}""";
|
||||||
|
|
||||||
proc historyWasFetched*(self: WalletView) {.signal.}
|
proc isFetchingHistory*(self: WalletView): bool {.slot.} =
|
||||||
|
if self.fetchingHistoryState.hasKey(self.currentAccount.address):
|
||||||
|
return self.fetchingHistoryState[self.currentAccount.address]
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc loadingTrxHistoryChanged*(self: WalletView, isLoading: bool, address: string) {.signal.}
|
||||||
|
|
||||||
|
proc loadTransactionsForAccount*(self: WalletView, address: string, toBlock: string = "0x0", limit: int = 20, loadMore: bool = false) {.slot.} =
|
||||||
|
self.loadingTrxHistoryChanged(true, address)
|
||||||
|
let toBlockParsed = stint.fromHex(Uint256, toBlock)
|
||||||
|
self.loadTransactions("setTrxHistoryResult", address, toBlockParsed, limit, loadMore)
|
||||||
|
|
||||||
proc setHistoryFetchState*(self: WalletView, accounts: seq[string], isFetching: bool) =
|
proc setHistoryFetchState*(self: WalletView, accounts: seq[string], isFetching: bool) =
|
||||||
for acc in accounts:
|
for acc in accounts:
|
||||||
self.fetchingHistoryState[acc] = isFetching
|
self.fetchingHistoryState[acc] = isFetching
|
||||||
if not isFetching: self.historyWasFetched()
|
self.loadingTrxHistoryChanged(isFetching, acc)
|
||||||
|
|
||||||
proc isFetchingHistory*(self: WalletView, address: string): bool {.slot.} =
|
proc initBalance(self: WalletView, acc: WalletAccount, loadTransactions: bool = true) =
|
||||||
if self.fetchingHistoryState.hasKey(address):
|
let
|
||||||
return self.fetchingHistoryState[address]
|
accountAddress = acc.address
|
||||||
return true
|
tokenList = acc.assetList.filter(proc(x:Asset): bool = x.address != "").map(proc(x: Asset): string = x.address)
|
||||||
|
self.initBalances("getAccountBalanceSuccess", accountAddress, tokenList)
|
||||||
|
if loadTransactions:
|
||||||
|
self.loadTransactionsForAccount(accountAddress)
|
||||||
|
|
||||||
proc isHistoryFetched*(self: WalletView, address: string): bool {.slot.} =
|
proc initBalance(self: WalletView, accountAddress: string, loadTransactions: bool = true) =
|
||||||
return self.currentTransactions.rowCount() > 0
|
var found = false
|
||||||
|
let acc = self.status.wallet.accounts.find(acc => acc.address.toLowerAscii == accountAddress.toLowerAscii, found)
|
||||||
proc loadingTrxHistoryChanged*(self: WalletView, isLoading: bool) {.signal.}
|
if not found:
|
||||||
|
error "Failed to init balance: could not find account", account=accountAddress
|
||||||
proc loadTransactionsForAccount*(self: WalletView, address: string) {.slot.} =
|
return
|
||||||
self.loadingTrxHistoryChanged(true)
|
self.initBalance(acc, loadTransactions)
|
||||||
self.loadTransactions("setTrxHistoryResult", address)
|
|
||||||
|
|
||||||
proc getLatestTransactionHistory*(self: WalletView, accounts: seq[string]) =
|
|
||||||
for acc in accounts:
|
|
||||||
self.loadTransactionsForAccount(acc)
|
|
||||||
|
|
||||||
proc initBalances*(self: WalletView, loadTransactions: bool = true) =
|
proc initBalances*(self: WalletView, loadTransactions: bool = true) =
|
||||||
for acc in self.status.wallet.accounts:
|
for acc in self.status.wallet.accounts:
|
||||||
let accountAddress = acc.address
|
self.initBalance(acc, loadTransactions)
|
||||||
let tokenList = acc.assetList.filter(proc(x:Asset): bool = x.address != "").map(proc(x: Asset): string = x.address)
|
|
||||||
self.initBalances("getAccountBalanceSuccess", accountAddress, tokenList)
|
proc initBalances*(self: WalletView, accounts: seq[string], loadTransactions: bool = true) =
|
||||||
if loadTransactions:
|
for acc in accounts:
|
||||||
self.loadTransactionsForAccount(accountAddress)
|
self.initBalance(acc, loadTransactions)
|
||||||
|
|
||||||
proc setTrxHistoryResult(self: WalletView, historyJSON: string) {.slot.} =
|
proc setTrxHistoryResult(self: WalletView, historyJSON: string) {.slot.} =
|
||||||
let historyData = parseJson(historyJSON)
|
let
|
||||||
let transactions = historyData["history"].to(seq[Transaction]);
|
historyData = parseJson(historyJSON)
|
||||||
let address = historyData["address"].getStr
|
transactions = historyData["history"].to(seq[Transaction])
|
||||||
let index = self.accounts.getAccountindexByAddress(address)
|
address = historyData["address"].getStr
|
||||||
|
wasFetchMore = historyData["loadMore"].getBool
|
||||||
|
isCurrentAccount = address.toLowerAscii == self.currentAccount.address.toLowerAscii
|
||||||
|
index = self.accounts.getAccountindexByAddress(address)
|
||||||
if index == -1: return
|
if index == -1: return
|
||||||
self.accounts.getAccount(index).transactions = transactions
|
|
||||||
if address == self.currentAccount.address:
|
let account = self.accounts.getAccount(index)
|
||||||
self.setCurrentTransactions(
|
# concatenate the new page of txs to existing account transactions,
|
||||||
self.accounts.getAccount(index).transactions)
|
# sort them by block number and nonce, then deduplicate them based on their
|
||||||
self.loadingTrxHistoryChanged(false)
|
# transaction id.
|
||||||
|
let existingAcctTxIds = account.transactions.data.map(tx => tx.id)
|
||||||
|
let hasNewTxs = transactions.len > 0 and transactions.any(tx => not existingAcctTxIds.contains(tx.id))
|
||||||
|
if hasNewTxs or not wasFetchMore:
|
||||||
|
var allTxs: seq[Transaction] = account.transactions.data.concat(transactions)
|
||||||
|
allTxs.sort(cmpTransactions, SortOrder.Descending)
|
||||||
|
allTxs.deduplicate(tx => tx.id)
|
||||||
|
account.transactions.data = allTxs
|
||||||
|
account.transactions.hasMore = true
|
||||||
|
if isCurrentAccount:
|
||||||
|
self.currentTransactions.setHasMore(true)
|
||||||
|
self.setCurrentTransactions(allTxs)
|
||||||
|
else:
|
||||||
|
account.transactions.hasMore = false
|
||||||
|
if isCurrentAccount:
|
||||||
|
self.currentTransactions.setHasMore(false)
|
||||||
|
self.currentTransactionsChanged()
|
||||||
|
self.loadingTrxHistoryChanged(false, address)
|
||||||
|
|
||||||
proc resolveENS*(self: WalletView, ens: string, uuid: string) {.slot.} =
|
proc resolveENS*(self: WalletView, ens: string, uuid: string) {.slot.} =
|
||||||
self.resolveEns("ensResolved", ens, uuid)
|
self.resolveEns("ensResolved", ens, uuid)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import NimQml, Tables, random, strformat, json_serialization
|
import NimQml, Tables, random, strformat, strutils, json_serialization
|
||||||
import sequtils as sequtils
|
import sequtils as sequtils
|
||||||
import account_item, asset_list
|
import account_item, asset_list
|
||||||
from ../../../status/wallet import WalletAccount, Asset, CollectibleList
|
from ../../../status/wallet import WalletAccount, Asset, CollectibleList
|
||||||
|
@ -54,7 +54,7 @@ QtObject:
|
||||||
proc getAccountindexByAddress*(self: AccountList, address: string): int =
|
proc getAccountindexByAddress*(self: AccountList, address: string): int =
|
||||||
var i = 0
|
var i = 0
|
||||||
for accountView in self.accounts:
|
for accountView in self.accounts:
|
||||||
if (accountView.account.address == address):
|
if (accountView.account.address.toLowerAscii == address.toLowerAscii):
|
||||||
return i
|
return i
|
||||||
i = i + 1
|
i = i + 1
|
||||||
return -1
|
return -1
|
||||||
|
|
|
@ -21,6 +21,7 @@ type
|
||||||
QtObject:
|
QtObject:
|
||||||
type TransactionList* = ref object of QAbstractListModel
|
type TransactionList* = ref object of QAbstractListModel
|
||||||
transactions*: seq[Transaction]
|
transactions*: seq[Transaction]
|
||||||
|
hasMore*: bool
|
||||||
|
|
||||||
proc setup(self: TransactionList) = self.QAbstractListModel.setup
|
proc setup(self: TransactionList) = self.QAbstractListModel.setup
|
||||||
|
|
||||||
|
@ -31,14 +32,29 @@ QtObject:
|
||||||
proc newTransactionList*(): TransactionList =
|
proc newTransactionList*(): TransactionList =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.transactions = @[]
|
result.transactions = @[]
|
||||||
|
result.hasMore = true
|
||||||
result.setup
|
result.setup
|
||||||
|
|
||||||
proc getLastTxBlockNumber*(self: TransactionList): string =
|
proc getLastTxBlockNumber*(self: TransactionList): string {.slot.} =
|
||||||
return self.transactions[^1].blockNumber
|
return self.transactions[^1].blockNumber
|
||||||
|
|
||||||
method rowCount*(self: TransactionList, index: QModelIndex = nil): int =
|
method rowCount*(self: TransactionList, index: QModelIndex = nil): int =
|
||||||
return self.transactions.len
|
return self.transactions.len
|
||||||
|
|
||||||
|
proc hasMoreChanged*(self: TransactionList) {.signal.}
|
||||||
|
|
||||||
|
proc getHasMore*(self: TransactionList): bool {.slot.} =
|
||||||
|
return self.hasMore
|
||||||
|
|
||||||
|
proc setHasMore*(self: TransactionList, hasMore: bool) {.slot.} =
|
||||||
|
self.hasMore = hasMore
|
||||||
|
self.hasMoreChanged()
|
||||||
|
|
||||||
|
QtProperty[bool] hasMore:
|
||||||
|
read = getHasMore
|
||||||
|
write = setHasMore
|
||||||
|
notify = currentTransactionsChanged
|
||||||
|
|
||||||
method data(self: TransactionList, index: QModelIndex, role: int): QVariant =
|
method data(self: TransactionList, index: QModelIndex, role: int): QVariant =
|
||||||
if not index.isValid:
|
if not index.isValid:
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,7 +5,7 @@ import
|
||||||
web3/[conversions, ethtypes], stint
|
web3/[conversions, ethtypes], stint
|
||||||
|
|
||||||
# TODO: make this public in nim-web3 lib
|
# TODO: make this public in nim-web3 lib
|
||||||
template stripLeadingZeros(value: string): string =
|
template stripLeadingZeros*(value: string): string =
|
||||||
var cidx = 0
|
var cidx = 0
|
||||||
# ignore the last character so we retain '0' on zero value
|
# ignore the last character so we retain '0' on zero value
|
||||||
while cidx < value.len - 1 and value[cidx] == '0':
|
while cidx < value.len - 1 and value[cidx] == '0':
|
||||||
|
|
|
@ -47,8 +47,9 @@ proc getContactByID*(id: string): string =
|
||||||
proc getBlockByNumber*(blockNumber: string): string =
|
proc getBlockByNumber*(blockNumber: string): string =
|
||||||
result = callPrivateRPC("eth_getBlockByNumber", %* [blockNumber, false])
|
result = callPrivateRPC("eth_getBlockByNumber", %* [blockNumber, false])
|
||||||
|
|
||||||
proc getTransfersByAddress*(address: string, limit: string, fetchMore: bool = false): string =
|
proc getTransfersByAddress*(address: string, toBlock: string, limit: string, fetchMore: bool = false): string =
|
||||||
result = callPrivateRPC("wallet_getTransfersByAddress", %* [address, newJNull(), limit, fetchMore])
|
let toBlockParsed = if not fetchMore: newJNull() else: %toBlock
|
||||||
|
result = callPrivateRPC("wallet_getTransfersByAddress", %* [address, toBlockParsed, limit, fetchMore])
|
||||||
|
|
||||||
proc signMessage*(rpcParams: string): string =
|
proc signMessage*(rpcParams: string): string =
|
||||||
return $status_go.signMessage(rpcParams)
|
return $status_go.signMessage(rpcParams)
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import json, options, typetraits, tables, sequtils
|
import # std libs
|
||||||
import web3/ethtypes, json_serialization, stint
|
json, options, typetraits, tables, sequtils, strutils
|
||||||
import accounts/constants
|
|
||||||
import ../../eventemitter
|
import # vendor libs
|
||||||
|
web3/ethtypes, json_serialization, stint
|
||||||
|
|
||||||
|
import # status-desktop libs
|
||||||
|
accounts/constants, ../../eventemitter
|
||||||
|
|
||||||
type SignalType* {.pure.} = enum
|
type SignalType* {.pure.} = enum
|
||||||
Message = "messages.new"
|
Message = "messages.new"
|
||||||
|
@ -109,6 +113,7 @@ type
|
||||||
|
|
||||||
type
|
type
|
||||||
Transaction* = ref object
|
Transaction* = ref object
|
||||||
|
id*: string
|
||||||
typeValue*: string
|
typeValue*: string
|
||||||
address*: string
|
address*: string
|
||||||
blockNumber*: string
|
blockNumber*: string
|
||||||
|
@ -123,6 +128,13 @@ type
|
||||||
value*: string
|
value*: string
|
||||||
fromAddress*: string
|
fromAddress*: string
|
||||||
to*: string
|
to*: string
|
||||||
|
|
||||||
|
proc cmpTransactions*(x, y: Transaction): int =
|
||||||
|
# Sort proc to compare transactions from a single account.
|
||||||
|
# Compares first by block number, then by nonce
|
||||||
|
result = cmp(x.blockNumber.parseHexInt, y.blockNumber.parseHexInt)
|
||||||
|
if result == 0:
|
||||||
|
result = cmp(x.nonce, y.nonce)
|
||||||
|
|
||||||
type
|
type
|
||||||
RpcException* = object of CatchableError
|
RpcException* = object of CatchableError
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import json, random, strutils, strformat, tables, chronicles, unicode
|
import # std libs
|
||||||
import stint
|
json, random, strutils, strformat, tables, times, unicode
|
||||||
from times import getTime, toUnix, nanosecond
|
from sugar import `=>`, `->`
|
||||||
import accounts/signing_phrases
|
|
||||||
|
import # vendor libs
|
||||||
|
stint, chronicles
|
||||||
from web3 import Address, fromHex
|
from web3 import Address, fromHex
|
||||||
|
|
||||||
|
import # status-desktop libs
|
||||||
|
accounts/signing_phrases
|
||||||
|
|
||||||
proc getTimelineChatId*(pubKey: string = ""): string =
|
proc getTimelineChatId*(pubKey: string = ""): string =
|
||||||
if pubKey == "":
|
if pubKey == "":
|
||||||
return "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a"
|
return "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a"
|
||||||
|
@ -116,5 +121,35 @@ proc find*[T](s: seq[T], pred: proc(x: T): bool {.closure.}): T {.inline.} =
|
||||||
return default(type(T))
|
return default(type(T))
|
||||||
result = results[0]
|
result = results[0]
|
||||||
|
|
||||||
|
proc find*[T](s: seq[T], pred: proc(x: T): bool {.closure.}, found: var bool): T {.inline.} =
|
||||||
|
let results = s.filter(pred)
|
||||||
|
if results.len == 0:
|
||||||
|
found = false
|
||||||
|
return default(type(T))
|
||||||
|
result = results[0]
|
||||||
|
found = true
|
||||||
|
|
||||||
proc parseAddress*(strAddress: string): Address =
|
proc parseAddress*(strAddress: string): Address =
|
||||||
fromHex(Address, strAddress)
|
fromHex(Address, strAddress)
|
||||||
|
|
||||||
|
proc hex2Time*(hex: string): Time =
|
||||||
|
# represents the time since 1970-01-01T00:00:00Z
|
||||||
|
fromUnix(fromHex[int64](hex))
|
||||||
|
|
||||||
|
proc hex2LocalDateTime*(hex: string): DateTime =
|
||||||
|
# Convert hex time (since 1970-01-01T00:00:00Z) into a DateTime using the
|
||||||
|
# local timezone.
|
||||||
|
hex.hex2Time.local
|
||||||
|
|
||||||
|
proc isUnique*[T](key: T, existingKeys: var seq[T]): bool =
|
||||||
|
# If the key doesn't exist in the existingKeys seq, add it and return true.
|
||||||
|
# Otherwise, the key already existed, so return false.
|
||||||
|
# Can be used to deduplicate sequences with `deduplicate[T]`.
|
||||||
|
if not existingKeys.contains(key):
|
||||||
|
existingKeys.add key
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc deduplicate*[T](txs: var seq[T], key: (T) -> string) =
|
||||||
|
var existingKeys: seq[string] = @[]
|
||||||
|
txs.keepIf(tx => tx.key().isUnique(existingKeys))
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import json, json, options, json_serialization, stint, chronicles
|
import # std libs
|
||||||
import core, types, utils, strutils, strformat
|
json, times, options, strutils, strformat
|
||||||
import utils
|
|
||||||
from status_go import validateMnemonic#, startWallet
|
import # vendor libs
|
||||||
import ../wallet/account
|
json_serialization, stint, chronicles, web3/ethtypes
|
||||||
import web3/ethtypes
|
from status_go import validateMnemonic
|
||||||
import ./types
|
|
||||||
|
import # status-desktop libs
|
||||||
|
../wallet/account, ./types, ./conversions, ./core, ./types, ./utils
|
||||||
|
|
||||||
proc getWalletAccounts*(): seq[WalletAccount] =
|
proc getWalletAccounts*(): seq[WalletAccount] =
|
||||||
try:
|
try:
|
||||||
|
@ -33,20 +35,24 @@ proc getWalletAccounts*(): seq[WalletAccount] =
|
||||||
proc getTransactionReceipt*(transactionHash: string): string =
|
proc getTransactionReceipt*(transactionHash: string): string =
|
||||||
result = callPrivateRPC("eth_getTransactionReceipt", %* [transactionHash])
|
result = callPrivateRPC("eth_getTransactionReceipt", %* [transactionHash])
|
||||||
|
|
||||||
proc getTransfersByAddress*(address: string): seq[types.Transaction] =
|
proc getTransfersByAddress*(address: string, toBlock: Uint256, limit: int, loadMore: bool = false): seq[types.Transaction] =
|
||||||
try:
|
try:
|
||||||
let transactionsResponse = getTransfersByAddress(address, "0x14")
|
let
|
||||||
let transactions = parseJson(transactionsResponse)["result"]
|
toBlockParsed = "0x" & stint.toHex(toBlock)
|
||||||
|
limitParsed = "0x" & limit.toHex.stripLeadingZeros
|
||||||
|
transactionsResponse = getTransfersByAddress(address, toBlockParsed, limitParsed, loadMore)
|
||||||
|
transactions = parseJson(transactionsResponse)["result"]
|
||||||
var accountTransactions: seq[types.Transaction] = @[]
|
var accountTransactions: seq[types.Transaction] = @[]
|
||||||
|
|
||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
accountTransactions.add(types.Transaction(
|
accountTransactions.add(types.Transaction(
|
||||||
|
id: transaction["id"].getStr,
|
||||||
typeValue: transaction["type"].getStr,
|
typeValue: transaction["type"].getStr,
|
||||||
address: transaction["address"].getStr,
|
address: transaction["address"].getStr,
|
||||||
contract: transaction["contract"].getStr,
|
contract: transaction["contract"].getStr,
|
||||||
blockNumber: transaction["blockNumber"].getStr,
|
blockNumber: transaction["blockNumber"].getStr,
|
||||||
blockHash: transaction["blockhash"].getStr,
|
blockHash: transaction["blockhash"].getStr,
|
||||||
timestamp: transaction["timestamp"].getStr,
|
timestamp: $hex2LocalDateTime(transaction["timestamp"].getStr()),
|
||||||
gasPrice: transaction["gasPrice"].getStr,
|
gasPrice: transaction["gasPrice"].getStr,
|
||||||
gasLimit: transaction["gasLimit"].getStr,
|
gasLimit: transaction["gasLimit"].getStr,
|
||||||
gasUsed: transaction["gasUsed"].getStr,
|
gasUsed: transaction["gasUsed"].getStr,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import # vendor libs
|
import # vendor libs
|
||||||
json_serialization
|
json_serialization#, stint
|
||||||
|
from eth/common/eth_types_json_serialization import writeValue, readValue
|
||||||
|
|
||||||
|
export writeValue, readValue
|
||||||
|
|
||||||
export json_serialization
|
export json_serialization
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import json, strformat, strutils, chronicles, sequtils, httpclient, tables, net
|
import json, strformat, strutils, chronicles, sequtils, sugar, httpclient, tables, net
|
||||||
import json_serialization, stint
|
import json_serialization, stint
|
||||||
from web3/ethtypes import Address, EthSend, Quantity
|
from web3/ethtypes import Address, EthSend, Quantity
|
||||||
from web3/conversions import `$`
|
from web3/conversions import `$`
|
||||||
|
@ -244,6 +244,11 @@ proc addNewGeneratedAccount(self: WalletModel, generatedAccount: GeneratedAccoun
|
||||||
var derivedAccount: DerivedAccount = status_accounts.saveAccount(generatedAccount, password, color, accountType, isADerivedAccount, walletIndex)
|
var derivedAccount: DerivedAccount = status_accounts.saveAccount(generatedAccount, password, color, accountType, isADerivedAccount, walletIndex)
|
||||||
var account = self.newAccount(accountType, derivedAccount.derivationPath, accountName, derivedAccount.address, color, fmt"0.00 {self.defaultCurrency}", derivedAccount.publicKey)
|
var account = self.newAccount(accountType, derivedAccount.derivationPath, accountName, derivedAccount.address, color, fmt"0.00 {self.defaultCurrency}", derivedAccount.publicKey)
|
||||||
self.accounts.add(account)
|
self.accounts.add(account)
|
||||||
|
# wallet_checkRecentHistory is required to be called when a new account is
|
||||||
|
# added before wallet_getTransfersByAddress can be called. This is because
|
||||||
|
# wallet_checkRecentHistory populates the status-go db that
|
||||||
|
# wallet_getTransfersByAddress reads from
|
||||||
|
discard status_wallet.checkRecentHistory(self.accounts.map(account => account.address))
|
||||||
self.events.emit("newAccountAdded", AccountArgs(account: account))
|
self.events.emit("newAccountAdded", AccountArgs(account: account))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise newException(StatusGoException, fmt"Error adding new account: {e.msg}")
|
raise newException(StatusGoException, fmt"Error adding new account: {e.msg}")
|
||||||
|
@ -314,6 +319,7 @@ proc changeAccountSettings*(self: WalletModel, address: string, accountName: str
|
||||||
|
|
||||||
proc deleteAccount*(self: WalletModel, address: string): string =
|
proc deleteAccount*(self: WalletModel, address: string): string =
|
||||||
result = status_accounts.deleteAccount(address)
|
result = status_accounts.deleteAccount(address)
|
||||||
|
self.accounts = self.accounts.filter(acc => acc.address.toLowerAscii != address.toLowerAscii)
|
||||||
|
|
||||||
proc toggleAsset*(self: WalletModel, symbol: string) =
|
proc toggleAsset*(self: WalletModel, symbol: string) =
|
||||||
self.tokens = status_tokens.toggleAsset(symbol)
|
self.tokens = status_tokens.toggleAsset(symbol)
|
||||||
|
@ -333,8 +339,8 @@ proc hideAsset*(self: WalletModel, symbol: string) =
|
||||||
proc addCustomToken*(self: WalletModel, symbol: string, enable: bool, address: string, name: string, decimals: int, color: string) =
|
proc addCustomToken*(self: WalletModel, symbol: string, enable: bool, address: string, name: string, decimals: int, color: string) =
|
||||||
addCustomToken(address, name, symbol, decimals, color)
|
addCustomToken(address, name, symbol, decimals, color)
|
||||||
|
|
||||||
proc getTransfersByAddress*(self: WalletModel, address: string): seq[Transaction] =
|
proc getTransfersByAddress*(self: WalletModel, address: string, toBlock: Uint256, limit: int, loadMore: bool): seq[Transaction] =
|
||||||
result = status_wallet.getTransfersByAddress(address)
|
result = status_wallet.getTransfersByAddress(address, toBlock, limit, loadMore)
|
||||||
|
|
||||||
proc validateMnemonic*(self: WalletModel, mnemonic: string): string =
|
proc validateMnemonic*(self: WalletModel, mnemonic: string): string =
|
||||||
result = status_wallet.validateMnemonic(mnemonic).parseJSON()["error"].getStr
|
result = status_wallet.validateMnemonic(mnemonic).parseJSON()["error"].getStr
|
||||||
|
|
|
@ -22,7 +22,7 @@ type WalletAccount* = ref object
|
||||||
assetList*: seq[Asset]
|
assetList*: seq[Asset]
|
||||||
wallet*, chat*: bool
|
wallet*, chat*: bool
|
||||||
collectiblesLists*: seq[CollectibleList]
|
collectiblesLists*: seq[CollectibleList]
|
||||||
transactions*: seq[Transaction]
|
transactions*: tuple[hasMore: bool, data: seq[Transaction]]
|
||||||
|
|
||||||
type AccountArgs* = ref object of Args
|
type AccountArgs* = ref object of Args
|
||||||
account*: WalletAccount
|
account*: WalletAccount
|
||||||
|
|
|
@ -189,7 +189,6 @@ Popup {
|
||||||
anchors.leftMargin: 32
|
anchors.leftMargin: 32
|
||||||
//% "History"
|
//% "History"
|
||||||
btnText: qsTrId("history")
|
btnText: qsTrId("history")
|
||||||
onClicked: historyTab.checkIfHistoryIsBeingFetched()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,9 @@ import "../../../shared/status/core"
|
||||||
import "../../../shared/status"
|
import "../../../shared/status"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
property int pageSize: 20 // number of transactions per page
|
||||||
property var tokens: {
|
property var tokens: {
|
||||||
const count = walletModel.defaultTokenList.rowCount()
|
let count = walletModel.defaultTokenList.rowCount()
|
||||||
const toks = []
|
const toks = []
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
toks.push({
|
toks.push({
|
||||||
|
@ -18,26 +19,25 @@ Item {
|
||||||
"symbol": walletModel.defaultTokenList.rowData(i, 'symbol')
|
"symbol": walletModel.defaultTokenList.rowData(i, 'symbol')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
count = walletModel.customTokenList.rowCount()
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
toks.push({
|
||||||
|
"address": walletModel.customTokenList.rowData(i, 'address'),
|
||||||
|
"symbol": walletModel.customTokenList.rowData(i, 'symbol')
|
||||||
|
})
|
||||||
|
}
|
||||||
return toks
|
return toks
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkIfHistoryIsBeingFetched() {
|
|
||||||
loadMoreButton.loadedMore = false;
|
|
||||||
|
|
||||||
// prevent history from being fetched everytime you click on
|
|
||||||
// the history tab
|
|
||||||
if (walletModel.isHistoryFetched(walletModel.currentAccount.account))
|
|
||||||
return;
|
|
||||||
|
|
||||||
fetchHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchHistory() {
|
function fetchHistory() {
|
||||||
if (walletModel.isFetchingHistory(walletModel.currentAccount.address)) {
|
if (walletModel.isFetchingHistory()) {
|
||||||
loadingImg.active = true
|
loadingImg.active = true
|
||||||
} else {
|
} else {
|
||||||
walletModel.loadTransactionsForAccount(
|
walletModel.loadTransactionsForAccount(
|
||||||
walletModel.currentAccount.address)
|
walletModel.currentAccount.address,
|
||||||
|
walletModel.transactions.getLastTxBlockNumber(),
|
||||||
|
pageSize,
|
||||||
|
true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ Item {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: Style.current.padding
|
anchors.rightMargin: Style.current.padding
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Style.currentPadding
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
@ -60,9 +59,10 @@ Item {
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: walletModel
|
target: walletModel
|
||||||
onHistoryWasFetched: checkIfHistoryIsBeingFetched()
|
|
||||||
onLoadingTrxHistoryChanged: {
|
onLoadingTrxHistoryChanged: {
|
||||||
loadingImg.active = isLoading
|
if (walletModel.currentAccount.address.toLowerCase() === address.toLowerCase()) {
|
||||||
|
loadingImg.active = isLoading
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@ Item {
|
||||||
id: transactionListItem
|
id: transactionListItem
|
||||||
property bool isHovered: false
|
property bool isHovered: false
|
||||||
property string symbol: ""
|
property string symbol: ""
|
||||||
|
property bool isIncoming: to === walletModel.currentAccount.address
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
height: 64
|
height: 64
|
||||||
|
@ -107,7 +108,12 @@ Item {
|
||||||
id: transactionModal
|
id: transactionModal
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Style.current.smallPadding
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: assetIcon
|
id: assetIcon
|
||||||
width: 40
|
width: 40
|
||||||
|
@ -115,9 +121,7 @@ Item {
|
||||||
source: "../../img/tokens/"
|
source: "../../img/tokens/"
|
||||||
+ (transactionListItem.symbol
|
+ (transactionListItem.symbol
|
||||||
!= "" ? transactionListItem.symbol : "ETH") + ".png"
|
!= "" ? transactionListItem.symbol : "ETH") + ".png"
|
||||||
anchors.left: parent.left
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 12
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (assetIcon.status == Image.Error) {
|
if (assetIcon.status == Image.Error) {
|
||||||
assetIcon.source = "../../img/tokens/DEFAULT-TOKEN@3x.png"
|
assetIcon.source = "../../img/tokens/DEFAULT-TOKEN@3x.png"
|
||||||
|
@ -129,100 +133,107 @@ Item {
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: transferIcon
|
id: transferIcon
|
||||||
anchors.topMargin: 25
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: assetIcon.right
|
|
||||||
anchors.leftMargin: 22
|
|
||||||
height: 15
|
height: 15
|
||||||
width: 15
|
width: 15
|
||||||
color: to !== walletModel.currentAccount.address ? "#4360DF" : "green"
|
color: isIncoming ? Style.current.success : Style.current.danger
|
||||||
text: to !== walletModel.currentAccount.address ? "↑" : "↓"
|
text: isIncoming ? "↓" : "↑"
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: transactionValue
|
id: transactionValue
|
||||||
anchors.left: transferIcon.right
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.leftMargin: Style.current.smallPadding
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: Style.current.bigPadding
|
|
||||||
font.pixelSize: 15
|
|
||||||
text: utilsModel.hex2Eth(value) + " " + transactionListItem.symbol
|
text: utilsModel.hex2Eth(value) + " " + transactionListItem.symbol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Row {
|
||||||
anchors.right: timeInfo.left
|
anchors.right: timeInfo.left
|
||||||
|
anchors.rightMargin: Style.current.smallPadding
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Style.current.bigPadding
|
anchors.topMargin: Style.current.bigPadding
|
||||||
width: children[0].width + children[1].width
|
spacing: 5
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: to !== walletModel.currentAccount.address ?
|
text: isIncoming ?
|
||||||
//% "To "
|
//% "From "
|
||||||
qsTrId("to-") :
|
qsTrId("from-") :
|
||||||
//% "From "
|
//% "To "
|
||||||
qsTrId("from-")
|
qsTrId("to-")
|
||||||
anchors.right: addressValue.left
|
|
||||||
color: Style.current.secondaryText
|
color: Style.current.secondaryText
|
||||||
anchors.top: parent.top
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
font.pixelSize: 15
|
|
||||||
font.strikeout: false
|
font.strikeout: false
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
Address {
|
||||||
id: addressValue
|
id: addressValue
|
||||||
font.family: Style.current.fontHexRegular.name
|
text: isIncoming ? fromAddress : to
|
||||||
text: to
|
maxWidth: 120
|
||||||
width: 100
|
width: 120
|
||||||
elide: Text.ElideMiddle
|
horizontalAlignment: Text.AlignRight
|
||||||
anchors.right: parent.right
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
anchors.top: parent.top
|
color: Style.current.textColor
|
||||||
font.pixelSize: 15
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Row {
|
||||||
id: timeInfo
|
id: timeInfo
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Style.current.smallPadding
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Style.current.bigPadding
|
anchors.topMargin: Style.current.bigPadding
|
||||||
width: children[0].width + children[1].width + children[2].width
|
spacing: 5
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "• "
|
text: " • "
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
anchors.right: timeIndicator.left
|
|
||||||
color: Style.current.secondaryText
|
color: Style.current.secondaryText
|
||||||
anchors.top: parent.top
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
font.pixelSize: 15
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: timeIndicator
|
id: timeIndicator
|
||||||
text: "At "
|
text: qsTr("At ")
|
||||||
anchors.right: timeValue.left
|
|
||||||
color: Style.current.secondaryText
|
color: Style.current.secondaryText
|
||||||
anchors.top: parent.top
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
font.pixelSize: 15
|
|
||||||
font.strikeout: false
|
font.strikeout: false
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: timeValue
|
id: timeValue
|
||||||
text: timestamp
|
text: new Date(timestamp).toLocaleString(globalSettings.locale)
|
||||||
anchors.right: parent.right
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
anchors.top: parent.top
|
|
||||||
font.pixelSize: 15
|
|
||||||
anchors.rightMargin: Style.current.smallPadding
|
anchors.rightMargin: Style.current.smallPadding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: nonArchivalNodeError
|
||||||
|
visible: walletModel.isNonArchivalNode
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.top: parent.top
|
||||||
|
text: qsTr("Status Desktop is connected to a non-archival node. Transaction history may be incomplete.")
|
||||||
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
|
color: Style.current.danger
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: noTxs
|
||||||
|
anchors.top: nonArchivalNodeError.bottom
|
||||||
|
visible: transactionListRoot.count === 0
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
text: qsTr("No transactions found")
|
||||||
|
font.pixelSize: Style.current.primaryTextFontSize
|
||||||
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: transactionListRoot
|
id: transactionListRoot
|
||||||
anchors.topMargin: 20
|
anchors.top: noTxs.bottom
|
||||||
height: parent.height - extraButtons.height
|
anchors.topMargin: Style.current.padding
|
||||||
|
anchors.bottom: loadMoreButton.top
|
||||||
|
anchors.bottomMargin: Style.current.padding
|
||||||
width: parent.width
|
width: parent.width
|
||||||
clip: true
|
clip: true
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
@ -238,26 +249,21 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
StatusButton {
|
||||||
id: extraButtons
|
id: loadMoreButton
|
||||||
anchors.left: parent.left
|
//% "Load More"
|
||||||
|
text: qsTrId("load-more")
|
||||||
|
// TODO: handle case when requested limit === transaction count -- there
|
||||||
|
// is currently no way to know that there are no more results
|
||||||
|
enabled: !loadingImg.active && walletModel.transactions.hasMore
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
height: loadMoreButton.height
|
anchors.bottomMargin: Style.current.padding
|
||||||
|
property bool loadedMore: false
|
||||||
|
|
||||||
StatusButton {
|
onClicked: {
|
||||||
id: loadMoreButton
|
fetchHistory()
|
||||||
//% "Load More"
|
loadMoreButton.loadedMore = true
|
||||||
text: qsTrId("load-more")
|
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
|
||||||
property bool loadedMore: false
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
onClicked: {
|
|
||||||
fetchHistory()
|
|
||||||
loadMoreButton.loadedMore = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ ColumnLayout {
|
||||||
onboardingModel.firstTimeLogin = false
|
onboardingModel.firstTimeLogin = false
|
||||||
walletModel.setInitialRange()
|
walletModel.setInitialRange()
|
||||||
}
|
}
|
||||||
walletModel.checkRecentHistory()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
|
@ -120,7 +119,6 @@ ColumnLayout {
|
||||||
anchors.leftMargin: 32
|
anchors.leftMargin: 32
|
||||||
//% "History"
|
//% "History"
|
||||||
btnText: qsTrId("history")
|
btnText: qsTrId("history")
|
||||||
onClicked: historyTab.checkIfHistoryIsBeingFetched()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ StatusRoundButton {
|
||||||
type: "secondary"
|
type: "secondary"
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
|
readonly property var onAfterAddAccount: function() {
|
||||||
|
walletInfoContainer.changeSelectedAccount(walletModel.accounts.rowCount() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (newAccountMenu.opened) {
|
if (newAccountMenu.opened) {
|
||||||
|
@ -24,15 +27,19 @@ StatusRoundButton {
|
||||||
|
|
||||||
GenerateAccountModal {
|
GenerateAccountModal {
|
||||||
id: generateAccountModal
|
id: generateAccountModal
|
||||||
|
onAfterAddAccount: function() { btnAdd.onAfterAddAccount() }
|
||||||
}
|
}
|
||||||
AddAccountWithSeed {
|
AddAccountWithSeed {
|
||||||
id: addAccountWithSeedModal
|
id: addAccountWithSeedModal
|
||||||
|
onAfterAddAccount: function() { btnAdd.onAfterAddAccount() }
|
||||||
}
|
}
|
||||||
AddAccountWithPrivateKey {
|
AddAccountWithPrivateKey {
|
||||||
id: addAccountWithPrivateKeydModal
|
id: addAccountWithPrivateKeydModal
|
||||||
|
onAfterAddAccount: function() { btnAdd.onAfterAddAccount() }
|
||||||
}
|
}
|
||||||
AddWatchOnlyAccount {
|
AddWatchOnlyAccount {
|
||||||
id: addWatchOnlyAccountModal
|
id: addWatchOnlyAccountModal
|
||||||
|
onAfterAddAccount: function() { btnAdd.onAfterAddAccount() }
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupMenu {
|
PopupMenu {
|
||||||
|
|
|
@ -16,6 +16,7 @@ ModalPopup {
|
||||||
property string privateKeyValidationError: ""
|
property string privateKeyValidationError: ""
|
||||||
property string accountNameValidationError: ""
|
property string accountNameValidationError: ""
|
||||||
property bool loading: false
|
property bool loading: false
|
||||||
|
property var onAfterAddAccount: function() {}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
if (passwordInput.text === "") {
|
if (passwordInput.text === "") {
|
||||||
|
@ -141,7 +142,7 @@ ModalPopup {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
popup.onAfterAddAccount()
|
||||||
popup.close();
|
popup.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ ModalPopup {
|
||||||
property string seedValidationError: ""
|
property string seedValidationError: ""
|
||||||
property string accountNameValidationError: ""
|
property string accountNameValidationError: ""
|
||||||
property bool loading: false
|
property bool loading: false
|
||||||
|
property var onAfterAddAccount: function() {}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
passwordInput.text = ""
|
passwordInput.text = ""
|
||||||
|
@ -142,6 +143,7 @@ ModalPopup {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
popup.onAfterAddAccount()
|
||||||
popup.reset()
|
popup.reset()
|
||||||
popup.close();
|
popup.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ ModalPopup {
|
||||||
property string addressError: ""
|
property string addressError: ""
|
||||||
property string accountNameValidationError: ""
|
property string accountNameValidationError: ""
|
||||||
property bool loading: false
|
property bool loading: false
|
||||||
|
property var onAfterAddAccount: function() {}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
if (addressInput.text === "") {
|
if (addressInput.text === "") {
|
||||||
|
@ -107,7 +108,7 @@ ModalPopup {
|
||||||
accountError.text = error
|
accountError.text = error
|
||||||
return accountError.open()
|
return accountError.open()
|
||||||
}
|
}
|
||||||
|
popup.onAfterAddAccount()
|
||||||
popup.close();
|
popup.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ ModalPopup {
|
||||||
property string passwordValidationError: ""
|
property string passwordValidationError: ""
|
||||||
property string accountNameValidationError: ""
|
property string accountNameValidationError: ""
|
||||||
property bool loading: false
|
property bool loading: false
|
||||||
|
property var onAfterAddAccount: function() {}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
if (passwordInput.text === "") {
|
if (passwordInput.text === "") {
|
||||||
|
@ -114,7 +115,7 @@ ModalPopup {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
popup.onAfterAddAccount();
|
||||||
popup.close();
|
popup.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue