feat(@desktop/communities): Add self-destruct tokens functionality

Issue #10052
This commit is contained in:
Michal Iskierko 2023-05-05 13:03:59 +02:00 committed by Michał Iskierko
parent c40472c285
commit 4c29343a4a
19 changed files with 417 additions and 106 deletions

View File

@ -43,14 +43,20 @@ proc init*(self: Controller) =
return return
self.communityTokensModule.onUserAuthenticated(args.password) self.communityTokensModule.onUserAuthenticated(args.password)
self.events.on(SIGNAL_COMPUTE_DEPLOY_FEE) do(e:Args): self.events.on(SIGNAL_COMPUTE_DEPLOY_FEE) do(e:Args):
let args = ComputeDeployFeeArgs(e) let args = ComputeFeeArgs(e)
self.communityTokensModule.onDeployFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode) self.communityTokensModule.onDeployFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode)
self.events.on(SIGNAL_COMPUTE_SELF_DESTRUCT_FEE) do(e:Args):
let args = ComputeFeeArgs(e)
self.communityTokensModule.onSelfDestructFeeComputed(args.ethCurrency, args.fiatCurrency, args.errorCode)
self.events.on(SIGNAL_COMMUNITY_TOKEN_DEPLOYED) do(e: Args): self.events.on(SIGNAL_COMMUNITY_TOKEN_DEPLOYED) do(e: Args):
let args = CommunityTokenDeployedArgs(e) let args = CommunityTokenDeployedArgs(e)
self.communityTokensModule.onCommunityTokenDeployStateChanged(args.communityToken.communityId, args.communityToken.chainId, args.transactionHash, args.communityToken.deployState) self.communityTokensModule.onCommunityTokenDeployStateChanged(args.communityToken.communityId, args.communityToken.chainId, args.transactionHash, args.communityToken.deployState)
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.communityTokensModule.onCommunityTokenDeployStateChanged(args.communityId, args.chainId, args.transactionHash, args.deployState) self.communityTokensModule.onCommunityTokenDeployStateChanged(args.communityId, args.chainId, args.transactionHash, args.deployState)
self.events.on(SIGNAL_REMOTE_DESTRUCT_STATUS) do(e: Args):
let args = RemoteDestructArgs(e)
self.communityTokensModule.onRemoteDestructStateChanged(args.communityToken.communityId, args.communityToken.name, args.communityToken.chainId, args.transactionHash, args.status)
proc deployCollectibles*(self: Controller, communityId: string, addressFrom: string, password: string, deploymentParams: DeploymentParameters, tokenMetadata: CommunityTokensMetadataDto, chainId: int) = proc deployCollectibles*(self: Controller, communityId: string, addressFrom: string, password: string, deploymentParams: DeploymentParameters, tokenMetadata: CommunityTokensMetadataDto, chainId: int) =
self.communityTokensService.deployCollectibles(communityId, addressFrom, password, deploymentParams, tokenMetadata, chainId) self.communityTokensService.deployCollectibles(communityId, addressFrom, password, deploymentParams, tokenMetadata, chainId)
@ -58,6 +64,9 @@ proc deployCollectibles*(self: Controller, communityId: string, addressFrom: str
proc airdropCollectibles*(self: Controller, communityId: string, password: string, collectiblesAndAmounts: seq[CommunityTokenAndAmount], walletAddresses: seq[string]) = proc airdropCollectibles*(self: Controller, communityId: string, password: string, collectiblesAndAmounts: seq[CommunityTokenAndAmount], walletAddresses: seq[string]) =
self.communityTokensService.airdropCollectibles(communityId, password, collectiblesAndAmounts, walletAddresses) self.communityTokensService.airdropCollectibles(communityId, password, collectiblesAndAmounts, walletAddresses)
proc selfDestructCollectibles*(self: Controller, communityId: string, password: string, walletAndAmounts: seq[WalletAndAmount], contractUniqueKey: string) =
self.communityTokensService.selfDestructCollectibles(communityId, password, walletAndAmounts, contractUniqueKey)
proc authenticateUser*(self: Controller, keyUid = "") = proc authenticateUser*(self: Controller, keyUid = "") =
let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_DEPLOY_COLLECTIBLES_COMMUNITY_TOKENS_MODULE_IDENTIFIER, keyUid: keyUid) let data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_DEPLOY_COLLECTIBLES_COMMUNITY_TOKENS_MODULE_IDENTIFIER, keyUid: keyUid)
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data) self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
@ -68,6 +77,9 @@ proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTo
proc computeDeployFee*(self: Controller, chainId: int, accountAddress: string) = proc computeDeployFee*(self: Controller, chainId: int, accountAddress: string) =
self.communityTokensService.computeDeployFee(chainId, accountAddress) self.communityTokensService.computeDeployFee(chainId, accountAddress)
proc computeSelfDestructFee*(self: Controller, walletAndAmountList: seq[WalletAndAmount], contractUniqueKey: string) =
self.communityTokensService.computeSelfDestructFee(walletAndAmountList, contractUniqueKey)
proc getCommunityTokenBySymbol*(self: Controller, communityId: string, symbol: string): CommunityTokenDto = proc getCommunityTokenBySymbol*(self: Controller, communityId: string, symbol: string): CommunityTokenDto =
return self.communityTokensService.getCommunityTokenBySymbol(communityId, symbol) return self.communityTokensService.getCommunityTokenBySymbol(communityId, symbol)

View File

@ -13,6 +13,9 @@ method load*(self: AccessInterface) {.base.} =
method airdropCollectibles*(self: AccessInterface, communityId: string, collectiblesJsonString: string, walletsJsonString: string) {.base.} = method airdropCollectibles*(self: AccessInterface, communityId: string, collectiblesJsonString: string, walletsJsonString: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method selfDestructCollectibles*(self: AccessInterface, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} =
raise newException(ValueError, "No implementation available")
method deployCollectible*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: int, infiniteSupply: bool, transferable: bool, method deployCollectible*(self: AccessInterface, communityId: string, address: string, name: string, symbol: string, description: string, supply: int, infiniteSupply: bool, transferable: bool,
selfDestruct: bool, chainId: int, image: string) {.base.} = selfDestruct: bool, chainId: int, image: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -26,8 +29,17 @@ method resetTempValues*(self: AccessInterface) {.base.} =
method computeDeployFee*(self: AccessInterface, chainId: int, accountAddress: string) {.base.} = method computeDeployFee*(self: AccessInterface, chainId: int, accountAddress: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method computeSelfDestructFee*(self: AccessInterface, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onDeployFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} = method onDeployFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onSelfDestructFeeComputed*(self: AccessInterface, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) {.base.} =
raise newException(ValueError, "No implementation available")
method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, chainId: int, transactionHash: string, deployState: DeployState) {.base.} = method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, chainId: int, transactionHash: string, deployState: DeployState) {.base.} =
raise newException(ValueError, "No implementation available")
method onRemoteDestructStateChanged*(self: AccessInterface, communityId: string, tokenName: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -13,17 +13,20 @@ type
tokenDto*: CommunityTokenDto tokenDto*: CommunityTokenDto
chainName*: string chainName*: string
chainIcon*: string chainIcon*: string
accountName*: string
tokenOwnersModel*: token_owners_model.TokenOwnersModel tokenOwnersModel*: token_owners_model.TokenOwnersModel
proc initTokenItem*( proc initTokenItem*(
tokenDto: CommunityTokenDto, tokenDto: CommunityTokenDto,
network: NetworkDto, network: NetworkDto,
tokenOwners: seq[CollectibleOwner] tokenOwners: seq[CollectibleOwner],
accountName: string
): TokenItem = ): TokenItem =
result.tokenDto = tokenDto result.tokenDto = tokenDto
if network != nil: if network != nil:
result.chainName = network.chainName result.chainName = network.chainName
result.chainIcon = network.iconURL result.chainIcon = network.iconURL
result.accountName = accountName
result.tokenOwnersModel = newTokenOwnersModel() result.tokenOwnersModel = newTokenOwnersModel()
result.tokenOwnersModel.setItems(tokenOwners.map(proc(owner: CollectibleOwner): TokenOwnersItem = result.tokenOwnersModel.setItems(tokenOwners.map(proc(owner: CollectibleOwner): TokenOwnersItem =
# TODO find member with the address - later when airdrop to member will be added # TODO find member with the address - later when airdrop to member will be added

View File

@ -4,10 +4,12 @@ import token_owners_item
import token_owners_model 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 import ../../../../../../app_service/service/collectible/dto
import ../../../../../../app_service/common/utils
type type
ModelRole {.pure.} = enum ModelRole {.pure.} = enum
TokenType = UserRole + 1 ContractUniqueKey = UserRole + 1
TokenType
TokenAddress TokenAddress
Name Name
Symbol Symbol
@ -22,6 +24,7 @@ type
ChainName ChainName
ChainIcon ChainIcon
TokenOwnersModel TokenOwnersModel
AccountName
QtObject: QtObject:
type TokenModel* = ref object of QAbstractListModel type TokenModel* = ref object of QAbstractListModel
@ -43,6 +46,7 @@ QtObject:
if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)): 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)
defer: index.delete
self.dataChanged(index, index, @[ModelRole.DeployState.int]) self.dataChanged(index, index, @[ModelRole.DeployState.int])
return return
@ -87,6 +91,7 @@ QtObject:
method roleNames(self: TokenModel): Table[int, string] = method roleNames(self: TokenModel): Table[int, string] =
{ {
ModelRole.ContractUniqueKey.int:"contractUniqueKey",
ModelRole.TokenType.int:"tokenType", ModelRole.TokenType.int:"tokenType",
ModelRole.TokenAddress.int:"tokenAddress", ModelRole.TokenAddress.int:"tokenAddress",
ModelRole.Name.int:"name", ModelRole.Name.int:"name",
@ -102,6 +107,7 @@ QtObject:
ModelRole.ChainName.int:"chainName", ModelRole.ChainName.int:"chainName",
ModelRole.ChainIcon.int:"chainIcon", ModelRole.ChainIcon.int:"chainIcon",
ModelRole.TokenOwnersModel.int:"tokenOwnersModel", ModelRole.TokenOwnersModel.int:"tokenOwnersModel",
ModelRole.AccountName.int:"accountName",
}.toTable }.toTable
method data(self: TokenModel, index: QModelIndex, role: int): QVariant = method data(self: TokenModel, index: QModelIndex, role: int): QVariant =
@ -112,6 +118,8 @@ QtObject:
let item = self.items[index.row] let item = self.items[index.row]
let enumRole = role.ModelRole let enumRole = role.ModelRole
case enumRole: case enumRole:
of ModelRole.ContractUniqueKey:
result = newQVariant(contractUniqueKey(item.tokenDto.chainId, item.tokenDto.address))
of ModelRole.TokenType: of ModelRole.TokenType:
result = newQVariant(item.tokenDto.tokenType.int) result = newQVariant(item.tokenDto.tokenType.int)
of ModelRole.TokenAddress: of ModelRole.TokenAddress:
@ -142,6 +150,8 @@ QtObject:
result = newQVariant(item.chainIcon) result = newQVariant(item.chainIcon)
of ModelRole.TokenOwnersModel: of ModelRole.TokenOwnersModel:
result = newQVariant(item.tokenOwnersModel) result = newQVariant(item.tokenOwnersModel)
of ModelRole.AccountName:
result = newQVariant(item.accountName)
proc `$`*(self: TokenModel): string = proc `$`*(self: TokenModel): string =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:

View File

@ -18,6 +18,7 @@ type
Unknown = 0 Unknown = 0
Deploy = 1 Deploy = 1
Airdrop = 2 Airdrop = 2
SelfDestruct = 3
type type
Module* = ref object of io_interface.AccessInterface Module* = ref object of io_interface.AccessInterface
@ -26,6 +27,7 @@ type
view: View view: View
viewVariant: QVariant viewVariant: QVariant
tempTokenAndAmountList: seq[CommunityTokenAndAmount] tempTokenAndAmountList: seq[CommunityTokenAndAmount]
tempWalletAndAmountList: seq[WalletAndAmount]
tempAddressFrom: string tempAddressFrom: string
tempCommunityId: string tempCommunityId: string
tempChainId: int tempChainId: int
@ -34,6 +36,7 @@ type
tempTokenMetadata: CommunityTokensMetadataDto tempTokenMetadata: CommunityTokensMetadataDto
tempWalletAddresses: seq[string] tempWalletAddresses: seq[string]
tempContractAction: ContractAction tempContractAction: ContractAction
tempContractUniqueKey: string
proc newCommunityTokensModule*( proc newCommunityTokensModule*(
parent: parent_interface.AccessInterface, parent: parent_interface.AccessInterface,
@ -62,6 +65,8 @@ method resetTempValues(self:Module) =
self.tempWalletAddresses = @[] self.tempWalletAddresses = @[]
self.tempContractAction = ContractAction.Unknown self.tempContractAction = ContractAction.Unknown
self.tempTokenAndAmountList = @[] self.tempTokenAndAmountList = @[]
self.tempWalletAndAmountList = @[]
self.tempContractUniqueKey = ""
method load*(self: Module) = method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("communityTokensModule", self.viewVariant) singletonInstance.engine.setRootContextProperty("communityTokensModule", self.viewVariant)
@ -92,6 +97,20 @@ method airdropCollectibles*(self: Module, communityId: string, collectiblesJsonS
self.tempContractAction = ContractAction.Airdrop self.tempContractAction = ContractAction.Airdrop
self.authenticate() self.authenticate()
proc getWalletAndAmountListFromJson(self: Module, collectiblesToBurnJsonString: string): seq[WalletAndAmount] =
let collectiblesToBurnJson = collectiblesToBurnJsonString.parseJson
for collectibleToBurn in collectiblesToBurnJson:
let walletAddress = collectibleToBurn["walletAddress"].getStr
let amount = collectibleToBurn["amount"].getInt
result.add(WalletAndAmount(walletAddress: walletAddress, amount: amount))
method selfDestructCollectibles*(self: Module, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) =
self.tempWalletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString)
self.tempCommunityId = communityId
self.tempContractUniqueKey = contractUniqueKey
self.tempContractAction = ContractAction.SelfDestruct
self.authenticate()
method deployCollectible*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string, method deployCollectible*(self: Module, communityId: string, fromAddress: string, name: string, symbol: string, description: string,
supply: int, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, image: string) = supply: int, infiniteSupply: bool, transferable: bool, selfDestruct: bool, chainId: int, image: string) =
self.tempAddressFrom = fromAddress self.tempAddressFrom = fromAddress
@ -119,14 +138,28 @@ method onUserAuthenticated*(self: Module, password: string) =
self.controller.deployCollectibles(self.tempCommunityId, self.tempAddressFrom, password, self.tempDeploymentParams, self.tempTokenMetadata, self.tempChainId) self.controller.deployCollectibles(self.tempCommunityId, self.tempAddressFrom, password, self.tempDeploymentParams, self.tempTokenMetadata, self.tempChainId)
elif self.tempContractAction == ContractAction.Airdrop: elif self.tempContractAction == ContractAction.Airdrop:
self.controller.airdropCollectibles(self.tempCommunityId, password, self.tempTokenAndAmountList, self.tempWalletAddresses) self.controller.airdropCollectibles(self.tempCommunityId, password, self.tempTokenAndAmountList, self.tempWalletAddresses)
elif self.tempContractAction == ContractAction.SelfDestruct:
self.controller.selfDestructCollectibles(self.tempCommunityId, password, self.tempWalletAndAmountList, self.tempContractUniqueKey)
method onDeployFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) = method onDeployFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) =
self.view.updateDeployFee(ethCurrency, fiatCurrency, errorCode.int) self.view.updateDeployFee(ethCurrency, fiatCurrency, errorCode.int)
method onSelfDestructFeeComputed*(self: Module, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: ComputeFeeErrorCode) =
self.view.updateSelfDestructFee(ethCurrency, fiatCurrency, errorCode.int)
method computeDeployFee*(self: Module, chainId: int, accountAddress: string) = method computeDeployFee*(self: Module, chainId: int, accountAddress: string) =
self.controller.computeDeployFee(chainId, accountAddress) self.controller.computeDeployFee(chainId, accountAddress)
method computeSelfDestructFee*(self: Module, collectiblesToBurnJsonString: string, contractUniqueKey: string) =
let walletAndAmountList = self.getWalletAndAmountListFromJson(collectiblesToBurnJsonString)
self.controller.computeSelfDestructFee(walletAndAmountList, contractUniqueKey)
method onCommunityTokenDeployStateChanged*(self: Module, communityId: string, chainId: int, transactionHash: string, deployState: DeployState) = method onCommunityTokenDeployStateChanged*(self: Module, communityId: string, chainId: int, transactionHash: string, deployState: DeployState) =
let network = self.controller.getNetwork(chainId) let network = self.controller.getNetwork(chainId)
let url = if network != nil: network.blockExplorerURL & "/tx/" & transactionHash else: "" let url = if network != nil: network.blockExplorerURL & "/tx/" & transactionHash else: ""
self.view.emitDeploymentStateChanged(communityId, deployState.int, url) self.view.emitDeploymentStateChanged(communityId, deployState.int, url)
method onRemoteDestructStateChanged*(self: Module, communityId: string, tokenName: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) =
let network = self.controller.getNetwork(chainId)
let url = if network != nil: network.blockExplorerURL & "/tx/" & transactionHash else: ""
self.view.emitRemoteDestructStateChanged(communityId, tokenName, status.int, url)

View File

@ -25,14 +25,28 @@ QtObject:
proc airdropCollectibles*(self: View, communityId: string, collectiblesJsonString: string, walletsJsonString: string) {.slot.} = proc airdropCollectibles*(self: View, communityId: string, collectiblesJsonString: string, walletsJsonString: string) {.slot.} =
self.communityTokensModule.airdropCollectibles(communityId, collectiblesJsonString, walletsJsonString) self.communityTokensModule.airdropCollectibles(communityId, collectiblesJsonString, walletsJsonString)
proc selfDestructCollectibles*(self: View, communityId: string, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} =
self.communityTokensModule.selfDestructCollectibles(communityId, collectiblesToBurnJsonString, contractUniqueKey)
proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.} proc deployFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.}
proc selfDestructFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int) {.signal.}
proc computeDeployFee*(self: View, chainId: int, accountAddress: string) {.slot.} = proc computeDeployFee*(self: View, chainId: int, accountAddress: string) {.slot.} =
self.communityTokensModule.computeDeployFee(chainId, accountAddress) self.communityTokensModule.computeDeployFee(chainId, accountAddress)
proc computeSelfDestructFee*(self: View, collectiblesToBurnJsonString: string, contractUniqueKey: string) {.slot.} =
self.communityTokensModule.computeSelfDestructFee(collectiblesToBurnJsonString, contractUniqueKey)
proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) = proc updateDeployFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) =
self.deployFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode) self.deployFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode)
proc updateSelfDestructFee*(self: View, ethCurrency: CurrencyAmount, fiatCurrency: CurrencyAmount, errorCode: int) =
self.selfDestructFeeUpdated(newQVariant(ethCurrency), newQVariant(fiatCurrency), errorCode)
proc deploymentStateChanged*(self: View, communityId: string, status: int, url: string) {.signal.} proc deploymentStateChanged*(self: View, communityId: string, status: int, url: string) {.signal.}
proc emitDeploymentStateChanged*(self: View, communityId: string, status: int, url: string) = proc emitDeploymentStateChanged*(self: View, communityId: string, status: int, url: string) =
self.deploymentStateChanged(communityId, status, url) self.deploymentStateChanged(communityId, status, url)
proc remoteDestructStateChanged*(self: View, communityId: string, tokenName: string, status: int, url: string) {.signal.}
proc emitRemoteDestructStateChanged*(self: View, communityId: string, tokenName: string, status: int, url: string) =
self.remoteDestructStateChanged(communityId, tokenName, status, url)

View File

@ -474,5 +474,8 @@ proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTo
proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] = proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] =
return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress) return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress)
proc getCommunityTokenOwnerName*(self: Controller, chainId: int, contractAddress: string): string =
return self.communityTokensService.contractOwnerName(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

@ -236,7 +236,8 @@ method delete*[T](self: Module[T]) =
proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto) : TokenItem = proc createTokenItem[T](self: Module[T], tokenDto: CommunityTokenDto) : TokenItem =
let network = self.controller.getNetwork(tokenDto.chainId) let network = self.controller.getNetwork(tokenDto.chainId)
let tokenOwners = self.controller.getCommunityTokenOwners(tokenDto.communityId, tokenDto.chainId, tokenDto.address) let tokenOwners = self.controller.getCommunityTokenOwners(tokenDto.communityId, tokenDto.chainId, tokenDto.address)
result = initTokenItem(tokenDto, network, tokenOwners) let ownerAddressName = self.controller.getCommunityTokenOwnerName(tokenDto.chainId, tokenDto.address)
result = initTokenItem(tokenDto, network, tokenOwners, ownerAddressName)
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

View File

@ -80,4 +80,7 @@ proc isPathOutOfTheDefaultStatusDerivationTree*(path: string): bool =
path.count("'") != 3 or path.count("'") != 3 or
path.count("/") != 5: path.count("/") != 5:
return true return true
return false return false
proc contractUniqueKey*(chainId: int, contractAddress: string): string =
return $chainId & "_" & contractAddress

View File

@ -1,5 +1,7 @@
import stint
include ../../common/json_utils include ../../common/json_utils
import ../../../backend/eth import ../../../backend/eth
import ../../../backend/community_tokens
import ../../../backend/collectibles import ../../../backend/collectibles
import ../../../app/core/tasks/common import ../../../app/core/tasks/common
import ../../../app/core/tasks/qt import ../../../app/core/tasks/qt
@ -22,6 +24,26 @@ const asyncGetSuggestedFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimca
"error": e.msg, "error": e.msg,
}) })
type
AsyncGetBurnFees = ref object of QObjectTaskArg
chainId: int
contractAddress: string
tokenIds: seq[UInt256]
const asyncGetBurnFeesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncGetBurnFees](argEncoded)
try:
let feesResponse = eth.suggestedFees(arg.chainId).result
let burnGas = community_tokens.estimateRemoteBurn(arg.chainId, arg.contractAddress, arg.tokenIds).result.getInt
arg.finish(%* {
"fees": feesResponse.toSuggestedFeesDto(),
"burnGas": burnGas,
"error": "" })
except Exception as e:
arg.finish(%* {
"error": e.msg,
})
type type
FetchCollectibleOwnersArg = ref object of QObjectTaskArg FetchCollectibleOwnersArg = ref object of QObjectTaskArg
chainId*: int chainId*: int

View File

@ -1,4 +1,4 @@
import NimQml, Tables, chronicles, json, stint, strutils import NimQml, Tables, chronicles, json, stint, strutils, sugar, sequtils
import ../../../app/core/eventemitter import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/modules/shared_models/currency_amount import ../../../app/modules/shared_models/currency_amount
@ -15,6 +15,8 @@ import ../collectible/dto as collectibles_dto
import ../../../backend/response_type import ../../../backend/response_type
import ../../common/conversion import ../../common/conversion
import ../../common/account_constants
import ../../common/utils as common_utils
import ../community/dto/community import ../community/dto/community
import ./dto/deployment_parameters import ./dto/deployment_parameters
@ -37,6 +39,11 @@ type
communityToken*: CommunityTokenDto communityToken*: CommunityTokenDto
amount*: int amount*: int
type
WalletAndAmount* = object
walletAddress*: string
amount*: int
type type
CommunityTokenDeployedStatusArgs* = ref object of Args CommunityTokenDeployedStatusArgs* = ref object of Args
communityId*: string communityId*: string
@ -50,6 +57,18 @@ type
communityToken*: CommunityTokenDto communityToken*: CommunityTokenDto
transactionHash*: string transactionHash*: string
type
ContractTransactionStatus* {.pure.} = enum
Failed,
InProgress,
Completed
type
RemoteDestructArgs* = ref object of Args
communityToken*: CommunityTokenDto
transactionHash*: string
status*: ContractTransactionStatus
type type
ComputeFeeErrorCode* {.pure.} = enum ComputeFeeErrorCode* {.pure.} = enum
Success, Success,
@ -58,7 +77,7 @@ type
Other Other
type type
ComputeDeployFeeArgs* = ref object of Args ComputeFeeArgs* = ref object of Args
ethCurrency*: CurrencyAmount ethCurrency*: CurrencyAmount
fiatCurrency*: CurrencyAmount fiatCurrency*: CurrencyAmount
errorCode*: ComputeFeeErrorCode errorCode*: ComputeFeeErrorCode
@ -79,7 +98,9 @@ type
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_COMPUTE_SELF_DESTRUCT_FEE* = "computeSelfDestructFee"
const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokenOwnersFetched" const SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED* = "communityTokenOwnersFetched"
const SIGNAL_REMOTE_DESTRUCT_STATUS* = "communityTokenRemoteDestructStatus"
QtObject: QtObject:
type type
@ -92,15 +113,20 @@ QtObject:
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
tempAccountAddress: string tempAccountAddress: string
tempChainId: int tempChainId: int
addressAndTxMap: Table[ContractTuple, string]
tokenOwnersTimer: QTimer tokenOwnersTimer: QTimer
tokenOwners1SecTimer: QTimer # used to update 1 sec after changes in owners
tempTokenOwnersToFetch: CommunityTokenDto # used by 1sec timer
tokenOwnersCache: Table[ContractTuple, seq[CollectibleOwner]] tokenOwnersCache: Table[ContractTuple, seq[CollectibleOwner]]
tempSuggestedFees: SuggestedFeesDto
tempGasUnits: int
# Forward declaration # Forward declaration
proc fetchAllTokenOwners*(self: Service) proc fetchAllTokenOwners*(self: Service)
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner]
proc delete*(self: Service) = proc delete*(self: Service) =
delete(self.tokenOwnersTimer) delete(self.tokenOwnersTimer)
delete(self.tokenOwners1SecTimer)
self.QObject.delete self.QObject.delete
proc newService*( proc newService*(
@ -119,10 +145,13 @@ QtObject:
result.tokenService = tokenService result.tokenService = tokenService
result.settingsService = settingsService result.settingsService = settingsService
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.addressAndTxMap = initTable[ContractTuple, string]()
result.tokenOwnersTimer = newQTimer() result.tokenOwnersTimer = newQTimer()
result.tokenOwnersTimer.setInterval(10*60*1000) result.tokenOwnersTimer.setInterval(10*60*1000)
signalConnect(result.tokenOwnersTimer, "timeout()", result, "onRefreshTransferableTokenOwners()", 2) signalConnect(result.tokenOwnersTimer, "timeout()", result, "onRefreshTransferableTokenOwners()", 2)
result.tokenOwners1SecTimer = newQTimer()
result.tokenOwners1SecTimer.setInterval(1000)
result.tokenOwners1SecTimer.setSingleShot(true)
signalConnect(result.tokenOwners1SecTimer, "timeout()", result, "onFetchTempTokenOwners()", 2)
proc init*(self: Service) = proc init*(self: Service) =
self.fetchAllTokenOwners() self.fetchAllTokenOwners()
@ -139,21 +168,27 @@ QtObject:
error "Error updating collectibles contract state", message = getCurrentExceptionMsg() error "Error updating collectibles contract state", message = getCurrentExceptionMsg()
let data = CommunityTokenDeployedStatusArgs(communityId: tokenDto.communityId, contractAddress: tokenDto.address, let data = CommunityTokenDeployedStatusArgs(communityId: tokenDto.communityId, contractAddress: tokenDto.address,
deployState: deployState, chainId: tokenDto.chainId, deployState: deployState, chainId: tokenDto.chainId,
transactionHash: self.addressAndTxMap.getOrDefault((tokenDto.address,tokenDto.chainId))) transactionHash: receivedData.transactionHash)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS, data) self.events.emit(SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS, data)
self.events.on(PendingTransactionTypeDto.CollectibleAirdrop.event) do(e: Args): self.events.on(PendingTransactionTypeDto.CollectibleAirdrop.event) do(e: Args):
var receivedData = TransactionMinedArgs(e) let receivedData = TransactionMinedArgs(e)
let airdropDetails = toAirdropDetails(parseJson(receivedData.data)) let airdropDetails = toAirdropDetails(parseJson(receivedData.data))
if not receivedData.success: if not receivedData.success:
error "Collectible airdrop failed", contractAddress=airdropDetails.contractAddress error "Collectible airdrop failed", contractAddress=airdropDetails.contractAddress
return return
try: #TODO signalize about airdrops - add when extending airdrops
# add holders to db self.events.on(PendingTransactionTypeDto.CollectibleRemoteSelfDestruct.event) do(e: Args):
discard addTokenOwners(airdropDetails.chainId, airdropDetails.contractAddress, let receivedData = TransactionMinedArgs(e)
airdropDetails.walletAddresses, airdropDetails.amount) let tokenDto = toCommunityTokenDto(parseJson(receivedData.data))
except RpcException: let transactionStatus = if receivedData.success: ContractTransactionStatus.Completed else: ContractTransactionStatus.Failed
error "Error adding collectible token owners", message = getCurrentExceptionMsg() let data = RemoteDestructArgs(communityToken: tokenDto, transactionHash: receivedData.transactionHash, status: transactionStatus)
self.events.emit(SIGNAL_REMOTE_DESTRUCT_STATUS, data)
# update owners list if burn was successfull
if receivedData.success:
self.tempTokenOwnersToFetch = tokenDto
self.tokenOwners1SecTimer.start()
proc deployCollectiblesEstimate*(self: Service): int = proc deployCollectiblesEstimate*(self: Service): int =
try: try:
@ -181,8 +216,6 @@ QtObject:
debug "Deployed contract address ", contractAddress=contractAddress debug "Deployed contract address ", contractAddress=contractAddress
debug "Deployment transaction hash ", transactionHash=transactionHash debug "Deployment transaction hash ", transactionHash=transactionHash
self.addressAndTxMap[(contractAddress, chainId)] = transactionHash
var communityToken: CommunityTokenDto var communityToken: CommunityTokenDto
communityToken.tokenType = TokenType.ERC721 communityToken.tokenType = TokenType.ERC721
communityToken.communityId = communityId communityToken.communityId = communityId
@ -245,6 +278,13 @@ QtObject:
except RpcException: except RpcException:
error "Error getting contract owner", message = getCurrentExceptionMsg() error "Error getting contract owner", message = getCurrentExceptionMsg()
proc contractOwnerName*(self: Service, chainId: int, contractAddress: string): string =
try:
let response = tokens_backend.contractOwner(chainId, contractAddress)
return self.walletAccountService.getAccountByAddress(response.result.getStr().toLower()).name
except RpcException:
error "Error getting contract owner name", message = getCurrentExceptionMsg()
proc airdropCollectibles*(self: Service, communityId: string, password: string, collectiblesAndAmounts: seq[CommunityTokenAndAmount], walletAddresses: seq[string]) = proc airdropCollectibles*(self: Service, communityId: string, password: string, collectiblesAndAmounts: seq[CommunityTokenAndAmount], walletAddresses: seq[string]) =
try: try:
for collectibleAndAmount in collectiblesAndAmounts: for collectibleAndAmount in collectiblesAndAmounts:
@ -286,52 +326,147 @@ QtObject:
let arg = AsyncGetSuggestedFees( let arg = AsyncGetSuggestedFees(
tptr: cast[ByteAddress](asyncGetSuggestedFeesTask), tptr: cast[ByteAddress](asyncGetSuggestedFeesTask),
vptr: cast[ByteAddress](self.vptr), vptr: cast[ByteAddress](self.vptr),
slot: "onSuggestedFees", slot: "onDeployFees",
chainId: chainId, chainId: chainId,
) )
self.threadpool.start(arg) self.threadpool.start(arg)
except Exception as e: except Exception as e:
error "Error loading fees", msg = e.msg error "Error loading fees", msg = e.msg
proc onSuggestedFees*(self:Service, response: string) {.slot.} = proc findContractByUniqueId(self: Service, contractUniqueKey: string): CommunityTokenDto =
let responseJson = response.parseJson() let allTokens = self.getAllCommunityTokens()
const ethSymbol = "ETH" for token in allTokens:
if common_utils.contractUniqueKey(token.chainId, token.address) == contractUniqueKey:
return token
if responseJson{"error"}.kind != JNull and responseJson{"error"}.getStr != "": proc getOwnerBalances(self: Service, contractOwners: seq[CollectibleOwner], ownerAddress: string): seq[CollectibleBalance] =
let errorMessage = responseJson["error"].getStr for owner in contractOwners:
var errorCode = ComputeFeeErrorCode.Other if owner.address == ownerAddress:
if errorMessage.contains("403 Forbidden") or errorMessage.contains("exceed"): return owner.balances
errorCode = ComputeFeeErrorCode.Infura
let ethCurrency = newCurrencyAmount(0.0, ethSymbol, 1, false) proc collectTokensToBurn(self: Service, walletAndAmountList: seq[WalletAndAmount], contractOwners: seq[CollectibleOwner]): seq[UInt256] =
let fiatCurrency = newCurrencyAmount(0.0, self.settingsService.getCurrency(), 1, false) if len(walletAndAmountList) == 0 or len(contractOwners) == 0:
let data = ComputeDeployFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode) return
self.events.emit(SIGNAL_COMPUTE_DEPLOY_FEE, data) for walletAndAmount in walletAndAmountList:
let ownerBalances = self.getOwnerBalances(contractOwners, walletAndAmount.walletAddress)
let amount = walletAndAmount.amount
if amount > len(ownerBalances):
error "amount to burn is higher than the number of tokens", amount=amount, balance=len(ownerBalances), owner=walletAndAmount.walletAddress
return return
for i in 0..amount-1: # add the amount of tokens
result.add(ownerBalances[i].tokenId)
let suggestedFees = decodeSuggestedFeesDto(responseJson["fees"]) proc getTokensToBurn(self: Service, walletAndAmountList: seq[WalletAndAmount], contract: CommunityTokenDto): seq[Uint256] =
let contractGasUnits = self.deployCollectiblesEstimate() if contract.address == "":
error "Can't find contract"
return
let tokenOwners = self.getCommunityTokenOwners(contract.communityId, contract.chainId, contract.address)
let tokenIds = self.collectTokensToBurn(walletAndAmountList, tokenOwners)
if len(tokenIds) == 0:
error "Can't find token ids to burn"
return tokenIds
# TODO use temp fees for deployment also
proc buildTransactionFromTempFees(self: Service, addressFrom: string): TransactionDataDto =
return ens_utils.buildTransaction(parseAddress(addressFrom), 0.u256, $self.tempGasUnits,
if self.tempSuggestedFees.eip1559Enabled: "" else: $self.tempSuggestedFees.gasPrice, self.tempSuggestedFees.eip1559Enabled,
if self.tempSuggestedFees.eip1559Enabled: $self.tempSuggestedFees.maxPriorityFeePerGas else: "",
if self.tempSuggestedFees.eip1559Enabled: $self.tempSuggestedFees.maxFeePerGasM else: "")
proc selfDestructCollectibles*(self: Service, communityId: string, password: string, walletAndAmounts: seq[WalletAndAmount], contractUniqueKey: string) =
try:
let contract = self.findContractByUniqueId(contractUniqueKey)
let tokenIds = self.getTokensToBurn(walletAndAmounts, contract)
if len(tokenIds) == 0:
return
let addressFrom = self.contractOwner(contract.chainId, contract.address)
let txData = self.buildTransactionFromTempFees(addressFrom)
debug "Remote destruct collectibles ", chainId=contract.chainId, address=contract.address, tokens=tokenIds
let response = tokens_backend.remoteBurn(contract.chainId, contract.address, %txData, password, tokenIds)
let transactionHash = response.result.getStr()
debug "Remote destruct transaction hash ", transactionHash=transactionHash
var data = RemoteDestructArgs(communityToken: contract, transactionHash: transactionHash, status: ContractTransactionStatus.InProgress)
self.events.emit(SIGNAL_REMOTE_DESTRUCT_STATUS, data)
# observe transaction state
self.transactionService.watchTransaction(
transactionHash,
addressFrom,
contract.address,
$PendingTransactionTypeDto.CollectibleRemoteSelfDestruct,
$contract.toJsonNode(),
contract.chainId,
)
except Exception as e:
error "Remote self destruct error", msg = e.msg
proc computeSelfDestructFee*(self: Service, walletAndAmountList: seq[WalletAndAmount], contractUniqueKey: string) =
try:
let contract = self.findContractByUniqueId(contractUniqueKey)
self.tempAccountAddress = self.contractOwner(contract.chainId, contract.address)
self.tempChainId = contract.chainId
let tokenIds = self.getTokensToBurn(walletAndAmountList, contract)
if len(tokenIds) == 0:
warn "token list is empty"
return
let arg = AsyncGetBurnFees(
tptr: cast[ByteAddress](asyncGetBurnFeesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onSelfDestructFees",
chainId: contract.chainId,
contractAddress: contract.address,
tokenIds: tokenIds
)
self.threadpool.start(arg)
except Exception as e:
error "Error loading fees", msg = e.msg
proc createComputeFeeArgs(self:Service, jsonNode: JsonNode, gasUnits: int, chainId: int, walletAddress: string): ComputeFeeArgs =
const ethSymbol = "ETH"
if jsonNode{"error"}.kind != JNull and jsonNode{"error"}.getStr != "":
let errorMessage = jsonNode["error"].getStr
var errorCode = ComputeFeeErrorCode.Other
if errorMessage.contains("403 Forbidden") or errorMessage.contains("exceed"):
errorCode = ComputeFeeErrorCode.Infura
let ethCurrency = newCurrencyAmount(0.0, ethSymbol, 1, false)
let fiatCurrency = newCurrencyAmount(0.0, self.settingsService.getCurrency(), 1, false)
return ComputeFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, errorCode: errorCode)
let suggestedFees = decodeSuggestedFeesDto(jsonNode["fees"])
# save suggested fees and use during operation, we always compute fees before operation
self.tempSuggestedFees = suggestedFees
self.tempGasUnits = gasUnits
let maxFees = suggestedFees.maxFeePerGasM let maxFees = suggestedFees.maxFeePerGasM
let gasPrice = if suggestedFees.eip1559Enabled: maxFees else: suggestedFees.gasPrice let gasPrice = if suggestedFees.eip1559Enabled: maxFees else: suggestedFees.gasPrice
let weiValue = gwei2Wei(gasPrice) * contractGasUnits.u256 let weiValue = gwei2Wei(gasPrice) * gasUnits.u256
let ethValueStr = wei2Eth(weiValue) let ethValueStr = wei2Eth(weiValue)
let ethValue = parseFloat(ethValueStr) let ethValue = parseFloat(ethValueStr)
let fiatValue = self.getFiatValue(ethValue, ethSymbol) let fiatValue = self.getFiatValue(ethValue, ethSymbol)
let wallet = self.walletAccountService.getAccountByAddress(self.tempAccountAddress) let wallet = self.walletAccountService.getAccountByAddress(walletAddress.toLower())
var balance = 0.0 var balance = 0.0
let tokens = wallet.tokens let tokens = wallet.tokens
for token in tokens: for token in tokens:
if token.symbol == ethSymbol: if token.symbol == ethSymbol:
balance = token.balancesPerChain[self.tempChainId].balance balance = token.balancesPerChain[chainId].balance
break break
let ethCurrency = newCurrencyAmount(ethValue, ethSymbol, 4, false) let ethCurrency = newCurrencyAmount(ethValue, ethSymbol, 4, false)
let fiatCurrency = newCurrencyAmount(fiatValue, self.settingsService.getCurrency(), 2, false) let fiatCurrency = newCurrencyAmount(fiatValue, self.settingsService.getCurrency(), 2, false)
let data = ComputeDeployFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency, return ComputeFeeArgs(ethCurrency: ethCurrency, fiatCurrency: fiatCurrency,
errorCode: (if ethValue > balance: ComputeFeeErrorCode.Balance else: ComputeFeeErrorCode.Success)) errorCode: (if ethValue > balance: ComputeFeeErrorCode.Balance else: ComputeFeeErrorCode.Success))
proc onSelfDestructFees*(self:Service, response: string) {.slot.} =
let responseJson = response.parseJson()
let burnGas = if responseJson{"burnGas"}.kind != JNull: responseJson{"burnGas"}.getInt else: 0
let data = self.createComputeFeeArgs(responseJson, burnGas, self.tempChainId, self.tempAccountAddress)
self.events.emit(SIGNAL_COMPUTE_SELF_DESTRUCT_FEE, data)
proc onDeployFees*(self:Service, response: string) {.slot.} =
let responseJson = response.parseJson()
let data = self.createComputeFeeArgs(responseJson, self.deployCollectiblesEstimate(), self.tempChainId, self.tempAccountAddress)
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) = proc fetchCommunityOwners*(self: Service, communityId: string, chainId: int, contractAddress: string) =
@ -359,7 +494,9 @@ QtObject:
let contractAddress = responseJson["contractAddress"].getStr let contractAddress = responseJson["contractAddress"].getStr
let communityId = responseJson["communityId"].getStr let communityId = responseJson["communityId"].getStr
let resultJson = responseJson["result"] let resultJson = responseJson["result"]
let owners = collectibles_dto.toCollectibleOwnershipDto(resultJson).owners var owners = collectibles_dto.toCollectibleOwnershipDto(resultJson).owners
owners = owners.filter(x => x.address != ZERO_ADDRESS)
self.tokenOwnersCache[(contractAddress, chainId)] = owners
let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: owners) let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: owners)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data) self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data)
@ -369,6 +506,9 @@ QtObject:
if token.transferable: if token.transferable:
self.fetchCommunityOwners(token.communityId, token.chainId, token.address) self.fetchCommunityOwners(token.communityId, token.chainId, token.address)
proc onFetchTempTokenOwners*(self: Service) {.slot.} =
self.fetchCommunityOwners(self.tempTokenOwnersToFetch.communityId, self.tempTokenOwnersToFetch.chainId, self.tempTokenOwnersToFetch.address)
proc fetchAllTokenOwners*(self: Service) = proc fetchAllTokenOwners*(self: Service) =
let allTokens = self.getAllCommunityTokens() let allTokens = self.getAllCommunityTokens()
for token in allTokens: for token in allTokens:

View File

@ -18,6 +18,7 @@ type
WalletTransfer = "WalletTransfer" WalletTransfer = "WalletTransfer"
CollectibleDeployment = "CollectibleDeployment" CollectibleDeployment = "CollectibleDeployment"
CollectibleAirdrop = "CollectibleAirdrop" CollectibleAirdrop = "CollectibleAirdrop"
CollectibleRemoteSelfDestruct = "CollectibleRemoteSelfDestruct"
proc event*(self:PendingTransactionTypeDto):string = proc event*(self:PendingTransactionTypeDto):string =
result = "transaction:" & $self result = "transaction:" & $self

View File

@ -1,4 +1,6 @@
import json import json, stint
import std/sequtils
import std/sugar
import ./eth import ./eth
import ../app_service/common/utils import ../app_service/common/utils
import ./core, ./response_type import ./core, ./response_type
@ -28,6 +30,14 @@ proc mintTo*(chainId: int, contractAddress: string, txData: JsonNode, password:
let payload = %* [chainId, contractAddress, txData, utils.hashPassword(password), walletAddresses, amount] let payload = %* [chainId, contractAddress, txData, utils.hashPassword(password), walletAddresses, amount]
return core.callPrivateRPC("collectibles_mintTo", payload) return core.callPrivateRPC("collectibles_mintTo", payload)
proc remoteBurn*(chainId: int, contractAddress: string, txData: JsonNode, password: string, tokenIds: seq[UInt256]): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [chainId, contractAddress, txData, utils.hashPassword(password), tokenIds.map(x => x.toString(10))]
return core.callPrivateRPC("collectibles_remoteBurn", payload)
proc estimateRemoteBurn*(chainId: int, contractAddress: string, tokenIds: seq[UInt256]): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [chainId, contractAddress, tokenIds.map(x => x.toString(10))]
return core.callPrivateRPC("collectibles_estimateRemoteBurn", payload)
proc contractOwner*(chainId: int, contractAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} = proc contractOwner*(chainId: int, contractAddress: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [chainId, contractAddress] let payload = %* [chainId, contractAddress]
return core.callPrivateRPC("collectibles_contractOwner", payload) return core.callPrivateRPC("collectibles_contractOwner", payload)

View File

@ -67,12 +67,11 @@ SettingsPageLayout {
signal signMintTransactionOpened(int chainId, string accountAddress) signal signMintTransactionOpened(int chainId, string accountAddress)
signal signSelfDestructTransactionOpened(int chainId) signal signSelfDestructTransactionOpened(var selfDestructTokensList, // [key , amount]
string contractUniqueKey)
signal remoteSelfDestructCollectibles(var selfDestructTokensList, // [key , amount] signal remoteSelfDestructCollectibles(var selfDestructTokensList, // [key , amount]
int chainId, string contractUniqueKey)
string accountName,
string accountAddress)
signal signBurnTransactionOpened(int chainId) signal signBurnTransactionOpened(int chainId)
@ -109,6 +108,7 @@ SettingsPageLayout {
property string accountName property string accountName
property int chainId property int chainId
property string chainName property string chainName
property string contractUniqueKey
property var tokenOwnersModel property var tokenOwnersModel
property var selfDestructTokensList property var selfDestructTokensList
@ -436,13 +436,9 @@ SettingsPageLayout {
property bool isRemotelyDestructTransaction property bool isRemotelyDestructTransaction
function signTransaction() { function signTransaction() {
root.isFeeLoading = true root.setFeeLoading()
root.feeText = ""
if(signTransactionPopup.isRemotelyDestructTransaction) { if(signTransactionPopup.isRemotelyDestructTransaction) {
root.remoteSelfDestructCollectibles(d.selfDestructTokensList, root.remoteSelfDestructCollectibles(d.selfDestructTokensList, d.contractUniqueKey)
d.chainId,
d.accountName,
d.accountAddress)
} else { } else {
root.burnCollectibles("TODO - KEY"/*d.tokenKey*/, d.burnAmount) root.burnCollectibles("TODO - KEY"/*d.tokenKey*/, d.burnAmount)
} }
@ -457,9 +453,13 @@ SettingsPageLayout {
networkName: d.chainName networkName: d.chainName
feeText: root.feeText feeText: root.feeText
isFeeLoading: root.isFeeLoading isFeeLoading: root.isFeeLoading
errorText: root.errorText
onOpened: signTransactionPopup.isRemotelyDestructTransaction ? root.signSelfDestructTransactionOpened(d.chainId) : onOpened: {
root.signBurnTransactionOpened(d.chainId) root.setFeeLoading()
signTransactionPopup.isRemotelyDestructTransaction ? root.signSelfDestructTransactionOpened(d.selfDestructTokensList, d.contractUniqueKey) :
root.signBurnTransactionOpened(d.chainId)
}
onCancelClicked: close() onCancelClicked: close()
onSignTransactionClicked: signTransaction() onSignTransactionClicked: signTransaction()
} }
@ -490,6 +490,7 @@ SettingsPageLayout {
onItemClicked: { onItemClicked: {
d.accountAddress = accountAddress d.accountAddress = accountAddress
d.chainId = chainId d.chainId = chainId
d.contractUniqueKey = contractUniqueKey
d.chainName = chainName d.chainName = chainName
d.accountName = accountName d.accountName = accountName
//d.tokenKey = key // TODO: Backend key role //d.tokenKey = key // TODO: Backend key role

View File

@ -275,6 +275,25 @@ StatusSectionLayout {
readonly property CommunityTokensStore communityTokensStore: readonly property CommunityTokensStore communityTokensStore:
rootStore.communityTokensStore rootStore.communityTokensStore
function setFeesInfo(ethCurrency, fiatCurrency, errorCode) {
if (errorCode === Constants.ComputeFeeErrorCode.Success || errorCode === Constants.ComputeFeeErrorCode.Balance) {
let valueStr = LocaleUtils.currencyAmountToLocaleString(ethCurrency) + "(" + LocaleUtils.currencyAmountToLocaleString(fiatCurrency) + ")"
mintPanel.feeText = valueStr
if (errorCode === Constants.ComputeFeeErrorCode.Balance) {
mintPanel.errorText = qsTr("Not enough funds to make transaction")
}
mintPanel.isFeeLoading = false
return
} else if (errorCode === Constants.ComputeFeeErrorCode.Infura) {
mintPanel.errorText = qsTr("Infura error")
mintPanel.isFeeLoading = true
return
}
mintPanel.errorText = qsTr("Unknown error")
mintPanel.isFeeLoading = true
}
communityName: root.community.name communityName: root.community.name
tokensModel: root.community.communityTokens tokensModel: root.community.communityTokens
layer1Networks: communityTokensStore.layer1Networks layer1Networks: communityTokensStore.layer1Networks
@ -315,12 +334,11 @@ StatusSectionLayout {
accountName, accountName,
artworkCropRect) artworkCropRect)
} }
onSignSelfDestructTransactionOpened: communityTokensStore.computeSelfDestructFee(chainId) onSignSelfDestructTransactionOpened: communityTokensStore.computeSelfDestructFee(selfDestructTokensList, contractUniqueKey)
onRemoteSelfDestructCollectibles: { onRemoteSelfDestructCollectibles: {
communityTokensStore.remoteSelfDestructCollectibles(selfDestructTokensList, communityTokensStore.remoteSelfDestructCollectibles(root.community.id,
chainId, selfDestructTokensList,
accountName, contractUniqueKey)
accountAddress)
} }
onSignBurnTransactionOpened: communityTokensStore.computeBurnFee(chainId) onSignBurnTransactionOpened: communityTokensStore.computeBurnFee(chainId)
onBurnCollectibles: communityTokensStore.burnCollectibles(tokenKey, amount) onBurnCollectibles: communityTokensStore.burnCollectibles(tokenKey, amount)
@ -329,22 +347,43 @@ StatusSectionLayout {
Connections { Connections {
target: rootStore.communityTokensStore target: rootStore.communityTokensStore
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) { function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
if (errorCode === Constants.ComputeFeeErrorCode.Success || errorCode === Constants.ComputeFeeErrorCode.Balance) { mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode)
let valueStr = LocaleUtils.currencyAmountToLocaleString(ethCurrency) + "(" + LocaleUtils.currencyAmountToLocaleString(fiatCurrency) + ")" }
mintPanel.feeText = valueStr
if (errorCode === Constants.ComputeFeeErrorCode.Balance) { function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
mintPanel.errorText = qsTr("Not enough funds to make transaction") mintPanel.setFeesInfo(ethCurrency, fiatCurrency, errorCode)
} }
mintPanel.isFeeLoading = false
return function onRemoteDestructStateChanged(communityId, tokenName, status, url) {
} else if (errorCode === Constants.ComputeFeeErrorCode.Infura) { if (root.community.id !== communityId) {
mintPanel.errorText = qsTr("Infura error")
mintPanel.isFeeLoading = true
return return
} }
mintPanel.errorText = qsTr("Unknown error") let title = ""
mintPanel.isFeeLoading = true let loading = false
let type = Constants.ephemeralNotificationType.normal
switch (status) {
case Constants.ContractTransactionStatus.InProgress:
title = qsTr("Remotely destroying tokens...")
loading = true
break
case Constants.ContractTransactionStatus.Completed:
title = qsTr("%1 tokens destroyed").arg(tokenName)
type = Constants.ephemeralNotificationType.success
break
case Constants.ContractTransactionStatus.Failed:
title = qsTr("%1 tokens destruction failed").arg(tokenName)
break
default:
console.warn("Unknown destruction state: "+status)
return
}
Global.displayToastMessage(title,
qsTr("View on etherscan"),
"",
loading,
type,
url)
} }
function onDeploymentStateChanged(communityId, status, url) { function onDeploymentStateChanged(communityId, status, url) {
@ -356,15 +395,15 @@ StatusSectionLayout {
let loading = false let loading = false
let type = Constants.ephemeralNotificationType.normal let type = Constants.ephemeralNotificationType.normal
switch (status) { switch (status) {
case Constants.BackendProcessState.InProgress: case Constants.ContractTransactionStatus.InProgress:
title = qsTr("Token is being minted...") title = qsTr("Token is being minted...")
loading = true loading = true
break break
case Constants.BackendProcessState.Completed: case Constants.ContractTransactionStatus.Completed:
title = qsTr("Token minting finished") title = qsTr("Token minting finished")
type = Constants.ephemeralNotificationType.success type = Constants.ephemeralNotificationType.success
break break
case Constants.BackendProcessState.Failed: case Constants.ContractTransactionStatus.Failed:
title = qsTr("Token minting failed") title = qsTr("Token minting failed")
break break
default: default:

View File

@ -17,6 +17,7 @@ StatusScrollView {
signal itemClicked(int index, signal itemClicked(int index,
int chainId, int chainId,
string contractUniqueKey,
string chainName, string chainName,
string accountName, string accountName,
string accountAddress) string accountAddress)
@ -25,11 +26,11 @@ StatusScrollView {
id: d id: d
function getSubtitle(deployState, remainingTokens, supply) { function getSubtitle(deployState, remainingTokens, supply) {
if(deployState === Constants.BackendProcessState.Failed) { if(deployState === Constants.ContractTransactionStatus.Failed) {
return qsTr("Minting failed") return qsTr("Minting failed")
} }
if(deployState === Constants.BackendProcessState.InProgress) { if(deployState === Constants.ContractTransactionStatus.InProgress) {
return qsTr("Minting...") return qsTr("Minting...")
} }
@ -71,13 +72,13 @@ StatusScrollView {
width: gridView.cellWidth width: gridView.cellWidth
title: model.name ? model.name : "..." title: model.name ? model.name : "..."
subTitle: d.getSubtitle(model.deployState, model.remainingTokens, model.supply) subTitle: d.getSubtitle(model.deployState, model.remainingTokens, model.supply)
subTitleColor: (model.deployState === Constants.BackendProcessState.Failed) ? Theme.palette.dangerColor1 : Theme.palette.baseColor1 subTitleColor: (model.deployState === Constants.ContractTransactionStatus.Failed) ? Theme.palette.dangerColor1 : Theme.palette.baseColor1
fallbackImageUrl: model.image ? model.image : "" fallbackImageUrl: model.image ? model.image : ""
backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" // TODO BACKEND backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" // TODO BACKEND
isLoading: false isLoading: false
navigationIconVisible: true navigationIconVisible: true
onClicked: root.itemClicked(model.index, model.chainId, model.chainName, model.accountName, model.address) // TODO: Replace to model.key when role exists in backend onClicked: root.itemClicked(model.index, model.chainId, model.contractUniqueKey, model.chainName, model.accountName, model.address)
} }
} }
} }

View File

@ -37,13 +37,13 @@ StatusScrollView {
property var tokenOwnersModel property var tokenOwnersModel
property int deployState: Constants.BackendProcessState.None property int deployState: Constants.ContractTransactionStatus.None
property int burnState: Constants.BackendProcessState.None property int burnState: Constants.ContractTransactionStatus.None
// Collectible object properties (ERC721) // Collectible object properties (ERC721)
property bool transferable property bool transferable
property bool selfDestruct property bool selfDestruct
property int remotelyDestructState: Constants.BackendProcessState.None property int remotelyDestructState: Constants.ContractTransactionStatus.None
// Asset properties (ERC20) // Asset properties (ERC20)
property alias assetDecimals: decimalsBox.value property alias assetDecimals: decimalsBox.value
@ -88,8 +88,8 @@ StatusScrollView {
contentHeight: mainLayout.height contentHeight: mainLayout.height
padding: 0 padding: 0
onRemotelyDestructStateChanged: if(remotelyDestructState === Constants.BackendProcessState.Completed) d.startAnimation(false) onRemotelyDestructStateChanged: if(remotelyDestructState === Constants.ContractTransactionStatus.Completed) d.startAnimation(false)
onBurnStateChanged: if(burnState === Constants.BackendProcessState.Completed) d.startAnimation(true) onBurnStateChanged: if(burnState === Constants.ContractTransactionStatus.Completed) d.startAnimation(true)
ColumnLayout { ColumnLayout {
id: mainLayout id: mainLayout
@ -98,16 +98,16 @@ StatusScrollView {
spacing: Style.current.padding spacing: Style.current.padding
RowLayout { RowLayout {
visible: !root.preview && ((root.deployState === Constants.BackendProcessState.InProgress) || visible: !root.preview && ((root.deployState === Constants.ContractTransactionStatus.InProgress) ||
(root.deployState === Constants.BackendProcessState.Failed)) (root.deployState === Constants.ContractTransactionStatus.Failed))
spacing: Style.current.halfPadding spacing: Style.current.halfPadding
StatusDotsLoadingIndicator { StatusDotsLoadingIndicator {
visible: (root.deployState === Constants.BackendProcessState.InProgress) visible: (root.deployState === Constants.ContractTransactionStatus.InProgress)
} }
StatusIcon { StatusIcon {
visible: (root.deployState === Constants.BackendProcessState.Failed) visible: (root.deployState === Constants.ContractTransactionStatus.Failed)
icon: "warning" icon: "warning"
color: Theme.palette.dangerColor1 color: Theme.palette.dangerColor1
} }
@ -115,12 +115,12 @@ StatusScrollView {
StatusBaseText { StatusBaseText {
elide: Text.ElideRight elide: Text.ElideRight
font.pixelSize: Theme.primaryTextFontSize font.pixelSize: Theme.primaryTextFontSize
text: (root.deployState === Constants.BackendProcessState.InProgress) ? text: (root.deployState === Constants.ContractTransactionStatus.InProgress) ?
(root.isAssetView ? (root.isAssetView ?
qsTr("Asset is being minted") : qsTr("Collectible is being minted")) : qsTr("Asset is being minted") : qsTr("Collectible is being minted")) :
(root.deployState === Constants.BackendProcessState.Failed) ? (root.deployState === Constants.ContractTransactionStatus.Failed) ?
(root.isAssetView ? qsTr("Asset minting failed") : qsTr("Collectible minting failed")) : "" (root.isAssetView ? qsTr("Asset minting failed") : qsTr("Collectible minting failed")) : ""
color: (root.deployState === Constants.BackendProcessState.Failed) ? Theme.palette.dangerColor1 : Theme.palette.directColor1 color: (root.deployState === Constants.ContractTransactionStatus.Failed) ? Theme.palette.dangerColor1 : Theme.palette.directColor1
} }
} }
@ -239,8 +239,8 @@ StatusScrollView {
label: qsTr("Total") label: qsTr("Total")
value: root.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(root.supplyAmount) value: root.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(root.supplyAmount)
isLoading: !root.infiniteSupply && isLoading: !root.infiniteSupply &&
((root.remotelyDestructState === Constants.BackendProcessState.InProgress) || ((root.remotelyDestructState === Constants.ContractTransactionStatus.InProgress) ||
(root.burnState === Constants.BackendProcessState.InProgress)) (root.burnState === Constants.ContractTransactionStatus.InProgress))
} }
CustomPreviewBox { CustomPreviewBox {
@ -248,7 +248,7 @@ StatusScrollView {
label: qsTr("Remaining") label: qsTr("Remaining")
value: root.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(root.remainingTokens) value: root.infiniteSupply ? d.infiniteSymbol : LocaleUtils.numberToLocaleString(root.remainingTokens)
isLoading: !root.infiniteSupply && (root.burnState === Constants.BackendProcessState.InProgress) isLoading: !root.infiniteSupply && (root.burnState === Constants.ContractTransactionStatus.InProgress)
} }
CustomPreviewBox { CustomPreviewBox {

View File

@ -14,10 +14,14 @@ QtObject {
property var allNetworks: networksModule.all property var allNetworks: networksModule.all
signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error) signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error)
signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error)
signal deploymentStateChanged(string communityId, int status, string url) signal deploymentStateChanged(string communityId, int status, string url)
signal selfDestructFeeUpdated(string value) // TO BE REMOVED
signal burnFeeUpdated(string value) // TO BE REMOVED signal burnFeeUpdated(string value) // TO BE REMOVED
signal remoteDestructStateChanged(string communityId, string tokenName, int status, string url)
// Minting tokens: // Minting tokens:
function deployCollectible(communityId, accountAddress, name, symbol, description, supply, function deployCollectible(communityId, accountAddress, name, symbol, description, supply,
infiniteSupply, transferable, selfDestruct, chainId, artworkSource, accountName, artworkCropRect) infiniteSupply, transferable, selfDestruct, chainId, artworkSource, accountName, artworkCropRect)
@ -43,26 +47,27 @@ QtObject {
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) { function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode) root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode)
} }
function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode)
}
function onDeploymentStateChanged(communityId, status, url) { function onDeploymentStateChanged(communityId, status, url) {
root.deploymentStateChanged(communityId, status, url) root.deploymentStateChanged(communityId, status, url)
} }
function onRemoteDestructStateChanged(communityId, tokenName, status, url) {
root.remoteDestructStateChanged(communityId, tokenName, status, url)
}
} }
function computeDeployFee(chainId, accountAddress) { function computeDeployFee(chainId, accountAddress) {
communityTokensModuleInst.computeDeployFee(chainId, accountAddress) communityTokensModuleInst.computeDeployFee(chainId, accountAddress)
} }
// Remotely destruct: function computeSelfDestructFee(selfDestructTokensList, contractUniqueKey) {
function computeSelfDestructFee(chainId) { communityTokensModuleInst.computeSelfDestructFee(JSON.stringify(selfDestructTokensList), contractUniqueKey)
// TODO BACKEND
root.selfDestructFeeUpdated("0,0005 ETH")
console.warn("TODO: Compute self-destruct fee backend")
} }
function remoteSelfDestructCollectibles(selfDestructTokensList, chainId, accountName, accountAddress) { function remoteSelfDestructCollectibles(communityId, selfDestructTokensList, contractUniqueKey) {
// TODO BACKEND communityTokensModuleInst.selfDestructCollectibles(communityId, JSON.stringify(selfDestructTokensList), contractUniqueKey)
// selfDestructTokensList is a js array with properties: `walletAddress` and `amount`
console.warn("TODO: Remote self-destruct collectible backend")
} }
// Burn: // Burn:

View File

@ -893,7 +893,8 @@ QtObject {
Everyone = 4 Everyone = 4
} }
enum BackendProcessState { // refers to ContractTransactionStatus and DeployState in Nim
enum ContractTransactionStatus {
Failed, Failed,
InProgress, InProgress,
Completed, Completed,