feat(@desktop/communities): Adding token owners model

- replace qml owners model with Nim one
- get token owners from wallet service
- keeping owners cache in community_tokens/service and refresh every 10 minutes

Issue #10254
This commit is contained in:
Michal Iskierko 2023-04-13 10:09:06 +02:00 committed by Jonathan Rainville
parent cc191ae0e4
commit 2087616b82
20 changed files with 327 additions and 111 deletions

View File

@ -247,5 +247,5 @@ proc requestCancelDiscordCommunityImport*(self: Controller, id: string) =
proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] = proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] =
self.communityTokensService.getCommunityTokens(communityId) self.communityTokensService.getCommunityTokens(communityId)
proc getNetworks*(self:Controller): seq[NetworkDto] = proc getNetwork*(self:Controller, chainId: int): NetworkDto =
self.networksService.getNetworks() self.networksService.getNetwork(chainId)

View File

@ -23,7 +23,6 @@ import ../../../../app_service/service/network/service as networks_service
import ../../../../app_service/service/transaction/service as transaction_service import ../../../../app_service/service/transaction/service as transaction_service
import ../../../../app_service/service/community_tokens/service as community_tokens_service import ../../../../app_service/service/community_tokens/service as community_tokens_service
import ../../../../app_service/service/chat/dto/chat import ../../../../app_service/service/chat/dto/chat
import ./tokens/models/token_item
import ./tokens/module as community_tokens_module import ./tokens/module as community_tokens_module
export io_interface export io_interface
@ -132,16 +131,6 @@ proc createMemberItem(self: Module, memberId, requestId: string): MemberItem =
isVerified = contactDetails.details.isContactVerified(), isVerified = contactDetails.details.isContactVerified(),
requestToJoinId = requestId) requestToJoinId = requestId)
proc createTokenItem(self: Module, tokenDto: CommunityTokenDto) : TokenItem =
var chainName, chainIcon: string
let networks = self.controller.getNetworks()
for network in networks:
if network.chainId == tokenDto.chainId:
chainName = network.chainName
chainIcon = network.iconURL
break
result = initTokenItem(tokenDto, chainName, chainIcon)
method getCommunityItem(self: Module, c: CommunityDto): SectionItem = method getCommunityItem(self: Module, c: CommunityDto): SectionItem =
return initItem( return initItem(
c.id, c.id,
@ -179,8 +168,7 @@ method getCommunityItem(self: Module, c: CommunityDto): SectionItem =
declinedMemberRequests = c.declinedRequestsToJoin.map(proc(requestDto: CommunityMembershipRequestDto): MemberItem = declinedMemberRequests = c.declinedRequestsToJoin.map(proc(requestDto: CommunityMembershipRequestDto): MemberItem =
result = self.createMemberItem(requestDto.publicKey, requestDto.id)), result = self.createMemberItem(requestDto.publicKey, requestDto.id)),
encrypted = c.encrypted, encrypted = c.encrypted,
communityTokens = self.controller.getCommunityTokens(c.id).map(proc(tokenDto: CommunityTokenDto): TokenItem = communityTokens = @[]
result = self.createTokenItem(tokenDto))
) )
proc getCuratedCommunityItem(self: Module, c: CommunityDto): CuratedCommunityItem = proc getCuratedCommunityItem(self: Module, c: CommunityDto): CuratedCommunityItem =

View File

@ -1,5 +1,10 @@
import strformat import strformat, sequtils
import ../../../../../../app_service/service/community_tokens/dto/community_token import ../../../../../../app_service/service/community_tokens/dto/community_token
import ../../../../../../app_service/service/collectible/dto
import ../../../../../../app_service/service/network/dto
import token_owners_model
import token_owners_item
export community_token export community_token
@ -8,20 +13,28 @@ type
tokenDto*: CommunityTokenDto tokenDto*: CommunityTokenDto
chainName*: string chainName*: string
chainIcon*: string chainIcon*: string
tokenOwnersModel*: token_owners_model.TokenOwnersModel
proc initTokenItem*( proc initTokenItem*(
tokenDto: CommunityTokenDto, tokenDto: CommunityTokenDto,
chainName: string, network: NetworkDto,
chainIcon: string, tokenOwners: seq[CollectibleOwner]
): TokenItem = ): TokenItem =
result.tokenDto = tokenDto result.tokenDto = tokenDto
result.chainName = chainName if network != nil:
result.chainIcon = chainIcon result.chainName = network.chainName
result.chainIcon = network.iconURL
result.tokenOwnersModel = newTokenOwnersModel()
result.tokenOwnersModel.setItems(tokenOwners.map(proc(owner: CollectibleOwner): TokenOwnersItem =
# TODO find member with the address - later when airdrop to member will be added
result = initTokenOwnersItem("", "", owner)
))
proc `$`*(self: TokenItem): string = proc `$`*(self: TokenItem): string =
result = fmt"""TokenItem( result = fmt"""TokenItem(
tokenDto: {self.tokenDto}, tokenDto: {self.tokenDto},
chainName: {self.chainName}, chainName: {self.chainName},
chainIcon: {self.chainIcon} chainIcon: {self.chainIcon},
tokenOwnersModel: {self.tokenOwnersModel}
]""" ]"""

View File

@ -1,6 +1,9 @@
import NimQml, Tables, strformat import NimQml, Tables, strformat, sequtils
import token_item import token_item
import token_owners_item
import token_owners_model
import ../../../../../../app_service/service/community_tokens/dto/community_token import ../../../../../../app_service/service/community_tokens/dto/community_token
import ../../../../../../app_service/service/collectible/dto
type type
ModelRole {.pure.} = enum ModelRole {.pure.} = enum
@ -18,6 +21,7 @@ type
Image Image
ChainName ChainName
ChainIcon ChainIcon
TokenOwnersModel
QtObject: QtObject:
type TokenModel* = ref object of QAbstractListModel type TokenModel* = ref object of QAbstractListModel
@ -34,14 +38,25 @@ QtObject:
new(result, delete) new(result, delete)
result.setup result.setup
proc updateDeployState*(self: TokenModel, contractAddress: string, deployState: DeployState) = proc updateDeployState*(self: TokenModel, chainId: int, contractAddress: string, deployState: DeployState) =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:
if(self.items[i].tokenDto.address == contractAddress): if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)):
self.items[i].tokenDto.deployState = deployState self.items[i].tokenDto.deployState = deployState
let index = self.createIndex(i, 0, nil) let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[ModelRole.DeployState.int]) self.dataChanged(index, index, @[ModelRole.DeployState.int])
return return
proc setCommunityTokenOwners*(self: TokenModel, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) =
for i in 0 ..< self.items.len:
if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)):
self.items[i].tokenOwnersModel.setItems(owners.map(proc(owner: CollectibleOwner): TokenOwnersItem =
# TODO find member with the address - later when airdrop to member will be added
result = initTokenOwnersItem("", "", owner)
))
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[ModelRole.TokenOwnersModel.int])
return
proc countChanged(self: TokenModel) {.signal.} proc countChanged(self: TokenModel) {.signal.}
proc setItems*(self: TokenModel, items: seq[TokenItem]) = proc setItems*(self: TokenModel, items: seq[TokenItem]) =
@ -85,6 +100,7 @@ QtObject:
ModelRole.Image.int:"image", ModelRole.Image.int:"image",
ModelRole.ChainName.int:"chainName", ModelRole.ChainName.int:"chainName",
ModelRole.ChainIcon.int:"chainIcon", ModelRole.ChainIcon.int:"chainIcon",
ModelRole.TokenOwnersModel.int:"tokenOwnersModel",
}.toTable }.toTable
method data(self: TokenModel, index: QModelIndex, role: int): QVariant = method data(self: TokenModel, index: QModelIndex, role: int): QVariant =
@ -123,6 +139,8 @@ QtObject:
result = newQVariant(item.chainName) result = newQVariant(item.chainName)
of ModelRole.ChainIcon: of ModelRole.ChainIcon:
result = newQVariant(item.chainIcon) result = newQVariant(item.chainIcon)
of ModelRole.TokenOwnersModel:
result = newQVariant(item.tokenOwnersModel)
proc `$`*(self: TokenModel): string = proc `$`*(self: TokenModel): string =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:

View File

@ -0,0 +1,28 @@
import strformat, stint
import ../../../../../../app_service/service/collectible/dto
type
TokenOwnersItem* = object
name*: string
imageSource*: string
ownerDetails*: CollectibleOwner
amount*: int
proc initTokenOwnersItem*(
name: string,
imageSource: string,
ownerDetails: CollectibleOwner
): TokenOwnersItem =
result.name = name
result.imageSource = imageSource
result.ownerDetails = ownerDetails
for balance in ownerDetails.balances:
result.amount = result.amount + balance.balance.truncate(int)
proc `$`*(self: TokenOwnersItem): string =
result = fmt"""TokenOwnersItem(
name: {self.name},
amount: {self.amount},
ownerDetails: {self.ownerDetails}
]"""

View File

@ -0,0 +1,73 @@
import NimQml, Tables, strformat
import token_owners_item
type
ModelRole {.pure.} = enum
Name = UserRole + 1
ImageSource
WalletAddress
Amount
QtObject:
type TokenOwnersModel* = ref object of QAbstractListModel
items*: seq[TokenOwnersItem]
proc setup(self: TokenOwnersModel) =
self.QAbstractListModel.setup
proc delete(self: TokenOwnersModel) =
self.items = @[]
self.QAbstractListModel.delete
proc newTokenOwnersModel*(): TokenOwnersModel =
new(result, delete)
result.setup
proc countChanged(self: TokenOwnersModel) {.signal.}
proc setItems*(self: TokenOwnersModel, items: seq[TokenOwnersItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
proc count*(self: TokenOwnersModel): int {.slot.} =
self.items.len
QtProperty[int] count:
read = count
notify = countChanged
method rowCount(self: TokenOwnersModel, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: TokenOwnersModel): Table[int, string] =
{
ModelRole.Name.int:"name",
ModelRole.ImageSource.int:"imageSource",
ModelRole.WalletAddress.int:"walletAddress",
ModelRole.Amount.int:"amount",
}.toTable
method data(self: TokenOwnersModel, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.items.len:
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Name:
result = newQVariant(item.name)
of ModelRole.ImageSource:
result = newQVariant(item.imageSource)
of ModelRole.WalletAddress:
result = newQVariant(item.ownerDetails.address)
of ModelRole.Amount:
result = newQVariant(item.amount)
proc `$`*(self: TokenOwnersModel): string =
for i in 0 ..< self.items.len:
result &= fmt"""TokenOwnersModel:
[{i}]:({$self.items[i]})
"""

View File

@ -341,7 +341,11 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS) do(e: Args): self.events.on(SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS) do(e: Args):
let args = CommunityTokenDeployedStatusArgs(e) let args = CommunityTokenDeployedStatusArgs(e)
self.delegate.onCommunityTokenDeployStateChanged(args.communityId, args.contractAddress, args.deployState) self.delegate.onCommunityTokenDeployStateChanged(args.communityId, args.chainId, args.contractAddress, args.deployState)
self.events.on(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED) do(e: Args):
let args = CommunityTokenOwnersArgs(e)
self.delegate.onCommunityTokenOwnersFetched(args.communityId, args.chainId, args.contractAddress, args.owners)
self.events.on(SIGNAL_ACCEPT_REQUEST_TO_JOIN_LOADING) do(e: Args): self.events.on(SIGNAL_ACCEPT_REQUEST_TO_JOIN_LOADING) do(e: Args):
var args = CommunityMemberArgs(e) var args = CommunityMemberArgs(e)
@ -467,5 +471,8 @@ proc getVerificationRequestFrom*(self: Controller, publicKey: string): Verificat
proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] = proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] =
self.communityTokensService.getCommunityTokens(communityId) self.communityTokensService.getCommunityTokens(communityId)
proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] =
return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress)
proc getNetwork*(self:Controller, chainId: int): NetworkDto = proc getNetwork*(self:Controller, chainId: int): NetworkDto =
self.networksService.getNetwork(chainId) self.networksService.getNetwork(chainId)

View File

@ -297,7 +297,10 @@ method onSharedKeycarModuleKeycardSyncPurposeTerminated*(self: AccessInterface,
method onCommunityTokenDeployed*(self: AccessInterface, communityToken: CommunityTokenDto) {.base.} = method onCommunityTokenDeployed*(self: AccessInterface, communityToken: CommunityTokenDto) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, contractAddress: string, deployState: DeployState) {.base.} = method onCommunityTokenOwnersFetched*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) {.base.} =
raise newException(ValueError, "No implementation available")
method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, deployState: DeployState) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onAcceptRequestToJoinFailed*(self: AccessInterface, communityId: string, memberKey: string, requestId: string) {.base.} = method onAcceptRequestToJoinFailed*(self: AccessInterface, communityId: string, memberKey: string, requestId: string) {.base.} =

View File

@ -237,12 +237,10 @@ method delete*[T](self: Module[T]) =
self.view.delete self.view.delete
self.viewVariant.delete self.viewVariant.delete
proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto, network: NetworkDto) : TokenItem = proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto) : TokenItem =
var chainName, chainIcon: string let network = self.controller.getNetwork(tokenDto.chainId)
if network != nil: let tokenOwners = self.controller.getCommunityTokenOwners(tokenDto.communityId, tokenDto.chainId, tokenDto.address)
chainName = network.chainName result = initTokenItem(tokenDto, network, tokenOwners)
chainIcon = network.iconURL
result = initTokenItem(tokenDto, chainName, chainIcon)
proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem = proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem =
let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community
@ -252,8 +250,7 @@ proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto):
communityDetails = self.controller.getCommunityById(channelGroup.id) communityDetails = self.controller.getCommunityById(channelGroup.id)
let communityTokens = self.controller.getCommunityTokens(channelGroup.id) let communityTokens = self.controller.getCommunityTokens(channelGroup.id)
communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem = communityTokensItems = communityTokens.map(proc(tokenDto: CommunityTokenDto): TokenItem =
let network = self.controller.getNetwork(tokenDto.chainId) result = self.createTokenItem(tokenDto)
result = self.createTokenItem(tokenDto, network)
) )
let unviewedCount = channelGroup.unviewedMessagesCount let unviewedCount = channelGroup.unviewedMessagesCount
@ -1004,16 +1001,20 @@ method contactsStatusUpdated*[T](self: Module[T], statusUpdates: seq[StatusUpdat
let status = toOnlineStatus(s.statusType) let status = toOnlineStatus(s.statusType)
self.view.activeSection().setOnlineStatusForMember(s.publicKey, status) self.view.activeSection().setOnlineStatusForMember(s.publicKey, status)
method onCommunityTokenDeployed*[T](self: Module[T], communityToken: CommunityTokenDto) {.base.} = method onCommunityTokenDeployed*[T](self: Module[T], communityToken: CommunityTokenDto) =
let item = self.view.model().getItemById(communityToken.communityId) let item = self.view.model().getItemById(communityToken.communityId)
if item.id != "": if item.id != "":
let network = self.controller.getNetwork(communityToken.chainId) item.appendCommunityToken(self.createTokenItem(communityToken))
item.appendCommunityToken(self.createTokenItem(communityToken, network))
method onCommunityTokenDeployStateChanged*[T](self: Module[T], communityId: string, contractAddress: string, deployState: DeployState) = method onCommunityTokenOwnersFetched*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) =
let item = self.view.model().getItemById(communityId) let item = self.view.model().getItemById(communityId)
if item.id != "": if item.id != "":
item.updateCommunityTokenDeployState(contractAddress, deployState) item.setCommunityTokenOwners(chainId, contractAddress, owners)
method onCommunityTokenDeployStateChanged*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string, deployState: DeployState) =
let item = self.view.model().getItemById(communityId)
if item.id != "":
item.updateCommunityTokenDeployState(chainId, contractAddress, deployState)
method onAcceptRequestToJoinLoading*[T](self: Module[T], communityId: string, memberKey: string) = method onAcceptRequestToJoinLoading*[T](self: Module[T], communityId: string, memberKey: string) =
let item = self.view.model().getItemById(communityId) let item = self.view.model().getItemById(communityId)
@ -1220,3 +1221,4 @@ method activateStatusDeepLink*[T](self: Module[T], statusDeepLink: string) =
method onDeactivateChatLoader*[T](self: Module[T], sectionId: string, chatId: string) = method onDeactivateChatLoader*[T](self: Module[T], sectionId: string, chatId: string) =
if (sectionId.len > 0 and self.channelGroupModules.contains(sectionId)): if (sectionId.len > 0 and self.channelGroupModules.contains(sectionId)):
self.channelGroupModules[sectionId].onDeactivateChatLoader(chatId) self.channelGroupModules[sectionId].onDeactivateChatLoader(chatId)

View File

@ -3,6 +3,7 @@ import ./member_model, ./member_item
import ../main/communities/models/[pending_request_item, pending_request_model] import ../main/communities/models/[pending_request_item, pending_request_model]
import ../main/communities/tokens/models/token_model as community_tokens_model import ../main/communities/tokens/models/token_model as community_tokens_model
import ../main/communities/tokens/models/token_item import ../main/communities/tokens/models/token_item
import ../../../app_service/service/collectible/dto
import ../../global/global_singleton import ../../global/global_singleton
@ -321,8 +322,11 @@ proc encrypted*(self: SectionItem): bool {.inline.} =
proc appendCommunityToken*(self: SectionItem, item: TokenItem) {.inline.} = proc appendCommunityToken*(self: SectionItem, item: TokenItem) {.inline.} =
self.communityTokensModel.appendItem(item) self.communityTokensModel.appendItem(item)
proc updateCommunityTokenDeployState*(self: SectionItem, contractAddress: string, deployState: DeployState) {.inline.} = proc updateCommunityTokenDeployState*(self: SectionItem, chainId: int, contractAddress: string, deployState: DeployState) {.inline.} =
self.communityTokensModel.updateDeployState(contractAddress, deployState) self.communityTokensModel.updateDeployState(chainId, contractAddress, deployState)
proc setCommunityTokenOwners*(self: SectionItem, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) {.inline.} =
self.communityTokensModel.setCommunityTokenOwners(chainId, contractAddress, owners)
proc communityTokens*(self: SectionItem): community_tokens_model.TokenModel {.inline.} = proc communityTokens*(self: SectionItem): community_tokens_model.TokenModel {.inline.} =
self.communityTokensModel self.communityTokensModel

View File

@ -1,5 +1,6 @@
include ../../common/json_utils include ../../common/json_utils
import ../../../backend/eth import ../../../backend/eth
import ../../../backend/collectibles
import ../../../app/core/tasks/common import ../../../app/core/tasks/common
import ../../../app/core/tasks/qt import ../../../app/core/tasks/qt
import ../transaction/dto import ../transaction/dto
@ -21,3 +22,30 @@ const asyncGetSuggestedFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimca
"error": e.msg, "error": e.msg,
}) })
type
FetchCollectibleOwnersArg = ref object of QObjectTaskArg
chainId*: int
contractAddress*: string
communityId*: string
const fetchCollectibleOwnersTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchCollectibleOwnersArg](argEncoded)
try:
let response = collectibles.getCollectibleOwnersByContractAddress(arg.chainId, arg.contractAddress)
let output = %* {
"chainId": arg.chainId,
"contractAddress": arg.contractAddress,
"communityId": arg.communityId,
"result": response.result,
"error": ""
}
arg.finish(output)
except Exception as e:
let output = %* {
"chainId": arg.chainId,
"contractAddress": arg.contractAddress,
"communityId": arg.communityId,
"result": "",
"error": e.msg
}
arg.finish(output)

View File

@ -0,0 +1,13 @@
import json
type
CommunityTokenOwner* = object
walletAddress*: string
amount*: int
proc `%`*(x: CommunityTokenOwner): JsonNode =
result = newJobject()
result["walletAddress"] = %x.walletAddress
result["amount"] = %x.amount

View File

@ -10,6 +10,7 @@ import ../settings/service as settings_service
import ../wallet_account/service as wallet_account_service import ../wallet_account/service as wallet_account_service
import ../ens/utils as ens_utils import ../ens/utils as ens_utils
import ../eth/dto/transaction import ../eth/dto/transaction
import ../collectible/dto as collectibles_dto
import ../../../backend/response_type import ../../../backend/response_type
@ -18,12 +19,15 @@ import ../community/dto/community
import ./dto/deployment_parameters import ./dto/deployment_parameters
import ./dto/community_token import ./dto/community_token
import ./airdrop_details import ./dto/community_token_owner
import airdrop_details
include async_tasks include async_tasks
export community_token export community_token
export deployment_parameters export deployment_parameters
export community_token_owner
logScope: logScope:
topics = "community-tokens-service" topics = "community-tokens-service"
@ -59,14 +63,23 @@ type
fiatCurrency*: CurrencyAmount fiatCurrency*: CurrencyAmount
errorCode*: ComputeFeeErrorCode errorCode*: ComputeFeeErrorCode
type ContractTuple = tuple type
ContractTuple = tuple
address: string address: string
chainId: int chainId: int
type
CommunityTokenOwnersArgs* = ref object of Args
communityId*: string
contractAddress*: string
chainId*: int
owners*: seq[CollectibleOwner]
# Signals which may be emitted by this service: # Signals which may be emitted by this service:
const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus" const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus"
const SIGNAL_COMMUNITY_TOKEN_DEPLOYED* = "communityTokenDeployed" const SIGNAL_COMMUNITY_TOKEN_DEPLOYED* = "communityTokenDeployed"
const SIGNAL_COMPUTE_DEPLOY_FEE* = "computeDeployFee" const SIGNAL_COMPUTE_DEPLOY_FEE* = "computeDeployFee"
const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokenOwnersFetched"
QtObject: QtObject:
type type
@ -80,8 +93,14 @@ QtObject:
tempAccountAddress: string tempAccountAddress: string
tempChainId: int tempChainId: int
addressAndTxMap: Table[ContractTuple, string] addressAndTxMap: Table[ContractTuple, string]
tokenOwnersTimer: QTimer
tokenOwnersCache: Table[ContractTuple, seq[CollectibleOwner]]
# Forward declaration
proc fetchAllTokenOwners*(self: Service)
proc delete*(self: Service) = proc delete*(self: Service) =
delete(self.tokenOwnersTimer)
self.QObject.delete self.QObject.delete
proc newService*( proc newService*(
@ -101,8 +120,13 @@ QtObject:
result.settingsService = settingsService result.settingsService = settingsService
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.addressAndTxMap = initTable[ContractTuple, string]() result.addressAndTxMap = initTable[ContractTuple, string]()
result.tokenOwnersTimer = newQTimer()
result.tokenOwnersTimer.setInterval(10*60*1000)
signalConnect(result.tokenOwnersTimer, "timeout()", result, "onRefreshTransferableTokenOwners()", 2)
proc init*(self: Service) = proc init*(self: Service) =
self.fetchAllTokenOwners()
self.tokenOwnersTimer.start()
self.events.on(PendingTransactionTypeDto.CollectibleDeployment.event) do(e: Args): self.events.on(PendingTransactionTypeDto.CollectibleDeployment.event) do(e: Args):
var receivedData = TransactionMinedArgs(e) var receivedData = TransactionMinedArgs(e)
let deployState = if receivedData.success: DeployState.Deployed else: DeployState.Failed let deployState = if receivedData.success: DeployState.Deployed else: DeployState.Failed
@ -140,7 +164,6 @@ QtObject:
proc deployCollectibles*(self: Service, communityId: string, addressFrom: string, password: string, deploymentParams: DeploymentParameters, tokenMetadata: CommunityTokensMetadataDto, chainId: int) = proc deployCollectibles*(self: Service, communityId: string, addressFrom: string, password: string, deploymentParams: DeploymentParameters, tokenMetadata: CommunityTokensMetadataDto, chainId: int) =
try: try:
# TODO this will come from SendModal
let suggestedFees = self.transactionService.suggestedFees(chainId) let suggestedFees = self.transactionService.suggestedFees(chainId)
let contractGasUnits = self.deployCollectiblesEstimate() let contractGasUnits = self.deployCollectiblesEstimate()
if suggestedFees == nil: if suggestedFees == nil:
@ -202,6 +225,13 @@ QtObject:
except RpcException: except RpcException:
error "Error getting community tokens", message = getCurrentExceptionMsg() error "Error getting community tokens", message = getCurrentExceptionMsg()
proc getAllCommunityTokens*(self: Service): seq[CommunityTokenDto] =
try:
let response = tokens_backend.getAllCommunityTokens()
return parseCommunityTokens(response)
except RpcException:
error "Error getting all community tokens", message = getCurrentExceptionMsg()
proc getCommunityTokenBySymbol*(self: Service, communityId: string, symbol: string): CommunityTokenDto = proc getCommunityTokenBySymbol*(self: Service, communityId: string, symbol: string): CommunityTokenDto =
let communityTokens = self.getCommunityTokens(communityId) let communityTokens = self.getCommunityTokens(communityId)
for token in communityTokens: for token in communityTokens:
@ -303,3 +333,43 @@ QtObject:
let data = ComputeDeployFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, let data = ComputeDeployFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency,
errorCode: (if ethValue > balance: ComputeFeeErrorCode.Balance else: ComputeFeeErrorCode.Success)) errorCode: (if ethValue > balance: ComputeFeeErrorCode.Balance else: ComputeFeeErrorCode.Success))
self.events.emit(SIGNAL_COMPUTE_DEPLOY_FEE, data) self.events.emit(SIGNAL_COMPUTE_DEPLOY_FEE, data)
proc fetchCommunityOwners*(self: Service, communityId: string, chainId: int, contractAddress: string) =
let arg = FetchCollectibleOwnersArg(
tptr: cast[ByteAddress](fetchCollectibleOwnersTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onCommunityTokenOwnersFetched",
chainId: chainId,
contractAddress: contractAddress,
communityId: communityId
)
self.threadpool.start(arg)
# get owners from cache
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] =
return self.tokenOwnersCache.getOrDefault((address: contractAddress, chainId: chainId))
proc onCommunityTokenOwnersFetched*(self:Service, response: string) {.slot.} =
let responseJson = response.parseJson()
if responseJson{"error"}.kind != JNull and responseJson{"error"}.getStr != "":
let errorMessage = responseJson["error"].getStr
error "Can't fetch community token owners", chainId=responseJson["chainId"], contractAddress=responseJson["contractAddress"], errorMsg=errorMessage
return
let chainId = responseJson["chainId"].getInt
let contractAddress = responseJson["contractAddress"].getStr
let communityId = responseJson["communityId"].getStr
let resultJson = responseJson["result"]
let owners = collectibles_dto.toCollectibleOwnershipDto(resultJson).owners
let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: owners)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data)
proc onRefreshTransferableTokenOwners*(self:Service) {.slot.} =
let allTokens = self.getAllCommunityTokens()
for token in allTokens:
if token.transferable:
self.fetchCommunityOwners(token.communityId, token.chainId, token.address)
proc fetchAllTokenOwners*(self: Service) =
let allTokens = self.getAllCommunityTokens()
for token in allTokens:
self.fetchCommunityOwners(token.communityId, token.chainId, token.address)

View File

@ -12,6 +12,10 @@ proc getCommunityTokens*(communityId: string): RpcResponse[JsonNode] {.raises: [
let payload = %* [communityId] let payload = %* [communityId]
return core.callPrivateRPC("wakuext_getCommunityTokens", payload) return core.callPrivateRPC("wakuext_getCommunityTokens", payload)
proc getAllCommunityTokens*(): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* []
return core.callPrivateRPC("wakuext_getAllCommunityTokens", payload)
proc addCommunityToken*(token: CommunityTokenDto): RpcResponse[JsonNode] {.raises: [Exception].} = proc addCommunityToken*(token: CommunityTokenDto): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [token.toJsonNode()] let payload = %* [token.toJsonNode()]
return core.callPrivateRPC("wakuext_addCommunityToken", payload) return core.callPrivateRPC("wakuext_addCommunityToken", payload)

View File

@ -20,7 +20,7 @@ SettingsPageLayout {
// Models: // Models:
property var tokensModel property var tokensModel
property var holdersModel
property string feeText property string feeText
property string errorText property string errorText
property bool isFeeLoading: true property bool isFeeLoading: true
@ -51,7 +51,7 @@ SettingsPageLayout {
signal signMintTransactionOpened(int chainId, string accountAddress) signal signMintTransactionOpened(int chainId, string accountAddress)
signal remoteSelfDestructCollectibles(var holdersModel, signal remoteSelfDestructCollectibles(var tokenOwnersModel,
int chainId, int chainId,
string accountName, string accountName,
string accountAddress) string accountAddress)
@ -89,6 +89,8 @@ SettingsPageLayout {
property int chainId property int chainId
property string chainName property string chainName
property var tokenOwnersModel
readonly property var initialItem: (root.tokensModel && root.tokensModel.count > 0) ? mintedTokensView : welcomeView readonly property var initialItem: (root.tokensModel && root.tokensModel.count > 0) ? mintedTokensView : welcomeView
onInitialItemChanged: updateInitialStackView() onInitialItemChanged: updateInitialStackView()
@ -228,7 +230,6 @@ SettingsPageLayout {
} }
viewWidth: root.viewWidth viewWidth: root.viewWidth
holdersModel: root.holdersModel
onMintCollectible: popup.open() onMintCollectible: popup.open()
@ -283,7 +284,7 @@ SettingsPageLayout {
id: remoteSelfdestructPopup id: remoteSelfdestructPopup
collectibleName: root.title collectibleName: root.title
model: root.holdersModel model: d.tokenOwnersModel
onSelfDestructClicked: { onSelfDestructClicked: {
alertPopup.tokenCount = tokenCount alertPopup.tokenCount = tokenCount
@ -303,7 +304,7 @@ SettingsPageLayout {
function signSelfRemoteDestructTransaction() { function signSelfRemoteDestructTransaction() {
root.isFeeLoading = true root.isFeeLoading = true
root.feeText = "" root.feeText = ""
root.remoteSelfDestructCollectibles(root.holdersModel, root.remoteSelfDestructCollectibles(d.tokenOwnersModel,
d.chainId, d.chainId,
d.accountName, d.accountName,
d.accountAddress) d.accountAddress)
@ -356,7 +357,6 @@ SettingsPageLayout {
property int index // TODO: Update it to key when model has role key implemented property int index // TODO: Update it to key when model has role key implemented
viewWidth: root.viewWidth viewWidth: root.viewWidth
holdersModel: root.holdersModel
Binding { Binding {
target: root target: root
@ -364,9 +364,16 @@ SettingsPageLayout {
value: view.name value: view.name
} }
Binding {
target: d
property: "tokenOwnersModel"
value: view.tokenOwnersModel
}
Instantiator { Instantiator {
id: instantiator id: instantiator
model: SortFilterProxyModel { model: SortFilterProxyModel {
sourceModel: root.tokensModel sourceModel: root.tokensModel
filters: IndexFilter { filters: IndexFilter {
@ -388,7 +395,8 @@ SettingsPageLayout {
Bind { property: "chainId"; value: model.chainId }, Bind { property: "chainId"; value: model.chainId },
Bind { property: "chainName"; value: model.chainName }, Bind { property: "chainName"; value: model.chainName },
Bind { property: "chainIcon"; value: model.chainIcon }, Bind { property: "chainIcon"; value: model.chainIcon },
Bind { property: "accountName"; value: model.accountName } Bind { property: "accountName"; value: model.accountName },
Bind { property: "tokenOwnersModel"; value: model.tokenOwnersModel }
] ]
} }
} }

View File

@ -15,7 +15,7 @@ import shared.controls 1.0
Control { Control {
id: root id: root
// Expected roles: ensName, walletAddress, imageSource, amount, selfDestructAmount and selfDestruct // Expected roles: name, walletAddress, imageSource, amount, selfDestructAmount and selfDestruct
property var model property var model
property string tokenName property string tokenName
@ -42,7 +42,7 @@ Control {
enabled: searcher.enabled enabled: searcher.enabled
expression: { expression: {
searcher.text searcher.text
return model.ensName.toLowerCase().includes(searcher.text.toLowerCase()) || return model.name.toLowerCase().includes(searcher.text.toLowerCase()) ||
model.walletAddress.toLowerCase().includes(searcher.text.toLowerCase()) model.walletAddress.toLowerCase().includes(searcher.text.toLowerCase())
} }
} }
@ -57,7 +57,7 @@ Control {
bottomPadding: 0 bottomPadding: 0
minimumHeight: 36 // by design minimumHeight: 36 // by design
maximumHeight: minimumHeight maximumHeight: minimumHeight
enabled: root.model.count > 0 enabled: root.model && root.model.count > 0
placeholderText: enabled ? qsTr("Search") : qsTr("No placeholders to search") placeholderText: enabled ? qsTr("Search") : qsTr("No placeholders to search")
} }
@ -81,8 +81,8 @@ Control {
spacing: Style.current.padding spacing: Style.current.padding
StatusListItem { StatusListItem {
readonly property bool unknownHolder: model.ensName === "" readonly property bool unknownHolder: model.name === ""
readonly property string formattedTitle: unknownHolder ? "?" : model.ensName readonly property string formattedTitle: unknownHolder ? "?" : model.name
Layout.fillWidth: true Layout.fillWidth: true

View File

@ -7,6 +7,7 @@ import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Popups.Dialog 0.1 import StatusQ.Popups.Dialog 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import AppLayouts.Chat.panels.communities 1.0 import AppLayouts.Chat.panels.communities 1.0
@ -36,11 +37,11 @@ StatusDialog {
} }
function calculateTotalTokensToDestruct() { function calculateTotalTokensToDestruct() {
tokenCount = 0 d.tokenCount = 0
for(var i = 0; i < tokenHoldersPanel.model.count; i ++) { for(var i = 0; i < tokenHoldersPanel.model.count; i ++) {
var item = tokenHoldersPanel.model.get(i) var item = ModelUtils.get(tokenHoldersPanel.model, i)
if(item.selfDestruct) { if(item.selfDestruct) {
tokenCount += item.selfDestructAmount d.tokenCount += item.selfDestructAmount
} }
} }
} }

View File

@ -277,7 +277,6 @@ StatusSectionLayout {
rootStore.communityTokensStore rootStore.communityTokensStore
tokensModel: root.community.communityTokens tokensModel: root.community.communityTokens
holdersModel: communityTokensStore.holdersModel
layer1Networks: communityTokensStore.layer1Networks layer1Networks: communityTokensStore.layer1Networks
layer2Networks: communityTokensStore.layer2Networks layer2Networks: communityTokensStore.layer2Networks
testNetworks: communityTokensStore.testNetworks testNetworks: communityTokensStore.testNetworks
@ -303,7 +302,7 @@ StatusSectionLayout {
} }
onSignSelfDestructTransactionOpened: communityTokensStore.computeSelfDestructFee(chainId) onSignSelfDestructTransactionOpened: communityTokensStore.computeSelfDestructFee(chainId)
onRemoteSelfDestructCollectibles: { onRemoteSelfDestructCollectibles: {
communityTokensStore.remoteSelfDestructCollectibles(holdersModel, communityTokensStore.remoteSelfDestructCollectibles(tokenOwnersModel,
chainId, chainId,
accountName, accountName,
accountAddress) accountAddress)

View File

@ -16,7 +16,6 @@ StatusScrollView {
property int viewWidth: 560 // by design property int viewWidth: 560 // by design
property bool preview: false property bool preview: false
property var holdersModel
// Collectible object properties: // Collectible object properties:
property alias artworkSource: image.source property alias artworkSource: image.source
@ -31,6 +30,8 @@ StatusScrollView {
property int chainId property int chainId
property string chainIcon property string chainIcon
property int deployState property int deployState
property var tokenOwnersModel
property alias accountName: accountBox.value property alias accountName: accountBox.value
signal mintCollectible(url artworkSource, signal mintCollectible(url artworkSource,
@ -248,11 +249,10 @@ StatusScrollView {
} }
} }
// Disabled until backend is ready (milestone 12)
TokenHoldersPanel { TokenHoldersPanel {
visible: false//!root.preview visible: !root.preview
tokenName: root.name tokenName: root.name
model: root.holdersModel model: root.tokenOwnersModel
Layout.topMargin: Style.current.padding Layout.topMargin: Style.current.padding
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true

View File

@ -13,48 +13,9 @@ QtObject {
property var enabledNetworks: networksModule.enabled property var enabledNetworks: networksModule.enabled
property var allNetworks: networksModule.all property var allNetworks: networksModule.all
// Token holders model: MOCKED DATA -> TODO: Update with real data signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error)
readonly property var holdersModel: ListModel { signal deploymentStateChanged(string communityId, int status, string url)
signal selfDestructFeeUpdated(string value) // TO BE REMOVED
readonly property string image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/
nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
Component.onCompleted:
append([
{
ensName: "carmen.eth",
walletAddress: "0xb794f5450ba39494ce839613fffba74279579268",
imageSource:image,
amount: 3,
selfDestructAmount: 0,
selfDestruct: false
},
{
ensName: "chris.eth",
walletAddress: "0xb794f5ea0ba39494ce839613fffba74279579268",
imageSource: image,
amount: 2,
selfDestructAmount: 0,
selfDestruct: false
},
{
ensName: "emily.eth",
walletAddress: "0xb794f5ea0ba39494ce839613fffba74279579268",
imageSource: image,
amount: 2,
selfDestructAmount: 0,
selfDestruct: false
},
{
ensName: "",
walletAddress: "0xb794f5ea0ba39494ce839613fffba74279579268",
imageSource: "",
amount: 1,
selfDestructAmount: 0,
selfDestruct: false
}
])
}
// Minting tokens: // Minting tokens:
function deployCollectible(communityId, accountAddress, name, symbol, description, supply, function deployCollectible(communityId, accountAddress, name, symbol, description, supply,
@ -65,10 +26,6 @@ QtObject {
infiniteSupply, transferable, selfDestruct, chainId, artworkSource) infiniteSupply, transferable, selfDestruct, chainId, artworkSource)
} }
signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error)
signal deploymentStateChanged(string communityId, int status, string url)
signal selfDestructFeeUpdated(string value) // TO BE REMOVED
readonly property Connections connections: Connections { readonly property Connections connections: Connections {
target: communityTokensModuleInst target: communityTokensModuleInst
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) { function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {