[Refactoring] make tokenAdaptor single for each wallet session #6322

pull/6322/head
Krypto Pank 2 years ago
parent cd919ac11f
commit 97672a4ee6
  1. 4
      AlphaWallet/AppCoordinator.swift
  2. 2
      AlphaWallet/Tokens/Coordinators/FungibleTokenCoordinator.swift
  3. 2
      AlphaWallet/Tokens/Coordinators/SingleChainTokenCoordinator.swift
  4. 4
      AlphaWallet/Tokens/ViewModels/FungibleTokenDetailsViewModel.swift
  5. 4
      AlphaWallet/Transfer/ViewModels/TransactionConfirmation/DappOrWalletConnectTransactionViewModel.swift
  6. 6
      AlphaWallet/Transfer/ViewModels/TransactionConfirmation/SendFungiblesTransactionViewModel.swift
  7. 15
      AlphaWalletTests/Factories/FakeSessionsProvider.swift
  8. 9
      AlphaWalletTests/Factories/WalletSession.swift
  9. 4
      AlphaWalletTests/Tokens/Helpers/TokenAdaptorTest.swift
  10. 6
      AlphaWalletTests/ViewControllers/PaymentCoordinatorTests.swift
  11. 3
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Activities/ActivitiesService.swift
  12. 10
      modules/AlphaWalletFoundation/AlphaWalletFoundation/TokenScriptClient/EventSource.swift
  13. 68
      modules/AlphaWalletFoundation/AlphaWalletFoundation/TokenScriptClient/Models/TokenScriptSupportable.swift
  14. 170
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/Helpers/TokenAdaptor.swift
  15. 27
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/TokensProcessingPipeline.swift
  16. 16
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Tokens/Types/TokenViewModel.swift
  17. 5
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Types/Session.swift
  18. 7
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Types/SessionsProvider.swift

@ -594,6 +594,7 @@ class AppCoordinator: NSObject, Coordinator {
analytics: analytics, analytics: analytics,
blockchainsProvider: blockchainsProvider, blockchainsProvider: blockchainsProvider,
tokensDataStore: tokensDataStore, tokensDataStore: tokensDataStore,
eventsDataStore: eventsDataStore,
assetDefinitionStore: assetDefinitionStore, assetDefinitionStore: assetDefinitionStore,
reachability: reachability, reachability: reachability,
wallet: wallet) wallet: wallet)
@ -614,7 +615,8 @@ class AppCoordinator: NSObject, Coordinator {
coinTickersFetcher: coinTickersFetcher, coinTickersFetcher: coinTickersFetcher,
assetDefinitionStore: assetDefinitionStore, assetDefinitionStore: assetDefinitionStore,
eventsDataStore: eventsDataStore, eventsDataStore: eventsDataStore,
currencyService: currencyService) currencyService: currencyService,
sessionsProvider: sessionsProvider)
pipeline.start() pipeline.start()

@ -154,7 +154,7 @@ extension FungibleTokenCoordinator: FungibleTokenDetailsViewControllerDelegate {
func didTap(action: TokenInstanceAction, token: Token, in viewController: FungibleTokenDetailsViewController) { func didTap(action: TokenInstanceAction, token: Token, in viewController: FungibleTokenDetailsViewController) {
guard let navigationController = viewController.navigationController else { return } guard let navigationController = viewController.navigationController else { return }
let tokenHolder = token.getTokenHolder(assetDefinitionStore: assetDefinitionStore, forWallet: session.account) let tokenHolder = session.tokenAdaptor.getTokenHolder(token: token)
delegate?.didPress(for: .send(type: .tokenScript(action: action, token: token, tokenHolder: tokenHolder)), viewController: navigationController, in: self) delegate?.didPress(for: .send(type: .tokenScript(action: action, token: token, tokenHolder: tokenHolder)), viewController: navigationController, in: self)
} }

@ -112,7 +112,7 @@ class SingleChainTokenCoordinator: Coordinator {
} }
private func showTokenInstanceActionView(forAction action: TokenInstanceAction, fungibleTokenObject token: Token, navigationController: UINavigationController) { private func showTokenInstanceActionView(forAction action: TokenInstanceAction, fungibleTokenObject token: Token, navigationController: UINavigationController) {
let tokenHolder = token.getTokenHolder(assetDefinitionStore: assetDefinitionStore, forWallet: session.account) let tokenHolder = session.tokenAdaptor.getTokenHolder(token: token)
delegate?.didPress(for: .send(type: .tokenScript(action: action, token: token, tokenHolder: tokenHolder)), viewController: navigationController, in: self) delegate?.didPress(for: .send(type: .tokenScript(action: action, token: token, tokenHolder: tokenHolder)), viewController: navigationController, in: self)
} }
} }

@ -24,7 +24,7 @@ final class FungibleTokenDetailsViewModel {
.map { $0?.balance.ticker } .map { $0?.balance.ticker }
.eraseToAnyPublisher() .eraseToAnyPublisher()
}() }()
private lazy var tokenHolder: TokenHolder = token.getTokenHolder(assetDefinitionStore: assetDefinitionStore, forWallet: session.account) private lazy var tokenHolder: TokenHolder = session.tokenAdaptor.getTokenHolder(token: token)
private let session: WalletSession private let session: WalletSession
private let assetDefinitionStore: AssetDefinitionStore private let assetDefinitionStore: AssetDefinitionStore
private let tokenActionsProvider: SupportedTokenActionsProvider private let tokenActionsProvider: SupportedTokenActionsProvider
@ -81,7 +81,7 @@ final class FungibleTokenDetailsViewModel {
} }
private func buildTokenActions() -> [TokenInstanceAction] { private func buildTokenActions() -> [TokenInstanceAction] {
let xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore) let xmlHandler = session.tokenAdaptor.xmlHandler(token: token)
let actionsFromTokenScript = xmlHandler.actions let actionsFromTokenScript = xmlHandler.actions
infoLog("[TokenScript] actions names: \(actionsFromTokenScript.map(\.name))") infoLog("[TokenScript] actions names: \(actionsFromTokenScript.map(\.name))")
if actionsFromTokenScript.isEmpty { if actionsFromTokenScript.isEmpty {

@ -124,7 +124,7 @@ extension TransactionConfirmationViewModel {
case .nativeCryptocurrency: case .nativeCryptocurrency:
symbol = transactionType.tokenObject.symbol symbol = transactionType.tokenObject.symbol
case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875: case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875:
symbol = transactionType.tokenObject.symbolInPluralForm(withAssetDefinitionStore: assetDefinitionStore) symbol = session.tokenAdaptor.tokenScriptOverrides(token: transactionType.tokenObject).symbolInPluralForm
} }
let newBalance = NumberFormatter.shortCrypto.string(for: newBalance) ?? "-" let newBalance = NumberFormatter.shortCrypto.string(for: newBalance) ?? "-"
@ -138,7 +138,7 @@ extension TransactionConfirmationViewModel {
case .nativeCryptocurrency: case .nativeCryptocurrency:
symbol = transactionType.tokenObject.symbol symbol = transactionType.tokenObject.symbol
case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875: case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875:
symbol = transactionType.tokenObject.symbolInPluralForm(withAssetDefinitionStore: assetDefinitionStore) symbol = session.tokenAdaptor.tokenScriptOverrides(token: transactionType.tokenObject).symbolInPluralForm
} }
let balance = NumberFormatter.shortCrypto.string(for: balance) let balance = NumberFormatter.shortCrypto.string(for: balance)

@ -144,7 +144,7 @@ extension TransactionConfirmationViewModel {
case .nativeCryptocurrency: case .nativeCryptocurrency:
symbol = transactionType.tokenObject.symbol symbol = transactionType.tokenObject.symbol
case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875: case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875:
symbol = transactionType.tokenObject.symbolInPluralForm(withAssetDefinitionStore: assetDefinitionStore) symbol = session.tokenAdaptor.tokenScriptOverrides(token: transactionType.tokenObject).symbolInPluralForm
} }
//TODO: extract to constants //TODO: extract to constants
@ -187,7 +187,7 @@ extension TransactionConfirmationViewModel {
case .nativeCryptocurrency: case .nativeCryptocurrency:
symbol = transactionType.tokenObject.symbol symbol = transactionType.tokenObject.symbol
case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875: case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875:
symbol = transactionType.tokenObject.symbolInPluralForm(withAssetDefinitionStore: assetDefinitionStore) symbol = session.tokenAdaptor.tokenScriptOverrides(token: transactionType.tokenObject).symbolInPluralForm
} }
let newBalance = NumberFormatter.shortCrypto.string(for: newBalance) ?? "-" let newBalance = NumberFormatter.shortCrypto.string(for: newBalance) ?? "-"
@ -201,7 +201,7 @@ extension TransactionConfirmationViewModel {
case .nativeCryptocurrency: case .nativeCryptocurrency:
symbol = transactionType.tokenObject.symbol symbol = transactionType.tokenObject.symbol
case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875: case .erc20, .erc1155, .erc721, .erc721ForTickets, .erc875:
symbol = transactionType.tokenObject.symbolInPluralForm(withAssetDefinitionStore: assetDefinitionStore) symbol = session.tokenAdaptor.tokenScriptOverrides(token: transactionType.tokenObject).symbolInPluralForm
} }
let balance = NumberFormatter.alternateAmount.string(double: balance) let balance = NumberFormatter.alternateAmount.string(double: balance)

@ -44,7 +44,8 @@ extension FakeSessionsProvider {
tokensDataStore: FakeTokensDataStore(servers: servers), tokensDataStore: FakeTokensDataStore(servers: servers),
assetDefinitionStore: .make(), assetDefinitionStore: .make(),
reachability: FakeReachabilityManager(true), reachability: FakeReachabilityManager(true),
wallet: wallet) wallet: wallet,
eventsDataStore: FakeEventsDataStore(account: wallet))
provider.start() provider.start()
@ -62,6 +63,7 @@ class FakeSessionsProvider: SessionsProvider {
private let assetDefinitionStore: AssetDefinitionStore private let assetDefinitionStore: AssetDefinitionStore
private let reachability: ReachabilityManagerProtocol private let reachability: ReachabilityManagerProtocol
private let wallet: Wallet private let wallet: Wallet
private let eventsDataStore: NonActivityEventsDataStore
var sessions: AnyPublisher<ServerDictionary<WalletSession>, Never> { var sessions: AnyPublisher<ServerDictionary<WalletSession>, Never> {
return sessionsSubject.eraseToAnyPublisher() return sessionsSubject.eraseToAnyPublisher()
@ -84,7 +86,8 @@ class FakeSessionsProvider: SessionsProvider {
tokensDataStore: FakeTokensDataStore(servers: servers), tokensDataStore: FakeTokensDataStore(servers: servers),
assetDefinitionStore: .make(), assetDefinitionStore: .make(),
reachability: FakeReachabilityManager(false), reachability: FakeReachabilityManager(false),
wallet: .make()) wallet: .make(),
eventsDataStore: FakeEventsDataStore(account: .make()))
} }
init(config: Config, init(config: Config,
@ -93,8 +96,10 @@ class FakeSessionsProvider: SessionsProvider {
tokensDataStore: TokensDataStore, tokensDataStore: TokensDataStore,
assetDefinitionStore: AssetDefinitionStore, assetDefinitionStore: AssetDefinitionStore,
reachability: ReachabilityManagerProtocol, reachability: ReachabilityManagerProtocol,
wallet: Wallet) { wallet: Wallet,
eventsDataStore: NonActivityEventsDataStore) {
self.eventsDataStore = eventsDataStore
self.wallet = wallet self.wallet = wallet
self.reachability = reachability self.reachability = reachability
self.assetDefinitionStore = assetDefinitionStore self.assetDefinitionStore = assetDefinitionStore
@ -149,6 +154,7 @@ class FakeSessionsProvider: SessionsProvider {
server: blockchain.server, server: blockchain.server,
reachability: reachability) reachability: reachability)
} }
let tokenAdaptor = TokenAdaptor(assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, wallet: wallet)
return WalletSession( return WalletSession(
account: wallet, account: wallet,
@ -158,7 +164,8 @@ class FakeSessionsProvider: SessionsProvider {
ercTokenProvider: ercTokenProvider, ercTokenProvider: ercTokenProvider,
importToken: importToken, importToken: importToken,
blockchainProvider: blockchain, blockchainProvider: blockchain,
nftProvider: FakeNftProvider()) nftProvider: FakeNftProvider(),
tokenAdaptor: tokenAdaptor)
} }
public func session(for server: RPCServer) -> WalletSession? { public func session(for server: RPCServer) -> WalletSession? {

@ -9,13 +9,15 @@ extension WalletSession {
let blockchainProvider = RpcBlockchainProvider(server: server, analytics: analytics, params: .defaultParams(for: server)) let blockchainProvider = RpcBlockchainProvider(server: server, analytics: analytics, params: .defaultParams(for: server))
let ercTokenProvider = TokenProvider(account: account, blockchainProvider: blockchainProvider) let ercTokenProvider = TokenProvider(account: account, blockchainProvider: blockchainProvider)
let importToken: ImportToken = .make(server: server) let importToken: ImportToken = .make(server: server)
return WalletSession(account: account, server: server, config: config, analytics: analytics, ercTokenProvider: ercTokenProvider, importToken: importToken, blockchainProvider: blockchainProvider, nftProvider: FakeNftProvider()) let tokenAdaptor = TokenAdaptor(assetDefinitionStore: .make(), eventsDataStore: FakeEventsDataStore(account: account), wallet: account)
return WalletSession(account: account, server: server, config: config, analytics: analytics, ercTokenProvider: ercTokenProvider, importToken: importToken, blockchainProvider: blockchainProvider, nftProvider: FakeNftProvider(), tokenAdaptor: tokenAdaptor)
} }
static func make(account: Wallet = .make(), server: RPCServer = .main, config: Config = .make(), analytics: AnalyticsLogger = FakeAnalyticsService(), importToken: TokenImportable & TokenOrContractFetchable) -> WalletSession { static func make(account: Wallet = .make(), server: RPCServer = .main, config: Config = .make(), analytics: AnalyticsLogger = FakeAnalyticsService(), importToken: TokenImportable & TokenOrContractFetchable) -> WalletSession {
let blockchainProvider = RpcBlockchainProvider(server: server, analytics: analytics, params: .defaultParams(for: server)) let blockchainProvider = RpcBlockchainProvider(server: server, analytics: analytics, params: .defaultParams(for: server))
let ercTokenProvider = TokenProvider(account: account, blockchainProvider: blockchainProvider) let ercTokenProvider = TokenProvider(account: account, blockchainProvider: blockchainProvider)
return WalletSession(account: account, server: server, config: config, analytics: analytics, ercTokenProvider: ercTokenProvider, importToken: importToken, blockchainProvider: blockchainProvider, nftProvider: FakeNftProvider()) let tokenAdaptor = TokenAdaptor(assetDefinitionStore: .make(), eventsDataStore: FakeEventsDataStore(account: account), wallet: account)
return WalletSession(account: account, server: server, config: config, analytics: analytics, ercTokenProvider: ercTokenProvider, importToken: importToken, blockchainProvider: blockchainProvider, nftProvider: FakeNftProvider(), tokenAdaptor: tokenAdaptor)
} }
static func makeStormBirdSession(account: Wallet = .makeStormBird(), server: RPCServer, config: Config = .make(), analytics: AnalyticsLogger = FakeAnalyticsService()) -> WalletSession { static func makeStormBirdSession(account: Wallet = .makeStormBird(), server: RPCServer, config: Config = .make(), analytics: AnalyticsLogger = FakeAnalyticsService()) -> WalletSession {
@ -23,6 +25,7 @@ extension WalletSession {
let ercTokenProvider = TokenProvider(account: account, blockchainProvider: blockchainProvider) let ercTokenProvider = TokenProvider(account: account, blockchainProvider: blockchainProvider)
let importToken: ImportToken = .make(server: server) let importToken: ImportToken = .make(server: server)
return WalletSession(account: account, server: server, config: config, analytics: analytics, ercTokenProvider: ercTokenProvider, importToken: importToken, blockchainProvider: blockchainProvider, nftProvider: FakeNftProvider()) let tokenAdaptor = TokenAdaptor(assetDefinitionStore: .make(), eventsDataStore: FakeEventsDataStore(account: account), wallet: account)
return WalletSession(account: account, server: server, config: config, analytics: analytics, ercTokenProvider: ercTokenProvider, importToken: importToken, blockchainProvider: blockchainProvider, nftProvider: FakeNftProvider(), tokenAdaptor: tokenAdaptor)
} }
} }

@ -43,7 +43,7 @@ class TokenAdaptorTest: XCTestCase {
] ]
let assetDefinitionStore = AssetDefinitionStore.make() let assetDefinitionStore = AssetDefinitionStore.make()
let token = Token(contract: Constants.nullAddress) let token = Token(contract: Constants.nullAddress)
let bundles = TokenAdaptor(token: token, assetDefinitionStore: assetDefinitionStore, eventsDataStore: FakeEventsDataStore()).bundleTestsOnly(tokens: tokens) let bundles = TokenAdaptor(assetDefinitionStore: assetDefinitionStore, eventsDataStore: FakeEventsDataStore(), wallet: .make()).bundleTestsOnly(tokens: tokens, token: token)
XCTAssertEqual(bundles.count, 2) XCTAssertEqual(bundles.count, 2)
} }
@ -94,7 +94,7 @@ class TokenAdaptorTest: XCTestCase {
let assetDefinitionStore = AssetDefinitionStore.make() let assetDefinitionStore = AssetDefinitionStore.make()
let token = Token(contract: Constants.nullAddress) let token = Token(contract: Constants.nullAddress)
let bundles = TokenAdaptor(token: token, assetDefinitionStore: assetDefinitionStore, eventsDataStore: FakeEventsDataStore()).bundleTestsOnly(tokens: tokens) let bundles = TokenAdaptor(assetDefinitionStore: assetDefinitionStore, eventsDataStore: FakeEventsDataStore(), wallet: .make()).bundleTestsOnly(tokens: tokens, token: token)
XCTAssertEqual(bundles.count, 2) XCTAssertEqual(bundles.count, 2)
} }

@ -35,7 +35,8 @@ extension WalletDataProcessingPipeline {
tokensDataStore: tokensDataStore, tokensDataStore: tokensDataStore,
assetDefinitionStore: .make(), assetDefinitionStore: .make(),
reachability: FakeReachabilityManager(true), reachability: FakeReachabilityManager(true),
wallet: wallet) wallet: wallet,
eventsDataStore: FakeEventsDataStore(account: wallet))
sessionsProvider.start() sessionsProvider.start()
@ -60,7 +61,8 @@ extension WalletDataProcessingPipeline {
coinTickersFetcher: coinTickersFetcher, coinTickersFetcher: coinTickersFetcher,
assetDefinitionStore: .make(), assetDefinitionStore: .make(),
eventsDataStore: eventsDataStore, eventsDataStore: eventsDataStore,
currencyService: currencyService) currencyService: currencyService,
sessionsProvider: sessionsProvider)
let fetcher = WalletBalanceFetcher(wallet: wallet, tokensService: pipeline, currencyService: .make()) let fetcher = WalletBalanceFetcher(wallet: wallet, tokensService: pipeline, currencyService: .make())

@ -212,6 +212,7 @@ public class ActivitiesService: NSObject, ActivitiesServiceType {
let activitiesForThisCard: [ActivityTokenObjectTokenHolder] = events.compactMap { eachEvent in let activitiesForThisCard: [ActivityTokenObjectTokenHolder] = events.compactMap { eachEvent in
guard let token = tokensService.token(for: contract, server: server) else { return nil } guard let token = tokensService.token(for: contract, server: server) else { return nil }
guard let session = sessions[safe: token.server] else { return nil }
let implicitAttributes = generateImplicitAttributesForToken(forContract: contract, server: server, symbol: token.symbol) let implicitAttributes = generateImplicitAttributesForToken(forContract: contract, server: server, symbol: token.symbol)
let tokenAttributes = implicitAttributes let tokenAttributes = implicitAttributes
@ -235,7 +236,7 @@ public class ActivitiesService: NSObject, ActivitiesServiceType {
tokenHolders = [TokenHolder(tokens: [_token], contractAddress: token.contractAddress, hasAssetDefinition: true)] tokenHolders = [TokenHolder(tokens: [_token], contractAddress: token.contractAddress, hasAssetDefinition: true)]
} else { } else {
tokenHolders = token.getTokenHolders(assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet) tokenHolders = session.tokenAdaptor.getTokenHolders(token: token)
} }
tokensAndTokenHolders[token.addressAndRPCServer] = tokenHolders tokensAndTokenHolders[token.addressAndRPCServer] = tokenHolders
} }

@ -18,6 +18,7 @@ final class EventSource: NSObject {
private var cancellable = Set<AnyCancellable>() private var cancellable = Set<AnyCancellable>()
private let tokensService: TokenProvidable private let tokensService: TokenProvidable
private let eventFetcher: EventFetcher private let eventFetcher: EventFetcher
private let sessionsProvider: SessionsProvider
init(wallet: Wallet, init(wallet: Wallet,
tokensService: TokenProvidable, tokensService: TokenProvidable,
@ -26,6 +27,7 @@ final class EventSource: NSObject {
config: Config, config: Config,
sessionsProvider: SessionsProvider) { sessionsProvider: SessionsProvider) {
self.sessionsProvider = sessionsProvider
self.wallet = wallet self.wallet = wallet
self.assetDefinitionStore = assetDefinitionStore self.assetDefinitionStore = assetDefinitionStore
self.eventsDataStore = eventsDataStore self.eventsDataStore = eventsDataStore
@ -89,13 +91,9 @@ final class EventSource: NSObject {
for each in xmlHandler.attributesWithEventSource { for each in xmlHandler.attributesWithEventSource {
guard let eventOrigin = each.eventOrigin else { continue } guard let eventOrigin = each.eventOrigin else { continue }
guard let session = sessionsProvider.session(for: token.server) else { continue }
let tokenHolders = token.getTokenHolders( let tokenHolders = session.tokenAdaptor.getTokenHolders(token: token, isSourcedFromEvents: false)
assetDefinitionStore: assetDefinitionStore,
eventsDataStore: eventsDataStore,
forWallet: wallet,
isSourcedFromEvents: false)
let tokenIds = tokenHolders.flatMap { $0.tokenIds } let tokenIds = tokenHolders.flatMap { $0.tokenIds }
cards.append((eventOrigin, tokenIds)) cards.append((eventOrigin, tokenIds))

@ -19,15 +19,15 @@ public protocol TokenScriptSupportable {
var balanceNft: [TokenBalanceValue] { get } var balanceNft: [TokenBalanceValue] { get }
} }
public extension TokenScriptSupportable { public extension TokenAdaptor {
func title(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore) -> String { func title(token: TokenScriptSupportable) -> String {
let localizedNameFromAssetDefinition = XMLHandler(token: self, assetDefinitionStore: assetDefinitionStore).getLabel(fallback: name) let localizedNameFromAssetDefinition = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore).getLabel(fallback: token.name)
return title(withAssetDefinitionStore: assetDefinitionStore, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition, symbol: symbol) return title(token: token, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition, symbol: token.symbol)
} }
func titleInPluralForm(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore, eventsDataStore: NonActivityEventsDataStore, forWallet wallet: Wallet) -> String? { func titleInPluralFormOptional(token: TokenScriptSupportable) -> String? {
if let tokenHolders = getTokenHolders(assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet).first { if let tokenHolders = getTokenHolders(token: token).first {
guard let name = tokenHolders.tokens.first?.values.collectionValue?.name, name.nonEmpty else { return nil } guard let name = tokenHolders.tokens.first?.values.collectionValue?.name, name.nonEmpty else { return nil }
return name return name
} else { } else {
@ -35,13 +35,13 @@ public extension TokenScriptSupportable {
} }
} }
func titleInPluralForm(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore) -> String { func titleInPluralForm(token: TokenScriptSupportable) -> String {
let localizedNameFromAssetDefinition = XMLHandler(token: self, assetDefinitionStore: assetDefinitionStore).getNameInPluralForm(fallback: name) let localizedNameFromAssetDefinition = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore).getNameInPluralForm(fallback: token.name)
return title(withAssetDefinitionStore: assetDefinitionStore, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition, symbol: symbol) return title(token: token, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition, symbol: token.symbol)
} }
private func title(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore, localizedNameFromAssetDefinition: String, symbol: String) -> String { private func title(token: TokenScriptSupportable, localizedNameFromAssetDefinition: String, symbol: String) -> String {
let compositeName = compositeTokenName(forContract: contractAddress, fromContractName: name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) let compositeName = compositeTokenName(forContract: token.contractAddress, fromContractName: token.name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
if compositeName.isEmpty { if compositeName.isEmpty {
return symbol return symbol
} else { } else {
@ -68,31 +68,31 @@ public extension TokenScriptSupportable {
// //
// Use shortest of name and symbol, but abbreviate to 5 characters or less and capitalise. // Use shortest of name and symbol, but abbreviate to 5 characters or less and capitalise.
func shortTitleInPluralForm(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore, eventsDataStore: NonActivityEventsDataStore, forWallet wallet: Wallet) -> String { func shortTitleInPluralForm(token: TokenScriptSupportable) -> String {
func compositeTokenNameAndSymbol(symbol: String, name: String) -> String { func compositeTokenNameAndSymbol(symbol: String, name: String) -> String {
let daiSymbol = "DAI\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}" let daiSymbol = "DAI\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}"
//We could have just trimmed away all trailing \0, but this is faster and safer since only DAI seems to have this problem //We could have just trimmed away all trailing \0, but this is faster and safer since only DAI seems to have this problem
if daiSymbol == symbol { if daiSymbol == symbol {
return "\(valueBI) (DAI)".uppercased() return "\(token.valueBI) (DAI)".uppercased()
} else { } else {
return "\(valueBI) (\(symbol))".uppercased() return "\(token.valueBI) (\(symbol))".uppercased()
} }
} }
let xmlHandler = XMLHandler(token: self, assetDefinitionStore: assetDefinitionStore) let xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore)
func _compositeTokenName(fallback: String = "") -> String { func _compositeTokenName(fallback: String = "") -> String {
let localizedNameFromAssetDefinition = xmlHandler.getNameInPluralForm(fallback: fallback) let localizedNameFromAssetDefinition = xmlHandler.getNameInPluralForm(fallback: fallback)
return compositeTokenName(forContract: contractAddress, fromContractName: name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) return compositeTokenName(forContract: token.contractAddress, fromContractName: token.name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
} }
let localizedNameFromAssetDefinition = _compositeTokenName() let localizedNameFromAssetDefinition = _compositeTokenName()
let symbol = self.symbol(withAssetDefinitionStore: assetDefinitionStore, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) let symbol = self.symbol(token: token, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
if localizedNameFromAssetDefinition.isEmpty { if localizedNameFromAssetDefinition.isEmpty {
if let name = titleInPluralForm(withAssetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet) { if let name = titleInPluralFormOptional(token: token) {
return name return name
} else { } else {
let tokenName = _compositeTokenName(fallback: name) let tokenName = _compositeTokenName(fallback: token.name)
if tokenName.isEmpty { if tokenName.isEmpty {
return symbol return symbol
@ -117,28 +117,28 @@ public extension TokenScriptSupportable {
} }
} }
func shortTitleInPluralForm(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore) -> String { func shortTitleInPluralForm2(token: TokenScriptSupportable) -> String {
func compositeTokenNameAndSymbol(symbol: String, name: String) -> String { func compositeTokenNameAndSymbol(symbol: String, name: String) -> String {
let daiSymbol = "DAI\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}" let daiSymbol = "DAI\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}"
//We could have just trimmed away all trailing \0, but this is faster and safer since only DAI seems to have this problem //We could have just trimmed away all trailing \0, but this is faster and safer since only DAI seems to have this problem
if daiSymbol == symbol { if daiSymbol == symbol {
return "\(valueBI) (DAI)".uppercased() return "\(token.valueBI) (DAI)".uppercased()
} else { } else {
return "\(valueBI) (\(symbol))".uppercased() return "\(token.valueBI) (\(symbol))".uppercased()
} }
} }
let xmlHandler = XMLHandler(token: self, assetDefinitionStore: assetDefinitionStore) let xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore)
func _compositeTokenName(fallback: String = "") -> String { func _compositeTokenName(fallback: String = "") -> String {
let localizedNameFromAssetDefinition = xmlHandler.getNameInPluralForm(fallback: fallback) let localizedNameFromAssetDefinition = xmlHandler.getNameInPluralForm(fallback: fallback)
return compositeTokenName(forContract: contractAddress, fromContractName: name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) return compositeTokenName(forContract: token.contractAddress, fromContractName: token.name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
} }
let localizedNameFromAssetDefinition = _compositeTokenName() let localizedNameFromAssetDefinition = _compositeTokenName()
let symbol = self.symbol(withAssetDefinitionStore: assetDefinitionStore, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) let symbol = self.symbol(token: token, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
if localizedNameFromAssetDefinition.isEmpty { if localizedNameFromAssetDefinition.isEmpty {
let tokenName = _compositeTokenName(fallback: name) let tokenName = _compositeTokenName(fallback: token.name)
if tokenName.isEmpty { if tokenName.isEmpty {
return symbol return symbol
@ -162,22 +162,22 @@ public extension TokenScriptSupportable {
} }
} }
func symbolInPluralForm(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore) -> String { func symbolInPluralForm2(token: TokenScriptSupportable) -> String {
let localizedNameFromAssetDefinition = XMLHandler(token: self, assetDefinitionStore: assetDefinitionStore).getNameInPluralForm(fallback: name) let localizedNameFromAssetDefinition = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore).getNameInPluralForm(fallback: token.name)
return symbol(withAssetDefinitionStore: assetDefinitionStore, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) return symbol(token: token, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
} }
private func symbol(withAssetDefinitionStore assetDefinitionStore: AssetDefinitionStore, localizedNameFromAssetDefinition: String) -> String { private func symbol(token: TokenScriptSupportable, localizedNameFromAssetDefinition: String) -> String {
let compositeName = compositeTokenName(forContract: contractAddress, fromContractName: name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition) let compositeName = compositeTokenName(forContract: token.contractAddress, fromContractName: token.name, localizedNameFromAssetDefinition: localizedNameFromAssetDefinition)
if compositeName.isEmpty { if compositeName.isEmpty {
return symbol return token.symbol
} else { } else {
let daiSymbol = "DAI\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}" let daiSymbol = "DAI\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}"
//We could have just trimmed away all trailing \0, but this is faster and safer since only DAI seems to have this problem //We could have just trimmed away all trailing \0, but this is faster and safer since only DAI seems to have this problem
if daiSymbol == symbol { if daiSymbol == token.symbol {
return "DAI" return "DAI"
} else { } else {
return symbol return token.symbol
} }
} }
} }

@ -35,30 +35,68 @@ extension TokenHolderState {
extension TokenHolder: ObservableObject { } extension TokenHolder: ObservableObject { }
extension TokenScriptSupportable { public struct TokenAdaptor {
let assetDefinitionStore: AssetDefinitionStore
let eventsDataStore: NonActivityEventsDataStore
let wallet: Wallet
public func getTokenHolders(assetDefinitionStore: AssetDefinitionStore, eventsDataStore: NonActivityEventsDataStore, forWallet account: Wallet, isSourcedFromEvents: Bool = true) -> [TokenHolder] { public init(assetDefinitionStore: AssetDefinitionStore,
let tokenAdaptor = TokenAdaptor(token: self, assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore) eventsDataStore: NonActivityEventsDataStore,
return tokenAdaptor.getTokenHolders(forWallet: account, isSourcedFromEvents: isSourcedFromEvents) wallet: Wallet) {
self.wallet = wallet
self.assetDefinitionStore = assetDefinitionStore
self.eventsDataStore = eventsDataStore
}
public func xmlHandler(token: TokenScriptSupportable) -> XMLHandler {
return XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore)
}
public func tokenScriptOverrides(token: TokenScriptSupportable) -> TokenScriptOverrides {
return TokenScriptOverrides(token: token, tokenAdaptor: self)
} }
/// Generates token holder for fungible token, with id 1 //`sourceFromEvents`: We'll usually source from events if available, except when we are actually using this func to create the filter to fetch the events
public func getTokenHolder(assetDefinitionStore: AssetDefinitionStore, forWallet account: Wallet) -> TokenHolder { public func getTokenHolders(token: TokenScriptSupportable, isSourcedFromEvents: Bool = true) -> [TokenHolder] {
switch token.type {
case .nativeCryptocurrency, .erc20, .erc875, .erc721ForTickets:
return getNotSupportedByNonFungibleJsonTokenHolders(token: token)
case .erc721, .erc1155:
let tokenType = NonFungibleFromJsonSupportedTokenHandling(token: token)
switch tokenType {
case .supported:
return getSupportedByNonFungibleJsonTokenHolders(token: token, isSourcedFromEvents: isSourcedFromEvents)
case .notSupported:
return getNotSupportedByNonFungibleJsonTokenHolders(token: token)
}
}
}
public func getTokenHolder(token: TokenScriptSupportable) -> TokenHolder {
//TODO id 1 for fungibles. Might come back to bite us? //TODO id 1 for fungibles. Might come back to bite us?
let hardcodedTokenIdForFungibles = BigUInt(1) let hardcodedTokenIdForFungibles = BigUInt(1)
let xmlHandler = XMLHandler(token: self, assetDefinitionStore: assetDefinitionStore) let xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore)
//TODO Event support, if/when designed for fungibles //TODO Event support, if/when designed for fungibles
let values = xmlHandler.resolveAttributesBypassingCache( let values = xmlHandler.resolveAttributesBypassingCache(
withTokenIdOrEvent: .tokenId(tokenId: hardcodedTokenIdForFungibles), withTokenIdOrEvent: .tokenId(tokenId: hardcodedTokenIdForFungibles),
server: server, server: token.server,
account: account, account: wallet,
assetDefinitionStore: assetDefinitionStore) assetDefinitionStore: assetDefinitionStore)
let subscribablesForAttributeValues = values.values let subscribablesForAttributeValues = values.values
let allResolved = subscribablesForAttributeValues.allSatisfy { $0.subscribableValue?.value != nil } let allResolved = subscribablesForAttributeValues.allSatisfy { $0.subscribableValue?.value != nil }
let token = TokenScript.Token(tokenIdOrEvent: .tokenId(tokenId: hardcodedTokenIdForFungibles), tokenType: type, index: 0, name: name, symbol: symbol, status: .available, values: values) let tokenScriptToken = TokenScript.Token(
let tokenHolder = TokenHolder(tokens: [token], contractAddress: contractAddress, hasAssetDefinition: true) tokenIdOrEvent: .tokenId(tokenId: hardcodedTokenIdForFungibles),
tokenType: token.type,
index: 0,
name: token.name,
symbol: token.symbol,
status: .available,
values: values)
let tokenHolder = TokenHolder(tokens: [tokenScriptToken], contractAddress: token.contractAddress, hasAssetDefinition: true)
if allResolved { if allResolved {
//no-op //no-op
@ -73,41 +111,9 @@ extension TokenScriptSupportable {
return tokenHolder return tokenHolder
} }
}
public class TokenAdaptor {
private let token: TokenScriptSupportable
private let assetDefinitionStore: AssetDefinitionStore
private let eventsDataStore: NonActivityEventsDataStore
private let xmlHandler: XMLHandler
public init(token: TokenScriptSupportable,
assetDefinitionStore: AssetDefinitionStore,
eventsDataStore: NonActivityEventsDataStore) {
self.token = token
self.assetDefinitionStore = assetDefinitionStore
self.eventsDataStore = eventsDataStore
self.xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore)
}
//`sourceFromEvents`: We'll usually source from events if available, except when we are actually using this func to create the filter to fetch the events //TODO: is it possible to make single token holder with multiple token ids without making each token holder for each token id?
public func getTokenHolders(forWallet account: Wallet, isSourcedFromEvents: Bool = true) -> [TokenHolder] { private func getNotSupportedByNonFungibleJsonTokenHolders(token: TokenScriptSupportable) -> [TokenHolder] {
switch token.type {
case .nativeCryptocurrency, .erc20, .erc875, .erc721ForTickets:
return getNotSupportedByNonFungibleJsonTokenHolders(forWallet: account)
case .erc721, .erc1155:
let tokenType = NonFungibleFromJsonSupportedTokenHandling(token: token)
switch tokenType {
case .supported:
return getSupportedByNonFungibleJsonTokenHolders(forWallet: account, isSourcedFromEvents: isSourcedFromEvents)
case .notSupported:
return getNotSupportedByNonFungibleJsonTokenHolders(forWallet: account)
}
}
}
private func getNotSupportedByNonFungibleJsonTokenHolders(forWallet account: Wallet) -> [TokenHolder] {
var tokens = [TokenScript.Token]() var tokens = [TokenScript.Token]()
switch token.type { switch token.type {
case .erc875, .erc721ForTickets, .erc721, .erc1155, .nativeCryptocurrency: case .erc875, .erc721ForTickets, .erc721, .erc1155, .nativeCryptocurrency:
@ -116,62 +122,56 @@ public class TokenAdaptor {
let id = item.balance let id = item.balance
guard isNonZeroBalance(id, tokenType: token.type) else { continue } guard isNonZeroBalance(id, tokenType: token.type) else { continue }
if let tokenInt = BigUInt(id.drop0x, radix: 16) { if let tokenInt = BigUInt(id.drop0x, radix: 16) {
let server = self.token.server
//TODO Event support, if/when designed, for non-OpenSea. Probably need `distinct` or something to that effect //TODO Event support, if/when designed, for non-OpenSea. Probably need `distinct` or something to that effect
let token = getToken(name: self.token.name, symbol: self.token.symbol, forTokenIdOrEvent: .tokenId(tokenId: tokenInt), index: UInt16(index), inWallet: account, server: server) let token = getToken(name: token.name, symbol: token.symbol, forTokenIdOrEvent: .tokenId(tokenId: tokenInt), index: UInt16(index), token: token)
tokens.append(token) tokens.append(token)
} }
} }
return bundle(tokens: tokens) return bundle(tokens: tokens, token: token)
case .erc20: case .erc20:
//For fungibles, we have to get 1 token even if the balance.count is 0. Maybe we check value? No, even if value is 0, there might be attributes //For fungibles, we have to get 1 token even if the balance.count is 0. Maybe we check value? No, even if value is 0, there might be attributes
let tokenInt: BigUInt = .init(1) let tokenInt: BigUInt = .init(1)
let index = 0 let index = 0
let server = self.token.server
//TODO Event support, if/when designed, for non-OpenSea. Probably need `distinct` or something to that effect //TODO Event support, if/when designed, for non-OpenSea. Probably need `distinct` or something to that effect
let token = getToken(name: self.token.name, symbol: self.token.symbol, forTokenIdOrEvent: .tokenId(tokenId: tokenInt), index: UInt16(index), inWallet: account, server: server) let tokenScriptToken = getToken(name: token.name, symbol: token.symbol, forTokenIdOrEvent: .tokenId(tokenId: tokenInt), index: UInt16(index), token: token)
tokens.append(token) tokens.append(tokenScriptToken)
return bundle(tokens: tokens) return bundle(tokens: tokens, token: token)
} }
} }
private func getSupportedByNonFungibleJsonTokenHolders(forWallet account: Wallet, isSourcedFromEvents: Bool) -> [TokenHolder] { private func getSupportedByNonFungibleJsonTokenHolders(token: TokenScriptSupportable, isSourcedFromEvents: Bool) -> [TokenHolder] {
var tokens = [TokenScript.Token]() var tokens = [TokenScript.Token]()
for nonFungibleBalance in token.balanceNft.compactMap({ $0.nonFungibleBalance }) { for nonFungibleBalance in token.balanceNft.compactMap({ $0.nonFungibleBalance }) {
if let token = getTokenForNonFungible(nonFungible: nonFungibleBalance, inWallet: account, server: self.token.server, isSourcedFromEvents: isSourcedFromEvents, tokenType: self.token.type) { if let token = getTokenForNonFungible(nonFungible: nonFungibleBalance, token: token, isSourcedFromEvents: isSourcedFromEvents) {
tokens.append(token) tokens.append(token)
} }
} }
return bundle(tokens: tokens) return bundle(tokens: tokens, token: token)
} }
//NOTE: internal for testing purposes //NOTE: internal for testing purposes
public func bundleTestsOnly(tokens: [TokenScript.Token]) -> [TokenHolder] { public func bundleTestsOnly(tokens: [TokenScript.Token], token: TokenScriptSupportable) -> [TokenHolder] {
bundle(tokens: tokens) bundle(tokens: tokens, token: token)
} }
private func bundle(tokens: [TokenScript.Token]) -> [TokenHolder] { private func bundle(tokens: [TokenScript.Token], token: TokenScriptSupportable) -> [TokenHolder] {
switch token.type { switch token.type {
case .nativeCryptocurrency, .erc20, .erc875: case .nativeCryptocurrency, .erc20, .erc875:
if !tokens.isEmpty && tokens[0].isSpawnableMeetupContract { if !tokens.isEmpty && tokens[0].isSpawnableMeetupContract {
return tokens.sorted { $0.id < $1.id }.map { getTokenHolder(for: [$0]) } return tokens.sorted { $0.id < $1.id }.map { getTokenHolder(for: [$0], token: token) }
} else { } else {
break var tokenHolders: [TokenHolder] = []
for each in groupTokensByFields(tokens: tokens) {
for tokens in breakBundlesFurtherToHaveContinuousSeatRange(tokens: each) {
tokenHolders.append(getTokenHolder(for: tokens, token: token))
}
}
return sortBundlesUpcomingFirst(bundles: tokenHolders)
} }
case .erc721, .erc721ForTickets, .erc1155: case .erc721, .erc721ForTickets, .erc1155:
return tokens.map { getTokenHolder(for: [$0]) } return tokens.map { getTokenHolder(for: [$0], token: token) }
}
var tokenHolders: [TokenHolder] = []
let groups = groupTokensByFields(tokens: tokens)
for each in groups {
let results = breakBundlesFurtherToHaveContinuousSeatRange(tokens: each)
for tokens in results {
tokenHolders.append(getTokenHolder(for: tokens))
}
} }
tokenHolders = sortBundlesUpcomingFirst(bundles: tokenHolders)
return tokenHolders
} }
private func sortBundlesUpcomingFirst(bundles: [TokenHolder]) -> [TokenHolder] { private func sortBundlesUpcomingFirst(bundles: [TokenHolder]) -> [TokenHolder] {
@ -229,21 +229,25 @@ public class TokenAdaptor {
symbol: String, symbol: String,
forTokenIdOrEvent tokenIdOrEvent: TokenIdOrEvent, forTokenIdOrEvent tokenIdOrEvent: TokenIdOrEvent,
index: UInt16, index: UInt16,
inWallet account: Wallet, token: TokenScriptSupportable) -> TokenScript.Token {
server: RPCServer) -> TokenScript.Token {
xmlHandler.getToken( let xmlHandler = xmlHandler(token: token)
return xmlHandler.getToken(
name: name, name: name,
symbol: symbol, symbol: symbol,
fromTokenIdOrEvent: tokenIdOrEvent, fromTokenIdOrEvent: tokenIdOrEvent,
index: index, index: index,
inWallet: account, inWallet: wallet,
server: server, server: token.server,
tokenType: token.type, tokenType: token.type,
assetDefinitionStore: assetDefinitionStore) assetDefinitionStore: assetDefinitionStore)
} }
private func getFirstMatchingEvent(nonFungible: NonFungibleFromJson, inWallet account: Wallet, isSourcedFromEvents: Bool) -> EventInstanceValue? { private func getFirstMatchingEvent(nonFungible: NonFungibleFromJson,
token: TokenScriptSupportable,
isSourcedFromEvents: Bool) -> EventInstanceValue? {
let xmlHandler = xmlHandler(token: token)
if isSourcedFromEvents, let attributeWithEventSource = xmlHandler.attributesWithEventSource.first, let eventFilter = attributeWithEventSource.eventOrigin?.eventFilter, let eventName = attributeWithEventSource.eventOrigin?.eventName, let eventContract = attributeWithEventSource.eventOrigin?.contract { if isSourcedFromEvents, let attributeWithEventSource = xmlHandler.attributesWithEventSource.first, let eventFilter = attributeWithEventSource.eventOrigin?.eventFilter, let eventName = attributeWithEventSource.eventOrigin?.eventName, let eventContract = attributeWithEventSource.eventOrigin?.contract {
let filterName = eventFilter.name let filterName = eventFilter.name
let filterValue: String let filterValue: String
@ -252,7 +256,7 @@ public class TokenAdaptor {
case .tokenId: case .tokenId:
filterValue = eventFilter.value.replacingOccurrences(of: "${tokenId}", with: nonFungible.tokenId) filterValue = eventFilter.value.replacingOccurrences(of: "${tokenId}", with: nonFungible.tokenId)
case .ownerAddress: case .ownerAddress:
filterValue = eventFilter.value.replacingOccurrences(of: "${ownerAddress}", with: account.address.eip55String) filterValue = eventFilter.value.replacingOccurrences(of: "${ownerAddress}", with: wallet.address.eip55String)
case .label, .contractAddress, .symbol: case .label, .contractAddress, .symbol:
filterValue = eventFilter.value filterValue = eventFilter.value
} }
@ -276,7 +280,7 @@ public class TokenAdaptor {
return tokenIdOrEvent return tokenIdOrEvent
} }
private func getTokenForNonFungible(nonFungible: NonFungibleFromJson, inWallet account: Wallet, server: RPCServer, isSourcedFromEvents: Bool, tokenType: TokenType) -> TokenScript.Token? { private func getTokenForNonFungible(nonFungible: NonFungibleFromJson, token: TokenScriptSupportable, isSourcedFromEvents: Bool) -> TokenScript.Token? {
switch nonFungible.tokenType { switch nonFungible.tokenType {
case .erc721: case .erc721:
break break
@ -284,13 +288,14 @@ public class TokenAdaptor {
guard !nonFungible.value.isZero else { return nil } guard !nonFungible.value.isZero else { return nil }
} }
let event = getFirstMatchingEvent(nonFungible: nonFungible, inWallet: account, isSourcedFromEvents: isSourcedFromEvents) let event = getFirstMatchingEvent(nonFungible: nonFungible, token: token, isSourcedFromEvents: isSourcedFromEvents)
let tokenIdOrEvent: TokenIdOrEvent = getTokenIdOrEvent(event: event, nonFungible: nonFungible) let tokenIdOrEvent: TokenIdOrEvent = getTokenIdOrEvent(event: event, nonFungible: nonFungible)
let xmlHandler = xmlHandler(token: token)
var values = xmlHandler.resolveAttributesBypassingCache( var values = xmlHandler.resolveAttributesBypassingCache(
withTokenIdOrEvent: tokenIdOrEvent, withTokenIdOrEvent: tokenIdOrEvent,
server: token.server, server: token.server,
account: account, account: wallet,
assetDefinitionStore: assetDefinitionStore) assetDefinitionStore: assetDefinitionStore)
values.setTokenId(string: nonFungible.tokenId) values.setTokenId(string: nonFungible.tokenId)
@ -350,7 +355,8 @@ public class TokenAdaptor {
values: values) values: values)
} }
private func getTokenHolder(for tokens: [TokenScript.Token]) -> TokenHolder { private func getTokenHolder(for tokens: [TokenScript.Token], token: TokenScriptSupportable) -> TokenHolder {
let xmlHandler = xmlHandler(token: token)
return TokenHolder( return TokenHolder(
tokens: tokens, tokens: tokens,
contractAddress: token.contractAddress, contractAddress: token.contractAddress,

@ -73,14 +73,16 @@ public final class WalletDataProcessingPipeline: TokensProcessingPipeline {
return Publishers.Merge(whenCollectionHasChanged, initialSnapshot) return Publishers.Merge(whenCollectionHasChanged, initialSnapshot)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
private let sessionsProvider: SessionsProvider
public init(wallet: Wallet, tokensService: TokensService, coinTickersFetcher: CoinTickersFetcher, assetDefinitionStore: AssetDefinitionStore, eventsDataStore: NonActivityEventsDataStore, currencyService: CurrencyService) { public init(wallet: Wallet, tokensService: TokensService, coinTickersFetcher: CoinTickersFetcher, assetDefinitionStore: AssetDefinitionStore, eventsDataStore: NonActivityEventsDataStore, currencyService: CurrencyService, sessionsProvider: SessionsProvider) {
self.wallet = wallet self.wallet = wallet
self.currencyService = currencyService self.currencyService = currencyService
self.eventsDataStore = eventsDataStore self.eventsDataStore = eventsDataStore
self.tokensService = tokensService self.tokensService = tokensService
self.coinTickersFetcher = coinTickersFetcher self.coinTickersFetcher = coinTickersFetcher
self.assetDefinitionStore = assetDefinitionStore self.assetDefinitionStore = assetDefinitionStore
self.sessionsProvider = sessionsProvider
} }
public func tokens(for servers: [RPCServer]) -> [Token] { public func tokens(for servers: [RPCServer]) -> [Token] {
@ -151,17 +153,18 @@ public final class WalletDataProcessingPipeline: TokensProcessingPipeline {
} }
public func tokenHolders(for token: TokenIdentifiable) -> [TokenHolder] { public func tokenHolders(for token: TokenIdentifiable) -> [TokenHolder] {
tokenViewModel(for: token.contractAddress, server: token.server).flatMap { [assetDefinitionStore, eventsDataStore, wallet] in guard let session = sessionsProvider.session(for: token.server) else { return [] }
$0.getTokenHolders(assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet) let tokenAdaptor = session.tokenAdaptor
} ?? [] return tokenViewModel(for: token.contractAddress, server: token.server).flatMap { tokenAdaptor.getTokenHolders(token: $0) } ?? []
} }
public func tokenHoldersPublisher(for token: TokenIdentifiable) -> AnyPublisher<[TokenHolder], Never> { public func tokenHoldersPublisher(for token: TokenIdentifiable) -> AnyPublisher<[TokenHolder], Never> {
tokenViewModelPublisher(for: token.contractAddress, server: token.server) guard let session = sessionsProvider.session(for: token.server) else { return .empty() }
let tokenAdaptor = session.tokenAdaptor
return tokenViewModelPublisher(for: token.contractAddress, server: token.server)
.compactMap { $0 } .compactMap { $0 }
.map { [assetDefinitionStore, eventsDataStore, wallet] in .map { tokenAdaptor.getTokenHolders(token: $0) }
$0.getTokenHolders(assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet) .eraseToAnyPublisher()
}.eraseToAnyPublisher()
} }
public func token(for contract: AlphaWallet.Address) -> Token? { public func token(for contract: AlphaWallet.Address) -> Token? {
@ -219,7 +222,9 @@ public final class WalletDataProcessingPipeline: TokensProcessingPipeline {
private func applyTokenScriptOverrides(tokens: [TokenViewModel]) -> AnyPublisher<[TokenViewModel], Never> { private func applyTokenScriptOverrides(tokens: [TokenViewModel]) -> AnyPublisher<[TokenViewModel], Never> {
let overrides = tokens.map { token -> TokenViewModel in let overrides = tokens.map { token -> TokenViewModel in
let overrides = TokenScriptOverrides(token: token, assetDefinitionStore: assetDefinitionStore, wallet: wallet, eventsDataStore: eventsDataStore) guard let session = sessionsProvider.session(for: token.server) else { return token }
let overrides = session.tokenAdaptor.tokenScriptOverrides(token: token)
return token.override(tokenScriptOverrides: overrides) return token.override(tokenScriptOverrides: overrides)
} }
return .just(overrides) return .just(overrides)
@ -227,8 +232,8 @@ public final class WalletDataProcessingPipeline: TokensProcessingPipeline {
private func applyTokenScriptOverrides(token: TokenViewModel?) -> TokenViewModel? { private func applyTokenScriptOverrides(token: TokenViewModel?) -> TokenViewModel? {
guard let token = token else { return token } guard let token = token else { return token }
guard let session = sessionsProvider.session(for: token.server) else { return token }
let overrides = TokenScriptOverrides(token: token, assetDefinitionStore: assetDefinitionStore, wallet: wallet, eventsDataStore: eventsDataStore) let overrides = session.tokenAdaptor.tokenScriptOverrides(token: token)
return token.override(tokenScriptOverrides: overrides) return token.override(tokenScriptOverrides: overrides)
} }

@ -34,18 +34,18 @@ public struct TokenScriptOverrides {
public let hasNoBaseAssetDefinition: Bool public let hasNoBaseAssetDefinition: Bool
public let server: RPCServerOrAny? public let server: RPCServerOrAny?
init(token: TokenScriptSupportable, assetDefinitionStore: AssetDefinitionStore, wallet: Wallet, eventsDataStore: NonActivityEventsDataStore) { init(token: TokenScriptSupportable, tokenAdaptor: TokenAdaptor) {
let xmlHandler = XMLHandler(token: token, assetDefinitionStore: assetDefinitionStore) let xmlHandler = tokenAdaptor.xmlHandler(token: token)
self.symbolInPluralForm = token.symbolInPluralForm(withAssetDefinitionStore: assetDefinitionStore) self.symbolInPluralForm = tokenAdaptor.symbolInPluralForm2(token: token)
self.title = token.title(withAssetDefinitionStore: assetDefinitionStore) self.title = tokenAdaptor.title(token: token)
//NOTE: replace if needed //NOTE: replace if needed
switch token.type { switch token.type {
case .erc20, .nativeCryptocurrency: case .erc20, .nativeCryptocurrency:
self.shortTitleInPluralForm = token.shortTitleInPluralForm(withAssetDefinitionStore: assetDefinitionStore) self.shortTitleInPluralForm = tokenAdaptor.shortTitleInPluralForm(token: token)
self.titleInPluralForm = token.titleInPluralForm(withAssetDefinitionStore: assetDefinitionStore) self.titleInPluralForm = tokenAdaptor.titleInPluralForm(token: token)
case .erc875, .erc1155, .erc721, .erc721ForTickets: case .erc875, .erc1155, .erc721, .erc721ForTickets:
self.shortTitleInPluralForm = token.shortTitleInPluralForm(withAssetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet) self.shortTitleInPluralForm = tokenAdaptor.shortTitleInPluralForm(token: token)
self.titleInPluralForm = token.titleInPluralForm(withAssetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, forWallet: wallet) ?? token.titleInPluralForm(withAssetDefinitionStore: assetDefinitionStore) self.titleInPluralForm = tokenAdaptor.titleInPluralFormOptional(token: token) ?? tokenAdaptor.titleInPluralForm(token: token)
} }
self.hasNoBaseAssetDefinition = xmlHandler.hasNoBaseAssetDefinition self.hasNoBaseAssetDefinition = xmlHandler.hasNoBaseAssetDefinition

@ -21,6 +21,7 @@ public final class WalletSession: Equatable {
} }
public let blockchainProvider: BlockchainProvider public let blockchainProvider: BlockchainProvider
public let nftProvider: NFTProvider public let nftProvider: NFTProvider
public let tokenAdaptor: TokenAdaptor
public init(account: Wallet, public init(account: Wallet,
server: RPCServer, server: RPCServer,
@ -29,8 +30,10 @@ public final class WalletSession: Equatable {
ercTokenProvider: TokenProviderType, ercTokenProvider: TokenProviderType,
importToken: TokenImportable & TokenOrContractFetchable, importToken: TokenImportable & TokenOrContractFetchable,
blockchainProvider: BlockchainProvider, blockchainProvider: BlockchainProvider,
nftProvider: NFTProvider) { nftProvider: NFTProvider,
tokenAdaptor: TokenAdaptor) {
self.tokenAdaptor = tokenAdaptor
self.nftProvider = nftProvider self.nftProvider = nftProvider
self.analytics = analytics self.analytics = analytics
self.account = account self.account = account

@ -26,6 +26,7 @@ open class BaseSessionsProvider: SessionsProvider {
private let assetDefinitionStore: AssetDefinitionStore private let assetDefinitionStore: AssetDefinitionStore
private let reachability: ReachabilityManagerProtocol private let reachability: ReachabilityManagerProtocol
private let wallet: Wallet private let wallet: Wallet
private let eventsDataStore: NonActivityEventsDataStore
public var sessions: AnyPublisher<ServerDictionary<WalletSession>, Never> { public var sessions: AnyPublisher<ServerDictionary<WalletSession>, Never> {
return sessionsSubject.eraseToAnyPublisher() return sessionsSubject.eraseToAnyPublisher()
@ -39,10 +40,12 @@ open class BaseSessionsProvider: SessionsProvider {
analytics: AnalyticsLogger, analytics: AnalyticsLogger,
blockchainsProvider: BlockchainsProvider, blockchainsProvider: BlockchainsProvider,
tokensDataStore: TokensDataStore, tokensDataStore: TokensDataStore,
eventsDataStore: NonActivityEventsDataStore,
assetDefinitionStore: AssetDefinitionStore, assetDefinitionStore: AssetDefinitionStore,
reachability: ReachabilityManagerProtocol, reachability: ReachabilityManagerProtocol,
wallet: Wallet) { wallet: Wallet) {
self.eventsDataStore = eventsDataStore
self.wallet = wallet self.wallet = wallet
self.reachability = reachability self.reachability = reachability
self.assetDefinitionStore = assetDefinitionStore self.assetDefinitionStore = assetDefinitionStore
@ -90,6 +93,7 @@ open class BaseSessionsProvider: SessionsProvider {
reachability: reachability) reachability: reachability)
let nftProvider = AlphaWalletNFTProvider(analytics: analytics, wallet: wallet, server: blockchain.server, config: config) let nftProvider = AlphaWalletNFTProvider(analytics: analytics, wallet: wallet, server: blockchain.server, config: config)
let tokenAdaptor = TokenAdaptor(assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, wallet: wallet)
return WalletSession( return WalletSession(
account: wallet, account: wallet,
@ -99,7 +103,8 @@ open class BaseSessionsProvider: SessionsProvider {
ercTokenProvider: ercTokenProvider, ercTokenProvider: ercTokenProvider,
importToken: importToken, importToken: importToken,
blockchainProvider: blockchain, blockchainProvider: blockchain,
nftProvider: nftProvider) nftProvider: nftProvider,
tokenAdaptor: tokenAdaptor)
} }
public func session(for server: RPCServer) -> WalletSession? { public func session(for server: RPCServer) -> WalletSession? {

Loading…
Cancel
Save