mirror of
https://github.com/status-im/status-desktop.git
synced 2025-03-04 00:11:12 +00:00
feat(@desktop/wallet): add support for collectible "watched contracts". Re-fetch collectibles periodically.
Fixes #9856
This commit is contained in:
parent
240fbfab60
commit
842e4565bd
@ -30,11 +30,22 @@ proc newController*(
|
||||
proc delete*(self: Controller) =
|
||||
discard
|
||||
|
||||
proc refreshCollectibles*(self: Controller, chainId: int, address: string) =
|
||||
let collectibles = self.collectibleService.getOwnedCollectibles(chainId, address)
|
||||
self.delegate.refreshCollectibles(chainId, address, collectibles)
|
||||
proc refreshCollectibles(self: Controller, chainId: int, address: string) =
|
||||
let data = self.collectibleService.getOwnedCollectibles(chainId, address)
|
||||
if not data.anyLoaded or data.lastLoadWasFromStart:
|
||||
self.delegate.setCollectibles(chainId, address, data)
|
||||
else:
|
||||
self.delegate.appendCollectibles(chainId, address, data)
|
||||
|
||||
proc init*(self: Controller) =
|
||||
self.events.on(SIGNAL_OWNED_COLLECTIBLES_RESET) do(e:Args):
|
||||
let args = OwnedCollectiblesUpdateArgs(e)
|
||||
self.refreshCollectibles(args.chainId, args.address)
|
||||
|
||||
self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATE_STARTED) do(e:Args):
|
||||
let args = OwnedCollectiblesUpdateArgs(e)
|
||||
self.delegate.onFetchStarted(args.chainId, args.address)
|
||||
|
||||
self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED) do(e:Args):
|
||||
let args = OwnedCollectiblesUpdateArgs(e)
|
||||
self.refreshCollectibles(args.chainId, args.address)
|
||||
@ -45,11 +56,14 @@ proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_serv
|
||||
proc getNetwork*(self: Controller): network_service.NetworkDto =
|
||||
return self.networkService.getNetworkForCollectibles()
|
||||
|
||||
proc getOwnedCollectibles*(self: Controller, chainId: int, address: string): CollectiblesData =
|
||||
return self.collectibleService.getOwnedCollectibles(chainId, address)
|
||||
|
||||
proc resetOwnedCollectibles*(self: Controller, chainId: int, address: string) =
|
||||
self.collectibleService.resetOwnedCollectibles(chainId, address)
|
||||
|
||||
proc fetchOwnedCollectibles*(self: Controller, chainId: int, address: string, limit: int) =
|
||||
self.collectibleService.fetchOwnedCollectibles(chainId, address, limit)
|
||||
proc fetchOwnedCollectibles*(self: Controller, chainId: int, address: string) =
|
||||
self.collectibleService.fetchOwnedCollectibles(chainId, address)
|
||||
|
||||
proc getCollectible*(self: Controller, chainId: int, id: UniqueID) : CollectibleDto =
|
||||
self.collectibleService.getCollectible(chainId, id)
|
||||
|
@ -16,10 +16,16 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
|
||||
method switchAccount*(self: AccessInterface, accountIndex: int) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method fetchOwnedCollectibles*(self: AccessInterface, limit: int) {.base.} =
|
||||
method fetchOwnedCollectibles*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method refreshCollectibles*(self: AccessInterface, chainId: int, address: string, collectibles: CollectiblesData) {.base.} =
|
||||
method onFetchStarted*(self: AccessInterface, chainId: int, address: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method setCollectibles*(self: AccessInterface, chainId: int, address: string, data: CollectiblesData) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method appendCollectibles*(self: AccessInterface, chainId: int, address: string, data: CollectiblesData) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method viewDidLoad*(self: AccessInterface) {.base.} =
|
||||
|
@ -18,6 +18,7 @@ type
|
||||
collectionSlug: string
|
||||
collectionImageUrl: string
|
||||
isLoading: bool
|
||||
isPinned: bool
|
||||
|
||||
proc initItem*(
|
||||
id: int,
|
||||
@ -33,7 +34,8 @@ proc initItem*(
|
||||
stats: seq[CollectibleTrait],
|
||||
collectionName: string,
|
||||
collectionSlug: string,
|
||||
collectionImageUrl: string
|
||||
collectionImageUrl: string,
|
||||
isPinned: bool
|
||||
): Item =
|
||||
result.id = id
|
||||
result.address = address
|
||||
@ -50,9 +52,10 @@ proc initItem*(
|
||||
result.collectionSlug = collectionSlug
|
||||
result.collectionImageUrl = collectionImageUrl
|
||||
result.isLoading = false
|
||||
result.isPinned = isPinned
|
||||
|
||||
proc initItem*: Item =
|
||||
result = initItem(-1, "", u256(0), "", "", "transparent", "Collectibles", "", @[], @[], @[], "", "", "")
|
||||
result = initItem(-1, "", u256(0), "", "", "transparent", "Collectibles", "", @[], @[], @[], "", "", "", false)
|
||||
|
||||
proc initLoadingItem*: Item =
|
||||
result = initItem()
|
||||
@ -71,6 +74,8 @@ proc `$`*(self: Item): string =
|
||||
collectionName: {self.collectionName},
|
||||
collectionSlug: {self.collectionSlug},
|
||||
collectionImageUrl: {self.collectionImageUrl},
|
||||
isLoading: {self.isLoading},
|
||||
isPinned: {self.isPinned},
|
||||
]"""
|
||||
|
||||
proc getId*(self: Item): int =
|
||||
@ -117,3 +122,6 @@ proc getCollectionImageUrl*(self: Item): string =
|
||||
|
||||
proc getIsLoading*(self: Item): bool =
|
||||
return self.isLoading
|
||||
|
||||
proc getIsPinned*(self: Item): bool =
|
||||
return self.isPinned
|
||||
|
@ -19,9 +19,9 @@ type
|
||||
CollectionSlug
|
||||
CollectionImageUrl
|
||||
IsLoading
|
||||
IsPinned
|
||||
|
||||
# Maximum number of owned collectibles to be fetched at a time
|
||||
const ownedCollectiblesFetchLimit = 200
|
||||
const loadingItemsCount = 50
|
||||
|
||||
QtObject:
|
||||
type
|
||||
@ -64,7 +64,7 @@ QtObject:
|
||||
QtProperty[bool] isFetching:
|
||||
read = getIsFetching
|
||||
notify = isFetchingChanged
|
||||
proc setIsFetching(self: Model, value: bool) =
|
||||
proc setIsFetching*(self: Model, value: bool) =
|
||||
if value == self.isFetching:
|
||||
return
|
||||
if value:
|
||||
@ -89,11 +89,10 @@ QtObject:
|
||||
method canFetchMore*(self: Model, parent: QModelIndex): bool =
|
||||
return not self.allCollectiblesLoaded and not self.isFetching
|
||||
|
||||
proc requestFetch(self: Model, limit: int) {.signal.}
|
||||
proc requestFetch(self: Model) {.signal.}
|
||||
method fetchMore*(self: Model, parent: QModelIndex) =
|
||||
if not self.isFetching:
|
||||
self.setIsFetching(true)
|
||||
self.requestFetch(ownedCollectiblesFetchLimit)
|
||||
self.requestFetch()
|
||||
|
||||
method rowCount*(self: Model, index: QModelIndex = nil): int =
|
||||
return self.items.len
|
||||
@ -115,6 +114,7 @@ QtObject:
|
||||
CollectibleRole.CollectionSlug.int:"collectionSlug",
|
||||
CollectibleRole.CollectionImageUrl.int:"collectionImageUrl",
|
||||
CollectibleRole.IsLoading.int:"isLoading",
|
||||
CollectibleRole.IsPinned.int:"isPinned",
|
||||
}.toTable
|
||||
|
||||
method data(self: Model, index: QModelIndex, role: int): QVariant =
|
||||
@ -164,37 +164,42 @@ QtObject:
|
||||
result = newQVariant(item.getCollectionImageUrl())
|
||||
of CollectibleRole.IsLoading:
|
||||
result = newQVariant(item.getIsLoading())
|
||||
of CollectibleRole.IsPinned:
|
||||
result = newQVariant(item.getIsPinned())
|
||||
|
||||
proc addLoadingItems(self: Model) =
|
||||
let parentModelIndex = newQModelIndex()
|
||||
defer: parentModelIndex.delete
|
||||
|
||||
let loadingItem = initLoadingItem()
|
||||
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + ownedCollectiblesFetchLimit - 1)
|
||||
for i in 1..ownedCollectiblesFetchLimit:
|
||||
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + loadingItemsCount - 1)
|
||||
for i in 1..loadingItemsCount:
|
||||
self.items.add(loadingItem)
|
||||
self.endInsertRows()
|
||||
self.countChanged()
|
||||
|
||||
proc removeLoadingItems(self: Model) =
|
||||
let parentModelIndex = newQModelIndex()
|
||||
defer: parentModelIndex.delete
|
||||
|
||||
for i in 0 ..< self.items.len:
|
||||
if self.items[i].getIsLoading():
|
||||
self.beginRemoveRows(newQModelIndex(), i, self.items.len-1)
|
||||
self.items.delete(i, self.items.len-1)
|
||||
self.beginRemoveRows(parentModelIndex, i, i + loadingItemsCount - 1)
|
||||
self.items.delete(i, i + loadingItemsCount - 1)
|
||||
self.endRemoveRows()
|
||||
self.countChanged()
|
||||
return
|
||||
|
||||
proc setItems*(self: Model, items: seq[Item], append: bool) =
|
||||
self.setIsFetching(false)
|
||||
if append:
|
||||
let parentModelIndex = newQModelIndex()
|
||||
defer: parentModelIndex.delete
|
||||
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + items.len - 1)
|
||||
self.items = concat(self.items, items)
|
||||
self.endInsertRows()
|
||||
else:
|
||||
self.beginResetModel()
|
||||
self.items = items
|
||||
self.endResetModel()
|
||||
proc setItems*(self: Model, items: seq[Item]) =
|
||||
self.beginResetModel()
|
||||
self.items = items
|
||||
self.endResetModel()
|
||||
self.countChanged()
|
||||
|
||||
proc appendItems*(self: Model, items: seq[Item]) =
|
||||
let parentModelIndex = newQModelIndex()
|
||||
defer: parentModelIndex.delete
|
||||
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + items.len - 1)
|
||||
self.items = concat(self.items, items)
|
||||
self.endInsertRows()
|
||||
self.countChanged()
|
||||
|
@ -1,8 +1,8 @@
|
||||
import sequtils, sugar
|
||||
import sequtils, sugar, times
|
||||
import ../../../../../../app_service/service/collectible/dto
|
||||
import collectibles_item, collectible_trait_item
|
||||
|
||||
proc collectibleToItem*(c: CollectibleDto, co: CollectionDto) : Item =
|
||||
proc collectibleToItem*(c: CollectibleDto, co: CollectionDto, isPinned: bool = false) : Item =
|
||||
return initItem(
|
||||
c.id,
|
||||
c.address,
|
||||
@ -17,5 +17,6 @@ proc collectibleToItem*(c: CollectibleDto, co: CollectionDto) : Item =
|
||||
c.statistics.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)),
|
||||
co.name,
|
||||
co.slug,
|
||||
co.imageUrl
|
||||
co.imageUrl,
|
||||
isPinned
|
||||
)
|
||||
|
@ -73,8 +73,8 @@ method viewDidLoad*(self: Module) =
|
||||
method currentCollectibleModuleDidLoad*(self: Module) =
|
||||
self.checkIfModuleDidLoad()
|
||||
|
||||
method fetchOwnedCollectibles*(self: Module, limit: int) =
|
||||
self.controller.fetchOwnedCollectibles(self.chainId, self.address, limit)
|
||||
method fetchOwnedCollectibles*(self: Module) =
|
||||
self.controller.fetchOwnedCollectibles(self.chainId, self.address)
|
||||
|
||||
method switchAccount*(self: Module, accountIndex: int) =
|
||||
let network = self.controller.getNetwork()
|
||||
@ -83,30 +83,42 @@ method switchAccount*(self: Module, accountIndex: int) =
|
||||
self.chainId = network.chainId
|
||||
self.address = account.address
|
||||
|
||||
# TODO: Implement a way to reduce the number of full re-fetches. It could be only
|
||||
# when NFT activity was detected for the given account, or if a certain amount of
|
||||
# time has passed. For now, we fetch every time we select the account.
|
||||
self.controller.resetOwnedCollectibles(self.chainId, self.address)
|
||||
|
||||
self.controller.refreshCollectibles(self.chainId, self.address)
|
||||
|
||||
self.currentCollectibleModule.setCurrentAddress(network, self.address)
|
||||
|
||||
method refreshCollectibles*(self: Module, chainId: int, address: string, collectibles: CollectiblesData) =
|
||||
let data = self.controller.getOwnedCollectibles(self.chainId, self.address)
|
||||
|
||||
# Trigger a fetch the first time we switch to an account
|
||||
if not data.anyLoaded:
|
||||
self.controller.fetchOwnedCollectibles(self.chainId, self.address)
|
||||
|
||||
self.setCollectibles(self.chainId, self.address, data)
|
||||
|
||||
proc ownedCollectibleToItem(self: Module, oc: OwnedCollectible): Item =
|
||||
let c = self.controller.getCollectible(self.chainId, oc.id)
|
||||
let col = self.controller.getCollection(self.chainId, c.collectionSlug)
|
||||
return collectibleToItem(c, col, oc.isFromWatchedContract)
|
||||
|
||||
method onFetchStarted*(self: Module, chainId: int, address: string) =
|
||||
if self.chainId == chainId and self.address == address:
|
||||
var idsToAdd = newSeq[UniqueID]()
|
||||
let append = not collectibles.lastLoadWasFromStart
|
||||
self.view.setIsFetching(true)
|
||||
|
||||
var startIdx = 0
|
||||
if append:
|
||||
for i in collectibles.ids.len - collectibles.lastLoadCount ..< collectibles.ids.len:
|
||||
idsToAdd.add(collectibles.ids[i])
|
||||
else:
|
||||
idsToAdd = collectibles.ids
|
||||
method setCollectibles*(self: Module, chainId: int, address: string, data: CollectiblesData) =
|
||||
if self.chainId == chainId and self.address == address:
|
||||
self.view.setIsFetching(data.isFetching)
|
||||
var newCollectibles = data.collectibles.map(oc => self.ownedCollectibleToItem(oc))
|
||||
self.view.setCollectibles(newCollectibles)
|
||||
self.view.setAllLoaded(data.allLoaded)
|
||||
|
||||
var newCollectibles = idsToAdd.map(id => (block:
|
||||
let c = self.controller.getCollectible(self.chainId, id)
|
||||
let co = self.controller.getCollection(self.chainId, c.collectionSlug)
|
||||
return collectibleToItem(c, co)
|
||||
))
|
||||
self.view.setCollectibles(newCollectibles, append, collectibles.allLoaded)
|
||||
|
||||
method appendCollectibles*(self: Module, chainId: int, address: string, data: CollectiblesData) =
|
||||
if self.chainId == chainId and self.address == address:
|
||||
self.view.setIsFetching(data.isFetching)
|
||||
|
||||
var ownedCollectiblesToAdd = newSeq[OwnedCollectible]()
|
||||
for i in data.collectibles.len - data.lastLoadCount ..< data.collectibles.len:
|
||||
ownedCollectiblesToAdd.add(data.collectibles[i])
|
||||
|
||||
let newCollectibles = ownedCollectiblesToAdd.map(oc => self.ownedCollectibleToItem(oc))
|
||||
|
||||
self.view.appendCollectibles(newCollectibles)
|
||||
self.view.setAllLoaded(data.allLoaded)
|
||||
|
@ -19,7 +19,7 @@ QtObject:
|
||||
result.QObject.setup
|
||||
result.delegate = delegate
|
||||
result.model = newModel()
|
||||
signalConnect(result.model, "requestFetch(int)", result, "fetchMoreOwnedCollectibles(int)")
|
||||
signalConnect(result.model, "requestFetch()", result, "fetchMoreOwnedCollectibles()")
|
||||
|
||||
proc load*(self: View) =
|
||||
self.delegate.viewDidLoad()
|
||||
@ -31,9 +31,17 @@ QtObject:
|
||||
read = getModel
|
||||
notify = modelChanged
|
||||
|
||||
proc fetchMoreOwnedCollectibles*(self: View, limit: int) {.slot.} =
|
||||
self.delegate.fetchOwnedCollectibles(limit)
|
||||
proc fetchMoreOwnedCollectibles*(self: View) {.slot.} =
|
||||
self.delegate.fetchOwnedCollectibles()
|
||||
|
||||
proc setCollectibles*(self: View, collectibles: seq[Item], append: bool, allLoaded: bool) =
|
||||
self.model.setItems(collectibles, append)
|
||||
proc setIsFetching*(self: View, isFetching: bool) =
|
||||
self.model.setIsFetching(isFetching)
|
||||
|
||||
proc setAllLoaded*(self: View, allLoaded: bool) =
|
||||
self.model.setAllCollectiblesLoaded(allLoaded)
|
||||
|
||||
proc setCollectibles*(self: View, collectibles: seq[Item]) =
|
||||
self.model.setItems(collectibles)
|
||||
|
||||
proc appendCollectibles*(self: View, collectibles: seq[Item]) =
|
||||
self.model.appendItems(collectibles)
|
||||
|
@ -21,6 +21,30 @@ const fetchOwnedCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, n
|
||||
error "error fetchOwnedCollectiblesTaskArg: ", errDesription
|
||||
arg.finish(output)
|
||||
|
||||
type
|
||||
FetchOwnedCollectiblesFromContractAddressesTaskArg = ref object of QObjectTaskArg
|
||||
chainId*: int
|
||||
address*: string
|
||||
contractAddresses*: seq[string]
|
||||
cursor: string
|
||||
limit: int
|
||||
|
||||
const fetchOwnedCollectiblesFromContractAddressesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||
let arg = decode[FetchOwnedCollectiblesFromContractAddressesTaskArg](argEncoded)
|
||||
let output = %* {
|
||||
"chainId": arg.chainId,
|
||||
"address": arg.address,
|
||||
"cursor": arg.cursor,
|
||||
"collectibles": ""
|
||||
}
|
||||
try:
|
||||
let response = collectibles.getOpenseaAssetsByOwnerAndContractAddressWithCursor(arg.chainId, arg.address, arg.contractAddresses, arg.cursor, arg.limit)
|
||||
output["collectibles"] = response.result
|
||||
except Exception as e:
|
||||
let errDesription = e.msg
|
||||
error "error fetchOwnedCollectiblesFromContractAddressesTaskArg: ", errDesription
|
||||
arg.finish(output)
|
||||
|
||||
type
|
||||
FetchCollectiblesTaskArg = ref object of QObjectTaskArg
|
||||
chainId*: int
|
||||
|
@ -1,4 +1,4 @@
|
||||
import json, Tables, stint, strformat, strutils
|
||||
import json, Tables, stint, strformat, strutils, times
|
||||
|
||||
# Unique identifier for collectible on a specific chain
|
||||
type
|
||||
|
@ -1,5 +1,6 @@
|
||||
import NimQml, Tables, chronicles, sequtils, json, sugar, stint, hashes, strformat, times
|
||||
import NimQml, Tables, chronicles, sequtils, json, sugar, stint, hashes, strformat, times, strutils
|
||||
import ../../../app/core/eventemitter
|
||||
import ../../../app/core/signals/types
|
||||
import ../../../app/core/tasks/[qt, threadpool]
|
||||
|
||||
import dto
|
||||
@ -16,10 +17,17 @@ logScope:
|
||||
topics = "collectible-service"
|
||||
|
||||
# Signals which may be emitted by this service:
|
||||
const SIGNAL_OWNED_COLLECTIBLES_RESET* = "ownedCollectiblesReset"
|
||||
const SIGNAL_OWNED_COLLECTIBLES_UPDATE_STARTED* = "ownedCollectiblesUpdateStarted"
|
||||
const SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED* = "ownedCollectiblesUpdateFinished"
|
||||
const SIGNAL_OWNED_COLLECTIBLES_FROM_WATCHED_CONTRACTS_FETCHED* = "ownedCollectiblesFromWatchedContractsFetched"
|
||||
const SIGNAL_COLLECTIBLES_UPDATED* = "collectiblesUpdated"
|
||||
|
||||
const INVALID_TIMESTAMP* = fromUnix(0)
|
||||
|
||||
# Maximum number of owned collectibles to be fetched at a time
|
||||
const ownedCollectiblesFetchLimit = 200
|
||||
|
||||
type
|
||||
OwnedCollectiblesUpdateArgs* = ref object of Args
|
||||
chainId*: int
|
||||
@ -30,45 +38,77 @@ type
|
||||
chainId*: int
|
||||
ids*: seq[UniqueID]
|
||||
|
||||
type
|
||||
OwnedCollectible* = ref object of Args
|
||||
id*: UniqueID
|
||||
isFromWatchedContract*: bool
|
||||
|
||||
proc `$`*(self: OwnedCollectible): string =
|
||||
return fmt"""OwnedCollectible(
|
||||
id:{self.id},
|
||||
isFromWatchedContract:{self.isFromWatchedContract}
|
||||
)"""
|
||||
|
||||
type
|
||||
CollectiblesData* = ref object
|
||||
isFetching*: bool
|
||||
anyLoaded*: bool
|
||||
allLoaded*: bool
|
||||
lastLoadWasFromStart*: bool
|
||||
lastLoadFromStartTimestamp*: DateTime
|
||||
lastLoadCount*: int
|
||||
previousCursor*: string
|
||||
nextCursor*: string
|
||||
ids*: seq[UniqueID]
|
||||
collectibles*: seq[OwnedCollectible]
|
||||
collectiblesFromWatchedContracts: seq[OwnedCollectible]
|
||||
|
||||
proc newCollectiblesData*(): CollectiblesData =
|
||||
proc newCollectiblesData(): CollectiblesData =
|
||||
new(result)
|
||||
result.isFetching = false
|
||||
result.anyLoaded = false
|
||||
result.allLoaded = false
|
||||
result.lastLoadWasFromStart = false
|
||||
result.lastLoadFromStartTimestamp = now() - initDuration(weeks = 1)
|
||||
result.lastLoadFromStartTimestamp = INVALID_TIMESTAMP.utc()
|
||||
result.lastLoadCount = 0
|
||||
result.previousCursor = ""
|
||||
result.nextCursor = ""
|
||||
result.ids = @[]
|
||||
result.collectibles = @[]
|
||||
result.collectiblesFromWatchedContracts = @[]
|
||||
|
||||
proc `$`*(self: CollectiblesData): string =
|
||||
return fmt"""CollectiblesData(
|
||||
isFetching:{self.isFetching},
|
||||
anyLoaded:{self.anyLoaded},
|
||||
allLoaded:{self.allLoaded},
|
||||
lastLoadWasFromStart:{self.lastLoadWasFromStart},
|
||||
lastLoadFromStartTimestamp:{self.lastLoadFromStartTimestamp},
|
||||
lastLoadCount:{self.lastLoadCount},
|
||||
previousCursor:{self.previousCursor},
|
||||
nextCursor:{self.nextCursor},
|
||||
ids:{self.ids}
|
||||
collectibles:{self.collectibles}
|
||||
)"""
|
||||
|
||||
type
|
||||
AdressesData = TableRef[string, CollectiblesData] # [address, CollectiblesData]
|
||||
OwnershipData* = ref object
|
||||
data*: CollectiblesData
|
||||
watchedContractAddresses*: seq[string]
|
||||
|
||||
proc newOwnershipData(): OwnershipData =
|
||||
new(result)
|
||||
result.data = newCollectiblesData()
|
||||
result.watchedContractAddresses = @[]
|
||||
|
||||
type
|
||||
ChainsData = TableRef[int, AdressesData] # [chainId, AdressesData]
|
||||
AddressesData = TableRef[string, OwnershipData] # [address, OwnershipData]
|
||||
|
||||
proc newAddressesData(): AddressesData =
|
||||
result = newTable[string, OwnershipData]()
|
||||
|
||||
type
|
||||
ChainsData = TableRef[int, AddressesData] # [chainId, AddressesData]
|
||||
|
||||
proc newChainsData(): ChainsData =
|
||||
result = newTable[int, AddressesData]()
|
||||
|
||||
type
|
||||
CollectiblesResult = tuple[success: bool, collectibles: seq[CollectibleDto], collections: seq[CollectionDto], previousCursor: string, nextCursor: string]
|
||||
@ -83,10 +123,14 @@ QtObject:
|
||||
events: EventEmitter
|
||||
threadpool: ThreadPool
|
||||
networkService: network_service.Service
|
||||
ownershipData: ChainsData
|
||||
accountsOwnershipData: ChainsData
|
||||
collectibles: TableRef[int, TableRef[UniqueID, CollectibleDto]] # [chainId, [UniqueID, CollectibleDto]]
|
||||
collections: TableRef[int, TableRef[string, CollectionDto]] # [chainId, [slug, CollectionDto]]
|
||||
|
||||
# Forward declarations
|
||||
proc resetOwnedCollectibles*(self: Service, chainId: int, address: string)
|
||||
proc resetAllOwnedCollectibles*(self: Service)
|
||||
|
||||
proc delete*(self: Service) =
|
||||
self.QObject.delete
|
||||
|
||||
@ -100,33 +144,49 @@ QtObject:
|
||||
result.events = events
|
||||
result.threadpool = threadpool
|
||||
result.networkService = networkService
|
||||
result.ownershipData = newTable[int, AdressesData]()
|
||||
result.accountsOwnershipData = newChainsData()
|
||||
result.collectibles = newTable[int, TableRef[UniqueID, CollectibleDto]]()
|
||||
result.collections = newTable[int, TableRef[string, CollectionDto]]()
|
||||
|
||||
proc init*(self: Service) =
|
||||
discard
|
||||
self.events.on(SignalType.Wallet.event) do(e:Args):
|
||||
var data = WalletSignal(e)
|
||||
case data.eventType:
|
||||
of "wallet-tick-reload":
|
||||
self.resetAllOwnedCollectibles()
|
||||
|
||||
proc prepareOwnershipData(self: Service, chainId: int, address: string, reset: bool = false) =
|
||||
if not self.ownershipData.hasKey(chainId):
|
||||
self.ownershipData[chainId] = newTable[string, CollectiblesData]()
|
||||
proc prepareOwnershipData(self: Service, chainId: int, address: string) =
|
||||
if not self.accountsOwnershipData.hasKey(chainId):
|
||||
self.accountsOwnershipData[chainId] = newAddressesData()
|
||||
|
||||
let chainData = self.ownershipData[chainId]
|
||||
if reset or not chainData.hasKey(address):
|
||||
chainData[address] = newCollectiblesData()
|
||||
let chainData = self.accountsOwnershipData[chainId]
|
||||
if not chainData.hasKey(address):
|
||||
chainData[address] = newOwnershipData()
|
||||
|
||||
let addressData = self.ownershipData[chainId][address]
|
||||
addressData.lastLoadWasFromStart = reset
|
||||
if reset:
|
||||
addressData.lastLoadFromStartTimestamp = now()
|
||||
|
||||
|
||||
proc updateOwnedCollectibles(self: Service, chainId: int, address: string, previousCursor: string, nextCursor: string, collectibles: seq[CollectibleDto]) =
|
||||
proc updateOwnedCollectibles(self: Service, chainId: int, address: string, previousCursor: string, nextCursor: string, collectibles: seq[CollectibleDto], isFromWatchedContract: bool) =
|
||||
let ownershipData = self.accountsOwnershipData[chainId][address]
|
||||
let collectiblesData = ownershipData.data
|
||||
try:
|
||||
let collectiblesData = self.ownershipData[chainId][address]
|
||||
collectiblesData.previousCursor = previousCursor
|
||||
collectiblesData.nextCursor = nextCursor
|
||||
collectiblesData.allLoaded = (nextCursor == "")
|
||||
if not (collectiblesData.nextCursor == previousCursor):
|
||||
# Async response from an old fetch request, disregard
|
||||
return
|
||||
if not collectiblesData.anyLoaded:
|
||||
collectiblesData.lastLoadWasFromStart = true
|
||||
collectiblesData.lastLoadFromStartTimestamp = now()
|
||||
else:
|
||||
collectiblesData.lastLoadWasFromStart = false
|
||||
|
||||
collectiblesData.anyLoaded = true
|
||||
|
||||
if isFromWatchedContract:
|
||||
# All fetched in one go, ignore cursors
|
||||
collectiblesData.previousCursor = ""
|
||||
collectiblesData.nextCursor = ""
|
||||
collectiblesData.allLoaded = false
|
||||
else:
|
||||
collectiblesData.previousCursor = previousCursor
|
||||
collectiblesData.nextCursor = nextCursor
|
||||
collectiblesData.allLoaded = (nextCursor == "")
|
||||
|
||||
var count = 0
|
||||
for collectible in collectibles:
|
||||
@ -134,8 +194,15 @@ QtObject:
|
||||
contractAddress: collectible.address,
|
||||
tokenId: collectible.tokenId
|
||||
)
|
||||
if not collectiblesData.ids.any(id => newId == id):
|
||||
collectiblesData.ids.add(newId)
|
||||
if not collectiblesData.collectibles.any(c => newId == c.id):
|
||||
let ownedCollectible = OwnedCollectible(
|
||||
id: newId,
|
||||
isFromWatchedContract: isFromWatchedContract
|
||||
)
|
||||
collectiblesData.collectibles.add(ownedCollectible)
|
||||
if isFromWatchedContract:
|
||||
collectiblesData.collectiblesFromWatchedContracts.add(ownedCollectible)
|
||||
|
||||
count = count + 1
|
||||
collectiblesData.lastLoadCount = count
|
||||
except Exception as e:
|
||||
@ -156,7 +223,6 @@ QtObject:
|
||||
let slug = collection.slug
|
||||
self.collections[chainId][slug] = collection
|
||||
|
||||
|
||||
for collectible in collectibles:
|
||||
let id = UniqueID(
|
||||
contractAddress: collectible.address,
|
||||
@ -167,12 +233,15 @@ QtObject:
|
||||
|
||||
self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data)
|
||||
|
||||
proc setWatchedContracts*(self: Service, chainId: int, address: string, contractAddresses: seq[string]) =
|
||||
self.prepareOwnershipData(chainId, address)
|
||||
self.accountsOwnershipData[chainId][address].watchedContractAddresses = contractAddresses
|
||||
# Re-fetch
|
||||
self.resetOwnedCollectibles(chainId, address)
|
||||
|
||||
proc getOwnedCollectibles*(self: Service, chainId: int, address: string) : CollectiblesData =
|
||||
try:
|
||||
return self.ownershipData[chainId][address]
|
||||
except:
|
||||
discard
|
||||
return newCollectiblesData()
|
||||
self.prepareOwnershipData(chainId, address)
|
||||
return self.accountsOwnershipData[chainId][address].data
|
||||
|
||||
proc getCollectible*(self: Service, chainId: int, id: UniqueID) : CollectibleDto =
|
||||
try:
|
||||
@ -238,7 +307,7 @@ QtObject:
|
||||
limit: len(ids)
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
|
||||
proc onRxOwnedCollectibles(self: Service, response: string) {.slot.} =
|
||||
var data = OwnedCollectiblesUpdateArgs()
|
||||
try:
|
||||
@ -250,27 +319,80 @@ QtObject:
|
||||
addressJson.kind == JString):
|
||||
data.chainId = chainIdJson.getInt()
|
||||
data.address = addressJson.getStr()
|
||||
self.ownershipData[data.chainId][data.address].isFetching = false
|
||||
let collectiblesData = self.accountsOwnershipData[data.chainId][data.address].data
|
||||
collectiblesData.isFetching = false
|
||||
let (success, collectibles, collections, prevCursor, nextCursor) = processCollectiblesResult(responseObj)
|
||||
if success:
|
||||
self.updateCollectiblesCache(data.chainId, collectibles, collections)
|
||||
self.updateOwnedCollectibles(data.chainId, data.address, prevCursor, nextCursor, collectibles)
|
||||
self.updateOwnedCollectibles(data.chainId, data.address, prevCursor, nextCursor, collectibles, false)
|
||||
except Exception as e:
|
||||
let errDescription = e.msg
|
||||
error "error onRxOwnedCollectibles: ", errDescription
|
||||
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED, data)
|
||||
|
||||
proc resetOwnedCollectibles*(self: Service, chainId: int, address: string) =
|
||||
self.prepareOwnershipData(chainId, address, true)
|
||||
proc fetchNextOwnedCollectiblesChunk(self: Service, chainId: int, address: string, limit: int = ownedCollectiblesFetchLimit) =
|
||||
self.prepareOwnershipData(chainId, address)
|
||||
|
||||
let ownershipData = self.accountsOwnershipData[chainId][address]
|
||||
let collectiblesData = ownershipData.data
|
||||
|
||||
var cursor = collectiblesData.nextCursor
|
||||
|
||||
let arg = FetchOwnedCollectiblesTaskArg(
|
||||
tptr: cast[ByteAddress](fetchOwnedCollectiblesTaskArg),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onRxOwnedCollectibles",
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
cursor: cursor,
|
||||
limit: limit
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
proc onRxOwnedCollectiblesFromWatchedContractAddresses(self: Service, response: string) {.slot.} =
|
||||
var data = OwnedCollectiblesUpdateArgs()
|
||||
data.chainId = chainId
|
||||
data.address = address
|
||||
try:
|
||||
let responseObj = response.parseJson
|
||||
if (responseObj.kind == JObject):
|
||||
let chainIdJson = responseObj["chainId"]
|
||||
let addressJson = responseObj["address"]
|
||||
if (chainIdJson.kind == JInt and
|
||||
addressJson.kind == JString):
|
||||
data.chainId = chainIdJson.getInt()
|
||||
data.address = addressJson.getStr()
|
||||
let collectiblesData = self.accountsOwnershipData[data.chainId][data.address].data
|
||||
collectiblesData.isFetching = false
|
||||
let (success, collectibles, collections, prevCursor, nextCursor) = processCollectiblesResult(responseObj)
|
||||
if success:
|
||||
self.updateCollectiblesCache(data.chainId, collectibles, collections)
|
||||
self.updateOwnedCollectibles(data.chainId, data.address, prevCursor, nextCursor, collectibles, true)
|
||||
except Exception as e:
|
||||
let errDescription = e.msg
|
||||
error "error onRxOwnedCollectiblesFromWatchedContractAddresses: ", errDescription
|
||||
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_FROM_WATCHED_CONTRACTS_FETCHED, data)
|
||||
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED, data)
|
||||
|
||||
proc fetchOwnedCollectibles*(self: Service, chainId: int, address: string, limit: int) =
|
||||
self.prepareOwnershipData(chainId, address, false)
|
||||
proc fetchOwnedCollectiblesFromWatchedContracts(self: Service, chainId: int, address: string) =
|
||||
let watchedContractAddresses = self.accountsOwnershipData[chainId][address].watchedContractAddresses
|
||||
|
||||
let collectiblesData = self.ownershipData[chainId][address]
|
||||
let arg = FetchOwnedCollectiblesFromContractAddressesTaskArg(
|
||||
tptr: cast[ByteAddress](fetchOwnedCollectiblesFromContractAddressesTaskArg),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onRxOwnedCollectiblesFromWatchedContractAddresses",
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
contractAddresses: watchedContractAddresses,
|
||||
cursor: "", # Always fetch from the beginning
|
||||
limit: 0 # Always fetch the complete list
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
|
||||
proc fetchOwnedCollectibles*(self: Service, chainId: int, address: string, limit: int = ownedCollectiblesFetchLimit) =
|
||||
self.prepareOwnershipData(chainId, address)
|
||||
|
||||
let ownershipData = self.accountsOwnershipData[chainId][address]
|
||||
let watchedContractAddresses = ownershipData.watchedContractAddresses
|
||||
let collectiblesData = ownershipData.data
|
||||
|
||||
if collectiblesData.isFetching:
|
||||
return
|
||||
@ -279,19 +401,31 @@ QtObject:
|
||||
return
|
||||
|
||||
collectiblesData.isFetching = true
|
||||
|
||||
var data = OwnedCollectiblesUpdateArgs()
|
||||
data.chainId = chainId
|
||||
data.address = address
|
||||
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATE_STARTED, data)
|
||||
|
||||
let arg = FetchOwnedCollectiblesTaskArg(
|
||||
tptr: cast[ByteAddress](fetchOwnedCollectiblesTaskArg),
|
||||
vptr: cast[ByteAddress](self.vptr),
|
||||
slot: "onRxOwnedCollectibles",
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
cursor: collectiblesData.nextCursor,
|
||||
limit: limit
|
||||
)
|
||||
self.threadpool.start(arg)
|
||||
# Full list of collectibles from watched contracts always get loaded first
|
||||
if not collectiblesData.anyLoaded and len(watchedContractAddresses) > 0:
|
||||
self.fetchOwnedCollectiblesFromWatchedContracts(chainId, address)
|
||||
else:
|
||||
self.fetchNextOwnedCollectiblesChunk(chainId, address)
|
||||
|
||||
proc resetOwnedCollectibles*(self: Service, chainId: int, address: string) =
|
||||
self.prepareOwnershipData(chainId, address)
|
||||
|
||||
let ownershipData = self.accountsOwnershipData[chainId][address]
|
||||
ownershipData.data = newCollectiblesData()
|
||||
|
||||
var data = OwnedCollectiblesUpdateArgs()
|
||||
data.chainId = chainId
|
||||
data.address = address
|
||||
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_RESET, data)
|
||||
|
||||
self.fetchOwnedCollectibles(chainId, address)
|
||||
|
||||
proc resetAllOwnedCollectibles*(self: Service) =
|
||||
for chainId, addressesData in self.accountsOwnershipData:
|
||||
for address, _ in addressesData:
|
||||
self.resetOwnedCollectibles(chainId, address)
|
@ -24,6 +24,13 @@ rpc(getOpenseaAssetsByOwnerWithCursor, "wallet"):
|
||||
cursor: string
|
||||
limit: int
|
||||
|
||||
rpc(getOpenseaAssetsByOwnerAndContractAddressWithCursor, "wallet"):
|
||||
chainId: int
|
||||
address: string
|
||||
contractAddresses: seq[string]
|
||||
cursor: string
|
||||
limit: int
|
||||
|
||||
rpc(getOpenseaAssetsByNFTUniqueID, "wallet"):
|
||||
chainId: int
|
||||
uniqueIds: seq[NFTUniqueID]
|
||||
|
2
vendor/status-go
vendored
2
vendor/status-go
vendored
@ -1 +1 @@
|
||||
Subproject commit 1453f5a0e3a2481a893ed7a233f40137eec9e9a1
|
||||
Subproject commit bd82250cf775e2f0cfd6acb37eb04349c52bd8a4
|
Loading…
x
Reference in New Issue
Block a user