feat(@dekstop/wallet): support collectibles in activity tab

Fixes #8811
This commit is contained in:
Dario Gabriel Lipicar 2023-02-28 14:15:22 -03:00 committed by dlipicar
parent 4ad4a7e2b5
commit eed98809d1
31 changed files with 587 additions and 172 deletions

View File

@ -31,21 +31,21 @@ proc delete*(self: Controller) =
discard
proc refreshCollections*(self: Controller, chainId: int, address: string) =
let collections = self.collectibleService.getCollections(chainId, address)
let collections = self.collectibleService.getOwnedCollections(chainId, address)
self.delegate.setCollections(collections)
proc refreshCollectibles*(self: Controller, chainId: int, address: string, collectionSlug: string) =
let collection = self.collectibleService.getCollection(chainId, address, collectionSlug)
let collection = self.collectibleService.getOwnedCollection(chainId, address, collectionSlug)
self.delegate.updateCollection(collection)
proc init*(self: Controller) =
self.events.on(SIGNAL_COLLECTIONS_UPDATED) do(e:Args):
let args = CollectionsUpdateArgs(e)
self.events.on(SIGNAL_OWNED_COLLECTIONS_UPDATED) do(e:Args):
let args = OwnedCollectionsUpdateArgs(e)
self.refreshCollections(args.chainId, args.address)
self.collectibleService.fetchAllCollectibles(args.chainId, args.address)
self.collectibleService.fetchAllOwnedCollectibles(args.chainId, args.address)
self.events.on(SIGNAL_COLLECTIBLES_UPDATED) do(e:Args):
let args = CollectiblesUpdateArgs(e)
self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATED) do(e:Args):
let args = OwnedCollectiblesUpdateArgs(e)
self.refreshCollectibles(args.chainId, args.address, args.collectionSlug)
proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_service.WalletAccountDto =
@ -54,8 +54,8 @@ proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_serv
proc getNetwork*(self: Controller): network_service.NetworkDto =
return self.networkService.getNetworkForCollectibles()
proc fetchCollections*(self: Controller, chainId: int, address: string) =
self.collectibleService.fetchCollections(chainId, address)
proc fetchOwnedCollections*(self: Controller, chainId: int, address: string) =
self.collectibleService.fetchOwnedCollections(chainId, address)
proc fetchCollectibles*(self: Controller, chainId: int, address: string, collectionSlug: string) =
self.collectibleService.fetchCollectibles(chainId, address, collectionSlug)
proc fetchOwnedCollectibles*(self: Controller, chainId: int, address: string, collectionSlug: string) =
self.collectibleService.fetchOwnedCollectibles(chainId, address, collectionSlug)

View File

@ -30,5 +30,5 @@ method setCurrentAddress*(self: Controller, network: network_dto.NetworkDto, add
self.address = address
proc update*(self: Controller, collectionSlug: string, id: int) =
let collection = self.collectibleService.getCollection(self.network.chainId, self.address, collectionSlug)
let collection = self.collectibleService.getOwnedCollection(self.network.chainId, self.address, collectionSlug)
self.delegate.setData(collection.collection, collection.collectibles[id], self.network)

View File

@ -1,4 +1,4 @@
import NimQml, sequtils, sugar
import NimQml, sequtils, sugar, stint
import ./io_interface
import ../../../../../../app_service/service/network/dto as network_dto
@ -20,6 +20,7 @@ QtObject:
name: string
id: string
tokenId: string
description: string
backgroundColor: string
imageUrl: string
@ -92,6 +93,15 @@ QtObject:
read = getID
notify = idChanged
proc getTokenID(self: View): QVariant {.slot.} =
return newQVariant(self.tokenId)
proc tokenIdChanged(self: View) {.signal.}
QtProperty[QVariant] tokenId:
read = getTokenID
notify = tokenIdChanged
proc getDescription(self: View): QVariant {.slot.} =
return newQVariant(self.description)
@ -206,6 +216,11 @@ QtObject:
self.id = idString
self.idChanged()
let tokenIdString = collectible.tokenId.toString()
if (self.tokenId != tokenIdString):
self.tokenId = tokenIdString
self.tokenIdChanged()
if (self.description != collectible.description):
self.description = collectible.description
self.descriptionChanged()

View File

@ -16,13 +16,13 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method switchAccount*(self: AccessInterface, accountIndex: int) {.base.} =
raise newException(ValueError, "No implementation available")
method fetchCollections*(self: AccessInterface) {.base.} =
method fetchOwnedCollections*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method setCollections*(self: AccessInterface, collections: CollectionsData) {.base.} =
raise newException(ValueError, "No implementation available")
method fetchCollectibles*(self: AccessInterface, collectionSlug: string) {.base.} =
method fetchOwnedCollectibles*(self: AccessInterface, collectionSlug: string) {.base.} =
raise newException(ValueError, "No implementation available")
method updateCollection*(self: AccessInterface, collection: CollectionData) {.base.} =

View File

@ -1,9 +1,10 @@
import strformat
import strformat, stint
import ./collectible_trait_item
type
Item* = object
id: int
tokenId: UInt256
name: string
imageUrl: string
backgroundColor: string
@ -15,6 +16,7 @@ type
proc initItem*(
id: int,
tokenId: UInt256,
name: string,
imageUrl: string,
backgroundColor: string,
@ -25,6 +27,7 @@ proc initItem*(
stats: seq[CollectibleTrait]
): Item =
result.id = id
result.tokenId = tokenId
result.name = name
result.imageUrl = imageUrl
result.backgroundColor = if (backgroundColor == ""): "transparent" else: ("#" & backgroundColor)
@ -35,11 +38,12 @@ proc initItem*(
result.stats = stats
proc initItem*: Item =
result = initItem(-1, "", "", "transparent", "Collectibles", "", @[], @[], @[])
result = initItem(-1, u256(0), "", "", "transparent", "Collectibles", "", @[], @[], @[])
proc `$`*(self: Item): string =
result = fmt"""Collectibles(
id: {self.id},
tokenId: {self.tokenId},
name: {self.name},
imageUrl: {self.imageUrl},
backgroundColor: {self.backgroundColor},
@ -50,6 +54,9 @@ proc `$`*(self: Item): string =
proc getId*(self: Item): int =
return self.id
proc getTokenId*(self: Item): UInt256 =
return self.tokenId
proc getName*(self: Item): string =
return self.name

View File

@ -1,10 +1,11 @@
import NimQml, Tables, strutils, strformat, sequtils
import NimQml, Tables, strutils, strformat, sequtils, stint
import ./collectibles_item, ./collectible_trait_model
type
CollectibleRole* {.pure.} = enum
Id = UserRole + 1,
TokenId
Name
ImageUrl
BackgroundColor
@ -51,6 +52,7 @@ QtObject:
method roleNames(self: Model): Table[int, string] =
{
CollectibleRole.Id.int:"id",
CollectibleRole.TokenId.int:"tokenId",
CollectibleRole.Name.int:"name",
CollectibleRole.ImageUrl.int:"imageUrl",
CollectibleRole.BackgroundColor.int:"backgroundColor",
@ -74,6 +76,8 @@ QtObject:
case enumRole:
of CollectibleRole.Id:
result = newQVariant(item.getId())
of CollectibleRole.TokenId:
result = newQVariant(item.getTokenId().toString())
of CollectibleRole.Name:
result = newQVariant(item.getName())
of CollectibleRole.ImageUrl:

View File

@ -5,6 +5,7 @@ import collectibles_item, collectible_trait_item
proc collectibleToItem*(c: CollectibleDto) : Item =
return initItem(
c.id,
c.tokenId,
c.name,
c.imageUrl,
c.backgroundColor,

View File

@ -1,4 +1,4 @@
import strformat
import strformat, stint
import ./collectibles_model as collectibles_model
import ./collectibles_item as collectibles_item
@ -7,11 +7,11 @@ type
name: string
slug: string
imageUrl: string
ownedAssetCount: int
ownedAssetCount: Uint256
collectiblesLoaded*: bool
collectiblesModel: collectibles_model.Model
proc initItem*(name, slug, imageUrl: string, ownedAssetCount: int, collectiblesLoaded: bool, collectibles: seq[collectibles_item.Item]): Item =
proc initItem*(name, slug, imageUrl: string, ownedAssetCount: Uint256, collectiblesLoaded: bool, collectibles: seq[collectibles_item.Item]): Item =
result.name = name
result.slug = slug
result.imageUrl = imageUrl
@ -20,7 +20,7 @@ proc initItem*(name, slug, imageUrl: string, ownedAssetCount: int, collectiblesL
result.collectiblesModel = collectibles_model.newModel(collectibles)
proc initItem*(): Item =
result = initItem("", "", "", 0, false, @[])
result = initItem("", "", "", u256(0), false, @[])
proc `$`*(self: Item): string =
result = fmt"""CollectibleCollection(
@ -41,7 +41,7 @@ proc getSlug*(self: Item): string =
proc getImageUrl*(self: Item): string =
return self.imageUrl
proc getOwnedAssetCount*(self: Item): int =
proc getOwnedAssetCount*(self: Item): Uint256 =
return self.ownedAssetCount
proc getCollectiblesLoaded*(self: Item): bool =

View File

@ -1,4 +1,4 @@
import NimQml, Tables, strutils, strformat
import NimQml, Tables, strutils, strformat, stint
import ./collections_item as collections_item
@ -81,7 +81,7 @@ QtObject:
of CollectionRole.ImageUrl:
result = newQVariant(item.getImageUrl())
of CollectionRole.OwnedAssetCount:
result = newQVariant(item.getOwnedAssetCount())
result = newQVariant(item.getOwnedAssetCount().toString())
of CollectionRole.CollectiblesLoaded:
result = newQVariant(item.getCollectiblesLoaded())
of CollectionRole.CollectiblesModel:

View File

@ -84,7 +84,7 @@ method switchAccount*(self: Module, accountIndex: int) =
self.address = account.address
self.controller.refreshCollections(self.chainId, self.address)
self.controller.fetchCollections(self.chainId, self.address)
self.controller.fetchOwnedCollections(self.chainId, self.address)
self.currentCollectibleModule.setCurrentAddress(network, self.address)
@ -115,8 +115,8 @@ method updateCollection*(self: Module, collection: CollectionData) =
collection.collectiblesLoaded
)
method fetchCollections*(self: Module) =
self.controller.fetchCollections(self.chainId, self.address)
method fetchOwnedCollections*(self: Module) =
self.controller.fetchOwnedCollections(self.chainId, self.address)
method fetchCollectibles*(self: Module, collectionSlug: string) =
self.controller.fetchCollectibles(self.chainId, self.address, collectionSlug)
method fetchOwnedCollectibles*(self: Module, collectionSlug: string) =
self.controller.fetchOwnedCollectibles(self.chainId, self.address, collectionSlug)

View File

@ -42,11 +42,11 @@ QtObject:
read = getFlatModel
notify = flatModelChanged
proc fetchCollections*(self: View) {.slot.} =
self.delegate.fetchCollections()
proc fetchOwnedCollections*(self: View) {.slot.} =
self.delegate.fetchOwnedCollections()
proc fetchCollectibles*(self: View, collectionSlug: string) {.slot.} =
self.delegate.fetchCollectibles(collectionSlug)
proc fetchOwnedCollectibles*(self: View, collectionSlug: string) {.slot.} =
self.delegate.fetchOwnedCollectibles(collectionSlug)
proc setCollections*(self: View, collections: seq[collections_item.Item], collectionsLoaded: bool) =
self.model.setCollections(collections, collectionsLoaded)

View File

@ -4,6 +4,7 @@ import ../../../../../app_service/service/transaction/service as transaction_ser
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/currency/service as currency_service
import ../../../../../app_service/service/collectible/service as collectible_service
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
import ../../../../core/[main]
@ -66,7 +67,7 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_TRANSACTIONS_LOADED) do(e:Args):
let args = TransactionsLoadedArgs(e)
self.delegate.setHistoryFetchState(@[args.address], isFetching = false)
self.delegate.setTrxHistoryResult(args.transactions, args.address, args.wasFetchMore)
self.delegate.setTrxHistoryResult(args.transactions, args.collectibles, args.address, args.wasFetchMore)
self.events.on(SIGNAL_TRANSACTION_SENT) do(e:Args):
self.delegate.transactionWasSent(TransactionSentArgs(e).result)
@ -88,6 +89,10 @@ proc init*(self: Controller) =
# TODO: Rebuild Transaction items
discard
self.events.on(SIGNAL_COLLECTIBLES_UPDATED) do(e:Args):
# TODO: Refresh collectible data in Transaction items
discard
proc watchPendingTransactions*(self: Controller): seq[TransactionDto] =
return self.transactionService.watchPendingTransactions()

View File

@ -1,7 +1,8 @@
import stint
import ../../../../../app_service/service/wallet_account/dto as WalletDto
import ../../../../../app_service/service/collectible/dto as CollectibleDto
import ../../../../../app_service/service/transaction/dto
export TransactionDto
export TransactionDto, CollectibleDto
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -28,7 +29,7 @@ method getAccountByAddress*(self: AccessInterface, address: string): WalletAccou
method loadTransactions*(self: AccessInterface, address: string, toBlock: string, limit: int, loadMore: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method setTrxHistoryResult*(self: AccessInterface, transactions: seq[TransactionDto], address: string, wasFetchMore: bool) {.base.} =
method setTrxHistoryResult*(self: AccessInterface, transactions: seq[TransactionDto], collectibles: seq[CollectibleDto], address: string, wasFetchMore: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method setHistoryFetchState*(self: AccessInterface, addresses: seq[string], isFetching: bool) {.base.} =

View File

@ -1,10 +1,10 @@
import strformat
import strformat, stint
import ../../../shared_models/currency_amount
type
Item* = object
id: string
typ: string
txType: string
address: string
blockNumber: string
blockHash: string
@ -14,7 +14,6 @@ type
gasUsed: int
nonce: string
txStatus: string
value: CurrencyAmount
fro: string
to: string
contract: string
@ -25,15 +24,22 @@ type
txHash: string
multiTransactionID: int
isTimeStamp: bool
isNFT: bool
baseGasFees: CurrencyAmount
totalFees: CurrencyAmount
maxTotalFees: CurrencyAmount
symbol: string
loadingTransaction: bool
# Applies only to isNFT == false
value: CurrencyAmount
symbol: string
# Applies only to isNFT == true
tokenID: UInt256
nftName: string
nftImageUrl: string
proc initItem*(
id: string,
typ: string,
txType: string,
address: string,
blockNumber: string,
blockHash: string,
@ -61,7 +67,7 @@ proc initItem*(
loadingTransaction: bool = false
): Item =
result.id = id
result.typ = typ
result.txType = txType
result.address = address
result.blockNumber = blockNumber
result.blockHash = blockHash
@ -82,12 +88,73 @@ proc initItem*(
result.txHash = txHash
result.multiTransactionID = multiTransactionID
result.isTimeStamp = isTimeStamp
result.isNFT = false
result.baseGasFees = baseGasFees
result.totalFees = totalFees
result.maxTotalFees = maxTotalFees
result.symbol = symbol
result.loadingTransaction = loadingTransaction
proc initNFTItem*(
id: string,
txType: string,
address: string,
blockNumber: string,
blockHash: string,
timestamp: int,
gasPrice: CurrencyAmount,
gasLimit: int,
gasUsed: int,
nonce: string,
txStatus: string,
fro: string,
to: string,
contract: string,
chainId: int,
maxFeePerGas: CurrencyAmount,
maxPriorityFeePerGas: CurrencyAmount,
input: string,
txHash: string,
multiTransactionID: int,
baseGasFees: CurrencyAmount,
totalFees: CurrencyAmount,
maxTotalFees: CurrencyAmount,
tokenID: UInt256,
nftName: string,
nftImageUrl: string,
loadingTransaction: bool = false
): Item =
result.id = id
result.txType = txType
result.address = address
result.blockNumber = blockNumber
result.blockHash = blockHash
result.timestamp = timestamp
result.gasPrice = gasPrice
result.gasLimit = gasLimit
result.gasUsed = gasUsed
result.nonce = nonce
result.txStatus = txStatus
result.value = newCurrencyAmount()
result.fro = fro
result.to = to
result.contract = contract
result.chainId = chainId
result.maxFeePerGas = maxFeePerGas
result.maxPriorityFeePerGas = maxPriorityFeePerGas
result.input = input
result.txHash = txHash
result.multiTransactionID = multiTransactionID
result.isTimeStamp = false
result.isNFT = true
result.baseGasFees = baseGasFees
result.totalFees = totalFees
result.maxTotalFees = maxTotalFees
result.loadingTransaction = loadingTransaction
result.tokenID = tokenID
result.nftName = nftName
result.nftImageUrl = nftImageUrl
proc initTimestampItem*(timestamp: int): Item =
result.timestamp = timestamp
result.gasPrice = newCurrencyAmount()
@ -116,9 +183,9 @@ proc initLoadingItem*(): Item =
result.loadingTransaction = true
proc `$`*(self: Item): string =
result = fmt"""AllTokensItem(
result = fmt"""TransactionsItem(
id: {self.id},
type: {self.typ},
txType: {self.txType},
address: {self.address},
blockNumber: {self.blockNumber},
blockHash: {self.blockHash},
@ -139,18 +206,22 @@ proc `$`*(self: Item): string =
txHash: {self.txHash},
multiTransactionID: {self.multiTransactionID},
isTimeStamp: {self.isTimeStamp},
isNFT: {self.isNFT},
baseGasFees: {self.baseGasFees},
totalFees: {self.totalFees},
maxTotalFees: {self.maxTotalFees},
symbol: {self.symbol},
loadingTransaction: {self.loadingTransaction},
tokenID: {self.tokenID},
nftName: {self.nftName},
nftImageUrl: {self.nftImageUrl},
]"""
proc getId*(self: Item): string =
return self.id
proc getType*(self: Item): string =
return self.typ
return self.txType
proc getAddress*(self: Item): string =
return self.address
@ -212,6 +283,9 @@ proc getMultiTransactionID*(self: Item): int =
proc getIsTimeStamp*(self: Item): bool =
return self.isTimeStamp
proc getIsNFT*(self: Item): bool =
return self.isNFT
proc getBaseGasFees*(self: Item): CurrencyAmount =
return self.baseGasFees
@ -226,3 +300,12 @@ proc getSymbol*(self: Item): string =
proc getLoadingTransaction*(self: Item): bool =
return self.loadingTransaction
proc getTokenID*(self: Item): UInt256 =
return self.tokenID
proc getNFTName*(self: Item): string =
return self.nftName
proc getNFTImageURL*(self: Item): string =
return self.nftImageUrl

View File

@ -16,7 +16,6 @@ type
GasUsed
Nonce
TxStatus
Value
From
To
Contract
@ -27,11 +26,18 @@ type
TxHash
MultiTransactionID
IsTimeStamp
IsNFT
BaseGasFees
TotalFees
MaxTotalFees
Symbol
LoadingTransaction
# Applies only to IsNFT == false
Value
Symbol
# Applies only to IsNFT == true
TokenID
NFTName
NFTImageURL
QtObject:
type
@ -93,11 +99,15 @@ QtObject:
ModelRole.TxHash.int:"txHash",
ModelRole.MultiTransactionID.int:"multiTransactionID",
ModelRole.IsTimeStamp.int: "isTimeStamp",
ModelRole.IsNFT.int: "isNFT",
ModelRole.BaseGasFees.int: "baseGasFees",
ModelRole.TotalFees.int: "totalFees",
ModelRole.MaxTotalFees.int: "maxTotalFees",
ModelRole.Symbol.int: "symbol",
ModelRole.LoadingTransaction.int: "loadingTransaction"
ModelRole.LoadingTransaction.int: "loadingTransaction",
ModelRole.TokenID.int: "tokenID",
ModelRole.NFTName.int: "nftName",
ModelRole.NFTImageURL.int: "nftImageUrl"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -155,6 +165,8 @@ QtObject:
result = newQVariant(item.getMultiTransactionID())
of ModelRole.IsTimeStamp:
result = newQVariant(item.getIsTimeStamp())
of ModelRole.IsNFT:
result = newQVariant(item.getIsNFT())
of ModelRole.BaseGasFees:
result = newQVariant(item.getBaseGasFees())
of ModelRole.TotalFees:
@ -165,6 +177,12 @@ QtObject:
result = newQVariant(item.getSymbol())
of ModelRole.LoadingTransaction:
result = newQVariant(item.getLoadingTransaction())
of ModelRole.TokenID:
result = newQVariant(item.getTokenID().toString())
of ModelRole.NFTName:
result = newQVariant(item.getNFTName())
of ModelRole.NFTImageURL:
result = newQVariant(item.getNFTImageURL())
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()

View File

@ -4,10 +4,12 @@ import ./io_interface, ./view, ./controller, ./item, ./utils
import ../io_interface as delegate_interface
import ../../../../global/global_singleton
import ../../../../core/eventemitter
import ../../../../../app_service/common/wallet_constants
import ../../../../../app_service/service/transaction/service as transaction_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/currency/service as currency_service
import ../../../../../app_service/service/collectible/service as collectible_service
export io_interface
@ -70,17 +72,24 @@ proc getResolvedSymbol*(self: Module, transaction: TransactionDto): string =
else:
result = "ETH"
proc transactionsToItems(self: Module, transactions: seq[TransactionDto]) : seq[Item] =
proc transactionsToItems(self: Module, transactions: seq[TransactionDto], collectibles: seq[CollectibleDto]) : seq[Item] =
let gweiFormat = self.controller.getCurrencyFormat("Gwei")
let ethFormat = self.controller.getCurrencyFormat("ETH")
transactions.map(t => (block:
if t.typeValue == ERC721_TRANSACTION_TYPE:
for c in collectibles:
if c.tokenId == t.tokenId and c.address == t.contract:
# Found matching collectible
return transactionToNFTItem(t, c, ethFormat, gweiFormat)
# Could not find matching collectible, use empty one
return transactionToNFTItem(t, newCollectibleDto(), ethFormat, gweiFormat)
let resolvedSymbol = self.getResolvedSymbol(t)
transactionToItem(t, resolvedSymbol, self.controller.getCurrencyFormat(resolvedSymbol), ethFormat, gweiFormat)
return transactionToItem(t, resolvedSymbol, self.controller.getCurrencyFormat(resolvedSymbol), ethFormat, gweiFormat)
))
proc setPendingTx(self: Module) =
self.view.setPendingTx(self.transactionsToItems(self.controller.watchPendingTransactions()))
self.view.setPendingTx(self.transactionsToItems(self.controller.watchPendingTransactions(), @[]))
method viewDidLoad*(self: Module) =
let accounts = self.getWalletAccounts()
@ -109,8 +118,8 @@ method loadTransactions*(self: Module, address: string, toBlock: string = "0x0",
self.controller.loadTransactions(address, toBlockParsed, txLimit, loadMore)
method setTrxHistoryResult*(self: Module, transactions: seq[TransactionDto], address: string, wasFetchMore: bool) =
self.view.setTrxHistoryResult(self.transactionsToItems(transactions), address, wasFetchMore)
method setTrxHistoryResult*(self: Module, transactions: seq[TransactionDto], collectibles: seq[CollectibleDto], address: string, wasFetchMore: bool) =
self.view.setTrxHistoryResult(self.transactionsToItems(transactions, collectibles), address, wasFetchMore)
method setHistoryFetchState*(self: Module, addresses: seq[string], isFetching: bool) =
self.view.setHistoryFetchStateForAccounts(addresses, isFetching)
@ -168,7 +177,7 @@ method onUserAuthenticated*(self: Module, password: string) =
method transactionWasSent*(self: Module, result: string) =
self.view.transactionWasSent(result)
self.view.setPendingTx(self.transactionsToItems(self.controller.getPendingTransactions()))
self.view.setPendingTx(self.transactionsToItems(self.controller.getPendingTransactions(), @[]))
method suggestedFees*(self: Module, chainId: int): string =
return self.controller.suggestedFees(chainId)

View File

@ -3,6 +3,7 @@ import ../../../../global/global_singleton
import ../../../../../app_service/service/transaction/dto
import ../../../../../app_service/service/currency/dto as currency_dto
import ../../../../../app_service/service/collectible/dto as collectible_dto
import ../../../shared_models/currency_amount
import ../../../shared_models/currency_amount_utils
@ -49,3 +50,33 @@ proc transactionToItem*(t: TransactionDto, resolvedSymbol: string, tokenFormat:
hex2GweiCurrencyAmount(t.maxTotalFees, gweiFormat),
resolvedSymbol
)
proc transactionToNFTItem*(t: TransactionDto, c: CollectibleDto, ethFormat: CurrencyFormatDto, gweiFormat: CurrencyFormatDto): Item =
return initNFTItem(
t.id,
t.typeValue,
t.address,
t.blockNumber,
t.blockHash,
toInt(t.timestamp),
hex2EthCurrencyAmount(t.gasPrice, ethFormat),
parseInt(singletonInstance.utils.hex2Dec(t.gasLimit)),
parseInt(singletonInstance.utils.hex2Dec(t.gasUsed)),
t.nonce,
t.txStatus,
t.fromAddress,
t.to,
t.contract,
t.chainId,
hex2GweiCurrencyAmount(t.maxFeePerGas, gweiFormat),
hex2GweiCurrencyAmount(t.maxPriorityFeePerGas, gweiFormat),
t.input,
t.txHash,
t.multiTransactionID,
hex2GweiCurrencyAmount(t.baseGasFees, gweiFormat),
hex2GweiCurrencyAmount(t.totalFees, gweiFormat),
hex2GweiCurrencyAmount(t.maxTotalFees, gweiFormat),
t.tokenId,
c.name,
c.imageUrl
)

View File

@ -0,0 +1,3 @@
const ETH_TRANSACTION_TYPE* = "eth"
const ERC20_TRANSACTION_TYPE* = "erc20"
const ERC721_TRANSACTION_TYPE* = "erc721"

View File

@ -1,32 +1,32 @@
type
FetchCollectionsTaskArg = ref object of QObjectTaskArg
FetchOwnedCollectionsTaskArg = ref object of QObjectTaskArg
chainId*: int
address*: string
const fetchCollectionsTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchCollectionsTaskArg](argEncoded)
const fetchOwnedCollectionsTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchOwnedCollectionsTaskArg](argEncoded)
let output = %* {
"chainId": arg.chainId,
"address": arg.address,
"collections": ""
}
try:
let response = backend.getOpenseaCollectionsByOwner(arg.chainId, arg.address)
let response = collectibles.getOpenseaCollectionsByOwner(arg.chainId, arg.address)
output["collections"] = response.result
except Exception as e:
let errDesription = e.msg
error "error fetchCollectionsTaskArg: ", errDesription
error "error fetchOwnedCollectionsTaskArg: ", errDesription
arg.finish(output)
type
FetchCollectiblesTaskArg = ref object of QObjectTaskArg
FetchOwnedCollectiblesTaskArg = ref object of QObjectTaskArg
chainId*: int
address*: string
collectionSlug: string
limit: int
const fetchCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchCollectiblesTaskArg](argEncoded)
const fetchOwnedCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchOwnedCollectiblesTaskArg](argEncoded)
let output = %* {
"chainId": arg.chainId,
"address": arg.address,
@ -34,7 +34,27 @@ const fetchCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcal
"collectibles": ""
}
try:
let response = backend.getOpenseaAssetsByOwnerAndCollection(arg.chainId, arg.address, arg.collectionSlug, arg.limit)
let response = collectibles.getOpenseaAssetsByOwnerAndCollection(arg.chainId, arg.address, arg.collectionSlug, arg.limit)
output["collectibles"] = response.result
except Exception as e:
let errDesription = e.msg
error "error fetchOwnedCollectiblesTaskArg: ", errDesription
arg.finish(output)
type
FetchCollectiblesTaskArg = ref object of QObjectTaskArg
chainId*: int
ids*: seq[collectibles.NFTUniqueID]
limit: int
const fetchCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchCollectiblesTaskArg](argEncoded)
let output = %* {
"chainId": arg.chainId,
"collectibles": ""
}
try:
let response = collectibles.getOpenseaAssetsByNFTUniqueID(arg.chainId, arg.ids, arg.limit)
output["collectibles"] = response.result
except Exception as e:
let errDesription = e.msg

View File

@ -1,4 +1,4 @@
import json, Tables, strformat, strutils
import json, Tables, stint, strformat, strutils
type CollectibleTraitType* {.pure.} = enum
@ -11,7 +11,7 @@ type CollectionTrait* = ref object
type CollectionDto* = ref object
name*, slug*, imageUrl*: string
ownedAssetCount*: int
ownedAssetCount*: Uint256
trait*: Table[string, CollectionTrait]
type CollectibleTrait* = ref object
@ -19,9 +19,18 @@ type CollectibleTrait* = ref object
type CollectibleDto* = ref object
id*: int
tokenId*: Uint256
name*, description*, permalink*, imageThumbnailUrl*, imageUrl*, address*, backgroundColor*: string
properties*, rankings*, statistics*: seq[CollectibleTrait]
proc newCollectibleDto*: CollectibleDto =
return CollectibleDto(
id: -1
)
proc isValid*(self: CollectibleDto): bool =
return self.id >= 0
proc isNumeric(s: string): bool =
try:
discard s.parseFloat()
@ -33,7 +42,7 @@ proc `$`*(self: CollectionDto): string =
return fmt"CollectionDto(name:{self.name}, slug:{self.slug}, owned asset count:{self.ownedAssetCount})"
proc `$`*(self: CollectibleDto): string =
return fmt"CollectibleDto(id:{self.id}, name:{self.name}, description:{self.description}, permalink:{self.permalink}, address:{self.address}, imageUrl: {self.imageUrl}, imageThumbnailUrl: {self.imageThumbnailUrl}, backgroundColor: {self.backgroundColor})"
return fmt"CollectibleDto(id:{self.id}, token_id:{self.tokenId}, name:{self.name}, description:{self.description}, permalink:{self.permalink}, address:{self.address}, imageUrl: {self.imageUrl}, imageThumbnailUrl: {self.imageThumbnailUrl}, backgroundColor: {self.backgroundColor})"
proc getCollectionTraits*(jsonCollection: JsonNode): Table[string, CollectionTrait] =
var traitList: Table[string, CollectionTrait] = initTable[string, CollectionTrait]()
@ -46,7 +55,7 @@ proc toCollectionDto*(jsonCollection: JsonNode): CollectionDto =
name: jsonCollection{"name"}.getStr,
slug: jsonCollection{"slug"}.getStr,
imageUrl: jsonCollection{"image_url"}.getStr,
ownedAssetCount: jsonCollection{"owned_asset_count"}.getInt,
ownedAssetCount: stint.parse(jsonCollection{"owned_asset_count"}.getStr, Uint256),
trait: getCollectionTraits(jsonCollection)
)
@ -70,6 +79,7 @@ proc getTrait*(jsonAsset: JsonNode, traitType: CollectibleTraitType): seq[Collec
proc toCollectibleDto*(jsonAsset: JsonNode): CollectibleDto =
return CollectibleDto(
id: jsonAsset{"id"}.getInt,
tokenId: stint.parse(jsonAsset{"token_id"}.getStr, Uint256),
name: jsonAsset{"name"}.getStr,
description: jsonAsset{"description"}.getStr,
permalink: jsonAsset{"permalink"}.getStr,

View File

@ -1,11 +1,11 @@
import NimQml, Tables, chronicles, sequtils, json, sugar
import NimQml, Tables, chronicles, sequtils, json, sugar, stint, hashes
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import dto
import ../network/service as network_service
import ../../../backend/backend
import ../../../backend/collectibles as collectibles
include ../../common/json_utils
include async_tasks
@ -16,25 +16,35 @@ logScope:
topics = "collectible-service"
# Signals which may be emitted by this service:
const SIGNAL_COLLECTIONS_UPDATE_STARTED* = "collectionsUpdateStarted"
const SIGNAL_COLLECTIONS_UPDATED* = "collectionsUpdated"
const SIGNAL_COLLECTIBLES_UPDATE_STARTED* = "collectiblesUpdateStarted"
const SIGNAL_OWNED_COLLECTIONS_UPDATED* = "ownedCollectionsUpdated"
const SIGNAL_OWNED_COLLECTIBLES_UPDATED* = "ownedCollectiblesUpdated"
const SIGNAL_COLLECTIBLES_UPDATED* = "collectiblesUpdated"
# Maximum number of collectibles to be fetched at a time
const limit = 200
# Unique identifier for collectible in a specific chain
type
CollectionsUpdateArgs* = ref object of Args
UniqueID* = object
contractAddress*: string
tokenId*: UInt256
type
OwnedCollectionsUpdateArgs* = ref object of Args
chainId*: int
address*: string
type
CollectiblesUpdateArgs* = ref object of Args
OwnedCollectiblesUpdateArgs* = ref object of Args
chainId*: int
address*: string
collectionSlug*: string
type
CollectiblesUpdateArgs* = ref object of Args
chainId*: int
ids*: seq[UniqueID]
type
CollectionData* = ref object
collection*: CollectionDto
@ -63,13 +73,18 @@ type
type
ChainsData* = TableRef[int, AdressesData] # [chainId, AdressesData]
proc hash(x: UniqueID): Hash =
result = x.contractAddress.hash !& x.tokenId.hash
result = !$result
QtObject:
type
Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
networkService: network_service.Service
data: ChainsData
ownershipData: ChainsData
data: TableRef[int, TableRef[UniqueID, CollectibleDto]] # [chainId, [UniqueID, CollectibleDto]]
proc delete*(self: Service) =
self.QObject.delete
@ -84,22 +99,23 @@ QtObject:
result.events = events
result.threadpool = threadpool
result.networkService = networkService
result.data = newTable[int, AdressesData]()
result.ownershipData = newTable[int, AdressesData]()
result.data = newTable[int, TableRef[UniqueID, CollectibleDto]]()
proc init*(self: Service) =
discard
proc insertAddressIfNeeded*(self: Service, chainId: int, address: string) =
if not self.data.hasKey(chainId):
self.data[chainId] = newTable[string, CollectionsData]()
if not self.ownershipData.hasKey(chainId):
self.ownershipData[chainId] = newTable[string, CollectionsData]()
let chainData = self.data[chainId]
let chainData = self.ownershipData[chainId]
if not chainData.hasKey(address):
chainData[address] = newCollectionsData()
proc setCollections*(self: Service, chainId: int, address: string, collections: seq[CollectionDto]) =
proc updateOwnedCollectionsCache*(self: Service, chainId: int, address: string, collections: seq[CollectionDto]) =
try:
let oldAddressData = self.data[chainId][address]
let oldAddressData = self.ownershipData[chainId][address]
# Start with empty object. Only add newly received collections, so removed ones are discarded
let newAddressData = newCollectionsData()
@ -113,46 +129,105 @@ QtObject:
newCollection.collectibles = oldCollection.collectibles
newAddressData.collectionsLoaded = true
self.data[chainId][address] = newAddressData
self.ownershipData[chainId][address] = newAddressData
var data = OwnedCollectionsUpdateArgs()
data.chainId = chainId
data.address = address
self.events.emit(SIGNAL_OWNED_COLLECTIONS_UPDATED, data)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc setCollectibles*(self: Service, chainId: int, address: string, collectionSlug: string, collectibles: seq[CollectibleDto]) =
proc updateOwnedCollectiblesCache*(self: Service, chainId: int, address: string, collectionSlug: string, collectibles: seq[CollectibleDto]) =
try:
let collection = self.data[chainId][address].collections[collectionSlug]
let collection = self.ownershipData[chainId][address].collections[collectionSlug]
collection.collectibles.clear()
for collectible in collectibles:
collection.collectibles[collectible.id] = collectible
collection.collectiblesLoaded = true
var data = OwnedCollectiblesUpdateArgs()
data.chainId = chainId
data.address = address
data.collectionSlug = collectionSlug
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATED, data)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc getCollections*(self: Service, chainId: int, address: string) : CollectionsData =
proc updateCollectiblesCache*(self: Service, chainId: int, collectibles: seq[CollectibleDto]) =
if not self.data.hasKey(chainId):
self.data[chainId] = newTable[UniqueID, CollectibleDto]()
var data = CollectiblesUpdateArgs()
data.chainId = chainId
for collectible in collectibles:
let id = UniqueID(
contractAddress: collectible.address,
tokenId: collectible.tokenId
)
self.data[chainId][id] = collectible
data.ids.add(id)
self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data)
proc getOwnedCollections*(self: Service, chainId: int, address: string) : CollectionsData =
try:
return self.data[chainId][address]
return self.ownershipData[chainId][address]
except:
discard
return newCollectionsData()
proc getCollection*(self: Service, chainId: int, address: string, collectionSlug: string) : CollectionData =
proc getOwnedCollection*(self: Service, chainId: int, address: string, collectionSlug: string) : CollectionData =
try:
return self.data[chainId][address].collections[collectionSlug]
return self.ownershipData[chainId][address].collections[collectionSlug]
except:
discard
return newCollectionData(CollectionDto())
proc getCollectible*(self: Service, chainId: int, address: string, collectionSlug: string, collectibleId: int) : CollectibleDto =
proc getCollectible*(self: Service, chainId: int, id: UniqueID) : CollectibleDto =
try:
return self.data[chainId][address].collections[collectionSlug].collectibles[collectibleId]
return self.data[chainId][id]
except:
discard
return CollectibleDto()
return newCollectibleDto()
proc onRxCollections*(self: Service, response: string) {.slot.} =
var data = CollectionsUpdateArgs()
proc onRxCollectibles*(self: Service, response: string) {.slot.} =
try:
let responseObj = response.parseJson
if (responseObj.kind == JObject):
let chainIdJson = responseObj["chainId"]
let collectiblesJson = responseObj["collectibles"]
if (chainIdJson.kind == JInt and
collectiblesJson.kind == JArray):
let chainId = chainIdJson.getInt()
let collectibles = map(collectiblesJson.getElems(), proc(x: JsonNode): CollectibleDto = x.toCollectibleDto())
self.updateCollectiblesCache(chainId, collectibles)
except Exception as e:
let errDescription = e.msg
error "error onRxCollectibles: ", errDescription
proc fetchCollectibles*(self: Service, chainId: int, ids: seq[UniqueID]) =
let arg = FetchCollectiblesTaskArg(
tptr: cast[ByteAddress](fetchCollectiblesTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onRxCollectibles",
chainId: chainId,
ids: ids.map(id => collectibles.NFTUniqueID(
contractAddress: id.contractAddress,
tokenID: id.tokenId.toString()
)),
limit: limit
)
self.threadpool.start(arg)
proc onRxOwnedCollections*(self: Service, response: string) {.slot.} =
try:
let responseObj = response.parseJson
if (responseObj.kind == JObject):
@ -162,38 +237,33 @@ QtObject:
let validAccount = (chainIdJson.kind == JInt and
addressJson.kind == JString)
if (validAccount):
data.chainId = chainIdJson.getInt()
data.address = addressJson.getStr()
let chainId = chainIdJson.getInt()
let address = addressJson.getStr()
var collections: seq[CollectionDto]
let collectionsJson = responseObj["collections"]
if (collectionsJson.kind == JArray):
collections = map(collectionsJson.getElems(), proc(x: JsonNode): CollectionDto = x.toCollectionDto())
self.setCollections(data.chainId, data.address, collections)
self.events.emit(SIGNAL_COLLECTIONS_UPDATED, data)
self.updateOwnedCollectionsCache(chainId, address, collections)
except Exception as e:
let errDescription = e.msg
error "error onRxCollections: ", errDescription
error "error onRxOwnedCollections: ", errDescription
proc fetchCollections*(self: Service, chainId: int, address: string) =
proc fetchOwnedCollections*(self: Service, chainId: int, address: string) =
self.insertAddressIfNeeded(chainId, address)
var data = CollectionsUpdateArgs()
data.chainId = chainId
data.address = address
self.events.emit(SIGNAL_COLLECTIONS_UPDATE_STARTED, data)
let arg = FetchCollectionsTaskArg(
tptr: cast[ByteAddress](fetchCollectionsTaskArg),
let arg = FetchOwnedCollectionsTaskArg(
tptr: cast[ByteAddress](fetchOwnedCollectionsTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onRxCollections",
slot: "onRxOwnedCollections",
chainId: chainId,
address: address,
)
self.threadpool.start(arg)
proc onRxCollectibles*(self: Service, response: string) {.slot.} =
var data = CollectiblesUpdateArgs()
proc onRxOwnedCollectibles*(self: Service, response: string) {.slot.} =
var data = OwnedCollectiblesUpdateArgs()
try:
let responseObj = response.parseJson
if (responseObj.kind == JObject):
@ -205,38 +275,32 @@ QtObject:
addressJson.kind == JString and
collectionSlugJson.kind == JString)
if (validCollection):
data.chainId = chainIdJson.getInt()
data.address = addressJson.getStr()
data.collectionSlug = collectionSlugJson.getStr()
let chainId = chainIdJson.getInt()
let address = addressJson.getStr()
let collectionSlug = collectionSlugJson.getStr()
var collectibles: seq[CollectibleDto]
let collectiblesJson = responseObj["collectibles"]
if (collectiblesJson.kind == JArray):
collectibles = map(collectiblesJson.getElems(), proc(x: JsonNode): CollectibleDto = x.toCollectibleDto())
self.setCollectibles(data.chainId, data.address, data.collectionSlug, collectibles)
self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data)
self.updateOwnedCollectiblesCache(chainId, address, collectionSlug, collectibles)
self.updateCollectiblesCache(data.chainId, collectibles)
except Exception as e:
let errDescription = e.msg
error "error onRxCollectibles: ", errDescription
error "error onRxOwnedCollectibles: ", errDescription
proc fetchCollectibles*(self: Service, chainId: int, address: string, collectionSlug: string) =
proc fetchOwnedCollectibles*(self: Service, chainId: int, address: string, collectionSlug: string) =
self.insertAddressIfNeeded(chainId, address)
let collections = self.data[chainId][address].collections
let collections = self.ownershipData[chainId][address].collections
if not collections.hasKey(collectionSlug):
error "error fetchCollectibles: Attempting to fetch collectibles from unknown collection: ", collectionSlug
error "error fetchOwnedCollectibles: Attempting to fetch collectibles from unknown collection: ", collectionSlug
return
var data = CollectiblesUpdateArgs()
data.chainId = chainId
data.address = address
data.collectionSlug = collectionSlug
self.events.emit(SIGNAL_COLLECTIBLES_UPDATE_STARTED, data)
let arg = FetchCollectiblesTaskArg(
tptr: cast[ByteAddress](fetchCollectiblesTaskArg),
let arg = FetchOwnedCollectiblesTaskArg(
tptr: cast[ByteAddress](fetchOwnedCollectiblesTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onRxCollectibles",
slot: "onRxOwnedCollectibles",
chainId: chainId,
address: address,
collectionSlug: collectionSlug,
@ -244,10 +308,10 @@ QtObject:
)
self.threadpool.start(arg)
proc fetchAllCollectibles*(self: Service, chainId: int, address: string) =
proc fetchAllOwnedCollectibles*(self: Service, chainId: int, address: string) =
try:
for collectionSlug, _ in self.data[chainId][address].collections:
self.fetchCollectibles(chainId, address, collectionSlug)
for collectionSlug, _ in self.ownershipData[chainId][address].collections:
self.fetchOwnedCollectibles(chainId, address, collectionSlug)
except Exception as e:
let errDescription = e.msg
error "error fetchAllCollectibles: ", errDescription
error "error fetchAllOwnedCollectibles: ", errDescription

View File

@ -4,6 +4,7 @@
import stint
import ../../common/conversion as service_conversion
import ../../common/wallet_constants
proc sortAsc[T](t1, t2: T): int =
if (t1.fromNetwork.chainId > t2.fromNetwork.chainId): return 1
@ -16,21 +17,46 @@ type
address: string
toBlock: Uint256
limit: int
collectiblesLimit: int
loadMore: bool
allTxLoaded: bool
const loadTransactionsTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let
arg = decode[LoadTransactionsTaskArg](argEncoded)
limitAsHex = "0x" & eth_utils.stripLeadingZeros(arg.limit.toHex)
response = transactions.getTransfersByAddress(arg.chainId, arg.address, arg.toBlock, limitAsHex, arg.loadMore).result
output = %*{
let arg = decode[LoadTransactionsTaskArg](argEncoded)
let output = %* {
"address": arg.address,
"chainId": arg.chainId,
"history": response,
"history": "",
"collectibles": "",
"loadMore": arg.loadMore,
"allTxLoaded": response.getElems().len < arg.limit
"allTxLoaded": ""
}
try:
let limitAsHex = "0x" & eth_utils.stripLeadingZeros(arg.limit.toHex)
let response = transactions.getTransfersByAddress(arg.chainId, arg.address, arg.toBlock, limitAsHex, arg.loadMore).result
output["history"] = response
output["allTxLoaded"] = %(response.getElems().len < arg.limit)
# Fetch collectibles for transactions
var uniqueIds: seq[collectibles.NFTUniqueID] = @[]
for txJson in response.getElems():
let tx = txJson.toTransactionDto()
if tx.typeValue == ERC721_TRANSACTION_TYPE:
let nftId = collectibles.NFTUniqueID(
contractAddress: tx.contract,
tokenID: tx.tokenId.toString(10)
)
if not uniqueIds.any(x => (x == nftId)):
uniqueIds.add(nftId)
if len(uniqueIds) > 0:
let collectiblesResponse = collectibles.getOpenseaAssetsByNFTUniqueID(arg.chainId, uniqueIds, arg.collectiblesLimit).result
output["collectibles"] = collectiblesResponse
except Exception as e:
let errDesription = e.msg
error "error loadTransactionsTask: ", errDesription
arg.finish(output)

View File

@ -49,6 +49,7 @@ type
nonce*: string
txStatus*: string
value*: string
tokenId*: UInt256
fromAddress*: string
to*: string
chainId*: int
@ -77,6 +78,7 @@ proc getMaxTotalFees(maxFee: string, gasLimit: string): string =
proc toTransactionDto*(jsonObj: JsonNode): TransactionDto =
result = TransactionDto()
result.timestamp = stint.fromHex(UInt256, jsonObj{"timestamp"}.getStr)
result.tokenId = stint.fromHex(UInt256, jsonObj{"tokenId"}.getStr)
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("type", result.typeValue)
discard jsonObj.getProp("address", result.address)
@ -101,10 +103,42 @@ proc toTransactionDto*(jsonObj: JsonNode): TransactionDto =
result.totalFees = getTotalFees(result.maxPriorityFeePerGas, result.baseGasFees, result.gasUsed, result.maxFeePerGas)
result.maxTotalFees = getMaxTotalFees(result.maxFeePerGas, result.gasLimit)
proc `$`*(self: TransactionDto): string =
return fmt"""TransactionDto(
id:{self.id},
typeValue:{self.typeValue},
address:{self.address},
blockNumber:{self.blockNumber},
blockHash:{self.blockHash},
contract:{self.contract},
timestamp:{self.timestamp},
gasPrice:{self.gasPrice},
gasLimit:{self.gasLimit},
gasUsed:{self.gasUsed},
nonce:{self.nonce},
txStatus:{self.txStatus},
value:{self.value},
tokenId:{self.tokenId},
fromAddress:{self.fromAddress},
to:{self.to},
chainId:{self.chainId},
maxFeePerGas:{self.maxFeePerGas},
maxPriorityFeePerGas:{self.maxPriorityFeePerGas},
input:{self.input},
txHash:{self.txHash},
multiTransactionID:{self.multiTransactionID},
baseGasFees:{self.baseGasFees},
totalFees:{self.totalFees},
maxTotalFees:{self.maxTotalFees},
additionalData:{self.additionalData},
symbol:{self.symbol}
)"""
proc toPendingTransactionDto*(jsonObj: JsonNode): TransactionDto =
result = TransactionDto()
result.value = "0x" & toHex(toUInt256(parseFloat(jsonObj{"value"}.getStr)))
result.timestamp = u256(jsonObj{"timestamp"}.getInt)
result.tokenId = stint.fromHex(UInt256, jsonObj{"tokenId"}.getStr)
discard jsonObj.getProp("hash", result.txHash)
discard jsonObj.getProp("from", result.fromAddress)
discard jsonObj.getProp("to", result.to)

View File

@ -1,5 +1,6 @@
import Tables, NimQml, chronicles, sequtils, sugar, stint, strutils, json, strformat, algorithm, math, random
import ../../../backend/collectibles as collectibles
import ../../../backend/transactions as transactions
import ../../../backend/backend
import ../../../backend/eth
@ -16,6 +17,7 @@ import ../wallet_account/service as wallet_account_service
import ../network/service as network_service
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 ./dto as transaction_dto
@ -32,6 +34,9 @@ logScope:
include async_tasks
include ../../common/json_utils
# Maximum number of collectibles to be fetched at a time
const collectiblesLimit = 200
# Signals which may be emitted by this service:
const SIGNAL_TRANSACTIONS_LOADED* = "transactionsLoaded"
const SIGNAL_TRANSACTION_SENT* = "transactionSent"
@ -68,6 +73,7 @@ type
type
TransactionsLoadedArgs* = ref object of Args
transactions*: seq[TransactionDto]
collectibles*: seq[CollectibleDto]
address*: string
wasFetchMore*: bool
allTxLoaded*: bool
@ -200,16 +206,20 @@ QtObject:
self.watchTransaction(tx.txHash, tx.fromAddress, tx.to, tx.typeValue, tx.input, tx.chainId, track = false)
return pendingTransactions
proc setTrxHistoryResult*(self: Service, historyJSON: string) {.slot.} =
proc onTransactionsLoaded*(self: Service, historyJSON: string) {.slot.} =
let historyData = parseJson(historyJSON)
let address = historyData["address"].getStr
let chainID = historyData["chainId"].getInt
let wasFetchMore = historyData["loadMore"].getBool
let allTxLoaded = historyData["allTxLoaded"].getBool
var transactions: seq[TransactionDto] = @[]
var collectibles: seq[CollectibleDto] = @[]
for tx in historyData["history"].getElems():
transactions.add(tx.toTransactionDto())
for c in historyData["collectibles"].getElems():
collectibles.add(c.toCollectibleDto())
if self.allTxLoaded.hasKey(address):
self.allTxLoaded[address] = self.allTxLoaded[address] and allTxLoaded
else:
@ -218,6 +228,7 @@ QtObject:
# emit event
self.events.emit(SIGNAL_TRANSACTIONS_LOADED, TransactionsLoadedArgs(
transactions: transactions,
collectibles: collectibles,
address: address,
wasFetchMore: wasFetchMore
))
@ -244,9 +255,10 @@ QtObject:
address: address,
tptr: cast[ByteAddress](loadTransactionsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setTrxHistoryResult",
slot: "onTransactionsLoaded",
toBlock: toBlock,
limit: limit,
collectiblesLimit: collectiblesLimit,
loadMore: loadMore,
chainId: network.chainId,
)

View File

@ -62,16 +62,6 @@ type
rpc(clientVersion, "web3"):
discard
rpc(getOpenseaCollectionsByOwner, "wallet"):
chainId: int
address: string
rpc(getOpenseaAssetsByOwnerAndCollection, "wallet"):
chainId: int
address: string
collectionSlug: string
limit: int
rpc(getEthereumChains, "wallet"):
onlyEnabled: bool

View File

@ -0,0 +1,34 @@
import json, json_serialization, strformat
import ./core, ./response_type
from ./gen import rpc
type
NFTUniqueID* = ref object of RootObj
contractAddress* {.serializedFieldName("contract_address").}: string
tokenID* {.serializedFieldName("token_id").}: string
proc `$`*(self: NFTUniqueID): string =
return fmt"""NFTUniqueID(
contractAddress:{self.contractAddress},
tokenID:{self.tokenID}
)"""
proc `==`*(a, b: NFTUniqueID): bool =
result = a.contractAddress == b.contractAddress and
a.tokenID == b.tokenID
rpc(getOpenseaCollectionsByOwner, "wallet"):
chainId: int
address: string
rpc(getOpenseaAssetsByOwnerAndCollection, "wallet"):
chainId: int
address: string
collectionSlug: string
limit: int
rpc(getOpenseaAssetsByNFTUniqueID, "wallet"):
chainId: int
uniqueIds: seq[NFTUniqueID]
limit: int

View File

@ -181,8 +181,8 @@ QtObject {
return globalUtils.hex2Dec(value)
}
function fetchCollectibles(slug) {
walletSectionCollectibles.fetchCollectibles(slug)
function fetchOwnedCollectibles(slug) {
walletSectionCollectibles.fetchOwnedCollectibles(slug)
}
function getCollectionMaxValue(traitType, value, maxValue, collectionIndex) {

View File

@ -29,6 +29,7 @@ Item {
QtObject {
id: d
readonly property bool isIncoming: root.isTransactionValid ? root.transaction.to === currentAccount.address : false
readonly property bool isNFT: root.isTransactionValid ? root.transaction.isNFT : false
readonly property string savedAddressNameTo: root.isTransactionValid ? d.getNameForSavedWalletAddress(transaction.to) : ""
readonly property string savedAddressNameFrom: root.isTransactionValid ? d.getNameForSavedWalletAddress(transaction.from): ""
readonly property string from: root.isTransactionValid ? !!savedAddressNameFrom ? savedAddressNameFrom : Utils.compactAddress(transaction.from, 4): ""
@ -72,9 +73,9 @@ Item {
symbol: root.isTransactionValid ? transaction.symbol : ""
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
shortTimeStamp: root.isTransactionValid ? LocaleUtils.formatTime(transaction.timestamp * 1000, Locale.ShortFormat): ""
savedAddressName: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
title: d.isIncoming ? qsTr("Received %1 from %2").arg(RootStore.formatCurrencyAmount(cryptoValue, symbol)).arg(d.from) :
qsTr("Sent %1 to %2").arg(RootStore.formatCurrencyAmount(cryptoValue, symbol)).arg(d.to)
savedAddressNameTo: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
savedAddressNameFrom: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.from): ""
isSummary: false
sensor.enabled: false
color: Theme.palette.statusListItem.backgroundColor
state: "big"
@ -153,9 +154,9 @@ Item {
symbol: root.isTransactionValid ? transaction.symbol : ""
transferStatus: root.isTransactionValid ? RootStore.hex2Dec(transaction.txStatus): ""
shortTimeStamp: root.isTransactionValid ? LocaleUtils.formatTime(transaction.timestamp * 1000, Locale.ShortFormat): ""
savedAddressName: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
title: d.isIncoming ? qsTr("Received %1 from %2").arg(RootStore.formatCurrencyAmount(cryptoValue, symbol)).arg(d.from) :
qsTr("Sent %1 to %2").arg(RootStore.formatCurrencyAmount(cryptoValue, symbol)).arg(d.to)
savedAddressNameTo: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.to): ""
savedAddressNameFrom: root.isTransactionValid ? RootStore.getNameForSavedWalletAddress(transaction.from): ""
isSummary: false
sensor.enabled: false
color: Theme.palette.statusListItem.backgroundColor
border.width: 1
@ -185,6 +186,12 @@ Item {
primaryText: qsTr("Nonce")
secondaryText: root.isTransactionValid ? RootStore.hex2Dec(root.transaction.nonce) : ""
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("TokenID")
secondaryText: root.isTransactionValid ? root.transaction.tokenID : ""
visible: root.isTransactionValid && d.isNFT
}
}
}
}

View File

@ -27,16 +27,54 @@ StatusListItem {
property string networkColor
property string networkName
property string shortTimeStamp
property string savedAddressName
property string savedAddressNameTo
property string savedAddressNameFrom
property bool isSummary: false
readonly property bool isModelDataValid: modelData !== undefined && !!modelData
readonly property bool isNFT: isModelDataValid && modelData.isNFT
readonly property string name: isModelDataValid ?
root.isNFT ?
modelData.nftName ?
modelData.nftName :
"#" + modelData.tokenID :
root.isSummary ? root.symbol : RootStore.formatCurrencyAmount(cryptoValue, symbol) :
"N/A"
readonly property string image: isModelDataValid ?
root.isNFT ?
modelData.nftImageUrl ?
modelData.nftImageUrl :
"" :
root.symbol ?
Style.png("tokens/%1".arg(root.symbol)) :
"" :
""
readonly property string toAddress: !!savedAddressNameTo ?
savedAddressNameTo :
isModelDataValid ?
Utils.compactAddress(modelData.to, 4) :
""
readonly property string fromAddress: !!savedAddressNameFrom ?
savedAddressNameFrom :
isModelDataValid ?
Utils.compactAddress(modelData.from, 4) :
""
state: "normal"
asset.isImage: !loading
asset.name: root.symbol ? Style.png("tokens/%1".arg(root.symbol)) : ""
asset.name: root.image
asset.isLetterIdenticon: loading
title: modelData !== undefined && !!modelData ?
isIncoming ? qsTr("Receive %1").arg(root.symbol) : !!savedAddressName ?
qsTr("Send %1 to %2").arg(root.symbol).arg(savedAddressName) :
qsTr("Send %1 to %2").arg(root.symbol).arg(Utils.compactAddress(modelData.to, 4)): ""
title: root.isModelDataValid ?
isIncoming ?
isSummary ?
qsTr("Receive %1").arg(root.name) :
qsTr("Received %1 from %2").arg(root.name).arg(root.fromAddress):
isSummary ?
qsTr("Send %1 to %2").arg(root.name).arg(root.toAddress) :
qsTr("Sent %1 to %2").arg(root.name).arg(root.toAddress) :
""
subTitle: shortTimeStamp
inlineTagModel: 1
inlineTagDelegate: InformationTag {
@ -63,6 +101,7 @@ StatusListItem {
}
components: [
ColumnLayout {
visible: !root.isNFT
Row {
Layout.alignment: Qt.AlignRight
spacing: 4

View File

@ -110,7 +110,9 @@ ColumnLayout {
symbol: modelDataValid && !!modelData.symbol ? modelData.symbol : ""
transferStatus: modelDataValid ? RootStore.hex2Dec(modelData.txStatus) : ""
shortTimeStamp: modelDataValid ? LocaleUtils.formatTime(modelData.timestamp * 1000, Locale.ShortFormat) : ""
savedAddressName: modelDataValid ? RootStore.getNameForSavedWalletAddress(modelData.to) : ""
savedAddressNameTo: modelDataValid ? RootStore.getNameForSavedWalletAddress(modelData.to) : ""
savedAddressNameFrom: modelDataValid ? RootStore.getNameForSavedWalletAddress(modelData.from) : ""
isSummary: true
onClicked: launchTransactionDetail(modelData)
loading: modelDataValid ? modelData.loadingTransaction : false
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 1b24342a75d99b8e2601d7639295899ea75b6fb3
Subproject commit 3f3e8f8894dfd5c8a6124bce805d0bd708fb6433