feat(Wallet): add activity filter basic API
Add the possibility of retrieving the data of wallet activity based on the given filter criteria. Major changes: - Bump status-go with the new equivalent API - Add temporary developer tools - Debugging ActivityView QML component to test filter and display the activity - Add activity Nim package and synchronous controller and model Considerations - Have the model synchronous for the first iteration and then move to async while implementing the fetching mechanism - Use QtObject instances in the model instead of roles over items as agreed with the team - Simplify the implementation by having a simple presentation layer using backend (service also in the future) - Small required fixes and improvements Closes: #10633 Updates #10366
This commit is contained in:
parent
cba67b3b23
commit
22df203653
|
@ -0,0 +1,131 @@
|
|||
import NimQml, logging, std/json, sequtils, sugar, options
|
||||
|
||||
import model
|
||||
import entry
|
||||
|
||||
import ../transactions/item
|
||||
import ../transactions/module as transactions_module
|
||||
|
||||
import backend/activity as backend_activity
|
||||
import backend/backend as backend
|
||||
import backend/transactions
|
||||
|
||||
import app_service/service/transaction/service as transaction_service
|
||||
|
||||
proc toRef*[T](obj: T): ref T =
|
||||
new(result)
|
||||
result[] = obj
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Controller* = ref object of QObject
|
||||
model: Model
|
||||
transactionsModule: transactions_module.AccessInterface
|
||||
currentActivityFilter: backend_activity.ActivityFilter
|
||||
|
||||
proc setup(self: Controller) =
|
||||
self.QObject.setup
|
||||
|
||||
proc delete*(self: Controller) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newController*(transactionsModule: transactions_module.AccessInterface): Controller =
|
||||
new(result, delete)
|
||||
result.model = newModel()
|
||||
result.transactionsModule = transactionsModule
|
||||
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
||||
result.setup()
|
||||
|
||||
proc getModel*(self: Controller): QVariant {.slot.} =
|
||||
return newQVariant(self.model)
|
||||
|
||||
QtProperty[QVariant] model:
|
||||
read = getModel
|
||||
|
||||
# TODO: move it to service, make it async and lazy load details for transactions
|
||||
proc backendToPresentation(self: Controller, backendEnties: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] =
|
||||
var multiTransactionsIds: seq[int] = @[]
|
||||
var transactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||
var pendingTransactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||
|
||||
# Extract metadata required to fetch details
|
||||
# TODO: temporary here to show the working API. Will be done as required on a detail request from UI
|
||||
for backendEntry in backendEnties:
|
||||
case backendEntry.transactionType:
|
||||
of MultiTransaction:
|
||||
multiTransactionsIds.add(backendEntry.id)
|
||||
of SimpleTransaction:
|
||||
transactionIdentities.add(backendEntry.transaction.get())
|
||||
of PendingTransaction:
|
||||
pendingTransactionIdentities.add(backendEntry.transaction.get())
|
||||
|
||||
var multiTransactions: seq[MultiTransactionDto] = @[]
|
||||
if len(multiTransactionsIds) > 0:
|
||||
multiTransactions = transaction_service.getMultiTransactions(multiTransactionsIds)
|
||||
|
||||
var transactions: seq[Item] = @[]
|
||||
if len(transactionIdentities) > 0:
|
||||
let response = backend.getTransfersForIdentities(transactionIdentities)
|
||||
let res = response.result
|
||||
if response.error != nil or res.kind != JArray or res.len == 0:
|
||||
raise newException(Defect, "failed fetching transaction details")
|
||||
|
||||
let transactionsDtos = res.getElems().map(x => x.toTransactionDto())
|
||||
transactions = self.transactionsModule.transactionsToItems(transactionsDtos, @[])
|
||||
|
||||
var pendingTransactions: seq[Item] = @[]
|
||||
if len(pendingTransactionIdentities) > 0:
|
||||
let response = backend.getPendingTransactionsForIdentities(pendingTransactionIdentities)
|
||||
let res = response.result
|
||||
if response.error != nil or res.kind != JArray or res.len == 0:
|
||||
raise newException(Defect, "failed fetching pending transactions details")
|
||||
|
||||
let pendingTransactionsDtos = res.getElems().map(x => x.toPendingTransactionDto())
|
||||
pendingTransactions = self.transactionsModule.transactionsToItems(pendingTransactionsDtos, @[])
|
||||
|
||||
# Merge detailed transaction info in order
|
||||
result = newSeq[entry.ActivityEntry](multiTransactions.len + transactions.len + pendingTransactions.len)
|
||||
var mtIndex = 0
|
||||
var tIndex = 0
|
||||
var ptIndex = 0
|
||||
for i in low(backendEnties) .. high(backendEnties):
|
||||
let backendEntry = backendEnties[i]
|
||||
case backendEntry.transactionType:
|
||||
of MultiTransaction:
|
||||
result[i] = entry.newMultiTransactionActivityEntry(multiTransactions[mtIndex])
|
||||
mtIndex += 1
|
||||
of SimpleTransaction:
|
||||
let refInstance = new(Item)
|
||||
refInstance[] = transactions[tIndex]
|
||||
result[i] = entry.newTransactionActivityEntry(refInstance, false)
|
||||
tIndex += 1
|
||||
of PendingTransaction:
|
||||
let refInstance = new(Item)
|
||||
refInstance[] = pendingTransactions[ptIndex]
|
||||
result[i] = entry.newTransactionActivityEntry(refInstance, true)
|
||||
ptIndex += 1
|
||||
|
||||
proc refreshData*(self: Controller) {.slot.} =
|
||||
# result type is RpcResponse
|
||||
let response = backend_activity.getActivityEntries(@["0x0000000000000000000000000000000000000001"], @[1], self.currentActivityFilter, 0, 10)
|
||||
# RPC returns null for result in case of empty array
|
||||
if response.error != nil or (response.result.kind != JArray and response.result.kind != JNull):
|
||||
error "error fetching activity entries: ", response.error
|
||||
return
|
||||
|
||||
if response.result.kind == JNull:
|
||||
self.model.setEntries(@[])
|
||||
return
|
||||
|
||||
var backendEnties = newSeq[backend_activity.ActivityEntry](response.result.len)
|
||||
for i in 0 ..< response.result.len:
|
||||
backendEnties[i] = fromJson(response.result[i], backend_activity.ActivityEntry)
|
||||
let entries = self.backendToPresentation(backendEnties)
|
||||
self.model.setEntries(entries)
|
||||
|
||||
# TODO: add all parameters and separate in different methods
|
||||
proc updateFilter*(self: Controller, startTimestamp: int, endTimestamp: int) {.slot.} =
|
||||
# Update filter
|
||||
self.currentActivityFilter.period = backend_activity.newPeriod(startTimestamp, endTimestamp)
|
||||
|
||||
self.refreshData()
|
|
@ -0,0 +1,127 @@
|
|||
import NimQml, tables, json, strformat, sequtils, strutils, logging
|
||||
|
||||
import ../transactions/view
|
||||
import ../transactions/item
|
||||
import ./backend/transactions
|
||||
|
||||
# The ActivityEntry contains one of the following instances transaction, pensing transaction or multi-transaction
|
||||
# It is used to display an activity history entry in the QML UI
|
||||
QtObject:
|
||||
type
|
||||
ActivityEntry* = ref object of QObject
|
||||
multi_transaction: MultiTransactionDto
|
||||
transaction: ref Item
|
||||
isPending: bool
|
||||
|
||||
proc setup(self: ActivityEntry) =
|
||||
self.QObject.setup
|
||||
|
||||
proc delete*(self: ActivityEntry) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newMultiTransactionActivityEntry*(mt: MultiTransactionDto): ActivityEntry =
|
||||
new(result, delete)
|
||||
result.multi_transaction = mt
|
||||
result.transaction = nil
|
||||
result.isPending = false
|
||||
result.setup()
|
||||
|
||||
proc newTransactionActivityEntry*(tr: ref Item, isPending: bool): ActivityEntry =
|
||||
new(result, delete)
|
||||
result.multi_transaction = nil
|
||||
result.transaction = tr
|
||||
result.isPending = isPending
|
||||
result.setup()
|
||||
|
||||
proc isMultiTransaction*(self: ActivityEntry): bool {.slot.} =
|
||||
return self.multi_transaction != nil
|
||||
|
||||
QtProperty[bool] isMultiTransaction:
|
||||
read = isMultiTransaction
|
||||
|
||||
proc isPendingTransaction*(self: ActivityEntry): bool {.slot.} =
|
||||
return (not self.isMultiTransaction()) and self.isPending
|
||||
|
||||
QtProperty[bool] isPendingTransaction:
|
||||
read = isPendingTransaction
|
||||
|
||||
proc `$`*(self: ActivityEntry): string =
|
||||
let mtStr = if self.multi_transaction != nil: $(self.multi_transaction.id) else: "0"
|
||||
let trStr = if self.transaction != nil: $(self.transaction[]) else: "nil"
|
||||
|
||||
return fmt"""ActivityEntry(
|
||||
multi_transaction.id:{mtStr},
|
||||
transaction:{trStr},
|
||||
isPending:{self.isPending}
|
||||
)"""
|
||||
|
||||
proc getMultiTransaction*(self: ActivityEntry): MultiTransactionDto =
|
||||
if not self.isMultiTransaction():
|
||||
raise newException(Defect, "ActivityEntry is not a MultiTransaction")
|
||||
return self.multi_transaction
|
||||
|
||||
proc getTransaction*(self: ActivityEntry, pending: bool): ref Item =
|
||||
if self.isMultiTransaction() or self.isPending != pending:
|
||||
raise newException(Defect, "ActivityEntry is not a " & (if pending: "pending" else: "") & " Transaction")
|
||||
return self.transaction
|
||||
|
||||
proc getSender*(self: ActivityEntry): string {.slot.} =
|
||||
# TODO: lookup sender's name from addressbook and cache it or in advance
|
||||
if self.isMultiTransaction():
|
||||
return self.multi_transaction.fromAddress
|
||||
|
||||
return self.transaction[].getfrom()
|
||||
|
||||
QtProperty[string] sender:
|
||||
read = getSender
|
||||
|
||||
proc getRecipient*(self: ActivityEntry): string {.slot.} =
|
||||
# TODO: lookup recipient name from addressbook and cache it or in advance
|
||||
if self.isMultiTransaction():
|
||||
return self.multi_transaction.toAddress
|
||||
|
||||
return self.transaction[].getTo()
|
||||
|
||||
QtProperty[string] recipient:
|
||||
read = getRecipient
|
||||
|
||||
# TODO: use CurrencyAmount?
|
||||
proc getFromAmount*(self: ActivityEntry): string {.slot.} =
|
||||
if self.isMultiTransaction():
|
||||
return self.multi_transaction.fromAmount
|
||||
error "getFromAmount: ActivityEntry is not a MultiTransaction"
|
||||
return "0"
|
||||
|
||||
QtProperty[string] fromAmount:
|
||||
read = getFromAmount
|
||||
|
||||
proc getToAmount*(self: ActivityEntry): string {.slot.} =
|
||||
if not self.isMultiTransaction():
|
||||
error "getToAmount: ActivityEntry is not a MultiTransaction"
|
||||
return "0"
|
||||
|
||||
return self.multi_transaction.fromAmount
|
||||
|
||||
QtProperty[string] toAmount:
|
||||
read = getToAmount
|
||||
|
||||
proc getAmount*(self: ActivityEntry): QVariant {.slot.} =
|
||||
if not self.isMultiTransaction():
|
||||
error "getAmount: ActivityEntry is not an transaction.Item"
|
||||
return newQVariant(0)
|
||||
|
||||
return newQVariant(self.transaction[].getValue())
|
||||
|
||||
QtProperty[QVariant] amount:
|
||||
read = getAmount
|
||||
|
||||
proc getTimestamp*(self: ActivityEntry): int {.slot.} =
|
||||
if self.isMultiTransaction():
|
||||
return self.multi_transaction.timestamp
|
||||
# TODO: should we account for self.transaction[].isTimeStamp?
|
||||
return self.transaction[].getTimestamp()
|
||||
|
||||
QtProperty[int] timestamp:
|
||||
read = getTimestamp
|
||||
|
||||
# TODO: properties - type, fromChains, toChains, fromAsset, toAsset, assetName
|
|
@ -0,0 +1,90 @@
|
|||
import NimQml, Tables, strutils, strformat, sequtils
|
||||
|
||||
import ./entry
|
||||
|
||||
# TODO - DEV: remove this
|
||||
import app_service/service/transaction/dto
|
||||
import app/modules/shared_models/currency_amount
|
||||
import ../transactions/item as transaction
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
ActivityEntryRole = UserRole + 1
|
||||
|
||||
QtObject:
|
||||
type
|
||||
Model* = ref object of QAbstractListModel
|
||||
entries: seq[ActivityEntry]
|
||||
hasMore: bool
|
||||
|
||||
proc delete(self: Model) =
|
||||
self.entries = @[]
|
||||
self.QAbstractListModel.delete
|
||||
|
||||
proc setup(self: Model) =
|
||||
self.QAbstractListModel.setup
|
||||
|
||||
proc newModel*(): Model =
|
||||
new(result, delete)
|
||||
result.entries = @[]
|
||||
result.setup
|
||||
result.hasMore = true
|
||||
|
||||
proc `$`*(self: Model): string =
|
||||
for i in 0 ..< self.entries.len:
|
||||
result &= fmt"""[{i}]:({$self.entries[i]})"""
|
||||
|
||||
proc countChanged(self: Model) {.signal.}
|
||||
|
||||
proc getCount*(self: Model): int {.slot.} =
|
||||
self.entries.len
|
||||
|
||||
QtProperty[int] count:
|
||||
read = getCount
|
||||
notify = countChanged
|
||||
|
||||
method rowCount(self: Model, index: QModelIndex = nil): int =
|
||||
return self.entries.len
|
||||
|
||||
method roleNames(self: Model): Table[int, string] =
|
||||
{
|
||||
ModelRole.ActivityEntryRole.int:"activityEntry",
|
||||
}.toTable
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
if (not index.isValid):
|
||||
return
|
||||
|
||||
if (index.row < 0 or index.row >= self.entries.len):
|
||||
return
|
||||
|
||||
let entry = self.entries[index.row]
|
||||
let enumRole = role.ModelRole
|
||||
|
||||
case enumRole:
|
||||
of ModelRole.ActivityEntryRole:
|
||||
result = newQVariant(entry)
|
||||
|
||||
proc setEntries*(self: Model, entries: seq[ActivityEntry]) =
|
||||
self.beginResetModel()
|
||||
self.entries = entries
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
# TODO: update data
|
||||
|
||||
# TODO: fetch more
|
||||
|
||||
proc hasMoreChanged*(self: Model) {.signal.}
|
||||
|
||||
proc getHasMore*(self: Model): bool {.slot.} =
|
||||
return self.hasMore
|
||||
|
||||
proc setHasMore*(self: Model, hasMore: bool) {.slot.} =
|
||||
self.hasMore = hasMore
|
||||
self.hasMoreChanged()
|
||||
|
||||
QtProperty[bool] hasMore:
|
||||
read = getHasMore
|
||||
write = setHasMore
|
||||
notify = hasMoreChanged
|
|
@ -15,7 +15,7 @@ type
|
|||
walletAccountService: wallet_account_service.Service
|
||||
currencyService: currency_service.Service
|
||||
networkService: network_service.Service
|
||||
|
||||
|
||||
proc newController*(
|
||||
delegate: io_interface.AccessInterface,
|
||||
settingsService: settings_service.Service,
|
||||
|
|
|
@ -16,6 +16,8 @@ import ./networks/module as networks_module
|
|||
import ./overview/module as overview_module
|
||||
import ./send/module as send_module
|
||||
|
||||
import ./activity/controller as activityc
|
||||
|
||||
import ../../../global/global_singleton
|
||||
import ../../../core/eventemitter
|
||||
import ../../../../app_service/service/keycard/service as keycard_service
|
||||
|
@ -34,7 +36,6 @@ import ../../../../app_service/service/network_connection/service as network_con
|
|||
logScope:
|
||||
topics = "wallet-section-module"
|
||||
|
||||
import io_interface
|
||||
export io_interface
|
||||
|
||||
type
|
||||
|
@ -42,7 +43,7 @@ type
|
|||
delegate: delegate_interface.AccessInterface
|
||||
events: EventEmitter
|
||||
moduleLoaded: bool
|
||||
controller: Controller
|
||||
controller: controller.Controller
|
||||
view: View
|
||||
filter: Filter
|
||||
|
||||
|
@ -61,6 +62,8 @@ type
|
|||
accountsService: accounts_service.Service
|
||||
walletAccountService: wallet_account_service.Service
|
||||
|
||||
activityController: activityc.Controller
|
||||
|
||||
proc newModule*(
|
||||
delegate: delegate_interface.AccessInterface,
|
||||
events: EventEmitter,
|
||||
|
@ -85,7 +88,6 @@ proc newModule*(
|
|||
result.walletAccountService = walletAccountService
|
||||
result.moduleLoaded = false
|
||||
result.controller = newController(result, settingsService, walletAccountService, currencyService, networkService)
|
||||
result.view = newView(result)
|
||||
|
||||
result.accountsModule = accounts_module.newModule(result, events, walletAccountService, networkService, currencyService)
|
||||
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService)
|
||||
|
@ -99,6 +101,10 @@ proc newModule*(
|
|||
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
|
||||
result.filter = initFilter(result.controller)
|
||||
|
||||
result.activityController = activityc.newController(result.transactionsModule)
|
||||
result.view = newView(result, result.activityController)
|
||||
|
||||
|
||||
method delete*(self: Module) =
|
||||
self.accountsModule.delete
|
||||
self.allTokensModule.delete
|
||||
|
@ -110,6 +116,7 @@ method delete*(self: Module) =
|
|||
self.sendModule.delete
|
||||
self.controller.delete
|
||||
self.view.delete
|
||||
self.activityController.delete
|
||||
|
||||
if not self.addAccountModule.isNil:
|
||||
self.addAccountModule.delete
|
||||
|
|
|
@ -9,6 +9,7 @@ import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_modu
|
|||
|
||||
import ../../../../core/[main]
|
||||
import ../../../../core/tasks/[qt, threadpool]
|
||||
import ./backend/transactions
|
||||
|
||||
type
|
||||
Controller* = ref object of RootObj
|
||||
|
@ -100,7 +101,7 @@ proc getWalletAccountByAddress*(self: Controller, address: string): WalletAccoun
|
|||
proc loadTransactions*(self: Controller, address: string, toBlock: Uint256, limit: int = 20, loadMore: bool = false) =
|
||||
self.transactionService.loadTransactions(address, toBlock, limit, loadMore)
|
||||
|
||||
proc getAllTransactions*(self: Controller, address: string): seq[TransactionDto] =
|
||||
proc getAllTransactions*(self: Controller, address: string): seq[TransactionDto] =
|
||||
return self.transactionService.getAllTransactions(address)
|
||||
|
||||
proc getChainIdForChat*(self: Controller): int =
|
||||
|
@ -122,4 +123,4 @@ proc findTokenSymbolByAddress*(self: Controller, address: string): string =
|
|||
return self.walletAccountService.findTokenSymbolByAddress(address)
|
||||
|
||||
proc getMultiTransactions*(self: Controller, transactionIDs: seq[int]): seq[MultiTransactionDto] =
|
||||
return self.transactionService.getMultiTransactions(transactionIDs)
|
||||
return transaction_service.getMultiTransactions(transactionIDs)
|
||||
|
|
|
@ -3,6 +3,8 @@ import ../../../../../app_service/service/collectible/dto as CollectibleDto
|
|||
import ../../../../../app_service/service/transaction/dto
|
||||
export TransactionDto, CollectibleDto
|
||||
|
||||
import ./item
|
||||
|
||||
type
|
||||
AccessInterface* {.pure inheritable.} = ref object of RootObj
|
||||
## Abstract class for any input/interaction with this module.
|
||||
|
@ -43,13 +45,13 @@ method setIsNonArchivalNode*(self: AccessInterface, isNonArchivalNode: bool) {.b
|
|||
method transactionWasSent*(self: AccessInterface, result: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getChainIdForChat*(self: AccessInterface): int =
|
||||
method getChainIdForChat*(self: AccessInterface): int {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method getChainIdForBrowser*(self: AccessInterface): int =
|
||||
method getChainIdForBrowser*(self: AccessInterface): int {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method refreshTransactions*(self: AccessInterface) {.base.} =
|
||||
method refreshTransactions*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
# View Delegate Interface
|
||||
|
@ -60,3 +62,6 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
|
|||
|
||||
method getLastTxBlockNumber*(self: AccessInterface): string {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method transactionsToItems*(self: AccessInterface, transactions: seq[TransactionDto], collectibles: seq[CollectibleDto]): seq[Item] {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -240,6 +240,7 @@ proc getTxStatus*(self: Item): string =
|
|||
proc getValue*(self: Item): CurrencyAmount =
|
||||
return self.value
|
||||
|
||||
# TODO: fix naming
|
||||
proc getfrom*(self: Item): string =
|
||||
return self.fro
|
||||
|
||||
|
|
|
@ -59,26 +59,10 @@ proc getResolvedSymbol*(self: Module, transaction: TransactionDto): string =
|
|||
else:
|
||||
result = "ETH"
|
||||
|
||||
proc transactionsToItems(self: Module, transactions: seq[TransactionDto], collectibles: seq[CollectibleDto]) : seq[Item] =
|
||||
method transactionsToItems*(self: Module, transactions: seq[TransactionDto], collectibles: seq[CollectibleDto]): seq[Item] =
|
||||
let gweiFormat = self.controller.getCurrencyFormat("Gwei")
|
||||
let ethFormat = self.controller.getCurrencyFormat("ETH")
|
||||
|
||||
# TODO: Continue merging multi-transactions with transactions
|
||||
#
|
||||
# let transactionIDs = transactions.filter(t => t.multiTransactionID != MultiTransactionMissingID).map(t => t.multiTransactionID)
|
||||
# let multiTransactions = self.controller.getMultiTransactions(transactionIDs)
|
||||
# for mt in multiTransactions:
|
||||
# let mtItem = multiTransactionToItem(mt)
|
||||
#
|
||||
# Tip: depending of the new design best will be to replace the transaction.View
|
||||
# with a new ActivityEntry that contains eighter a transaction or a multi-transaction
|
||||
# Refactor transaction Model to serve ActivityEntry istead of Views
|
||||
#
|
||||
# Here we should filter all transactions that are part of a multi-transaciton
|
||||
# and add them to the multi-transaction View associated with an ActivityEntry
|
||||
# and the remaining "free" transactions to the corresponding ActivityEntry
|
||||
# TODO: check TransactionsItem changes
|
||||
|
||||
transactions.map(t => (block:
|
||||
if t.typeValue == ERC721_TRANSACTION_TYPE:
|
||||
for c in collectibles:
|
||||
|
@ -138,7 +122,7 @@ method setHistoryFetchState*(self: Module, addresses: seq[string], isFetching: b
|
|||
|
||||
method setHistoryFetchState*(self: Module, address: string, allTxLoaded: bool, isFetching: bool) =
|
||||
self.view.setHistoryFetchState(address, allTxLoaded, isFetching)
|
||||
|
||||
|
||||
method setIsNonArchivalNode*(self: Module, isNonArchivalNode: bool) =
|
||||
self.view.setIsNonArchivalNode(isNonArchivalNode)
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import strformat
|
||||
|
||||
import ../../../../../app_service/service/transaction/dto
|
||||
import ./backend/transactions
|
||||
|
||||
const MultiTransactionMissingID* = 0
|
||||
|
||||
# TODO: make it a Qt object to be referenced in QML via ActivityView
|
||||
type
|
||||
MultiTransactionItem* = object
|
||||
id: int
|
||||
|
|
|
@ -10,6 +10,8 @@ import ../../../shared_models/currency_amount
|
|||
import ./item
|
||||
import ./multi_transaction_item
|
||||
|
||||
import ./backend/transactions
|
||||
|
||||
proc hex2GweiCurrencyAmount(hexValueStr: string, gweiFormat: CurrencyFormatDto): CurrencyAmount =
|
||||
let value = parseFloat(singletonInstance.utils.hex2Gwei(hexValueStr))
|
||||
return currencyAmountToItem(value, gweiFormat)
|
||||
|
|
|
@ -83,7 +83,7 @@ QtObject:
|
|||
for tx in transactions:
|
||||
if not self.enabledChainIds.contains(tx.getChainId()):
|
||||
continue
|
||||
|
||||
|
||||
toAddTransactions.add(tx)
|
||||
|
||||
if not self.models.hasKey(address):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import NimQml, json
|
||||
|
||||
import ./activity/controller as activityc
|
||||
import ./io_interface
|
||||
import ../../shared_models/currency_amount
|
||||
|
||||
|
@ -13,6 +14,7 @@ QtObject:
|
|||
isMnemonicBackedUp: bool
|
||||
tmpAmount: float # shouldn't be used anywhere except in prepare*/getPrepared* procs
|
||||
tmpSymbol: string # shouldn't be used anywhere except in prepare*/getPrepared* procs
|
||||
activityController: activityc.Controller
|
||||
|
||||
proc setup(self: View) =
|
||||
self.QObject.setup
|
||||
|
@ -20,9 +22,10 @@ QtObject:
|
|||
proc delete*(self: View) =
|
||||
self.QObject.delete
|
||||
|
||||
proc newView*(delegate: io_interface.AccessInterface): View =
|
||||
proc newView*(delegate: io_interface.AccessInterface, activityController: activityc.Controller): View =
|
||||
new(result, delete)
|
||||
result.delegate = delegate
|
||||
result.activityController = activityController
|
||||
result.setup()
|
||||
|
||||
proc load*(self: View) =
|
||||
|
@ -120,4 +123,9 @@ QtObject:
|
|||
|
||||
proc destroyAddAccountPopup*(self: View) {.signal.}
|
||||
proc emitDestroyAddAccountPopup*(self: View) =
|
||||
self.destroyAddAccountPopup()
|
||||
self.destroyAddAccountPopup()
|
||||
|
||||
proc getActivityController(self: View): QVariant {.slot.} =
|
||||
return newQVariant(self.activityController)
|
||||
QtProperty[QVariant] activityController:
|
||||
read = getActivityController
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import json, strutils, stint, json_serialization, strformat
|
||||
|
||||
import
|
||||
web3/ethtypes, json_serialization
|
||||
web3/ethtypes
|
||||
|
||||
include ../../common/json_utils
|
||||
import ../network/dto
|
||||
import ../../common/conversion as service_conversion
|
||||
|
||||
import ./backend/transactions
|
||||
|
||||
type
|
||||
PendingTransactionTypeDto* {.pure.} = enum
|
||||
RegisterENS = "RegisterENS",
|
||||
|
@ -20,20 +22,6 @@ type
|
|||
proc event*(self:PendingTransactionTypeDto):string =
|
||||
result = "transaction:" & $self
|
||||
|
||||
type
|
||||
MultiTransactionType* = enum
|
||||
MultiTransactionSend = 0, MultiTransactionSwap = 1, MultiTransactionBridge = 2
|
||||
|
||||
type MultiTransactionDto* = ref object of RootObj
|
||||
id* {.serializedFieldName("id").}: int
|
||||
timestamp* {.serializedFieldName("timestamp").}: int
|
||||
fromAddress* {.serializedFieldName("fromAddress").}: string
|
||||
toAddress* {.serializedFieldName("toAddress").}: string
|
||||
fromAsset* {.serializedFieldName("fromAsset").}: string
|
||||
toAsset* {.serializedFieldName("toAsset").}: string
|
||||
fromAmount* {.serializedFieldName("fromAmount").}: string
|
||||
multiTxtype* {.serializedFieldName("type").}: MultiTransactionType
|
||||
|
||||
type
|
||||
TransactionDto* = ref object of RootObj
|
||||
id*: string
|
||||
|
|
|
@ -18,7 +18,7 @@ import ../token/service as token_service
|
|||
import ../settings/service as settings_service
|
||||
import ../collectible/dto
|
||||
import ../eth/dto/transaction as transaction_data_dto
|
||||
import ../eth/dto/[method_dto, coder, method_dto]
|
||||
import ../eth/dto/[coder, method_dto]
|
||||
import ./dto as transaction_dto
|
||||
import ./cryptoRampDto
|
||||
import ../eth/utils as eth_utils
|
||||
|
@ -158,16 +158,6 @@ QtObject:
|
|||
error "error: ", errDescription
|
||||
return
|
||||
|
||||
proc getMultiTransactions*(self: Service, transactionIDs: seq[int]): seq[MultiTransactionDto] =
|
||||
try:
|
||||
let response = transactions.getMultiTransactions(transactionIDs).result
|
||||
|
||||
return response.getElems().map(x => x.toMultiTransactionDto())
|
||||
except Exception as e:
|
||||
let errDescription = e.msg
|
||||
error "error: ", errDescription
|
||||
return
|
||||
|
||||
proc getAllTransactions*(self: Service, address: string): seq[TransactionDto] =
|
||||
if not self.allTransactions.hasKey(address):
|
||||
return @[]
|
||||
|
@ -375,7 +365,7 @@ QtObject:
|
|||
fromAsset: tokenSymbol,
|
||||
toAsset: tokenSymbol,
|
||||
fromAmount: "0x" & amountToSend.toHex,
|
||||
multiTxtype: MultiTransactionType.MultiTransactionSend,
|
||||
multiTxtype: transactions.MultiTransactionType.MultiTransactionSend,
|
||||
),
|
||||
paths,
|
||||
password,
|
||||
|
@ -442,7 +432,7 @@ QtObject:
|
|||
fromAsset: tokenSymbol,
|
||||
toAsset: tokenSymbol,
|
||||
fromAmount: "0x" & amountToSend.toHex,
|
||||
multiTxtype: MultiTransactionType.MultiTransactionSend,
|
||||
multiTxtype: transactions.MultiTransactionType.MultiTransactionSend,
|
||||
),
|
||||
paths,
|
||||
password,
|
||||
|
@ -547,3 +537,13 @@ QtObject:
|
|||
except Exception as e:
|
||||
error "Error getting latest block number", message = e.msg
|
||||
return ""
|
||||
|
||||
proc getMultiTransactions*(transactionIDs: seq[int]): seq[MultiTransactionDto] =
|
||||
try:
|
||||
let response = transactions.getMultiTransactions(transactionIDs).result
|
||||
|
||||
return response.getElems().map(x => x.toMultiTransactionDto())
|
||||
except Exception as e:
|
||||
let errDescription = e.msg
|
||||
error "error: ", errDescription
|
||||
return
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
import times, strformat
|
||||
import json, json_serialization
|
||||
import options
|
||||
import ./core, ./response_type
|
||||
from ./gen import rpc
|
||||
import ./backend
|
||||
import transactions
|
||||
|
||||
export response_type
|
||||
|
||||
# TODO: consider using common status-go types via protobuf
|
||||
# TODO: consider using flags instead of list of enums
|
||||
type
|
||||
Period* = object
|
||||
startTimestamp*: int
|
||||
endTimestamp*: int
|
||||
|
||||
# see status-go/services/wallet/activity/filter.go Type
|
||||
ActivityType* {.pure.} = enum
|
||||
Send, Receive, Buy, Swap, Bridge
|
||||
|
||||
# see status-go/services/wallet/activity/filter.go Status
|
||||
ActivityStatus* {.pure.} = enum
|
||||
Failed, Pending, Complete, Finalized
|
||||
|
||||
# see status-go/services/wallet/activity/filter.go TokenType
|
||||
TokenType* {.pure.} = enum
|
||||
Asset, Collectibles
|
||||
|
||||
# see status-go/services/wallet/activity/filter.go Filter
|
||||
ActivityFilter* = object
|
||||
period* {.serializedFieldName("period").}: Period
|
||||
types* {.serializedFieldName("types").}: seq[ActivityType]
|
||||
statuses* {.serializedFieldName("statuses").}: seq[ActivityStatus]
|
||||
tokenTypes* {.serializedFieldName("tokenTypes").}: seq[TokenType]
|
||||
counterpartyAddresses* {.serializedFieldName("counterpartyAddresses").}: seq[string]
|
||||
|
||||
proc newPeriod*(startTime: Option[DateTime], endTime: Option[DateTime]): Period =
|
||||
if startTime.isSome:
|
||||
result.startTimestamp = startTime.get().toTime().toUnix().int
|
||||
else:
|
||||
result.startTimestamp = 0
|
||||
if endTime.isSome:
|
||||
result.endTimestamp = endTime.get().toTime().toUnix().int
|
||||
else:
|
||||
result.endTimestamp = 0
|
||||
|
||||
proc newPeriod*(startTimestamp: int, endTimestamp: int): Period =
|
||||
result.startTimestamp = startTimestamp
|
||||
result.endTimestamp = endTimestamp
|
||||
|
||||
proc getIncludeAllActivityFilter*(): ActivityFilter =
|
||||
result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)), types: @[], statuses: @[], tokenTypes: @[], counterpartyAddresses: @[])
|
||||
|
||||
# Empty sequence for paramters means include all
|
||||
proc newActivityFilter*(period: Period, activityType: seq[ActivityType], activityStatus: seq[ActivityStatus], tokenType: seq[TokenType], counterpartyAddress: seq[string]): ActivityFilter =
|
||||
result.period = period
|
||||
result.types = activityType
|
||||
result.statuses = activityStatus
|
||||
result.tokenTypes = tokenType
|
||||
result.counterpartyAddresses = counterpartyAddress
|
||||
|
||||
# Mirrors status-go/services/wallet/activity/activity.go PayloadType
|
||||
type
|
||||
PayloadType* {.pure.} = enum
|
||||
MultiTransaction = 1
|
||||
SimpleTransaction
|
||||
PendingTransaction
|
||||
|
||||
# Define toJson proc for PayloadType
|
||||
proc toJson*(x: PayloadType): JsonNode {.inline.} =
|
||||
return %*(ord(x))
|
||||
|
||||
# Define fromJson proc for PayloadType
|
||||
proc fromJson*(x: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
||||
return cast[PayloadType](x.getInt())
|
||||
|
||||
# TODO: hide internals behind safe interface
|
||||
type
|
||||
ActivityEntry* = object
|
||||
transactionType* {.serializedFieldName("transactionType").}: PayloadType
|
||||
transaction* {.serializedFieldName("transaction").}: Option[TransactionIdentity]
|
||||
id* {.serializedFieldName("id").}: int
|
||||
timestamp* {.serializedFieldName("timestamp").}: int
|
||||
activityType* {.serializedFieldName("activityType").}: MultiTransactionType
|
||||
|
||||
proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] =
|
||||
if jsonObj.kind != JNull:
|
||||
return some(to(jsonObj, T))
|
||||
else:
|
||||
return none(T)
|
||||
|
||||
proc toJson[T](obj: Option[T]): JsonNode =
|
||||
if obj.isSome:
|
||||
toJson(obj.get())
|
||||
else:
|
||||
newJNull()
|
||||
|
||||
# Define toJson proc for PayloadType
|
||||
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
||||
return %*(ae)
|
||||
|
||||
# Define fromJson proc for PayloadType
|
||||
proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} =
|
||||
result = T(
|
||||
transactionType: fromJson(e["transactionType"], PayloadType),
|
||||
transaction: if e.hasKey("transaction"): fromJson(e["transaction"], Option[TransactionIdentity]) else: none(TransactionIdentity),
|
||||
id: e["id"].getInt(),
|
||||
timestamp: e["timestamp"].getInt()
|
||||
)
|
||||
|
||||
proc `$`*(self: ActivityEntry): string =
|
||||
let transactionStr = if self.transaction.isSome: $self.transaction.get() else: "none(TransactionIdentity)"
|
||||
return fmt"""ActivityEntry(
|
||||
transactionType:{self.transactionType.int},
|
||||
transaction:{transactionStr},
|
||||
id:{self.id},
|
||||
timestamp:{self.timestamp},
|
||||
)"""
|
||||
|
||||
rpc(getActivityEntries, "wallet"):
|
||||
addresses: seq[string]
|
||||
chainIds: seq[int]
|
||||
filter: ActivityFilter
|
||||
offset: int
|
||||
limit: int
|
|
@ -1,4 +1,4 @@
|
|||
import json, json_serialization
|
||||
import json, json_serialization, strformat
|
||||
import ./core, ./response_type
|
||||
from ./gen import rpc
|
||||
|
||||
|
@ -96,6 +96,25 @@ rpc(getTokensBalancesForChainIDs, "wallet"):
|
|||
rpc(getPendingTransactionsByChainIDs, "wallet"):
|
||||
chainIds: seq[int]
|
||||
|
||||
type
|
||||
TransactionIdentity* = ref object
|
||||
chainId* {.serializedFieldName("chainId").}: int
|
||||
hash* {.serializedFieldName("hash").}: string
|
||||
address* {.serializedFieldName("address").}: string
|
||||
|
||||
proc `$`*(self: TransactionIdentity): string =
|
||||
return fmt"""TransactionIdentity(
|
||||
chainId:{self.chainId},
|
||||
hash:{self.hash},
|
||||
address:{self.address},
|
||||
)"""
|
||||
|
||||
rpc(getPendingTransactionsForIdentities, "wallet"):
|
||||
identities = seq[TransactionIdentity]
|
||||
|
||||
rpc(getTransfersForIdentities, "wallet"):
|
||||
identities = seq[TransactionIdentity]
|
||||
|
||||
rpc(getWalletToken, "wallet"):
|
||||
accounts: seq[string]
|
||||
|
||||
|
|
|
@ -1,10 +1,27 @@
|
|||
import json, stint
|
||||
import json, stint, json_serialization
|
||||
|
||||
import ../app_service/service/transaction/dto
|
||||
import ../app_service/service/eth/dto/transaction
|
||||
import ./core as core
|
||||
import ../app_service/common/utils
|
||||
|
||||
# mirrors the MultiTransactionType from status-go, services/wallet/transfer/transaction.go
|
||||
type
|
||||
MultiTransactionType* = enum
|
||||
MultiTransactionSend = 0, MultiTransactionSwap = 1, MultiTransactionBridge = 2
|
||||
|
||||
MultiTransactionDto* = ref object of RootObj
|
||||
id* {.serializedFieldName("id").}: int
|
||||
timestamp* {.serializedFieldName("timestamp").}: int
|
||||
fromAddress* {.serializedFieldName("fromAddress").}: string
|
||||
toAddress* {.serializedFieldName("toAddress").}: string
|
||||
fromAsset* {.serializedFieldName("fromAsset").}: string
|
||||
toAsset* {.serializedFieldName("toAsset").}: string
|
||||
fromAmount* {.serializedFieldName("fromAmount").}: string
|
||||
multiTxtype* {.serializedFieldName("type").}: MultiTransactionType
|
||||
|
||||
proc getTransactionByHash*(chainId: int, hash: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
core.callPrivateRPCWithChainId("eth_getTransactionByHash", chainId, %* [hash])
|
||||
|
||||
proc checkRecentHistory*(chainIds: seq[int], addresses: seq[string]) {.raises: [Exception].} =
|
||||
let payload = %* [chainIds, addresses]
|
||||
discard core.callPrivateRPC("wallet_checkRecentHistoryForChainIDs", payload)
|
||||
|
|
|
@ -29,6 +29,7 @@ QtObject {
|
|||
|
||||
property var walletSectionInst: walletSection
|
||||
property var totalCurrencyBalance: walletSection.totalCurrencyBalance
|
||||
property var activityController: walletSection.activityController
|
||||
property string signingPhrase: walletSection.signingPhrase
|
||||
property string mnemonicBackedUp: walletSection.isMnemonicBackedUp
|
||||
|
||||
|
|
|
@ -83,6 +83,13 @@ Item {
|
|||
width: implicitWidth
|
||||
text: qsTr("Activity")
|
||||
}
|
||||
// TODO - DEV: remove me
|
||||
// currentIndex: 3
|
||||
// StatusTabButton {
|
||||
// rightPadding: 0
|
||||
// width: implicitWidth
|
||||
// text: qsTr("DEV activity")
|
||||
// }
|
||||
}
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
|
@ -114,6 +121,14 @@ Item {
|
|||
stack.currentIndex = 3
|
||||
}
|
||||
}
|
||||
// TODO: replace with the real activity view
|
||||
// Enable for debugging activity filter
|
||||
// ActivityView {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.fillHeight: true
|
||||
|
||||
// controller: RootStore.activityController
|
||||
// }
|
||||
}
|
||||
}
|
||||
CollectibleDetailView {
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
import QtQuick 2.15
|
||||
import QtQml 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import "../panels"
|
||||
import "../popups"
|
||||
import "../stores"
|
||||
import "../controls"
|
||||
|
||||
// Temporary developer view to test the filter APIs
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var controller
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
id: filterLayout
|
||||
|
||||
readonly property int millisInADay: 24 * 60 * 60 * 1000
|
||||
property int start: fromSlider.value > 0 ? Math.floor(new Date(new Date() - (fromSlider.value * millisInADay)).getTime() / 1000) : 0
|
||||
property int end: toSlider.value > 0 ? Math.floor(new Date(new Date() - (toSlider.value * millisInADay)).getTime() / 1000) : 0
|
||||
|
||||
function updateFilter() { controller.updateFilter(start, end) }
|
||||
|
||||
RowLayout {
|
||||
Label { text: "Past Days Span: 100" }
|
||||
Slider {
|
||||
id: fromSlider
|
||||
|
||||
Layout.preferredWidth: 200
|
||||
Layout.preferredHeight: 50
|
||||
|
||||
from: 100
|
||||
to: 0
|
||||
|
||||
stepSize: 1
|
||||
value: 0
|
||||
|
||||
onPressedChanged: { if (!pressed) filterLayout.updateFilter() }
|
||||
}
|
||||
Label { text: `${fromSlider.value}d - ${toSlider.value}d` }
|
||||
Slider {
|
||||
id: toSlider
|
||||
|
||||
Layout.preferredWidth: 200
|
||||
Layout.preferredHeight: 50
|
||||
|
||||
enabled: fromSlider.value > 1
|
||||
|
||||
from: fromSlider.value - 1
|
||||
to: 0
|
||||
|
||||
stepSize: 1
|
||||
value: 0
|
||||
|
||||
onPressedChanged: { if (!pressed) filterLayout.updateFilter() }
|
||||
}
|
||||
Label { text: "0" }
|
||||
}
|
||||
Label { text: `Interval: ${filterLayout.start > 0 ? root.epochToDateStr(filterLayout.start) : "all time"} - ${filterLayout.end > 0 ? root.epochToDateStr(filterLayout.end) : "now"}` }
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
model: controller.model
|
||||
|
||||
delegate: Item {
|
||||
width: parent ? parent.width : 0
|
||||
height: itemLayout.implicitHeight
|
||||
|
||||
readonly property var entry: model.activityEntry
|
||||
|
||||
RowLayout {
|
||||
id: itemLayout
|
||||
anchors.fill: parent
|
||||
|
||||
Label { text: entry.isMultiTransaction ? "MT" : entry.isPendingTransaction ? "PT" : " T" }
|
||||
Label { text: `[${root.epochToDateStr(entry.timestamp)}] ` }
|
||||
Label { text: entry.isMultiTransaction ? entry.fromAmount : entry.amount }
|
||||
Label { text: "from"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||
Label { text: entry.sender; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
||||
Label { text: "to"; Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
||||
Label { text: entry.recipient; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
||||
Label { text: "got"; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||
Label { text: entry.toAmount; Layout.leftMargin: 5; Layout.rightMargin: 5; visible: entry.isMultiTransaction }
|
||||
RowLayout {} // Spacer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function epochToDateStr(epochTimestamp) {
|
||||
var date = new Date(epochTimestamp * 1000);
|
||||
return date.toLocaleString(Qt.locale(), "dd-MM-yyyy hh:mm");
|
||||
}
|
||||
}
|
|
@ -13,3 +13,4 @@ TokenListView 1.0 TokenListView.qml
|
|||
TransactionPreview 1.0 TransactionPreview.qml
|
||||
TransactionSigner 1.0 TransactionSigner.qml
|
||||
TransactionStackView 1.0 TransactionStackView.qml
|
||||
ActivityView 1.0 ActivityView.qml
|
||||
|
|
Loading…
Reference in New Issue