feat(@desktop/wallet): add support for collectible "watched contracts". Re-fetch collectibles periodically.

Fixes #9856
This commit is contained in:
Dario Gabriel Lipicar 2023-03-17 18:24:52 -03:00 committed by dlipicar
parent 240fbfab60
commit 842e4565bd
12 changed files with 341 additions and 122 deletions

View File

@ -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)

View File

@ -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.} =

View File

@ -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

View File

@ -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()

View File

@ -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
)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

@ -1 +1 @@
Subproject commit 1453f5a0e3a2481a893ed7a233f40137eec9e9a1
Subproject commit bd82250cf775e2f0cfd6acb37eb04349c52bd8a4