feat: introduce first JoinCommunityView for token-gated communities

This does a few things:

- It integrates with the latest `CommunityTokensMetadata` to access
  community specific ERC721 token
- It changes `ChatLayout` such that it conditionally loads either
  `ChatView` or `JoinCommunityView`. `JoinCommunityView` has been
  specifically designed for token-gated communities

Here's what works (in terms of token permissions):

1. If a community has token permissions and the the current users is not
   a member of that community, we show `JoinCommunityView` instead of
   `ChatView`
2. Any community token permissions of type "Become member" are listed in
   the `JoinCommunityView`
3. There are different types of token critera a permission can have:
   ERC20 token, ERC721 token, or ENS (which is also ERC721 but we have
   a type for that nonetheless)

   Only ERC20 token balances are checked for the known wallet accounts.
   This happens every time the known token list has been updated (every
   10 min atm).

   We still need to add balance checks for any ERC721 tokens and ENS.
4. If token permissions are created, updated or deleted by the community
   owner, the `JoinCommunityView` will update in real-time.

You'll also notice that the `Reveal my address and request access`
button will be enabled if any of the token permissions are fulfilled
(only ERC20 at the time being). Clicking that button will not yet send
a request.

This will be done in the next step as part of https://github.com/status-im/status-desktop/issues/9761
This commit is contained in:
Pascal Precht 2023-03-07 17:51:06 +01:00 committed by r4bbit
parent 8f3a965a49
commit 25b0641cc2
14 changed files with 241 additions and 80 deletions

View File

@ -244,6 +244,12 @@ proc init*(self: Controller) =
if (args.communityId == self.sectionId):
self.delegate.onCommunityTokenPermissionDeletionFailed(args.communityId)
self.events.on(SIGNAL_COMMUNITY_TOKEN_METADATA_ADDED) do(e: Args):
let args = CommunityTokenMetadataArgs(e)
if (args.communityId == self.sectionId):
self.delegate.onCommunityTokenMetadataAdded(args.communityId, args.tokenMetadata)
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e: Args):
self.delegate.onWalletAccountTokensRebuilt()

View File

@ -353,5 +353,8 @@ method onCommunityTokenPermissionDeleted*(self: AccessInterface, communityId: st
method onCommunityTokenPermissionDeletionFailed*(self: AccessInterface, communityId: string) =
raise newException(ValueError, "No implementation available")
method onCommunityTokenMetadataAdded*(self: AccessInterface, communityId: string, tokenMetadata: CommunityTokensMetadataDto) =
raise newException(ValueError, "No implementation available")
method onWalletAccountTokensRebuilt*(self: AccessInterface) =
raise newException(ValueError, "No implementation available")

View File

@ -12,6 +12,7 @@ import ../../shared_models/token_permission_item
import ../../shared_models/token_criteria_item
import ../../shared_models/token_criteria_model
import ../../shared_models/token_list_item
import ../../shared_models/token_list_model
import chat_content/module as chat_content_module
import chat_content/users/module as users_module
@ -66,6 +67,8 @@ proc buildChatSectionUI(self: Module,
proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem
proc buildTokenList*(self: Module)
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
@ -303,6 +306,8 @@ proc initContactRequestsModel(self: Module) =
self.view.contactRequestsModel().addItems(contactsWhoAddedMe)
proc rebuildCommunityTokenPermissionsModel(self: Module) =
self.buildTokenList()
let community = self.controller.getMyCommunity()
var tokenPermissionsItems: seq[TokenPermissionItem] = @[]
var allTokenRequirementsMet = false
@ -322,17 +327,17 @@ proc rebuildCommunityTokenPermissionsModel(self: Module) =
self.view.tokenPermissionsModel().setItems(tokenPermissionsItems)
self.view.setAllTokenRequirementsMet(allTokenRequirementsMet)
self.view.setRequiresTokenPermissionToJoin(tokenPermissionsItems.len > 0)
proc initCommunityTokenPermissionsModel(self: Module) =
self.rebuildCommunityTokenPermissionsModel()
proc buildTokenList(self: Module) =
var tokenListItems: seq[TokenListItem]
var collectiblesListItems: seq[TokenListItem]
let community = self.controller.getMyCommunity()
let erc20Tokens = self.controller.getTokenList()
let communityTokens = self.controller.getCommunityTokenList()
for token in erc20Tokens:
let tokenListItem = initTokenListItem(
@ -346,7 +351,7 @@ proc buildTokenList(self: Module) =
tokenListItems.add(tokenListItem)
for token in communityTokens:
for token in community.communityTokensMetadata:
let tokenListItem = initTokenListItem(
key = token.symbol,
name = token.name,
@ -402,8 +407,9 @@ method load*(
self.initContactRequestsModel()
else:
self.usersModule.load()
let community = self.controller.getMyCommunity()
self.view.setAmIMember(community.joined)
self.initCommunityTokenPermissionsModel()
self.buildTokenList()
let activeChatId = self.controller.getActiveChatId()
let isCurrentSectionActive = self.controller.getIsCurrentSectionActive()
@ -743,14 +749,18 @@ method onChatUnmuted*(self: Module, chatId: string) =
method onCommunityTokenPermissionDeleted*(self: Module, communityId: string, permissionId: string) =
self.view.tokenPermissionsModel().removeItemWithId(permissionId)
self.view.setRequiresTokenPermissionToJoin(self.view.tokenPermissionsModel().getCount() > 0)
singletonInstance.globalEvents.showCommunityTokenPermissionDeletedNotification(communityId, "Community permission deleted", "A token permission has been removed")
method onCommunityTokenPermissionCreated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) =
if tokenPermission.`type` == TokenPermissionType.BecomeMember:
let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission)
self.view.tokenPermissionsModel.addItem(tokenPermissionItem)
if tokenPermissionItem.tokenCriteriaMet:
self.view.setAllTokenRequirementsMet(true)
self.view.tokenPermissionsModel.addItem(tokenPermissionItem)
self.view.setRequiresTokenPermissionToJoin(true)
singletonInstance.globalEvents.showCommunityTokenPermissionCreatedNotification(communityId, "Community permission created", "A token permission has been added")
method onCommunityTokenPermissionUpdated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) =
@ -784,6 +794,23 @@ method onCommunityTokenPermissionUpdateFailed*(self: Module, communityId: string
method onCommunityTokenPermissionDeletionFailed*(self: Module, communityId: string) =
singletonInstance.globalEvents.showCommunityTokenPermissionDeletionFailedNotification(communityId, "Failed to delete community permission", "Something went wrong")
method onCommunityTokenMetadataAdded*(self: Module, communityId: string, tokenMetadata: CommunityTokensMetadataDto) =
let tokenListItem = initTokenListItem(
key = tokenMetadata.symbol,
name = tokenMetadata.name,
symbol = tokenMetadata.symbol,
color = "", # tokenMetadata doesn't provide a color
image = tokenMetadata.image,
category = ord(TokenListItemCategory.Community)
)
if tokenMetadata.tokenType == community_dto.TokenType.ERC721 and not self.view.collectiblesListModel().hasItem(tokenMetadata.symbol):
self.view.collectiblesListModel.addItems(@[tokenListItem])
return
if tokenMetadata.tokenType == community_dto.TokenType.ERC20 and not self.view.tokenListModel().hasItem(tokenMetadata.symbol):
self.view.tokenListModel.addItems(@[tokenListItem])
method onMarkAllMessagesRead*(self: Module, chatId: string) =
self.updateBadgeNotifications(chatId, hasUnreadMessages=false, unviewedMentionsCount=0)
let chatDetails = self.controller.getChatDetails(chatId)

View File

@ -32,6 +32,8 @@ QtObject:
collectiblesListModel: TokenListModel
collectiblesListModelVariant: QVariant
allTokenRequirementsMet: bool
requiresTokenPermissionToJoin: bool
amIMember: bool
proc delete*(self: View) =
self.model.delete
@ -74,6 +76,8 @@ QtObject:
result.tokenListModelVariant = newQVariant(result.tokenListModel)
result.collectiblesListModel = newTokenListModel()
result.collectiblesListModelVariant = newQVariant(result.collectiblesListModel)
result.amIMember = false
result.requiresTokenPermissionToJoin = false
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -381,6 +385,36 @@ QtObject:
proc deleteCommunityTokenPermission*(self: View, communityId: string, permissionId: string) {.slot.} =
self.delegate.deleteCommunityTokenPermission(communityId, permissionId)
proc requiresTokenPermissionToJoinChanged*(self: View) {.signal.}
proc getRequiresTokenPermissionToJoin(self: View): bool {.slot.} =
return self.requiresTokenPermissionToJoin
proc setRequiresTokenPermissionToJoin*(self: View, value: bool) =
if (value == self.requiresTokenPermissionToJoin):
return
self.requiresTokenPermissionToJoin = value
self.requiresTokenPermissionToJoinChanged()
QtProperty[bool] requiresTokenPermissionToJoin:
read = getRequiresTokenPermissionToJoin
notify = requiresTokenPermissionToJoinChanged
proc getAmIMember*(self: View): bool {.slot.} =
return self.amIMember
proc amIMemberChanged*(self: View) {.signal.}
proc setAmIMember*(self: View, value: bool) =
if (value == self.amIMember):
return
self.amIMember = value
self.amIMemberChanged()
QtProperty[bool] amIMember:
read = getAmIMember
notify = amIMemberChanged
proc getAllTokenRequirementsMet*(self: View): bool {.slot.} =
return self.allTokenRequirementsMet

View File

@ -78,6 +78,12 @@ QtObject:
proc getItems*(self: TokenCriteriaModel): seq[TokenCriteriaItem] =
return self.items
proc setItems*(self: TokenCriteriaModel, items: seq[TokenCriteriaItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
proc addItem*(self: TokenCriteriaModel, item: TokenCriteriaItem) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete

View File

@ -35,13 +35,24 @@ QtObject:
read = getCount
notify = countChanged
proc setItems*(self: TokenlistModel, items: seq[TokenListItem]) =
proc setItems*(self: TokenListModel, items: seq[TokenListItem]) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
proc addItems*(self: TokenlistModel, items: seq[TokenListItem]) =
proc hasItem*(self: TokenListModel, symbol: string): bool =
for item in self.items:
if item.getSymbol() == symbol:
return true
return false
proc getItem*(self: TokenListModel, symbol: string): TokenListItem =
for item in self.items:
if item.getSymbol() == symbol:
return item
proc addItems*(self: TokenListModel, items: seq[TokenListItem]) =
if(items.len == 0):
return

View File

@ -1,5 +1,6 @@
import NimQml, Tables
import token_permission_item
import token_criteria_model
type
ModelRole {.pure.} = enum
@ -36,7 +37,7 @@ QtObject:
}.toTable
proc countChanged(self: TokenPermissionsModel) {.signal.}
proc getCount(self: TokenPermissionsModel): int {.slot.} =
proc getCount*(self: TokenPermissionsModel): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
@ -107,9 +108,8 @@ QtObject:
if(idx == -1):
return
self.items[idx].id = permissionId
self.items[idx].`type` = item.`type`
self.items[idx].tokenCriteria = item.tokenCriteria
self.items[idx].tokenCriteria.setItems(item.tokenCriteria.getItems())
self.items[idx].isPrivate = item.isPrivate
let index = self.createIndex(idx, 0, nil)

View File

@ -64,6 +64,7 @@ type CommunityTokensMetadataDto* = object
description*: string
image*: string
symbol*: string
name*: string
tokenType*: TokenType
type CommunityDto* = object
@ -165,6 +166,7 @@ proc toCommunityTokensMetadataDto*(jsonObj: JsonNode): CommunityTokensMetadataDt
discard jsonObj.getProp("description", result.description)
discard jsonObj.getProp("image", result.image)
discard jsonObj.getProp("symbol", result.symbol)
discard jsonObj.getProp("name", result.name)
var tokenTypeInt: int
discard jsonObj.getProp("tokenType", tokenTypeInt)
result.tokenType = intToEnum(tokenTypeInt, TokenType.ERC721)

View File

@ -21,8 +21,6 @@ export community_dto
logScope:
topics = "community-service"
include ../../common/json_utils
type
CommunityArgs* = ref object of Args
community*: CommunityDto
@ -90,6 +88,10 @@ type
tokenPermission*: CommunityTokenPermissionDto
error*: string
CommunityTokenMetadataArgs* = ref object of Args
communityId*: string
tokenMetadata*: CommunityTokensMetadataDto
CommunityTokenPermissionRemovedArgs* = ref object of Args
communityId*: string
permissionId*: string
@ -156,6 +158,7 @@ const SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED* = "communityTokenPermissionUpda
const SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED* = "communityTokenPermissionUpdateFailed"
const SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED* = "communityTokenPermissionDeleted"
const SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED* = "communityTokenPermissionDeletionFailed"
const SIGNAL_COMMUNITY_TOKEN_METADATA_ADDED* = "communityTokenMetadataAdded"
const SIGNAL_CURATED_COMMUNITIES_LOADING* = "curatedCommunitiesLoading"
const SIGNAL_CURATED_COMMUNITIES_LOADED* = "curatedCommunitiesLoaded"
@ -357,6 +360,14 @@ QtObject:
return idx
return -1
proc findIndexBySymbol(symbol: string, tokens: seq[CommunityTokensMetadataDto]): int =
var idx = -1
for token in tokens:
inc idx
if(token.symbol == symbol):
return idx
return -1
proc saveUpdatedCommunity(self: Service, community: var CommunityDto) =
# Community data we get from the signals and responses don't contgain the pending requests
# therefore, we must keep the old one
@ -512,6 +523,15 @@ QtObject:
self.events.emit(SIGNAL_COMMUNITY_MEMBERS_CHANGED,
CommunityMembersArgs(communityId: community.id, members: community.members))
# token metadata was added
if community.communityTokensMetadata.len > prev_community.communityTokensMetadata.len:
for tokenMetadata in community.communityTokensMetadata:
if findIndexBySymbol(tokenMetadata.symbol, prev_community.communityTokensMetadata) == -1:
self.communities[community.id].communityTokensMetadata.add(tokenMetadata)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_METADATA_ADDED,
CommunityTokenMetadataArgs(communityId: community.id,
tokenMetadata: tokenMetadata))
# tokenPermission was added
if community.tokenPermissions.len > prev_community.tokenPermissions.len:
for id, tokenPermission in community.tokenPermissions:

View File

@ -5,14 +5,18 @@ import QtQuick.Layouts 1.14
import utils 1.0
import "views"
import "views/communities"
import "stores"
import "popups/community"
import AppLayouts.Chat.stores 1.0
StackLayout {
id: root
property RootStore rootStore
readonly property var contactsStore: rootStore.contactsStore
readonly property var permissionsStore: rootStore.permissionsStore
property var emojiPopup
property var stickersPopup
@ -37,28 +41,69 @@ StackLayout {
}
}
ChatView {
id: chatView
emojiPopup: root.emojiPopup
stickersPopup: root.stickersPopup
contactsStore: root.contactsStore
rootStore: root.rootStore
membershipRequestPopup: membershipRequestPopupComponent
Loader {
onCommunityInfoButtonClicked: root.currentIndex = 1
onCommunityManageButtonClicked: root.currentIndex = 1
readonly property var chatItem: root.rootStore.chatCommunitySectionModule
sourceComponent: chatItem.isCommunity() && chatItem.requiresTokenPermissionToJoin && !chatItem.amIMember ? joinCommunityViewComponent : chatViewComponent
}
onImportCommunityClicked: {
root.importCommunityClicked();
Component {
id: joinCommunityViewComponent
JoinCommunityView {
id: joinCommunityView
readonly property var communityData: root.rootStore.mainModuleInst ? root.rootStore.mainModuleInst.activeSection || {} : {}
name: communityData.name
communityDesc: communityData.description
color: communityData.color
image: communityData.image
membersCount: communityData.members.count
accessType: communityData.access
joinCommunity: true
amISectionAdmin: communityData.amISectionAdmin
communityItemsModel: root.rootStore.communityItemsModel
requirementsMet: root.permissionsStore.allTokenRequirementsMet
communityHoldingsModel: root.permissionsStore.permissionsModel
assetsModel: root.rootStore.assetsModel
collectiblesModel: root.rootStore.collectiblesModel
isInvitationPending: root.rootStore.isCommunityRequestPending(communityData.id)
Connections {
target: root.rootStore.communitiesModuleInst
function onCommunityAccessRequested(communityId: string) {
if (communityId === joinCommunityView.communityData.id) {
joinCommunityView.isInvitationPending = root.rootStore.isCommunityRequestPending(communityData.id)
}
}
}
}
onCreateCommunityClicked: {
root.createCommunityClicked();
}
onProfileButtonClicked: {
root.profileButtonClicked()
}
onOpenAppSearch: {
root.openAppSearch()
}
Component {
id: chatViewComponent
ChatView {
id: chatView
emojiPopup: root.emojiPopup
stickersPopup: root.stickersPopup
contactsStore: root.contactsStore
rootStore: root.rootStore
membershipRequestPopup: membershipRequestPopupComponent
onCommunityInfoButtonClicked: root.currentIndex = 1
onCommunityManageButtonClicked: root.currentIndex = 1
onImportCommunityClicked: {
root.importCommunityClicked();
}
onCreateCommunityClicked: {
root.createCommunityClicked();
}
onProfileButtonClicked: {
root.profileButtonClicked()
}
onOpenAppSearch: {
root.openAppSearch()
}
}
}

View File

@ -11,6 +11,8 @@ QtObject {
readonly property bool isOwner: false
readonly property bool allTokenRequirementsMet: chatCommunitySectionModuleInst.allTokenRequirementsMet
readonly property QtObject _d: QtObject {
id: d

View File

@ -1,6 +1,7 @@
import QtQuick 2.13
import utils 1.0
import SortFilterProxyModel 0.2
import StatusQ.Core.Utils 0.1 as StatusQUtils
import shared.stores 1.0
@ -30,6 +31,57 @@ QtObject {
// Each `ChatLayout` has its own chatCommunitySectionModule
// (on the backend chat and community sections share the same module since they are actually the same)
property var chatCommunitySectionModule
property var communityItemsModel: chatCommunitySectionModule.model
property var assetsModel: SortFilterProxyModel {
sourceModel: chatCommunitySectionModule.tokenList
proxyRoles: ExpressionRole {
// list of symbols for which pngs are stored to avoid
// accessing not existing resources and providing
// default icon
readonly property var pngs: [
"aKNC", "AST", "BLT", "CND", "DNT", "EQUAD", "HEZ", "LOOM", "MTH",
"PAY", "RCN", "SALT", "STRK", "TRST", "WBTC", "AKRO", "aSUSD", "BLZ",
"COB", "DPY", "ETH2x-FLI", "HST", "LPT", "MTL", "PBTC", "RDN", "SAN",
"STT", "TRX", "WETH", "0-native", "aLEND", "ATMChain", "BNB", "COMP",
"DRT", "ETHOS", "HT", "LRC", "MYB", "PLR", "renBCH", "SNGLS", "STX",
"TUSD", "WINGS", "0XBTC", "aLINK", "aTUSD", "BNT", "CUSTOM-TOKEN",
"DTA", "ETH", "ICN", "MANA", "NEXO", "POE", "renBTC", "SNM", "SUB",
"UBT", "WTC", "1ST", "aMANA", "aUSDC", "BQX", "CVC", "EDG", "EVX",
"ICOS", "MCO", "NEXXO", "POLY", "REN", "SNT", "SUPR", "UKG", "XAUR",
"aBAT", "AMB", "aUSDT", "BRLN", "DAI", "EDO", "FUEL", "IOST", "MDA",
"NMR", "POWR", "renZEC", "SNX", "SUSD", "UNI", "XPA", "ABT", "aMKR",
"aWBTC", "BTM", "DATA", "EKG", "FUN", "KDO", "MET", "NPXS", "PPP",
"REP", "SOCKS", "TAAS", "UPP", "XRL", "aBUSD", "AMPL", "aYFI", "BTU",
"DAT", "EKO", "FXC", "KIN", "MFG", "OGN", "PPT", "REQ", "SPANK",
"TAUD", "USDC", "XUC", "ABYSS", "ANT", "aZRX", "CDAI", "DCN", "ELF",
"GDC", "KNC", "MGO", "OMG", "PT", "RHOC", "SPIKE", "TCAD", "USDS",
"ZRX", "aDAI", "APPC", "BAL", "CDT", "DEFAULT-TOKEN", "EMONA", "GEN",
"Kudos", "MKR", "OST", "QKC", "RLC", "SPN", "TGBP", "USDT", "ZSC",
"aENJ", "aREN", "BAM", "Centra", "DGD", "ENG", "GNO", "LEND", "MLN",
"OTN", "QRL", "ROL", "STORJ", "TKN", "VERI", "AE", "aREP", "BAND",
"CFI", "DGX", "ENJ", "GNT", "LINK", "MOC", "PAXG", "QSP", "R",
"STORM", "TKX", "VIB", "aETH", "aSNX", "BAT", "CK", "DLT", "EOS",
"GRID", "LISK", "MOD", "PAX", "RAE", "SAI", "ST", "TNT", "WABI"
]
function icon(symbol) {
if (pngs.indexOf(symbol) !== -1)
return Style.png("tokens/" + symbol)
return Style.png("tokens/DEFAULT-TOKEN")
}
name: "iconSource"
expression: !!model.icon ? model.icon : icon(model.symbol)
}
}
property var collectiblesModel: chatCommunitySectionModule.collectiblesModel
// Since qml component doesn't follow encaptulation from the backend side, we're introducing
// a method which will return appropriate chat content module for selected chat/channel
function currentChatContentModule(){
@ -170,9 +222,6 @@ QtObject {
stickersModule: stickersModuleInst
}
property var assetsModel: chatCommunitySectionModule.tokenList
property var collectiblesModel: chatCommunitySectionModule.collectiblesModel
function sendSticker(channelId, hash, replyTo, pack, url) {
stickersModuleInst.send(channelId, hash, replyTo, pack, url)
}

View File

@ -257,52 +257,8 @@ StatusSectionLayout {
// method is used in wallet (constructing filename from asset's
// symbol) and is intended to be replaced by more robust
// solution soon.
assetsModel: SortFilterProxyModel {
sourceModel: rootStore.assetsModel
proxyRoles: ExpressionRole {
// list of symbols for which pngs are stored to avoid
// accessing not existing resources and providing
// default icon
readonly property var pngs: [
"aKNC", "AST", "BLT", "CND", "DNT", "EQUAD", "HEZ", "LOOM", "MTH",
"PAY", "RCN", "SALT", "STRK", "TRST", "WBTC", "AKRO", "aSUSD", "BLZ",
"COB", "DPY", "ETH2x-FLI", "HST", "LPT", "MTL", "PBTC", "RDN", "SAN",
"STT", "TRX", "WETH", "0-native", "aLEND", "ATMChain", "BNB", "COMP",
"DRT", "ETHOS", "HT", "LRC", "MYB", "PLR", "renBCH", "SNGLS", "STX",
"TUSD", "WINGS", "0XBTC", "aLINK", "aTUSD", "BNT", "CUSTOM-TOKEN",
"DTA", "ETH", "ICN", "MANA", "NEXO", "POE", "renBTC", "SNM", "SUB",
"UBT", "WTC", "1ST", "aMANA", "aUSDC", "BQX", "CVC", "EDG", "EVX",
"ICOS", "MCO", "NEXXO", "POLY", "REN", "SNT", "SUPR", "UKG", "XAUR",
"aBAT", "AMB", "aUSDT", "BRLN", "DAI", "EDO", "FUEL", "IOST", "MDA",
"NMR", "POWR", "renZEC", "SNX", "SUSD", "UNI", "XPA", "ABT", "aMKR",
"aWBTC", "BTM", "DATA", "EKG", "FUN", "KDO", "MET", "NPXS", "PPP",
"REP", "SOCKS", "TAAS", "UPP", "XRL", "aBUSD", "AMPL", "aYFI", "BTU",
"DAT", "EKO", "FXC", "KIN", "MFG", "OGN", "PPT", "REQ", "SPANK",
"TAUD", "USDC", "XUC", "ABYSS", "ANT", "aZRX", "CDAI", "DCN", "ELF",
"GDC", "KNC", "MGO", "OMG", "PT", "RHOC", "SPIKE", "TCAD", "USDS",
"ZRX", "aDAI", "APPC", "BAL", "CDT", "DEFAULT-TOKEN", "EMONA", "GEN",
"Kudos", "MKR", "OST", "QKC", "RLC", "SPN", "TGBP", "USDT", "ZSC",
"aENJ", "aREN", "BAM", "Centra", "DGD", "ENG", "GNO", "LEND", "MLN",
"OTN", "QRL", "ROL", "STORJ", "TKN", "VERI", "AE", "aREP", "BAND",
"CFI", "DGX", "ENJ", "GNT", "LINK", "MOC", "PAXG", "QSP", "R",
"STORM", "TKX", "VIB", "aETH", "aSNX", "BAT", "CK", "DLT", "EOS",
"GRID", "LISK", "MOD", "PAX", "RAE", "SAI", "ST", "TNT", "WABI"
]
function icon(symbol) {
if (pngs.indexOf(symbol) !== -1)
return Style.png("tokens/" + symbol)
return Style.png("tokens/DEFAULT-TOKEN")
}
name: "iconSource"
expression: !!model.icon ? model.icon : icon(model.symbol)
}
}
assetsModel: rootStore.assetsModel
collectiblesModel: rootStore.collectiblesModel
channelsModel: rootStore.chatCommunitySectionModule.model

2
vendor/status-go vendored

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