Support multi XML, read from asset definition repo

pull/508/head
Hwee-Boon Yar 6 years ago
parent a05636498c
commit 2b75883871
  1. 84
      AlphaWallet.xcodeproj/project.pbxproj
  2. 0
      AlphaWallet/AssetDefinition/AssetAttribute.swift
  3. 15
      AlphaWallet/AssetDefinition/AssetDefinitionBackingStore.swift
  4. 50
      AlphaWallet/AssetDefinition/AssetDefinitionDiskBackingStore.swift
  5. 28
      AlphaWallet/AssetDefinition/AssetDefinitionInMemoryBackingStore.swift
  6. 125
      AlphaWallet/AssetDefinition/AssetDefinitionStore.swift
  7. 0
      AlphaWallet/AssetDefinition/GeneralisedTime.swift
  8. 0
      AlphaWallet/AssetDefinition/XMLAccessorExtension.swift
  9. 64
      AlphaWallet/AssetDefinition/XMLHandler.swift
  10. 19
      AlphaWallet/FetchAssetDefinitionsCoordinator.swift
  11. 18
      AlphaWallet/Foundation/AssetDefinitionXML.swift
  12. 34
      AlphaWallet/InCoordinator.swift
  13. 76
      AlphaWallet/Market/Coordinators/UniversalLinkCoordinator.swift
  14. 12
      AlphaWallet/Market/ViewControllers/ImportTicketViewController.swift
  15. 8
      AlphaWallet/Redeem/Helpers/CreateRedeem.swift
  16. 17
      AlphaWallet/Redeem/ViewControllers/RedeemTicketsQuantitySelectionViewController.swift
  17. 16
      AlphaWallet/Redeem/ViewControllers/RedeemTicketsViewController.swift
  18. 18
      AlphaWallet/Redeem/ViewControllers/TicketRedemptionViewController.swift
  19. 12
      AlphaWallet/Sell/ViewControllers/EnterSellTicketsPriceQuantityViewController.swift
  20. 12
      AlphaWallet/Sell/ViewControllers/SellTicketsViewController.swift
  21. 12
      AlphaWallet/Sell/ViewControllers/SetSellTicketsExpiryDateViewController.swift
  22. 46
      AlphaWallet/Settings/Types/Config.swift
  23. 5
      AlphaWallet/Settings/Types/Constants.swift
  24. 14
      AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift
  25. 2
      AlphaWallet/Tokens/Helpers/TicketAdaptor.swift
  26. 14
      AlphaWallet/Tokens/Types/TokensDataStore.swift
  27. 16
      AlphaWallet/Tokens/ViewControllers/TicketsViewController.swift
  28. 12
      AlphaWallet/Tokens/ViewControllers/VerifiableStatusViewController.swift
  29. 4
      AlphaWallet/Tokens/ViewModels/TokensViewModel.swift
  30. 33
      AlphaWallet/Transactions/Coordinators/TicketsCoordinator.swift
  31. 12
      AlphaWallet/Transfer/ViewControllers/ChooseTicketTransferModeViewController.swift
  32. 12
      AlphaWallet/Transfer/ViewControllers/SetTransferTicketsExpiryDateViewController.swift
  33. 16
      AlphaWallet/Transfer/ViewControllers/TransferTicketsQuantitySelectionViewController.swift
  34. 17
      AlphaWallet/Transfer/ViewControllers/TransferTicketsViaWalletAddressViewController.swift
  35. 16
      AlphaWallet/Transfer/ViewControllers/TransferTicketsViewController.swift
  36. 19
      AlphaWalletTests/AssetDefinition/AssetDefinitionStoreTests.swift
  37. 2
      AlphaWalletTests/AssetDefinition/XMLHandlerTest.swift
  38. 3
      AlphaWalletTests/Coordinators/TokensCoordinatorTests.swift
  39. 17
      AlphaWalletTests/Extensions/StringExtensionTests.swift
  40. 2
      AlphaWalletTests/Factories/FakeTokensDataStore.swift
  41. 2
      AlphaWalletTests/Market/UniversalLinkHandlerTests.swift
  42. 3
      AlphaWalletTests/Redeem/CreateRedeemTests.swift
  43. 4
      AlphaWalletTests/Transfer/ViewControllers/TransferTicketsQuantitySelectionViewControllerTests.swift

@ -298,6 +298,7 @@
5E7C731B88842C036A74A039 /* AlphaWalletSettingsButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */; };
5E7C731D0F6128BE8885A2D3 /* ServersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B8FD1E2BCC325DF4EE4 /* ServersCoordinator.swift */; };
5E7C732BD09AABEEE6096BF4 /* ServersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74C0C1803DD17FE9EBA7 /* ServersViewController.swift */; };
5E7C7330220A51D25C4F9BAA /* FetchAssetDefinitionsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C764DD34226D5639672B7 /* FetchAssetDefinitionsCoordinator.swift */; };
5E7C733638D7596F93DEE2A9 /* OnboardingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */; };
5E7C7376B566E5A59CC8F463 /* ImportTicketViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72D0E7CA03ADE5CFAE7A /* ImportTicketViewControllerViewModel.swift */; };
5E7C73FC3990D110C474C3D6 /* WalletFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */; };
@ -314,7 +315,6 @@
5E7C74BD08801CABF9695853 /* LocaleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79778E4BFE1322711EA6 /* LocaleViewModel.swift */; };
5E7C74DBAE43954C185057B3 /* ChooseTicketTransferModeViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BA578BE5FB0E613A6D6 /* ChooseTicketTransferModeViewControllerViewModel.swift */; };
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70088832B2D161EB4AAB /* SendViewController.swift */; };
5E7C75C3A94BD70993010B97 /* AssetAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C759DB34C60C21213FE2A /* AssetAttribute.swift */; };
5E7C75C99B9F595F26EDC405 /* LockPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */; };
5E7C75D46140FACBD12333BF /* EthTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE374A74F2B00013C18 /* EthTokenViewCell.swift */; };
5E7C75E5C64619ABFD246183 /* TransferTicketsViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C78B63FDE2FAF25389260 /* TransferTicketsViaWalletAddressViewController.swift */; };
@ -326,11 +326,14 @@
5E7C76696EF7F27EC0788CDD /* GenerateTransferMagicLinkViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EEAAE9C23B68419E9F5 /* GenerateTransferMagicLinkViewControllerViewModel.swift */; };
5E7C7669BBE6255A2377E070 /* SetSellTicketsExpiryDateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7962AE417E12F13FF58E /* SetSellTicketsExpiryDateViewController.swift */; };
5E7C7692C981580CD32228EB /* ChooseTicketTransferModeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70FB40612BB02594EC00 /* ChooseTicketTransferModeViewController.swift */; };
5E7C769D2BFC2809F0EA5AA3 /* GeneralisedTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7142F1598ECC93F3A673 /* GeneralisedTime.swift */; };
5E7C76A0365D128B7F19A0C2 /* ProtectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74BEC095303B66FB4B1E /* ProtectionCoordinator.swift */; };
5E7C76A65C14D0F11AF7848F /* TicketRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7742709724B3BD0C2A0D /* TicketRowViewModel.swift */; };
5E7C76B917517C93D1E26B0A /* LockEnterPasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */; };
5E7C76E816E216D5C69D3D7B /* AssetDefinitionBackingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C963C42DE81F82732E5 /* AssetDefinitionBackingStore.swift */; };
5E7C76F8CB67466725C590CE /* TokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */; };
5E7C7700014B93A966BBA463 /* BaseTicketTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77061BEF269BCE358086 /* BaseTicketTableViewCellViewModel.swift */; };
5E7C7705B09D780E84E2FDA5 /* XMLHandlerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C702300BB7DB0FD7788EF /* XMLHandlerTest.swift */; };
5E7C774B5332AC0DC19C5B1B /* EthTokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B82783A94091A43470 /* EthTokenViewCellViewModel.swift */; };
5E7C776BE1B19F824954962D /* BaseTicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F5C10E3895E805EA7E0 /* BaseTicketTableViewCell.swift */; };
5E7C776CF721EBBD43195926 /* GenerateSellMagicLinkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79CF6150E4CD4A276FC0 /* GenerateSellMagicLinkViewController.swift */; };
@ -340,11 +343,13 @@
5E7C77A8425E0AFAB11F1FCD /* PromptBackupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7ADD0FBE8708A6E98AF8 /* PromptBackupCoordinator.swift */; };
5E7C77AD9FAAC18211B6F355 /* TransferTicketsQuantitySelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7419F47CC8B2996AA8F9 /* TransferTicketsQuantitySelectionViewController.swift */; };
5E7C77E844D710D7AFBC58D4 /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */; };
5E7C782410321CE6CEE68275 /* AssetDefinitionDiskBackingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BF5551BF64D2AE8AD66 /* AssetDefinitionDiskBackingStore.swift */; };
5E7C783B4784DE76971EEBB4 /* StatusViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */; };
5E7C78407F6DCB0EDD562DF6 /* TicketTokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C731B6F01534683227123 /* TicketTokenViewCellViewModel.swift */; };
5E7C786AD8E4877C36D3B14A /* TicketAdaptorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C775FD95FE80B0F1CEA33 /* TicketAdaptorTest.swift */; };
5E7C78B3FD5CA87E395E1861 /* OnboardingPageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AF9A592D7224ED58016 /* OnboardingPageStyle.swift */; };
5E7C78F1D29280E3FF4EAF5E /* RoundedBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75918317E13AD540DCA7 /* RoundedBackground.swift */; };
5E7C793F7E346402CDAF771F /* AssetDefinitionStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FE30D58E4022AF04E48 /* AssetDefinitionStoreTests.swift */; };
5E7C797BE2C8DB7EF6F217B3 /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */; };
5E7C798E5F5EE00D405B91AE /* TicketRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */; };
5E7C79D78AA5E774119BE49B /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7CFDE7DEA8C06C4100AF /* TextField.swift */; };
@ -363,8 +368,8 @@
5E7C7C7142C4519873B2BB4E /* ImportWalletTabBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */; };
5E7C7C98EAF40E8110241DBD /* TicketTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */; };
5E7C7C9E89056069C8FEFA76 /* AlphaWalletSettingsSwitchRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7534FB6BF4D199643246 /* AlphaWalletSettingsSwitchRow.swift */; };
5E7C7CBB48D096078A2B233E /* AssetDefinitionInMemoryBackingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D07B7D0738A1832AB58 /* AssetDefinitionInMemoryBackingStore.swift */; };
5E7C7CCA357CB7BF12E1F2B4 /* UIStackView+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73ED9226646D562B5A3C /* UIStackView+Array.swift */; };
5E7C7CCAC47BA869B06FB418 /* GeneralisedTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76A485E6CC53E08FC9BC /* GeneralisedTime.swift */; };
5E7C7CCC8D376C6E5C245715 /* EthCurrencyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73DF5FBFE756097D32B1 /* EthCurrencyHelper.swift */; };
5E7C7CDB837DCD57E0594CBA /* TicketsViewControllerTitleHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */; };
5E7C7CE5CA19183FCED8C907 /* TokensViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE467A7F5F2E5B1F660 /* TokensViewModel.swift */; };
@ -379,6 +384,7 @@
5E7C7DD506747B6224C28721 /* TransferTicketsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E2486CDE31871C98FC7 /* TransferTicketsViewModel.swift */; };
5E7C7E04D4DDD7D8881A2AB1 /* UniversalLinkCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76AF81B8DFF605558499 /* UniversalLinkCoordinator.swift */; };
5E7C7E1B18EC7F7FD6D64439 /* SellTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FF84A4377FC395772C3 /* SellTicketsViewController.swift */; };
5E7C7E26D26DF13249F1C0C9 /* XMLAccessorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70D75A0BDB884B3E1EA3 /* XMLAccessorExtension.swift */; };
5E7C7E2F558A1DFF078B61F9 /* TransferTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */; };
5E7C7E47C3C412A52DED7380 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AC5A210D034DBC75FB0 /* TextView.swift */; };
5E7C7E4B4054AAD41C5BE3EC /* SettingsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7564AF453BAB0BDAAA57 /* SettingsAction.swift */; };
@ -387,9 +393,11 @@
5E7C7EAEBB435F3909DA36FB /* TransferTicketsViaWalletAddressViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76D3CFA12C2236E73E10 /* TransferTicketsViaWalletAddressViewControllerViewModel.swift */; };
5E7C7EAED92E4AE8B99217AB /* TransferTicketsQuantitySelectionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7021EE19C4B81CAAF3C0 /* TransferTicketsQuantitySelectionViewControllerTests.swift */; };
5E7C7EB845B0EE96CC8DCF43 /* ServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FCE2427A30ACD860DF8 /* ServerViewModel.swift */; };
5E7C7EC61482FC780432A0FC /* AssetAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7596408BA84E95C90ADA /* AssetAttribute.swift */; };
5E7C7ECE164289A89734B4EF /* LocalesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C76C895E7BFA47233068C /* LocalesCoordinator.swift */; };
5E7C7EDA1BB781A45C1C19CD /* ImportWalletTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73D26F24C4AAE981E2F2 /* ImportWalletTab.swift */; };
5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C755132D9B6F95080A1BE /* TransferTicketsCoordinator.swift */; };
5E7C7F287415575EDF33DDEB /* XMLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7633741EA2029D541466 /* XMLHandler.swift */; };
5E7C7F60056FDD6ACC390400 /* UniversalLinkInPasteboardCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7F3DD81D44A996789FC4 /* UniversalLinkInPasteboardCoordinator.swift */; };
5E7C7FAF2A07E7AE21BF09AF /* AlphaWalletSettingsTextRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */; };
5E7C7FC0770A411DB09F8C09 /* TokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C077372C3F2A4349FA1 /* TokenViewCell.swift */; };
@ -398,6 +406,7 @@
5E7C7FDD73F658772181896B /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7607B0EF9B8F1BC41073 /* TermsOfServiceViewController.swift */; };
5E7C7FE10C2FEA7316401F04 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71CE10548877F1124BF2 /* WelcomeViewModel.swift */; };
5E7C7FE8247F0E50BEF35D77 /* HowDoITransferETHIntoMyWalletInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D4F7C566EDD30EF1C19 /* HowDoITransferETHIntoMyWalletInfoViewController.swift */; };
5E7C7FF4210029C482BCE4C0 /* AssetDefinitionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7982FA14CBFDFD93B3D0 /* AssetDefinitionStore.swift */; };
613D04891FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer in Resources */ = {isa = PBXBuildFile; fileRef = 613D04881FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer */; };
613D048B1FDE162B008DE72E /* TrustProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613D048A1FDE162B008DE72E /* TrustProvider.swift */; };
61C359E02002AA5A0097B04D /* TransactionSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C359DF2002AA590097B04D /* TransactionSigning.swift */; };
@ -475,11 +484,7 @@
AAEB8DA2204BC7B700CB0B2C /* RedeemTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEB8DA1204BC7B700CB0B2C /* RedeemTicketsViewController.swift */; };
AAEF2CAB2050A68A0038BE0D /* SignatureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEF2CAA2050A68A0038BE0D /* SignatureHelper.swift */; };
B138ABD0208C2C93000FC28A /* MonkeyTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B138ABCF208C2C93000FC28A /* MonkeyTest.swift */; };
B13994EF20B96DE10048D821 /* XMLAccessorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B13994EE20B96DE10048D821 /* XMLAccessorExtension.swift */; };
B13E57D720AC324B001719BF /* contracts in Resources */ = {isa = PBXBuildFile; fileRef = B13E57D620AC324B001719BF /* contracts */; };
B1DB1A95207DBF6D00CA2B77 /* XMLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB1A94207DBF6D00CA2B77 /* XMLHandler.swift */; };
B1DB1A97207DCECF00CA2B77 /* AssetDefinitionXML.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB1A96207DCECF00CA2B77 /* AssetDefinitionXML.swift */; };
B1DB1A99207DDF9600CA2B77 /* XMLHandlerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB1A98207DDF9600CA2B77 /* XMLHandlerTest.swift */; };
B1DC375D203AEAE200C9756D /* MarketQueueHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DC375C203AEAE100C9756D /* MarketQueueHandler.swift */; };
B1DC375F203AEB4800C9756D /* MarketQueueHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DC375E203AEB4800C9756D /* MarketQueueHandlerTests.swift */; };
BB5D6A9E20232EE8000FC5AB /* CurrencyRate+Fee.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5D6A9D20232EE8000FC5AB /* CurrencyRate+Fee.swift */; };
@ -804,10 +809,13 @@
5E7C700CD3E43689E88FBE9B /* SetSellTicketsExpiryDateViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSellTicketsExpiryDateViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsViewController.swift; sourceTree = "<group>"; };
5E7C7021EE19C4B81CAAF3C0 /* TransferTicketsQuantitySelectionViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewControllerTests.swift; sourceTree = "<group>"; };
5E7C702300BB7DB0FD7788EF /* XMLHandlerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLHandlerTest.swift; sourceTree = "<group>"; };
5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewModel.swift; sourceTree = "<group>"; };
5E7C70CC85B337061151724E /* ImportWalletHelpBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleView.swift; sourceTree = "<group>"; };
5E7C70D75A0BDB884B3E1EA3 /* XMLAccessorExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLAccessorExtension.swift; sourceTree = "<group>"; };
5E7C70FB40612BB02594EC00 /* ChooseTicketTransferModeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTicketTransferModeViewController.swift; sourceTree = "<group>"; };
5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPage.swift; sourceTree = "<group>"; };
5E7C7142F1598ECC93F3A673 /* GeneralisedTime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralisedTime.swift; sourceTree = "<group>"; };
5E7C715F395B973FB61056CF /* HelpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpViewController.swift; sourceTree = "<group>"; };
5E7C71684B93F60206992E10 /* AlphaWalletSettingsTextRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsTextRow.swift; path = Views/AlphaWalletSettingsTextRow.swift; sourceTree = "<group>"; };
5E7C71CE10548877F1124BF2 /* WelcomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewModel.swift; sourceTree = "<group>"; };
@ -839,18 +847,19 @@
5E7C755132D9B6F95080A1BE /* TransferTicketsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsCoordinator.swift; sourceTree = "<group>"; };
5E7C7564AF453BAB0BDAAA57 /* SettingsAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAction.swift; sourceTree = "<group>"; };
5E7C75918317E13AD540DCA7 /* RoundedBackground.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedBackground.swift; sourceTree = "<group>"; };
5E7C759DB34C60C21213FE2A /* AssetAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetAttribute.swift; sourceTree = "<group>"; };
5E7C7596408BA84E95C90ADA /* AssetAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetAttribute.swift; sourceTree = "<group>"; };
5E7C75B5AF76279A71395FC7 /* AddressTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressTextField.swift; sourceTree = "<group>"; };
5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterViewModel.swift; sourceTree = "<group>"; };
5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingCollectionViewController.swift; sourceTree = "<group>"; };
5E7C75F877B2F2E24C7EF258 /* TicketTableViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTableViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C7607B0EF9B8F1BC41073 /* TermsOfServiceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsOfServiceViewController.swift; sourceTree = "<group>"; };
5E7C7624D6F7EA55F6F167B3 /* GenerateSellMagicLinkViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateSellMagicLinkViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7633741EA2029D541466 /* XMLHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLHandler.swift; sourceTree = "<group>"; };
5E7C7646352F10C96B5FC6F6 /* HelpViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpViewCell.swift; sourceTree = "<group>"; };
5E7C764B98F526271E4C2A6A /* StaticHTMLViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticHTMLViewController.swift; sourceTree = "<group>"; };
5E7C764DD34226D5639672B7 /* FetchAssetDefinitionsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchAssetDefinitionsCoordinator.swift; sourceTree = "<group>"; };
5E7C765E0DC0174E9788CCF9 /* EnterSellTicketsPriceQuantityViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterSellTicketsPriceQuantityViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C767497AD8DEE83F384D7 /* RequestViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestViewModel.swift; sourceTree = "<group>"; };
5E7C76A485E6CC53E08FC9BC /* GeneralisedTime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralisedTime.swift; sourceTree = "<group>"; };
5E7C76AF81B8DFF605558499 /* UniversalLinkCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkCoordinator.swift; sourceTree = "<group>"; };
5E7C76C895E7BFA47233068C /* LocalesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesCoordinator.swift; sourceTree = "<group>"; };
5E7C76D3CFA12C2236E73E10 /* TransferTicketsViaWalletAddressViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsViaWalletAddressViewControllerViewModel.swift; sourceTree = "<group>"; };
@ -872,6 +881,7 @@
5E7C7962AE417E12F13FF58E /* SetSellTicketsExpiryDateViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetSellTicketsExpiryDateViewController.swift; sourceTree = "<group>"; };
5E7C79778E4BFE1322711EA6 /* LocaleViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleViewModel.swift; sourceTree = "<group>"; };
5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockEnterPasscodeCoordinator.swift; sourceTree = "<group>"; };
5E7C7982FA14CBFDFD93B3D0 /* AssetDefinitionStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStore.swift; sourceTree = "<group>"; };
5E7C79CF6150E4CD4A276FC0 /* GenerateSellMagicLinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateSellMagicLinkViewController.swift; sourceTree = "<group>"; };
5E7C79D674D45A07E694CE31 /* LockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = "<group>"; };
5E7C79E3BC4CACB123840A42 /* LocaleViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleViewCell.swift; sourceTree = "<group>"; };
@ -896,15 +906,18 @@
5E7C7BA578BE5FB0E613A6D6 /* ChooseTicketTransferModeViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTicketTransferModeViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C7BF09AD68C113D58344C /* LocalesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesViewController.swift; sourceTree = "<group>"; };
5E7C7BF5551BF64D2AE8AD66 /* AssetDefinitionDiskBackingStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionDiskBackingStore.swift; sourceTree = "<group>"; };
5E7C7C01F8C42D7A43792C26 /* HiddenContract.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HiddenContract.swift; sourceTree = "<group>"; };
5E7C7C077372C3F2A4349FA1 /* TokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenViewCell.swift; sourceTree = "<group>"; };
5E7C7C2872E213BBB05D55BA /* ImportWalletTabBarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletTabBarViewModel.swift; sourceTree = "<group>"; };
5E7C7C58586099F082973073 /* WalletFilterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFilterView.swift; sourceTree = "<group>"; };
5E7C7C963C42DE81F82732E5 /* AssetDefinitionBackingStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionBackingStore.swift; sourceTree = "<group>"; };
5E7C7CBEBF984CFCA29D6866 /* ServersViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServersViewModel.swift; sourceTree = "<group>"; };
5E7C7CC48CA7A1EA7D539C87 /* VerifiableStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerifiableStatusViewController.swift; sourceTree = "<group>"; };
5E7C7CD7ABB18C1121D5776F /* LiveLocaleSwitcherBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLocaleSwitcherBundle.swift; sourceTree = "<group>"; };
5E7C7CDB0BAD5D27D2F24F57 /* ServerViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewCell.swift; sourceTree = "<group>"; };
5E7C7CFDE7DEA8C06C4100AF /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
5E7C7D07B7D0738A1832AB58 /* AssetDefinitionInMemoryBackingStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionInMemoryBackingStore.swift; sourceTree = "<group>"; };
5E7C7D2AAB777BF35B8B56BD /* AlphaWalletSettingPushRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingPushRow.swift; path = Views/AlphaWalletSettingPushRow.swift; sourceTree = "<group>"; };
5E7C7D46C7CABC31A7477F37 /* GenerateTransferMagicLinkViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateTransferMagicLinkViewController.swift; sourceTree = "<group>"; };
5E7C7D4F7C566EDD30EF1C19 /* HowDoITransferETHIntoMyWalletInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoITransferETHIntoMyWalletInfoViewController.swift; sourceTree = "<group>"; };
@ -923,6 +936,7 @@
5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCoordinator.swift; sourceTree = "<group>"; };
5E7C7FB99843529061368DA1 /* LocalesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesViewModel.swift; sourceTree = "<group>"; };
5E7C7FCE2427A30ACD860DF8 /* ServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewModel.swift; sourceTree = "<group>"; };
5E7C7FE30D58E4022AF04E48 /* AssetDefinitionStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStoreTests.swift; sourceTree = "<group>"; };
5E7C7FF84A4377FC395772C3 /* SellTicketsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTicketsViewController.swift; sourceTree = "<group>"; };
613D04881FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer */ = {isa = PBXFileReference; lastKnownFileType = file.cer; path = "COMODO ECC Domain Validation Secure Server CA 2.cer"; sourceTree = "<group>"; };
613D048A1FDE162B008DE72E /* TrustProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustProvider.swift; sourceTree = "<group>"; };
@ -1010,12 +1024,8 @@
AAEB8DA1204BC7B700CB0B2C /* RedeemTicketsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedeemTicketsViewController.swift; sourceTree = "<group>"; };
AAEF2CAA2050A68A0038BE0D /* SignatureHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureHelper.swift; sourceTree = "<group>"; };
B138ABCF208C2C93000FC28A /* MonkeyTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonkeyTest.swift; sourceTree = "<group>"; };
B13994EE20B96DE10048D821 /* XMLAccessorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLAccessorExtension.swift; sourceTree = "<group>"; };
B13A87D3BA5167741E5D0801 /* Pods-Trust.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Trust.release.xcconfig"; path = "Pods/Target Support Files/Pods-AlphaWallet/Pods-Trust.release.xcconfig"; sourceTree = "<group>"; };
B13E57D620AC324B001719BF /* contracts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = contracts; sourceTree = "<group>"; };
B1DB1A94207DBF6D00CA2B77 /* XMLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLHandler.swift; sourceTree = "<group>"; };
B1DB1A96207DCECF00CA2B77 /* AssetDefinitionXML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetDefinitionXML.swift; sourceTree = "<group>"; };
B1DB1A98207DDF9600CA2B77 /* XMLHandlerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLHandlerTest.swift; sourceTree = "<group>"; };
B1DC375C203AEAE100C9756D /* MarketQueueHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketQueueHandler.swift; sourceTree = "<group>"; };
B1DC375E203AEB4800C9756D /* MarketQueueHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketQueueHandlerTests.swift; sourceTree = "<group>"; };
B2CF9CDF557F98DECE6D0AF6 /* Pods_AlphaWalletUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AlphaWalletUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1172,6 +1182,8 @@
5E7C7ACB32FB112CD7D92977 /* AlphaWalletHelp */,
5E7C7C37B4E80BA2E0DC7FA4 /* Marketplace */,
5E7C71698FE1429F1AC0777D /* Sell */,
5E7C764DD34226D5639672B7 /* FetchAssetDefinitionsCoordinator.swift */,
5E7C7EFD61F536B335D5FD3F /* AssetDefinition */,
);
path = AlphaWallet;
sourceTree = "<group>";
@ -1198,6 +1210,7 @@
5E7C78E79A2C45A2124F259D /* Tokens */,
5E7C7F50E8B41B876A07AD3A /* Extensions */,
5E7C7393B08745287A266000 /* StringExtensionTests.swift */,
5E7C7F74815AFC150C0AF14D /* AssetDefinition */,
);
path = AlphaWalletTests;
sourceTree = "<group>";
@ -1776,7 +1789,6 @@
61FC5ED01FCFBDEB00CCB12A /* EtherNumberFormatterTests.swift */,
73D2683A202E8411009777A1 /* DecimalNumberFormatterTest.swift */,
73ED85A82034C42D00593BF3 /* StringFormatterTest.swift */,
B1DB1A98207DDF9600CA2B77 /* XMLHandlerTest.swift */,
B138ABCF208C2C93000FC28A /* MonkeyTest.swift */,
);
path = Foundation;
@ -1920,11 +1932,6 @@
298542F81FBE9A0100CB5081 /* CryptoAddressValidator.swift */,
61FC5ECE1FCFBAE500CCB12A /* EtherNumberFormatter.swift */,
73ED85A420349BE400593BF3 /* StringFormatter.swift */,
B1DB1A94207DBF6D00CA2B77 /* XMLHandler.swift */,
B1DB1A96207DCECF00CA2B77 /* AssetDefinitionXML.swift */,
B13994EE20B96DE10048D821 /* XMLAccessorExtension.swift */,
5E7C759DB34C60C21213FE2A /* AssetAttribute.swift */,
5E7C76A485E6CC53E08FC9BC /* GeneralisedTime.swift */,
);
path = Foundation;
sourceTree = "<group>";
@ -2489,6 +2496,21 @@
path = ViewControllers;
sourceTree = "<group>";
};
5E7C7EFD61F536B335D5FD3F /* AssetDefinition */ = {
isa = PBXGroup;
children = (
5E7C7633741EA2029D541466 /* XMLHandler.swift */,
5E7C7982FA14CBFDFD93B3D0 /* AssetDefinitionStore.swift */,
5E7C70D75A0BDB884B3E1EA3 /* XMLAccessorExtension.swift */,
5E7C7142F1598ECC93F3A673 /* GeneralisedTime.swift */,
5E7C7596408BA84E95C90ADA /* AssetAttribute.swift */,
5E7C7C963C42DE81F82732E5 /* AssetDefinitionBackingStore.swift */,
5E7C7BF5551BF64D2AE8AD66 /* AssetDefinitionDiskBackingStore.swift */,
5E7C7D07B7D0738A1832AB58 /* AssetDefinitionInMemoryBackingStore.swift */,
);
path = AssetDefinition;
sourceTree = "<group>";
};
5E7C7F50E8B41B876A07AD3A /* Extensions */ = {
isa = PBXGroup;
children = (
@ -2504,6 +2526,15 @@
path = Views;
sourceTree = "<group>";
};
5E7C7F74815AFC150C0AF14D /* AssetDefinition */ = {
isa = PBXGroup;
children = (
5E7C7FE30D58E4022AF04E48 /* AssetDefinitionStoreTests.swift */,
5E7C702300BB7DB0FD7788EF /* XMLHandlerTest.swift */,
);
path = AssetDefinition;
sourceTree = "<group>";
};
615F10571FCBEF6A008A45AF /* Views */ = {
isa = PBXGroup;
children = (
@ -3249,7 +3280,6 @@
77872D232023F43B0032D687 /* TransactionsTracker.swift in Sources */,
29FA00CE201CA64E002F7DC5 /* DappCommand.swift in Sources */,
29FF12F61F74799D00AFD326 /* NSAttributedString.swift in Sources */,
B1DB1A97207DCECF00CA2B77 /* AssetDefinitionXML.swift in Sources */,
AA26C62320412A4100318B9B /* UIViewInspectableEnhancements.swift in Sources */,
290B2B651F91A4880053C83E /* TransactionsFooterView.swift in Sources */,
293B8B431F70815900356286 /* BalanceTitleView.swift in Sources */,
@ -3465,7 +3495,6 @@
29F1C83E1FEB5C91003780D8 /* EditTokensViewController.swift in Sources */,
296AF9A31F733AB30058AF78 /* WalletCoordinator.swift in Sources */,
442FCBBFCC5926B4D416E6D3 /* GetNameCoordinator.swift in Sources */,
B1DB1A95207DBF6D00CA2B77 /* XMLHandler.swift in Sources */,
442FCB9850FC6F2D28978199 /* GetERC20Symbol.swift in Sources */,
442FC5F70AF003F331F7C841 /* GetSymbolCoordinator.swift in Sources */,
442FCB2B0DD39EB2D9233A2F /* GetStormBirdBalance.swift in Sources */,
@ -3565,7 +3594,6 @@
5E7C7C0FAC500A6651E663FD /* TransferTicketsQuantitySelectionViewModel.swift in Sources */,
5E7C77AD9FAAC18211B6F355 /* TransferTicketsQuantitySelectionViewController.swift in Sources */,
5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */,
B13994EF20B96DE10048D821 /* XMLAccessorExtension.swift in Sources */,
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */,
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */,
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */,
@ -3617,8 +3645,15 @@
5E7C7788FA549A0402BB33CB /* HiddenContract.swift in Sources */,
5E7C7F60056FDD6ACC390400 /* UniversalLinkInPasteboardCoordinator.swift in Sources */,
5E7C7402B29A987B0AF7061D /* VerifiableStatusViewController.swift in Sources */,
5E7C75C3A94BD70993010B97 /* AssetAttribute.swift in Sources */,
5E7C7CCAC47BA869B06FB418 /* GeneralisedTime.swift in Sources */,
5E7C7330220A51D25C4F9BAA /* FetchAssetDefinitionsCoordinator.swift in Sources */,
5E7C7F287415575EDF33DDEB /* XMLHandler.swift in Sources */,
5E7C7FF4210029C482BCE4C0 /* AssetDefinitionStore.swift in Sources */,
5E7C7E26D26DF13249F1C0C9 /* XMLAccessorExtension.swift in Sources */,
5E7C769D2BFC2809F0EA5AA3 /* GeneralisedTime.swift in Sources */,
5E7C7EC61482FC780432A0FC /* AssetAttribute.swift in Sources */,
5E7C76E816E216D5C69D3D7B /* AssetDefinitionBackingStore.swift in Sources */,
5E7C782410321CE6CEE68275 /* AssetDefinitionDiskBackingStore.swift in Sources */,
5E7C7CBB48D096078A2B233E /* AssetDefinitionInMemoryBackingStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3680,7 +3715,6 @@
29BDF19D1FEE50E90023A45F /* GasPriceConfigurationTests.swift in Sources */,
295996141FAB09A200DB66A8 /* DepositCoordinatorTests.swift in Sources */,
778EAF7D1FF10AF400C8E2AB /* SettingsCoordinatorTests.swift in Sources */,
B1DB1A99207DDF9600CA2B77 /* XMLHandlerTest.swift in Sources */,
732E0F502022716700B39C1F /* LockEnterPasscodeCoordinatorTest.swift in Sources */,
29F114E91FA3EC9E00114A29 /* InCoordinatorTests.swift in Sources */,
2981F4731F8303E600CA6590 /* TransactionCoordinatorTests.swift in Sources */,
@ -3696,6 +3730,8 @@
5E7C7EAED92E4AE8B99217AB /* TransferTicketsQuantitySelectionViewControllerTests.swift in Sources */,
5E7C7B4E3DEA90147A5A9E0A /* TokensDataStoreTest.swift in Sources */,
5E7C7A242DDFB1F1C77DCBBF /* StringExtensionTests.swift in Sources */,
5E7C793F7E346402CDAF771F /* AssetDefinitionStoreTests.swift in Sources */,
5E7C7705B09D780E84E2FDA5 /* XMLHandlerTest.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -0,0 +1,15 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
protocol AssetDefinitionBackingStore {
subscript(contract: String) -> String? { get set }
func lastModifiedDataOfCachedAssetDefinitionFile(forContract contract: String) -> Date?
func forEachContractWithXML(_ body: (String) -> Void)
}
extension AssetDefinitionBackingStore {
func standardizedName(ofContract contract: String) -> String {
return contract.add0x.lowercased()
}
}

@ -0,0 +1,50 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
class AssetDefinitionDiskBackingStore: AssetDefinitionBackingStore {
init() {
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
}
private let documentsDirectory = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
lazy private var directory = documentsDirectory.appendingPathComponent("assetDefinitions")
private func localURLOfXML(for contract: String) -> URL {
return directory.appendingPathComponent(filename(fromContract: contract))
}
private func filename(fromContract contract: String) -> String {
let name = standardizedName(ofContract: contract)
return "\(name).xml"
}
subscript(contract: String) -> String? {
get {
let path = localURLOfXML(for: contract)
return try? String(contentsOf: path)
}
set(xml) {
guard let xml = xml else { return }
//TODO validate XML signature first
let path = localURLOfXML(for: contract)
try? xml.write(to: path, atomically: true, encoding: .utf8)
}
}
func lastModifiedDataOfCachedAssetDefinitionFile(forContract contract: String) -> Date? {
let path = localURLOfXML(for: contract)
guard let lastModified = try? path.resourceValues(forKeys: [.contentModificationDateKey]) else { return nil }
return lastModified.contentModificationDate as? Date
}
func forEachContractWithXML(_ body: (String) -> Void) {
if let files = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) {
for each in files {
guard each.pathExtension == "xml" else { continue }
let contract = each.deletingPathExtension().lastPathComponent
body(contract)
}
}
}
}

@ -0,0 +1,28 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
class AssetDefinitionInMemoryBackingStore: AssetDefinitionBackingStore {
private var xmls = [String: String]()
subscript(contract: String) -> String? {
get {
return xmls[contract]
}
set(xml) {
guard let xml = xml else { return }
//TODO validate XML signature first
xmls[contract] = xml
}
}
func lastModifiedDataOfCachedAssetDefinitionFile(forContract contract: String) -> Date? {
return nil
}
func forEachContractWithXML(_ body: (String) -> Void) {
xmls.forEach { contract, _ in
body(contract)
}
}
}

@ -0,0 +1,125 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Alamofire
/// Manage access to and cache asset definition XML files
class AssetDefinitionStore {
enum Result {
case cached
case updated
case unmodified
case error
}
private var httpHeaders: HTTPHeaders = {
guard let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { return [:] }
return [
"Accept": "text/xml; charset=UTF-8",
"X-Client-Name": Constants.repoClientName,
"X-Client-Version": appVersion,
"X-Platform-Name": Constants.repoPlatformName,
"X-Platform-Version": UIDevice.current.systemVersion
]
}()
private var lastModifiedDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "E, dd MMM yyyy HH:mm:ss z"
df.timeZone = TimeZone(secondsFromGMT: 0)
return df
}()
private var lastContractInPasteboard: String?
private var subscribers: [(String, String) -> Void] = []
private var backingStore: AssetDefinitionBackingStore
init(backingStore: AssetDefinitionBackingStore = AssetDefinitionDiskBackingStore()) {
self.backingStore = backingStore
}
func enableFetchXMLForContractInPasteboard() {
NotificationCenter.default.addObserver(self, selector: #selector(fetchXMLForContractInPasteboard), name: .UIApplicationDidBecomeActive, object: nil)
}
func fetchXMLs(forContracts contracts: [String]) {
for each in contracts {
fetchXML(forContract: each)
}
}
subscript(contract: String) -> String? {
get {
return backingStore[contract]
}
set(xml) {
backingStore[contract] = xml
}
}
func subscribe(_ subscribe: @escaping (_ xml: String, _ contract: String) -> Void) {
subscribers.append(subscribe)
}
/// useCacheAndFetch: when true, the completionHandler will be called immediately and a second time if an updated XML is fetched. When false, the completionHandler will only be called up fetching an updated XML
func fetchXML(forContract contract: String, useCacheAndFetch: Bool = false, completionHandler: ((Result) -> Void)? = nil) {
let contract = contract.add0x.lowercased()
if useCacheAndFetch {
completionHandler?(.cached)
}
guard let url = urlToFetch(contract: contract) else { return }
Alamofire.request(
url,
method: .get,
headers: httpHeadersWithLastModifiedTimestamp(forContract: contract)
).response() { response in
if response.response?.statusCode == 304 {
completionHandler?(.unmodified)
} else if response.response?.statusCode == 406 {
completionHandler?(.error)
} else {
if let data = response.data, let xml = String(data: data, encoding: .utf8), !xml.isEmpty {
self[contract] = xml
XMLHandler.invalidate(forContract: contract)
completionHandler?(.updated)
self.subscribers.forEach { $0(xml, contract) }
} else {
completionHandler?(.error)
}
}
}
}
@objc private func fetchXMLForContractInPasteboard() {
guard let contents = UIPasteboard.general.string?.trimmed else { return }
guard lastContractInPasteboard != contents else { return }
guard CryptoAddressValidator.isValidAddress(contents) else { return }
defer { lastContractInPasteboard = contents }
fetchXML(forContract: contents)
}
private func urlToFetch(contract: String) -> URL? {
let name = backingStore.standardizedName(ofContract: contract)
return URL(string: Constants.repoServer)?.appendingPathComponent(name)
}
private func lastModifiedDataOfCachedAssetDefinitionFile(forContract contract: String) -> Date? {
return backingStore.lastModifiedDataOfCachedAssetDefinitionFile(forContract: contract)
}
private func httpHeadersWithLastModifiedTimestamp(forContract contract: String) -> HTTPHeaders {
var result = httpHeaders
if let lastModified = lastModifiedDataOfCachedAssetDefinitionFile(forContract: contract) {
result["IF-Modified-Since"] = string(fromLastModifiedDate: lastModified)
return result
} else {
return result
}
}
func string(fromLastModifiedDate date: Date) -> String {
return lastModifiedDateFormatter.string(from: date)
}
func forEachContractWithXML(_ body: (String) -> Void) {
backingStore.forEachContractWithXML(body)
}
}

@ -10,16 +10,20 @@ import SwiftyXMLParser
import BigInt
import TrustKeystore
// Dictionary class for non fungible token
// TODO handle flexible attribute names e.g. asset, contract
// Handle generics for multiple asset defintions
// Interface to extract data from non fungible token
private class PrivateXMLHandler {
private let xml = try! XML.parse(AssetDefinitionXML().assetDefinitionString)
private let xml: XML.Accessor
let contractAddress: String
//TODO do we always want the first one?
lazy var contract = xml["token"]["contract"][0]
lazy var fields = extractFields()
init(contract: String) {
contractAddress = contract.add0x.lowercased()
xml = try! XML.parse(AssetDefinitionStore()[contract] ?? "")
}
func getFifaInfoForTicket(tokenId tokenBytes32: BigUInt, index: UInt16) -> Ticket {
//check if leading or trailing zeros
let tokenId = tokenBytes32
@ -52,6 +56,13 @@ private class PrivateXMLHandler {
)
}
func isVerified(for server: RPCServer) -> Bool {
let contractElement = xml["token"]["contract"].getElement(attributeName: "id", attributeValue: "holding_contract")
let addressElement = contractElement?["address"].getElement(attributeName: "network", attributeValue: String(server.chainID))
guard let contractInXML = addressElement?.text else { return false }
return contractInXML.sameContract(as: contractAddress)
}
private func extractFields() -> [String: AssetAttribute] {
let lang = getLang()
var fields = [String: AssetAttribute]()
@ -63,19 +74,6 @@ private class PrivateXMLHandler {
return fields
}
func getAddressFromXML(server: RPCServer) -> Address {
if server == .ropsten {
if let address = contract["address"][1].text {
return Address(string: address)!
}
} else {
if let address = contract["address"][0].text {
return Address(string: address)!
}
}
return Address(string: Constants.ticketContractAddressRopsten)!
}
func getName(lang: String) -> String {
if let name = contract["name"].getElementWithLangAttribute(equals: lang)?.text {
return name
@ -98,26 +96,38 @@ private class PrivateXMLHandler {
}
}
/// This class delegates all the functionality to a singleton of the actual XML parser, so we just parse the XML file 1 time only
/// This class delegates all the functionality to a singleton of the actual XML parser. 1 for each contract. So we just parse the XML file 1 time only for each contract
public class XMLHandler {
fileprivate static var privateXMLHandler = PrivateXMLHandler()
var contract: XML.Accessor {
return XMLHandler.privateXMLHandler.contract
fileprivate static var xmlHandlers: [String: PrivateXMLHandler] = [:]
private let privateXMLHandler: PrivateXMLHandler
init(contract: String) {
let contract = contract.add0x.lowercased()
if let handler = XMLHandler.xmlHandlers[contract] {
privateXMLHandler = handler
} else {
privateXMLHandler = PrivateXMLHandler(contract: contract)
XMLHandler.xmlHandlers[contract] = privateXMLHandler
}
}
func getFifaInfoForTicket(tokenId tokenBytes32: BigUInt, index: UInt16) -> Ticket {
return XMLHandler.privateXMLHandler.getFifaInfoForTicket(tokenId: tokenBytes32, index: index)
public static func invalidate(forContract contract: String) {
xmlHandlers[contract.add0x.lowercased()] = nil
}
func getAddressFromXML(server: RPCServer) -> Address {
return XMLHandler.privateXMLHandler.getAddressFromXML(server: server)
func getFifaInfoForTicket(tokenId tokenBytes32: BigUInt, index: UInt16) -> Ticket {
return privateXMLHandler.getFifaInfoForTicket(tokenId: tokenBytes32, index: index)
}
func getName(lang: String) -> String {
return XMLHandler.privateXMLHandler.getName(lang: lang)
return privateXMLHandler.getName(lang: lang)
}
func getLang() -> String {
return XMLHandler.privateXMLHandler.getLang()
return privateXMLHandler.getLang()
}
func isVerified(for server: RPCServer) -> Bool {
return privateXMLHandler.isVerified(for: server)
}
}

@ -0,0 +1,19 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
class FetchAssetDefinitionsCoordinator: Coordinator {
var coordinators: [Coordinator] = []
private let assetDefinitionStore: AssetDefinitionStore
private let tokensDataStore: TokensDataStore
init(assetDefinitionStore: AssetDefinitionStore, tokensDataStore: TokensDataStore) {
self.assetDefinitionStore = assetDefinitionStore
self.tokensDataStore = tokensDataStore
}
func start() {
let contracts = tokensDataStore.enabledObject.filter { $0.isStormBird}.map { $0.contract }
assetDefinitionStore.fetchXMLs(forContracts: contracts)
}
}

@ -1,18 +0,0 @@
//
// AssetDefinitionXML.swift
// AlphaWallet
//
// Created by James Sangalli on 11/4/18.
//
import Foundation
public class AssetDefinitionXML {
public var assetDefinitionString = ""
init() {
if let path = Bundle.main.path(forResource: "TicketingContract", ofType: "xml", inDirectory: "contracts") {
assetDefinitionString = try! String(contentsOf: URL(string: "file://" + path)!)
}
}
}

@ -35,6 +35,7 @@ class InCoordinator: Coordinator {
let initialWallet: Wallet
var keystore: Keystore
private var config: Config
private let assetDefinitionStore: AssetDefinitionStore
let appTracker: AppTracker
lazy var ethPrice: Subscribable<Double> = {
var value = Subscribable<Double>(nil)
@ -78,6 +79,8 @@ class InCoordinator: Coordinator {
self.keystore = keystore
self.config = config
self.appTracker = appTracker
self.assetDefinitionStore = AssetDefinitionStore()
self.assetDefinitionStore.enableFetchXMLForContractInPasteboard()
}
func start() {
@ -86,6 +89,7 @@ class InCoordinator: Coordinator {
helpUsCoordinator.start()
addCoordinator(helpUsCoordinator)
fetchXMLAssetDefinitions()
}
func fetchEthPrice() {
@ -95,7 +99,7 @@ class InCoordinator: Coordinator {
let web3 = self.web3(for: config.server)
web3.start()
let realm = self.realm(for: migration.config)
let tokensStorage = TokensDataStore(realm: realm, account: keystore.recentlyUsedWallet!, config: config, web3: web3)
let tokensStorage = TokensDataStore(realm: realm, account: keystore.recentlyUsedWallet!, config: config, web3: web3, assetDefinitionStore: assetDefinitionStore)
tokensStorage.updatePrices()
let etherToken = TokensDataStore.etherToken(for: config)
@ -119,8 +123,8 @@ class InCoordinator: Coordinator {
let web3 = self.web3(for: config.server)
web3.start()
let realm = self.realm(for: migration.config)
let tokensStorage = TokensDataStore(realm: realm, account: account, config: config, web3: web3)
let alphaWalletTokensStorage = TokensDataStore(realm: realm, account: account, config: config, web3: web3)
let tokensStorage = TokensDataStore(realm: realm, account: account, config: config, web3: web3, assetDefinitionStore: assetDefinitionStore)
let alphaWalletTokensStorage = TokensDataStore(realm: realm, account: account, config: config, web3: web3, assetDefinitionStore: assetDefinitionStore)
let balanceCoordinator = GetBalanceCoordinator(web3: web3)
let balance = BalanceCoordinator(wallet: account, config: config, storage: tokensStorage)
let session = WalletSession(
@ -166,7 +170,8 @@ class InCoordinator: Coordinator {
let tokensCoordinator = TokensCoordinator(
session: session,
keystore: keystore,
tokensStorage: alphaWalletTokensStorage
tokensStorage: alphaWalletTokensStorage,
assetDefinitionStore: assetDefinitionStore
)
tokensCoordinator.rootViewController.tabBarItem = UITabBarItem(title: R.string.localizable.walletTokensTabbarItemTitle(), image: R.image.tab_wallet()?.withRenderingMode(.alwaysOriginal), selectedImage: R.image.tab_wallet())
tokensCoordinator.delegate = self
@ -269,6 +274,7 @@ class InCoordinator: Coordinator {
coordinator.stop()
removeAllCoordinators()
showTabBar(for: account)
fetchXMLAssetDefinitions()
}
func removeAllCoordinators() {
@ -342,7 +348,8 @@ class InCoordinator: Coordinator {
keystore: keystore,
tokensStorage: tokenStorage,
ethPrice: ethPrice,
token: token
token: token,
assetDefinitionStore: assetDefinitionStore
)
addCoordinator(ticketsCoordinator)
ticketsCoordinator.type = type
@ -385,6 +392,20 @@ class InCoordinator: Coordinator {
alertController.addAction(UIAlertAction(title: R.string.localizable.oK(), style: UIAlertActionStyle.default, handler: nil))
navigationController.present(alertController, animated: true, completion: nil)
}
private func fetchXMLAssetDefinitions() {
let keystore = try! EtherKeystore()
let migration = MigrationInitializer(account: keystore.recentlyUsedWallet!, chainID: config.chainID)
migration.perform()
let web3 = self.web3(for: config.server)
web3.start()
let realm = self.realm(for: migration.config)
let tokensStorage = TokensDataStore(realm: realm, account: keystore.recentlyUsedWallet!, config: config, web3: web3, assetDefinitionStore: assetDefinitionStore)
let coordinator = FetchAssetDefinitionsCoordinator(assetDefinitionStore: assetDefinitionStore, tokensDataStore: tokensStorage)
coordinator.start()
addCoordinator(coordinator)
}
}
extension InCoordinator: TicketsCoordinatorDelegate {
@ -513,7 +534,8 @@ extension InCoordinator: TokensCoordinatorDelegate {
realm: realm,
account: wallet,
config: self.config,
web3: web3
web3: web3,
assetDefinitionStore: self.assetDefinitionStore
)
let balance = BalanceCoordinator(

@ -24,6 +24,8 @@ class UniversalLinkCoordinator: Coordinator {
var hasCompleted = false
var addressOfNewWallet: String?
private var getStormbirdBalanceCoordinator: GetStormBirdBalanceCoordinator?
//TODO better to make sure ticketHolder is non-optional. But be careful that ImportTicketViewController also handles when viewModel always has a TicketHolder. Needs good defaults in TicketHolder that can be displayed
var ticketHolder: TicketHolder?
init(config: Config) {
self.config = config
@ -59,7 +61,7 @@ class UniversalLinkCoordinator: Coordinator {
return parameters
}
func handlePaidUniversalLink(signedOrder: SignedOrder, ticketHolder: TicketHolder) -> Bool {
func handlePaidUniversalLink(signedOrder: SignedOrder) -> Bool {
//TODO localize
//TODO improve. Not an obvious link between the variables used in the if-statement and the body
if delegate?.viewControllerForPresenting(in: self) != nil {
@ -77,16 +79,12 @@ class UniversalLinkCoordinator: Coordinator {
}
if let price = ethPrice {
let ethCost = self.convert(ethCost: signedOrder.order.price)
self.promptImportUniversalLink(
ticketHolder: ticketHolder,
ethCost: ethCost.description
)
self.promptImportUniversalLink(ethCost: ethCost.description)
price.subscribe { [weak self] value in
if let price = price.value {
if let celf = self {
let (ethCost, dollarCost) = celf.convert(ethCost: signedOrder.order.price, rate: price)
celf.promptImportUniversalLink(
ticketHolder: ticketHolder,
ethCost: ethCost.description,
dollarCost: dollarCost.description
)
@ -102,7 +100,7 @@ class UniversalLinkCoordinator: Coordinator {
return true
}
func usePaymentServerForFreeTransferLinks(signedOrder: SignedOrder, ticketHolder: TicketHolder) -> Bool {
func usePaymentServerForFreeTransferLinks(signedOrder: SignedOrder) -> Bool {
let parameters = createHTTPParametersForPaymentServer(signedOrder: signedOrder, isForTransfer: true)
let query = Constants.paymentServer
//TODO improve. Not an obvious link between the variables used in the if-statement and the body
@ -113,7 +111,6 @@ class UniversalLinkCoordinator: Coordinator {
}
//nil or "" implies free, if using payment server it is always free
self.promptImportUniversalLink(
ticketHolder: ticketHolder,
ethCost: "",
dollarCost: ""
)
@ -134,8 +131,8 @@ class UniversalLinkCoordinator: Coordinator {
self.showImportError(errorMessage: R.string.localizable.aClaimTicketInvalidLinkTryAgain())
return false
}
let xmlAddress = XMLHandler().getAddressFromXML(server: RPCServer(chainID: config.chainID))
let isStormBirdContract = xmlAddress.eip55String.sameContract(as: signedOrder.order.contractAddress)
let isVerified = XMLHandler(contract: signedOrder.order.contractAddress).isVerified(for: config.server)
let isStormBirdContract = isVerified
importTicketViewController?.url = url
importTicketViewController?.contract = signedOrder.order.contractAddress
//need to hash message here because the web3swift implementation adds prefix
@ -170,22 +167,18 @@ class UniversalLinkCoordinator: Coordinator {
self.showImportError(errorMessage: R.string.localizable.aClaimTicketInvalidLinkTryAgain())
}
let ticketHolder = self.sortTickets(
self.makeTicketHolder(
filteredTokens,
signedOrder.order.indices,
signedOrder.order.contractAddress
)
if signedOrder.order.price > 0 || !isStormBirdContract {
self.handlePaidImports(signedOrder: signedOrder, ticketHolder: ticketHolder)
self.handlePaidImports(signedOrder: signedOrder)
} else {
//free transfer
let _ = self.usePaymentServerForFreeTransferLinks(
signedOrder: signedOrder,
ticketHolder: ticketHolder
)
let _ = self.usePaymentServerForFreeTransferLinks(signedOrder: signedOrder)
}
}
case .failure(let error):
//TODO handle. Show error maybe?
@ -196,18 +189,17 @@ class UniversalLinkCoordinator: Coordinator {
return true
}
private func handlePaidImports(signedOrder: SignedOrder, ticketHolder: TicketHolder) {
private func handlePaidImports(signedOrder: SignedOrder) {
if let balance = self.ethBalance {
balance.subscribeOnce { value in
if value > signedOrder.order.price {
let _ = self.handlePaidUniversalLink(signedOrder: signedOrder, ticketHolder: ticketHolder)
let _ = self.handlePaidUniversalLink(signedOrder: signedOrder)
} else {
if let price = self.ethPrice {
if price.value == nil {
let ethCost = self.convert(ethCost: signedOrder.order.price)
self.showImportError(
errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(),
ticketHolder: ticketHolder,
ethCost: ethCost.description
)
}
@ -215,7 +207,7 @@ class UniversalLinkCoordinator: Coordinator {
if let celf = self {
if let price = price.value {
let (ethCost, dollarCost) = celf.convert(ethCost: signedOrder.order.price, rate: price)
celf.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(), ticketHolder: ticketHolder, ethCost: ethCost.description, dollarCost: dollarCost.description)
celf.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedNotEnoughEthTitle(), ethCost: ethCost.description, dollarCost: dollarCost.description)
}
}
}
@ -246,9 +238,26 @@ class UniversalLinkCoordinator: Coordinator {
return filteredTokens
}
private func sortTickets(_ bytes32Tickets: [String], _ indices: [UInt16], _ contractAddress: String) -> TicketHolder {
private func makeTicketHolder(_ bytes32Tickets: [String], _ indices: [UInt16], _ contractAddress: String) {
//TODO better to pass in the store instance once UniversalLinkCoordinator is owned by InCoordinator
AssetDefinitionStore().fetchXML(forContract: contractAddress, useCacheAndFetch: true) { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .cached:
strongSelf.makeTicketHolderImpl(bytes32Tickets: bytes32Tickets, contractAddress: contractAddress)
case .updated:
strongSelf.makeTicketHolderImpl(bytes32Tickets: bytes32Tickets, contractAddress: contractAddress)
strongSelf.updateTicketFields()
break
case .unmodified, .error:
break
}
}
}
private func makeTicketHolderImpl(bytes32Tickets: [String], contractAddress: String) {
var tickets = [Ticket]()
let xmlHandler = XMLHandler()
let xmlHandler = XMLHandler(contract: contractAddress)
for i in 0..<bytes32Tickets.count {
let ticket = bytes32Tickets[i]
if let tokenId = BigUInt(ticket.drop0x, radix: 16) {
@ -256,12 +265,11 @@ class UniversalLinkCoordinator: Coordinator {
tickets.append(ticket)
}
}
let ticketHolder = TicketHolder(
self.ticketHolder = TicketHolder(
tickets: tickets,
status: .available,
contractAddress: contractAddress
)
return ticketHolder
}
private func preparingToImportUniversalLink() {
@ -275,7 +283,15 @@ class UniversalLinkCoordinator: Coordinator {
}
}
private func updateImportTicketController(with state: ImportTicketViewControllerViewModel.State, ticketHolder: TicketHolder? = nil, ethCost: String? = nil, dollarCost: String? = nil) {
private func updateTicketFields() {
guard let ticketHolder = ticketHolder else { return }
if let vc = importTicketViewController, var viewModel = vc.viewModel {
viewModel.ticketHolder = ticketHolder
vc.configure(viewModel: viewModel)
}
}
private func updateImportTicketController(with state: ImportTicketViewControllerViewModel.State, ethCost: String? = nil, dollarCost: String? = nil) {
guard !hasCompleted else { return }
if let vc = importTicketViewController, var viewModel = vc.viewModel {
viewModel.state = state
@ -293,8 +309,8 @@ class UniversalLinkCoordinator: Coordinator {
hasCompleted = state.hasCompleted
}
private func promptImportUniversalLink(ticketHolder: TicketHolder, ethCost: String, dollarCost: String? = nil) {
updateImportTicketController(with: .promptImport, ticketHolder: ticketHolder, ethCost: ethCost, dollarCost: dollarCost)
private func promptImportUniversalLink(ethCost: String, dollarCost: String? = nil) {
updateImportTicketController(with: .promptImport, ethCost: ethCost, dollarCost: dollarCost)
}
private func showImportSuccessful() {
@ -310,8 +326,8 @@ class UniversalLinkCoordinator: Coordinator {
coordinator.start()
}
private func showImportError(errorMessage: String, ticketHolder: TicketHolder? = nil, ethCost: String? = nil, dollarCost: String? = nil) {
updateImportTicketController(with: .failed(errorMessage: errorMessage), ticketHolder: ticketHolder, ethCost: ethCost, dollarCost: dollarCost)
private func showImportError(errorMessage: String, ethCost: String? = nil, dollarCost: String? = nil) {
updateImportTicketController(with: .failed(errorMessage: errorMessage), ethCost: ethCost, dollarCost: dollarCost)
}
func importPaidSignedOrder(signedOrder: SignedOrder, tokenObject: TokenObject) {

@ -8,8 +8,9 @@ protocol ImportTicketViewControllerDelegate: class {
func didPressImport(in viewController: ImportTicketViewController)
}
class ImportTicketViewController: UIViewController, VerifiableStatusViewController {
private let config: Config
//class ImportTicketViewController: UIViewController, VerifiableStatusViewController {
class ImportTicketViewController: UIViewController, TicketVerifiableStatusViewController {
let config: Config
weak var delegate: ImportTicketViewControllerDelegate?
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
@ -32,10 +33,7 @@ class ImportTicketViewController: UIViewController, VerifiableStatusViewControll
var contract: String? {
didSet {
guard url != nil else { return }
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if let contract = contract, !contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false, hasShowInfoButton: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified, hasShowInfoButton: false)
}
}
var url: URL? {
@ -238,6 +236,8 @@ class ImportTicketViewController: UIViewController, VerifiableStatusViewControll
actionButton.isHidden = !viewModel.showActionButton
buttonSeparator.isHidden = !viewModel.showActionButton
updateNavigationRightBarButtons(isVerified: isContractVerified, hasShowInfoButton: false)
}
}

@ -8,9 +8,11 @@ import TrustKeystore
class CreateRedeem {
private let config: Config
private let token: TokenObject
init(config: Config) {
init(config: Config, token: TokenObject) {
self.config = config
self.token = token
}
func generateTimeStamp() -> String {
@ -21,9 +23,9 @@ class CreateRedeem {
}
func redeemMessage(ticketIndices: [UInt16]) -> (message: String, qrCode: String) {
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
let contractAddress = token.contract.add0x.lowercased()
let messageForSigning = formIndicesSelection(indices: ticketIndices)
+ "," + generateTimeStamp() + "," + contractAddress.lowercased()
+ "," + generateTimeStamp() + "," + contractAddress
let qrCodeData = formIndicesSelection(indices: ticketIndices)
return (messageForSigning, qrCodeData)
}

@ -14,9 +14,13 @@ protocol RedeemTicketsQuantitySelectionViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: RedeemTicketsQuantitySelectionViewController)
}
class RedeemTicketsQuantitySelectionViewController: UIViewController, VerifiableStatusViewController {
class RedeemTicketsQuantitySelectionViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return token.contract
}
private let token: TokenObject
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let subtitleLabel = UILabel()
@ -26,8 +30,9 @@ class RedeemTicketsQuantitySelectionViewController: UIViewController, Verifiable
var viewModel: RedeemTicketsQuantitySelectionViewModel!
weak var delegate: RedeemTicketsQuantitySelectionViewControllerDelegate?
init(config: Config) {
init(config: Config, token: TokenObject) {
self.config = config
self.token = token
super.init(nibName: nil, bundle: nil)
updateNavigationRightBarButtons(isVerified: true)
@ -123,11 +128,7 @@ class RedeemTicketsQuantitySelectionViewController: UIViewController, Verifiable
func configure(viewModel: RedeemTicketsQuantitySelectionViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if viewModel.token.contract != contractAddress {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -14,9 +14,13 @@ protocol RedeemTicketsViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: RedeemTicketsViewController)
}
class RedeemTicketsViewController: UIViewController, VerifiableStatusViewController {
class RedeemTicketsViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return token.contract
}
private let token: TokenObject
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let tableView = UITableView(frame: .zero, style: .plain)
@ -24,8 +28,9 @@ class RedeemTicketsViewController: UIViewController, VerifiableStatusViewControl
var viewModel: RedeemTicketsViewModel!
weak var delegate: RedeemTicketsViewControllerDelegate?
init(config: Config) {
init(config: Config, token: TokenObject) {
self.config = config
self.token = token
super.init(nibName: nil, bundle: nil)
updateNavigationRightBarButtons(isVerified: true)
@ -82,10 +87,7 @@ class RedeemTicketsViewController: UIViewController, VerifiableStatusViewControl
func configure(viewModel: RedeemTicketsViewModel) {
self.viewModel = viewModel
tableView.dataSource = self
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if viewModel.token.contract != contractAddress {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
header.configure(title: viewModel.title)
tableView.tableHeaderView = header

@ -8,18 +8,25 @@
import UIKit
class TicketRedemptionViewController: UIViewController, VerifiableStatusViewController {
class TicketRedemptionViewController: UIViewController, TicketVerifiableStatusViewController {
let config: Config
var contract: String? {
return token.contract
}
var viewModel: TicketRedemptionViewModel!
var titleLabel = UILabel()
let imageView = UIImageView()
let ticketView = TicketRowView()
var timer: Timer!
var session: WalletSession
private let token: TokenObject
let redeemListener = RedeemEventListener()
init(session: WalletSession) {
init(config: Config, session: WalletSession, token: TokenObject) {
self.config = config
self.session = session
self.token = token
super.init(nibName: nil, bundle: nil)
updateNavigationRightBarButtons(isVerified: true)
@ -94,7 +101,7 @@ class TicketRedemptionViewController: UIViewController, VerifiableStatusViewCont
@objc
private func configureUI() {
let redeem = CreateRedeem(config: session.config)
let redeem = CreateRedeem(config: session.config, token: token)
let redeemData = redeem.redeemMessage(ticketIndices: viewModel.ticketHolder.indices)
switch session.account.type {
case .real(let account):
@ -138,10 +145,7 @@ class TicketRedemptionViewController: UIViewController, VerifiableStatusViewCont
func configure(viewModel: TicketRedemptionViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: session.config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -8,9 +8,12 @@ protocol EnterSellTicketsPriceQuantityViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: EnterSellTicketsPriceQuantityViewController)
}
class EnterSellTicketsPriceQuantityViewController: UIViewController, VerifiableStatusViewController {
class EnterSellTicketsPriceQuantityViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return viewModel.token.contract
}
let storage: TokensDataStore
let roundedBackground = RoundedBackground()
let scrollView = UIScrollView()
@ -251,10 +254,7 @@ class EnterSellTicketsPriceQuantityViewController: UIViewController, VerifiableS
func configure(viewModel: EnterSellTicketsPriceQuantityViewControllerViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -8,9 +8,12 @@ protocol SellTicketsViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: SellTicketsViewController)
}
class SellTicketsViewController: UIViewController, VerifiableStatusViewController {
class SellTicketsViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return viewModel.token.contract
}
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let tableView = UITableView(frame: .zero, style: .plain)
@ -78,10 +81,7 @@ class SellTicketsViewController: UIViewController, VerifiableStatusViewControlle
func configure(viewModel: SellTicketsViewModel) {
self.viewModel = viewModel
tableView.dataSource = self
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
header.configure(title: viewModel.title)
tableView.tableHeaderView = header

@ -8,9 +8,12 @@ protocol SetSellTicketsExpiryDateViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: SetSellTicketsExpiryDateViewController)
}
class SetSellTicketsExpiryDateViewController: UIViewController, VerifiableStatusViewController {
class SetSellTicketsExpiryDateViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return viewModel.token.contract
}
let storage: TokensDataStore
let roundedBackground = RoundedBackground()
let scrollView = UIScrollView()
@ -240,10 +243,7 @@ class SetSellTicketsExpiryDateViewController: UIViewController, VerifiableStatus
func configure(viewModel: SetSellTicketsExpiryDateViewControllerViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -127,16 +127,56 @@ struct Config {
}
var ticketContractAddress: String? {
return createDefaultTicketToken()?.contract.eip55String
switch server {
case .main:
return "0xA66A3F08068174e8F005112A8b2c7A507a822335"
case .ropsten:
return "0xd8e5f58de3933e1e35f9c65eb72cb188674624f3"
default:
return nil
}
}
///Debugging flag. Set to false to disable auto fetching prices, etc to cut down on network calls
let isAutoFetchingDisabled = false
func createDefaultTicketToken() -> ERCToken? {
let xmlHandler = XMLHandler()
guard let contract = ticketContractAddress else { return nil }
guard let contractAddress = Address(string: contract) else { return nil}
let xmlHandler = XMLHandler(contract: contract)
let lang = xmlHandler.getLang()
let name = xmlHandler.getName(lang: lang)
//TODO get symbol from RPC node, but this doesn't provide much benefit as it is a hardcoded
//placeholder anyway
//GetSymbolCoordinator(web3: Web3Swift()).getSymbol(for: contractAddress) { result in }
switch server {
case .main:
return ERCToken(
contract: contractAddress,
name: Constants.event + " " + name,
symbol: "SHANKAI",
decimals: 0,
isStormBird: true,
balance: []
)
case .ropsten:
return ERCToken(
contract: contractAddress,
name: name,
symbol: "TEST",
decimals: 0,
isStormBird: true,
balance: []
)
case .kovan, .rinkeby, .poa, .sokol, .classic, .callisto, .custom:
return nil
}
}
func createDefaultTicketToken(forContract contract: String) -> ERCToken? {
guard let contractAddress = Address(string: contract) else { return nil }
let xmlHandler = XMLHandler(contract: contract)
let lang = xmlHandler.getLang()
let contractAddress = xmlHandler.getAddressFromXML(server: self.server)
let name = xmlHandler.getName(lang: lang)
//TODO get symbol from RPC node, but this doesn't provide much benefit as it is a hardcoded
//placeholder anyway

@ -11,6 +11,11 @@ public struct Constants {
public static let gasLimit = BigInt(300000)
public static let gasPriceDefaultStormbird = BigInt("22000000000")!
// XML repo
public static let repoServer = "https://repo.awallet.io"
public static let repoClientName = "AlphaWallet"
public static let repoPlatformName = "iOS"
// fee master
public static let paymentServer = "https://app.awallet.io:80/api/claimToken"
public static let getTicketInfoFromServer = "https://app.awallet.io:80/api/ecrecoverAndGetTickets"

@ -28,6 +28,7 @@ class TokensCoordinator: Coordinator {
let keystore: Keystore
var coordinators: [Coordinator] = []
let storage: TokensDataStore
private let assetDefinitionStore: AssetDefinitionStore
lazy var tokensViewController: TokensViewController = {
let controller = TokensViewController(
@ -48,25 +49,34 @@ class TokensCoordinator: Coordinator {
navigationController: UINavigationController = NavigationController(),
session: WalletSession,
keystore: Keystore,
tokensStorage: TokensDataStore
tokensStorage: TokensDataStore,
assetDefinitionStore: AssetDefinitionStore
) {
self.navigationController = navigationController
self.navigationController.modalPresentationStyle = .formSheet
self.session = session
self.keystore = keystore
self.storage = tokensStorage
self.assetDefinitionStore = assetDefinitionStore
}
func start() {
addFIFAToken()
autoDetectTokens()
showTokens()
refreshUponAssetDefinitionChanges()
}
func showTokens() {
navigationController.viewControllers = [rootViewController]
}
private func refreshUponAssetDefinitionChanges() {
assetDefinitionStore.subscribe { [weak self] xml, contract in
self?.storage.updateERC875TokensToLocalizedName()
}
}
///Implementation: We refresh once only, after all the auto detected tokens' data have been pulled because each refresh pulls every tokens' (including those that already exist before the this auto detection) price as well as balance, placing heavy and redundant load on the device. After a timeout, we refresh once just in case it took too long, so user at least gets the chance to see some auto detected tokens
private func autoDetectTokens() {
//TODO we don't auto detect tokens if we are running tests. Maybe better to move this into app delegate's application(_:didFinishLaunchingWithOptions:)
@ -211,6 +221,8 @@ class TokensCoordinator: Coordinator {
completion(.failed(networkReachable: NetworkReachabilityManager()?.isReachable))
}
assetDefinitionStore.fetchXML(forContract: address)
self.storage.getContractName(for: address) { result in
switch result {
case .success(let name):

@ -83,7 +83,7 @@ class TicketAdaptor {
//TODO pass lang into here
private func getTicket(for id: BigUInt, index: UInt16, in token: TokenObject) -> Ticket {
return XMLHandler().getFifaInfoForTicket(tokenId: id, index: index)
return XMLHandler(contract: token.contract).getFifaInfoForTicket(tokenId: id, index: index)
}
private func getTicketHolder(for tickets: [Ticket]) -> TicketHolder {

@ -51,6 +51,7 @@ class TokensDataStore {
let account: Wallet
let config: Config
let web3: Web3Swift
let assetDefinitionStore: AssetDefinitionStore
weak var delegate: TokensDataStoreDelegate?
let realm: Realm
var tickers: [String: CoinTicker]? = .none
@ -77,17 +78,19 @@ class TokensDataStore {
realm: Realm,
account: Wallet,
config: Config,
web3: Web3Swift
web3: Web3Swift,
assetDefinitionStore: AssetDefinitionStore
) {
self.account = account
self.config = config
self.web3 = web3
self.assetDefinitionStore = assetDefinitionStore
self.realm = realm
self.addEthToken()
self.scheduledTimerForPricesUpdate()
self.scheduledTimerForEthBalanceUpdate()
updateTicketTokenToLocalizedName()
updateERC875TokensToLocalizedName()
}
private func addEthToken() {
//Check if we have previos values.
@ -367,15 +370,18 @@ class TokensDataStore {
}, selector: #selector(Operation.main), userInfo: nil, repeats: true)
}
private func updateTicketTokenToLocalizedName() {
if let token = config.createDefaultTicketToken() {
public func updateERC875TokensToLocalizedName() {
assetDefinitionStore.forEachContractWithXML { contract in
if let token = config.createDefaultTicketToken(forContract: contract) {
let contract = token.contract.eip55String
let localizedName = token.name
if let storedTicketToken = enabledObject.first(where: { $0.contract == contract }) {
//TODO multiple realm writes in a loop. Should we group them together?
updateTicketTokenName(token: storedTicketToken, to: localizedName)
}
}
}
}
private func updateTicketTokenName(token: TokenObject, to name: String) {
try! realm.write {

@ -20,8 +20,12 @@ protocol TicketsViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: TicketsViewController)
}
class TicketsViewController: UIViewController, VerifiableStatusViewController {
class TicketsViewController: UIViewController, TicketVerifiableStatusViewController {
let config: Config
var contract: String? {
return tokenObject.contract
}
var tokenObject: TokenObject
//TODO forced unwraps aren't good
var viewModel: TicketsViewModel!
@ -37,7 +41,8 @@ class TicketsViewController: UIViewController, VerifiableStatusViewController {
let sellButton = UIButton(type: .system)
let transferButton = UIButton(type: .system)
init(tokenObject: TokenObject) {
init(config: Config, tokenObject: TokenObject) {
self.config = config
self.tokenObject = tokenObject
super.init(nibName: nil, bundle: nil)
@ -122,10 +127,7 @@ class TicketsViewController: UIViewController, VerifiableStatusViewController {
func configure(viewModel: TicketsViewModel) {
self.viewModel = viewModel
tableView.dataSource = self
let contractAddress = XMLHandler().getAddressFromXML(server: session.config.server).eip55String
if !tokenObject.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
header.configure(viewModel: .init(config: session.config, tokenObject: tokenObject))
tableView.tableHeaderView = header
@ -141,6 +143,8 @@ class TicketsViewController: UIViewController, VerifiableStatusViewController {
transferButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
transferButton.backgroundColor = viewModel.buttonBackgroundColor
transferButton.titleLabel?.font = viewModel.buttonFont
tableView.reloadData()
}
override

@ -48,3 +48,15 @@ extension VerifiableStatusViewController where Self: UIViewController {
return button
}
}
protocol TicketVerifiableStatusViewController: VerifiableStatusViewController {
var contract: String? { get }
var config: Config { get }
}
extension TicketVerifiableStatusViewController {
var isContractVerified: Bool {
guard let contract = contract else { return false }
return XMLHandler(contract: contract).isVerified(for: config.server)
}
}

@ -119,8 +119,8 @@ class TokensViewModel {
//FIFA make the FIFA token be index 1. Can remove the function and replace with the argument when we no longer need this
private func reorderTokensSoFIFAAtIndex1(tokens: [TokenObject]) -> [TokenObject] {
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
let index = tokens.index { $0.address.eip55String == contractAddress }
guard let contract = config.ticketContractAddress else { return tokens }
let index = tokens.index { $0.address.eip55String.sameContract(as: contract) }
if let index = index, tokens.count >= 2 {
var reorderedTokens = tokens
let target = reorderedTokens[index]

@ -42,6 +42,7 @@ class TicketsCoordinator: NSObject, Coordinator {
let navigationController: UINavigationController
var coordinators: [Coordinator] = []
var ethPrice: Subscribable<Double>
let assetDefinitionStore: AssetDefinitionStore
init(
session: WalletSession,
@ -49,7 +50,8 @@ class TicketsCoordinator: NSObject, Coordinator {
keystore: Keystore,
tokensStorage: TokensDataStore,
ethPrice: Subscribable<Double>,
token: TokenObject
token: TokenObject,
assetDefinitionStore: AssetDefinitionStore
) {
self.session = session
self.keystore = keystore
@ -57,6 +59,7 @@ class TicketsCoordinator: NSObject, Coordinator {
self.tokensStorage = tokensStorage
self.ethPrice = ethPrice
self.token = token
self.assetDefinitionStore = assetDefinitionStore
}
func start() {
@ -66,10 +69,20 @@ class TicketsCoordinator: NSObject, Coordinator {
rootViewController.tokenObject = token
rootViewController.configure(viewModel: viewModel)
navigationController.viewControllers = [rootViewController]
refreshUponAssetDefinitionChanges()
}
private func refreshUponAssetDefinitionChanges() {
assetDefinitionStore.subscribe { [weak self] xml, contract in
guard let strongSelf = self else { return }
guard contract.sameContract(as: strongSelf.token.contract) else { return }
let viewModel = TicketsViewModel(token: strongSelf.token)
strongSelf.rootViewController.configure(viewModel: viewModel)
}
}
private func makeTicketsViewController(with account: Wallet) -> TicketsViewController {
let controller = TicketsViewController(tokenObject: token)
let controller = TicketsViewController(config: session.config, tokenObject: token)
controller.account = account
controller.session = session
controller.tokensStorage = tokensStorage
@ -150,7 +163,8 @@ class TicketsCoordinator: NSObject, Coordinator {
return vc
}
private func showEnterSellTicketsExpiryDateViewController(token: TokenObject,
private func showEnterSellTicketsExpiryDateViewController(
token: TokenObject,
for ticketHolder: TicketHolder,
ethCost: String,
in viewController: EnterSellTicketsPriceQuantityViewController) {
@ -179,7 +193,7 @@ class TicketsCoordinator: NSObject, Coordinator {
}
private func makeRedeemTicketsViewController() -> RedeemTicketsViewController {
let controller = RedeemTicketsViewController(config: session.config)
let controller = RedeemTicketsViewController(config: session.config, token: token)
let viewModel = RedeemTicketsViewModel(token: token)
controller.configure(viewModel: viewModel)
controller.delegate = self
@ -195,7 +209,7 @@ class TicketsCoordinator: NSObject, Coordinator {
}
private func makeRedeemTicketsQuantitySelectionViewController(token: TokenObject, for ticketHolder: TicketHolder) -> RedeemTicketsQuantitySelectionViewController {
let controller = RedeemTicketsQuantitySelectionViewController(config: session.config)
let controller = RedeemTicketsQuantitySelectionViewController(config: session.config, token: token)
let viewModel = RedeemTicketsQuantitySelectionViewModel(token: token, ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
controller.delegate = self
@ -219,7 +233,7 @@ class TicketsCoordinator: NSObject, Coordinator {
}
private func makeTransferTicketsViaWalletAddressViewController(token: TokenObject, for ticketHolder: TicketHolder, paymentFlow: PaymentFlow) -> TransferTicketsViaWalletAddressViewController {
let controller = TransferTicketsViaWalletAddressViewController(config: session.config, ticketHolder: ticketHolder, paymentFlow: paymentFlow)
let controller = TransferTicketsViaWalletAddressViewController(config: session.config, token: token, ticketHolder: ticketHolder, paymentFlow: paymentFlow)
let viewModel = TransferTicketsViaWalletAddressViewControllerViewModel(token: token, ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
controller.delegate = self
@ -235,14 +249,14 @@ class TicketsCoordinator: NSObject, Coordinator {
}
private func makeTicketRedemptionViewController(token: TokenObject, for ticketHolder: TicketHolder) -> TicketRedemptionViewController {
let controller = TicketRedemptionViewController(session: session)
let controller = TicketRedemptionViewController(config: session.config, session: session, token: token)
let viewModel = TicketRedemptionViewModel(token: token, ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
return controller
}
private func makeTransferTicketsViewController(paymentFlow: PaymentFlow) -> TransferTicketsViewController {
let controller = TransferTicketsViewController(config: session.config, paymentFlow: paymentFlow)
let controller = TransferTicketsViewController(config: session.config, paymentFlow: paymentFlow, token: token)
let viewModel = TransferTicketsViewModel(token: token)
controller.configure(viewModel: viewModel)
controller.delegate = self
@ -257,7 +271,7 @@ class TicketsCoordinator: NSObject, Coordinator {
}
private func makeTransferTicketsQuantitySelectionViewController(token: TokenObject, for ticketHolder: TicketHolder, paymentFlow: PaymentFlow) -> TransferTicketsQuantitySelectionViewController {
let controller = TransferTicketsQuantitySelectionViewController(config: session.config, paymentFlow: paymentFlow)
let controller = TransferTicketsQuantitySelectionViewController(config: session.config, paymentFlow: paymentFlow, token: token)
let viewModel = TransferTicketsQuantitySelectionViewModel(token: token, ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
controller.delegate = self
@ -273,7 +287,6 @@ class TicketsCoordinator: NSObject, Coordinator {
}
private func generateTransferLink(ticketHolder: TicketHolder, linkExpiryDate: Date, paymentFlow: PaymentFlow) -> String {
let contractAddress = XMLHandler().getAddressFromXML(server: session.config.server).eip55String
let order = Order(
price: BigUInt("0")!,
indices: ticketHolder.indices,

@ -9,10 +9,13 @@ protocol ChooseTicketTransferModeViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: ChooseTicketTransferModeViewController)
}
class ChooseTicketTransferModeViewController: UIViewController, VerifiableStatusViewController {
class ChooseTicketTransferModeViewController: UIViewController, TicketVerifiableStatusViewController {
let horizontalAdjustmentForLongMagicLinkButtonTitle = CGFloat(20)
private let config: Config
let config: Config
var contract: String? {
return viewModel.token.contract
}
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let ticketView = TicketRowView()
@ -116,10 +119,7 @@ class ChooseTicketTransferModeViewController: UIViewController, VerifiableStatus
func configure(viewModel: ChooseTicketTransferModeViewControllerViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -8,9 +8,12 @@ protocol SetTransferTicketsExpiryDateViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: SetTransferTicketsExpiryDateViewController)
}
class SetTransferTicketsExpiryDateViewController: UIViewController, VerifiableStatusViewController {
class SetTransferTicketsExpiryDateViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return viewModel.token.contract
}
let roundedBackground = RoundedBackground()
let scrollView = UIScrollView()
let header = TicketsViewControllerTitleHeader()
@ -237,10 +240,7 @@ class SetTransferTicketsExpiryDateViewController: UIViewController, VerifiableSt
func configure(viewModel: SetTransferTicketsExpiryDateViewControllerViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if viewModel.token.contract != contractAddress {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -8,9 +8,12 @@ protocol TransferTicketsQuantitySelectionViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: TransferTicketsQuantitySelectionViewController)
}
class TransferTicketsQuantitySelectionViewController: UIViewController, VerifiableStatusViewController {
class TransferTicketsQuantitySelectionViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return token.contract
}
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let subtitleLabel = UILabel()
@ -19,11 +22,13 @@ class TransferTicketsQuantitySelectionViewController: UIViewController, Verifiab
let nextButton = UIButton(type: .system)
var viewModel: TransferTicketsQuantitySelectionViewModel!
var paymentFlow: PaymentFlow
private let token: TokenObject
weak var delegate: TransferTicketsQuantitySelectionViewControllerDelegate?
init(config: Config = Config(), paymentFlow: PaymentFlow) {
init(config: Config = Config(), paymentFlow: PaymentFlow, token: TokenObject) {
self.config = config
self.paymentFlow = paymentFlow
self.token = token
super.init(nibName: nil, bundle: nil)
updateNavigationRightBarButtons(isVerified: true)
@ -119,10 +124,7 @@ class TransferTicketsQuantitySelectionViewController: UIViewController, Verifiab
func configure(viewModel: TransferTicketsQuantitySelectionViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if viewModel.token.contract != contractAddress {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -9,9 +9,12 @@ protocol TransferTicketsViaWalletAddressViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: TransferTicketsViaWalletAddressViewController)
}
class TransferTicketsViaWalletAddressViewController: UIViewController, VerifiableStatusViewController {
private let config: Config
class TransferTicketsViaWalletAddressViewController: UIViewController, TicketVerifiableStatusViewController {
let config: Config
var contract: String? {
return token.contract
}
private let token: TokenObject
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let ticketView = TicketRowView()
@ -22,8 +25,9 @@ class TransferTicketsViaWalletAddressViewController: UIViewController, Verifiabl
var paymentFlow: PaymentFlow
weak var delegate: TransferTicketsViaWalletAddressViewControllerDelegate?
init(config: Config, ticketHolder: TicketHolder, paymentFlow: PaymentFlow) {
init(config: Config, token: TokenObject, ticketHolder: TicketHolder, paymentFlow: PaymentFlow) {
self.config = config
self.token = token
self.ticketHolder = ticketHolder
self.paymentFlow = paymentFlow
super.init(nibName: nil, bundle: nil)
@ -112,10 +116,7 @@ class TransferTicketsViaWalletAddressViewController: UIViewController, Verifiabl
func configure(viewModel: TransferTicketsViaWalletAddressViewControllerViewModel) {
self.viewModel = viewModel
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
view.backgroundColor = viewModel.backgroundColor

@ -8,20 +8,25 @@ protocol TransferTicketsViewControllerDelegate: class {
func didPressViewContractWebPage(in viewController: TransferTicketsViewController)
}
class TransferTicketsViewController: UIViewController, VerifiableStatusViewController {
class TransferTicketsViewController: UIViewController, TicketVerifiableStatusViewController {
private let config: Config
let config: Config
var contract: String? {
return viewModel.token.contract
}
let roundedBackground = RoundedBackground()
let header = TicketsViewControllerTitleHeader()
let tableView = UITableView(frame: .zero, style: .plain)
let nextButton = UIButton(type: .system)
var viewModel: TransferTicketsViewModel!
var paymentFlow: PaymentFlow
private let token: TokenObject
weak var delegate: TransferTicketsViewControllerDelegate?
init(config: Config, paymentFlow: PaymentFlow) {
init(config: Config, paymentFlow: PaymentFlow, token: TokenObject) {
self.config = config
self.paymentFlow = paymentFlow
self.token = token
super.init(nibName: nil, bundle: nil)
updateNavigationRightBarButtons(isVerified: true)
@ -78,10 +83,7 @@ class TransferTicketsViewController: UIViewController, VerifiableStatusViewContr
func configure(viewModel: TransferTicketsViewModel) {
self.viewModel = viewModel
tableView.dataSource = self
let contractAddress = XMLHandler().getAddressFromXML(server: config.server).eip55String
if !viewModel.token.contract.sameContract(as: contractAddress) {
updateNavigationRightBarButtons(isVerified: false)
}
updateNavigationRightBarButtons(isVerified: isContractVerified)
header.configure(title: viewModel.title)
tableView.tableHeaderView = header

@ -0,0 +1,19 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import XCTest
@testable import Trust
class AssetDefinitionStoreTests: XCTestCase {
func testConvertsModifiedDateToStringForHTTPHeaderIfModifiedSince() {
let date = GeneralisedTime(string: "20230405111234+0000")!.date
XCTAssertEqual(AssetDefinitionStore().string(fromLastModifiedDate: date), "Wed, 05 Apr 2023 11:12:34 GMT")
}
func testXMLAccess() {
let store = AssetDefinitionStore(backingStore: AssetDefinitionInMemoryBackingStore())
XCTAssertNil(store["0x1"])
store["0x1"] = "xml1"
XCTAssertEqual(store["0x1"], "xml1")
}
}

@ -15,7 +15,7 @@ class XMLHandlerTest: XCTestCase {
let tokenHex = "0x00000000000000000000000000000000fefe5ae99a3000000000000000010001".substring(from: 2)
func testParser() {
let fifaDetails = XMLHandler().getFifaInfoForTicket(
let fifaDetails = XMLHandler(contract: "0x").getFifaInfoForTicket(
tokenId: BigUInt(tokenHex, radix: 16)!, index: UInt16(1)
)
XCTAssertNotNil(fifaDetails)

@ -10,7 +10,8 @@ class TokensCoordinatorTests: XCTestCase {
navigationController: FakeNavigationController(),
session: .make(),
keystore: FakeKeystore(),
tokensStorage: FakeTokensDataStore()
tokensStorage: FakeTokensDataStore(),
assetDefinitionStore: AssetDefinitionStore()
)
coordinator.start()

@ -0,0 +1,17 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import XCTest
@testable import Trust
class StringExtensionTests: XCTestCase {
func testAdd0x() {
XCTAssertEqual("001".add0x, "0x001")
XCTAssertEqual("0x001".add0x, "0x001")
}
func testDrop0x() {
XCTAssertEqual("001".drop0x, "001")
XCTAssertEqual("0x001".drop0x, "001")
}
}

@ -10,6 +10,6 @@ class FakeTokensDataStore: TokensDataStore {
let account: Wallet = .make()
let config: Config = .make()
let web3: Web3Swift = Web3Swift()
self.init(realm: realm, account: account, config: config, web3: web3)
self.init(realm: realm, account: account, config: config, web3: web3, assetDefinitionStore: AssetDefinitionStore())
}
}

@ -23,7 +23,7 @@ class UniversalLinkHandlerTests: XCTestCase {
func testCreateUniversalLink() {
var indices = [UInt16]()
indices.append(1)
let contractAddress = XMLHandler().getAddressFromXML(server: Config().server).eip55String
let contractAddress = Config().ticketContractAddress!
let testOrder1 = Order(price: BigUInt("1000000000")!,
indices: indices,
expiry: BigUInt("0")!,

@ -16,7 +16,7 @@ class CreateRedeemTests: XCTestCase {
indices.append(1)
indices.append(2)
let account = keyStore.createAccount(password: "test")
let message = CreateRedeem(config: Config()).redeemMessage(ticketIndices: indices).0
let message = CreateRedeem(config: Config(), token: TokenObject()).redeemMessage(ticketIndices: indices).0
print(message)
let data = message.data(using: String.Encoding.utf8)
@ -24,6 +24,7 @@ class CreateRedeemTests: XCTestCase {
//message and signature is to go in qr code
print("message: " + message)
print(try! "signature: " + signature.dematerialize().hexString)
//TODO no test?
}
}

@ -16,8 +16,8 @@ class TransferTicketsQuantitySelectionViewControllerTests: FBSnapshotTestCase {
let type = PaymentFlow.send(type: .stormBird(token))
let ticket = Ticket(id: "1", index: 1, city: "", name: "", venue: "", match: 9, date: GeneralisedTime(string: "20010203160500+0300")!, seatId: 1, category: "MATCH CLUB", countryA: "Team A", countryB: "Team B")
let ticketHolder = TicketHolder(tickets: [ticket], status: .available, contractAddress: "0x1")
let controller = TransferTicketsQuantitySelectionViewController(paymentFlow: type)
let viewModel = TransferTicketsQuantitySelectionViewModel(token: TokenObject(), ticketHolder: ticketHolder)
let controller = TransferTicketsQuantitySelectionViewController(paymentFlow: type, token: token)
let viewModel = TransferTicketsQuantitySelectionViewModel(token: token, ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
FBSnapshotVerifyView(controller.view)

Loading…
Cancel
Save