Merge branch 'master' into hardcode-fifa-ui

pull/112/head
James Sangalli 7 years ago committed by GitHub
commit 4f7e59e157
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      Trust.xcodeproj/project.pbxproj
  2. 47
      Trust/AppDelegate.swift
  3. 21
      Trust/Assets.xcassets/ticket_bundle_checked.imageset/Contents.json
  4. BIN
      Trust/Assets.xcassets/ticket_bundle_checked.imageset/ticket_bundle_checked.pdf
  5. 21
      Trust/Assets.xcassets/ticket_bundle_unchecked.imageset/Contents.json
  6. BIN
      Trust/Assets.xcassets/ticket_bundle_unchecked.imageset/ticket_bundle_unchecked.pdf
  7. 5
      Trust/Browser/Types/DappAction.swift
  8. 2
      Trust/EtherClient/EtherKeystore.swift
  9. 2
      Trust/EtherClient/Keystore.swift
  10. 12
      Trust/EtherClient/TransactionSigning.swift
  11. 1
      Trust/Extensions/String.swift
  12. 100
      Trust/InCoordinator.swift
  13. 2
      Trust/Info.plist
  14. 6
      Trust/Localization/en.lproj/Localizable.strings
  15. 103
      Trust/Market/MarketQueueHandler.swift
  16. 50
      Trust/Market/OrderHandler.swift
  17. 77
      Trust/Market/OrdersRequest.swift
  18. 160
      Trust/Market/UniversalLinkHandler.swift
  19. 2
      Trust/Models/SignTransaction.swift
  20. 130
      Trust/Redeem/ViewControllers/QuantitySelectionViewController.swift
  21. 231
      Trust/Redeem/ViewControllers/RedeemTickets.storyboard
  22. 99
      Trust/Redeem/ViewControllers/RedeemTicketsViewController.swift
  23. 97
      Trust/Redeem/ViewControllers/TicketRedemptionViewController.swift
  24. 60
      Trust/Redeem/ViewModels/QuantitySelectionViewModel.swift
  25. 57
      Trust/Redeem/ViewModels/RedeemTicketTableViewCellViewModel.swift
  26. 36
      Trust/Redeem/ViewModels/RedeemTicketsViewModel.swift
  27. 40
      Trust/Redeem/ViewModels/TicketRedemptionViewModel.swift
  28. 53
      Trust/Redeem/ViewModels/TicketRowViewModel.swift
  29. 49
      Trust/Redeem/Views/RedeemTicketTableViewCell.swift
  30. 150
      Trust/Redeem/Views/TicketRowView.swift
  31. 53
      Trust/Redeem/Views/TicketsViewControllerTitleHeader.swift
  32. 2
      Trust/Settings/Types/Config.swift
  33. 50
      Trust/Tokens/Coordinators/ClaimOrderCoordinator.swift
  34. 22
      Trust/Tokens/Coordinators/TokensCoordinator.swift
  35. 1
      Trust/Tokens/Types/TokenType.swift
  36. 16
      Trust/Tokens/Types/TokensDataStore.swift
  37. 1
      Trust/Tokens/ViewControllers/NewTokenViewController.swift
  38. 16
      Trust/Tokens/ViewControllers/TokensViewController.swift
  39. 48
      Trust/Tokens/ViewModels/TicketTableViewCellViewModel.swift
  40. 115
      Trust/Tokens/Views/TicketTableViewCell.swift
  41. 63
      Trust/Tokens/Views/TicketView.swift
  42. 100
      Trust/Tokens/Views/TicketView.xib
  43. 10
      Trust/Transactions/Coordinators/BalanceCoordinator.swift
  44. 15
      Trust/Transactions/Coordinators/TicketsCoordinator.swift
  45. 105
      Trust/Transfer/Controllers/TransactionConfigurator.swift
  46. 1
      Trust/Transfer/Coordinators/SendCoordinator.swift
  47. 12
      Trust/Transfer/Coordinators/SendTransactionCoordinator.swift
  48. 2
      Trust/Transfer/Types/SentTransaction.swift
  49. 5
      Trust/Transfer/Types/TransferType.swift
  50. 7
      Trust/Transfer/Types/UnconfirmedTransaction.swift
  51. 2
      Trust/Transfer/ViewControllers/ConfirmPaymentViewController.swift
  52. 6
      Trust/Transfer/ViewControllers/SendViewController.swift
  53. 1
      Trust/Transfer/ViewModels/ConfigureTransactionViewModel.swift
  54. 4
      Trust/Transfer/ViewModels/ConfirmPaymentDetailsViewModel.swift
  55. 2
      Trust/Transfer/ViewModels/SendViewModel.swift
  56. 5
      Trust/Trust.entitlements
  57. 1
      Trust/UI/BalanceTitleView.swift
  58. 109
      Trust/UI/NumberStepper.swift
  59. 33
      Trust/Vendors/New Group/Commands/ClaimStormBirdOrder.swift
  60. 5
      Trust/Vendors/New Group/Commands/ContractERC20Transfer.swift
  61. 2
      Trust/Vendors/New Group/Commands/ContractStormBirdTransfer.swift
  62. 4
      Trust/Vendors/New Group/Commands/GetIsStormBird.swift
  63. 4
      Trust/Vendors/New Group/Web3Swift.swift
  64. 98
      TrustTests/Coordinators/ClaimOrderCoordinatorTests.swift
  65. 3
      TrustTests/Coordinators/SendCoordinatorTests.swift
  66. 6
      TrustTests/EtherClient/TransactionSigningTests.swift
  67. 8
      TrustTests/Factories/Address.swift
  68. 11
      TrustTests/Factories/FakeClaimOrderCoordinator.swift
  69. 2
      TrustTests/Factories/FakeKeystore.swift
  70. 4
      TrustTests/Factories/UnconfirmedTransaction.swift
  71. 10
      TrustTests/Factories/Wallet.swift
  72. 16
      TrustTests/Factories/WalletSession.swift
  73. 59
      TrustTests/Market/MarketQueueHandlerTests.swift
  74. 64
      TrustTests/Market/OrderRequestTest.swift
  75. 56
      TrustTests/Market/OrderSigningTests.swift
  76. 20
      TrustTests/Market/UniversalLinkHandlerTests.swift
  77. 2
      TrustTests/Redeem/CreateRedeemTests.swift

@ -254,7 +254,6 @@
442FC01053E2F1B59355A961 /* Arguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCC7DC5A13C023F7F2C26 /* Arguments.swift */; };
442FC03669B2BB02B0876278 /* StormBirdTokenSummaryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCFA81968EBB72349E849 /* StormBirdTokenSummaryTableViewCell.swift */; };
442FC0B59B23C0F3068621C0 /* NumberStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC575B6A4A50B0555E1B0 /* NumberStepper.swift */; };
442FC11C94F17E8FDC0DA7B7 /* RedeemTickets.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 442FCC6869F8D9EBCE80C1E0 /* RedeemTickets.storyboard */; };
442FC126C8A73F70F2782025 /* TicketDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC6911EEE85B669D7BDA1 /* TicketDecode.swift */; };
442FC249C2A39F85BDBD7987 /* GetDecimalsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC0F2E3BB3EE420BC2C16 /* GetDecimalsCoordinator.swift */; };
442FC258BAEFDE2D64E48D0D /* TicketsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCC8150042FE9179D574A /* TicketsCoordinator.swift */; };
@ -266,6 +265,7 @@
442FC65BFA2D42879648EC06 /* Int.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCCAC6A172506637A2FF6 /* Int.swift */; };
442FC7777D495D3095836CA6 /* RedeemTicketsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCB2915417EB871E40D6C /* RedeemTicketsViewModel.swift */; };
442FC81F38F80EEF54C69C5B /* TicketAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC20E6470B92A46479342 /* TicketAdaptor.swift */; };
442FC8E82231273273B95D85 /* FakeClaimOrderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCE526E2089CAE88D5602 /* FakeClaimOrderCoordinator.swift */; };
442FC9C4FAC64192FF5DCC40 /* GetERC20Decimals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC72F9D22CC36AC015274 /* GetERC20Decimals.swift */; };
442FCA2F6C6A21C27B628AA4 /* RedeemTicketTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FCD4311FCAFE6FB288A5E /* RedeemTicketTableViewCell.swift */; };
442FCACD46848927C84DE66E /* EndPoints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 442FC203B8266AB92D8F4032 /* EndPoints.swift */; };
@ -307,8 +307,10 @@
5E7C75F80A7E178B49830BCD /* TicketsViewControllerHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C796039C0F47CDCA236C0 /* TicketsViewControllerHeader.swift */; };
5E7C760C7D55C97424F55138 /* TicketTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75F877B2F2E24C7EF258 /* TicketTableViewCellViewModel.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 */; };
5E7C76F8CB67466725C590CE /* TokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */; };
5E7C7700014B93A966BBA463 /* RedeemTicketTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77061BEF269BCE358086 /* RedeemTicketTableViewCellViewModel.swift */; };
5E7C774B5332AC0DC19C5B1B /* EthTokenViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B82783A94091A43470 /* EthTokenViewCellViewModel.swift */; };
5E7C7793AB6B577906F2BCA3 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AFE9AF9FE6B58C925D4 /* SettingsViewController.swift */; };
5E7C77E844D710D7AFBC58D4 /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */; };
@ -316,6 +318,7 @@
5E7C78B3FD5CA87E395E1861 /* OnboardingPageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AF9A592D7224ED58016 /* OnboardingPageStyle.swift */; };
5E7C78D6C94739B1ADDFBB5B /* WhyUseEthereumInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74FABE14B7B1BEEC4F5E /* WhyUseEthereumInfoViewController.swift */; };
5E7C797BE2C8DB7EF6F217B3 /* OnboardingPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7103135DCCCAB96EE5FC /* OnboardingPage.swift */; };
5E7C798E5F5EE00D405B91AE /* TicketRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */; };
5E7C7AB2ECFB589632F2A26C /* WalletFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7E2DCCE0D775ECF83088 /* WalletFilter.swift */; };
5E7C7AB6950E43BD6E8D0CBE /* TokensViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B3302309706CA0F972A /* TokensViewController.swift */; };
5E7C7B3E08EEA63C5B68B9C4 /* TicketRedemptionInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */; };
@ -323,6 +326,7 @@
5E7C7C658D619C70F1E3DE59 /* AdvancedSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C719B717E002583B1E2E9 /* AdvancedSettingsViewModel.swift */; };
5E7C7C98EAF40E8110241DBD /* TicketTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */; };
5E7C7C9E89056069C8FEFA76 /* AlphaWalletSettingsSwitchRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7534FB6BF4D199643246 /* AlphaWalletSettingsSwitchRow.swift */; };
5E7C7CDB837DCD57E0594CBA /* TicketsViewControllerTitleHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */; };
5E7C7CE5CA19183FCED8C907 /* TokensViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE467A7F5F2E5B1F660 /* TokensViewModel.swift */; };
5E7C7CF06533EDACC8E220B3 /* StaticHTMLViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C764B98F526271E4C2A6A /* StaticHTMLViewController.swift */; };
5E7C7CF3BB38045FA40F38AE /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72142D5817EF8FA8CADA /* PrivacyPolicyViewController.swift */; };
@ -367,9 +371,14 @@
73ED85A72034BFEF00593BF3 /* UITextFieldAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED85A62034BFEF00593BF3 /* UITextFieldAdditions.swift */; };
73ED85A92034C42D00593BF3 /* StringFormatterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73ED85A82034C42D00593BF3 /* StringFormatterTest.swift */; };
76F1D137B10D8309E513BBDD /* OrderSigningTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */; };
76F1D5AF727A83205BBCF0EC /* SignOrders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DADFD07E2941897FD2E1 /* SignOrders.swift */; };
76F1D13FC8A41AD967C59947 /* ClaimOrderCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */; };
76F1D5AF727A83205BBCF0EC /* OrderHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */; };
76F1D76BF700FCC461B11CC0 /* ClaimStormBirdOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D1417613174D447DEE56 /* ClaimStormBirdOrder.swift */; };
76F1D91659771C9EEA7B48DC /* CreateRedeem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DF80932454E9F58B7830 /* CreateRedeem.swift */; };
76F1DB9E1443DCFC36228B08 /* ClaimOrderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */; };
76F1DC92CDEB695115DBC47C /* UniversalLinkHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */; };
76F1DD10DF9A6C844E5F57D6 /* CreateRedeemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1D8877226D5DD086B135D /* CreateRedeemTests.swift */; };
76F1DEFF94F9A1F67BDF2735 /* UniversalLinkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */; };
771A8471202F067D00528D28 /* NetworksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771A8470202F067D00528D28 /* NetworksViewController.swift */; };
771A847320322F2500528D28 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771A847220322F2500528D28 /* PreferencesViewController.swift */; };
771A847520322FD700528D28 /* PreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 771A847420322FD700528D28 /* PreferencesViewModel.swift */; };
@ -416,11 +425,9 @@
AA893ED5203C3E5400CDCED1 /* TokenBalance.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA893ED4203C3E5400CDCED1 /* TokenBalance.swift */; };
AAEB8DA0204BBDB200CB0B2C /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEB8D9F204BBDB100CB0B2C /* RadioButton.swift */; };
AAEB8DA2204BC7B700CB0B2C /* RedeemTicketsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEB8DA1204BC7B700CB0B2C /* RedeemTicketsViewController.swift */; };
AAEB8DA6204D5CDE00CB0B2C /* TicketView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AAEB8DA5204D5CDE00CB0B2C /* TicketView.xib */; };
AAEB8DA8204D5CFA00CB0B2C /* TicketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEB8DA7204D5CFA00CB0B2C /* TicketView.swift */; };
AAEF2CAB2050A68A0038BE0D /* SignatureHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEF2CAA2050A68A0038BE0D /* SignatureHelper.swift */; };
B1DC375D203AEAE200C9756D /* OrdersRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DC375C203AEAE100C9756D /* OrdersRequest.swift */; };
B1DC375F203AEB4800C9756D /* OrderRequestTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DC375E203AEB4800C9756D /* OrderRequestTest.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 */; };
BBF4F9B72029D0B3009E04C0 /* GasViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBF4F9B62029D0B2009E04C0 /* GasViewModel.swift */; };
C868C5292053BDE00059672B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C868C5282053BDE00059672B /* LaunchScreen.storyboard */; };
@ -746,11 +753,11 @@
442FCBDB86579889BC773540 /* TicketRedemptionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRedemptionViewModel.swift; sourceTree = "<group>"; };
442FCBDD3A69DF75551B10AC /* GetERC20Symbol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetERC20Symbol.swift; sourceTree = "<group>"; };
442FCC17EAB857C58732831E /* QuantitySelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuantitySelectionViewController.swift; sourceTree = "<group>"; };
442FCC6869F8D9EBCE80C1E0 /* RedeemTickets.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RedeemTickets.storyboard; sourceTree = "<group>"; };
442FCC7DC5A13C023F7F2C26 /* Arguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Arguments.swift; sourceTree = "<group>"; };
442FCC8150042FE9179D574A /* TicketsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsCoordinator.swift; sourceTree = "<group>"; };
442FCCAC6A172506637A2FF6 /* Int.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Int.swift; sourceTree = "<group>"; };
442FCD4311FCAFE6FB288A5E /* RedeemTicketTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemTicketTableViewCell.swift; sourceTree = "<group>"; };
442FCE526E2089CAE88D5602 /* FakeClaimOrderCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeClaimOrderCoordinator.swift; sourceTree = "<group>"; };
442FCEBC98D0A367D258D2C3 /* GetIsStormBirdCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetIsStormBirdCoordinator.swift; sourceTree = "<group>"; };
442FCFA81968EBB72349E849 /* StormBirdTokenSummaryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StormBirdTokenSummaryTableViewCell.swift; sourceTree = "<group>"; };
442FCFEB2D7443C4E0B889B0 /* TicketHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketHolder.swift; sourceTree = "<group>"; };
@ -781,9 +788,12 @@
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>"; };
5E7C767497AD8DEE83F384D7 /* RequestViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestViewModel.swift; sourceTree = "<group>"; };
5E7C77061BEF269BCE358086 /* RedeemTicketTableViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemTicketTableViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C77316522DF2B256F1F92 /* TicketsViewControllerHeaderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsViewControllerHeaderViewModel.swift; sourceTree = "<group>"; };
5E7C7742709724B3BD0C2A0D /* TicketRowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRowViewModel.swift; sourceTree = "<group>"; };
5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsEthereumInfoViewController.swift; sourceTree = "<group>"; };
5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRedemptionInfoViewController.swift; sourceTree = "<group>"; };
5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsViewControllerTitleHeader.swift; sourceTree = "<group>"; };
5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCell.swift; sourceTree = "<group>"; };
5E7C78581AA28CA5C3CBC468 /* AdvancedSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewController.swift; sourceTree = "<group>"; };
5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoIGetMyMoneyInfoViewController.swift; sourceTree = "<group>"; };
@ -794,6 +804,7 @@
5E7C79D674D45A07E694CE31 /* LockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = "<group>"; };
5E7C79ED9F842D3FC102AC54 /* TokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeCoordinator.swift; sourceTree = "<group>"; };
5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRowView.swift; sourceTree = "<group>"; };
5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactUsBannerView.swift; sourceTree = "<group>"; };
5E7C7AF9A592D7224ED58016 /* OnboardingPageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPageStyle.swift; sourceTree = "<group>"; };
5E7C7AFE9AF9FE6B58C925D4 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
@ -841,8 +852,13 @@
73ED85A420349BE400593BF3 /* StringFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatter.swift; sourceTree = "<group>"; };
73ED85A62034BFEF00593BF3 /* UITextFieldAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITextFieldAdditions.swift; sourceTree = "<group>"; };
73ED85A82034C42D00593BF3 /* StringFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringFormatterTest.swift; sourceTree = "<group>"; };
76F1D1417613174D447DEE56 /* ClaimStormBirdOrder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimStormBirdOrder.swift; sourceTree = "<group>"; };
76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinator.swift; sourceTree = "<group>"; };
76F1D8877226D5DD086B135D /* CreateRedeemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeemTests.swift; sourceTree = "<group>"; };
76F1DADFD07E2941897FD2E1 /* SignOrders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignOrders.swift; sourceTree = "<group>"; };
76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandlerTests.swift; sourceTree = "<group>"; };
76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimOrderCoordinatorTests.swift; sourceTree = "<group>"; };
76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderHandler.swift; sourceTree = "<group>"; };
76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalLinkHandler.swift; sourceTree = "<group>"; };
76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderSigningTests.swift; sourceTree = "<group>"; };
76F1DF80932454E9F58B7830 /* CreateRedeem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateRedeem.swift; sourceTree = "<group>"; };
771A8470202F067D00528D28 /* NetworksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworksViewController.swift; sourceTree = "<group>"; };
@ -901,12 +917,10 @@
AA893ED4203C3E5400CDCED1 /* TokenBalance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenBalance.swift; sourceTree = "<group>"; };
AAEB8D9F204BBDB100CB0B2C /* RadioButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = "<group>"; };
AAEB8DA1204BC7B700CB0B2C /* RedeemTicketsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedeemTicketsViewController.swift; sourceTree = "<group>"; };
AAEB8DA5204D5CDE00CB0B2C /* TicketView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TicketView.xib; sourceTree = "<group>"; };
AAEB8DA7204D5CFA00CB0B2C /* TicketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicketView.swift; sourceTree = "<group>"; };
AAEF2CAA2050A68A0038BE0D /* SignatureHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureHelper.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-Trust/Pods-Trust.release.xcconfig"; sourceTree = "<group>"; };
B1DC375C203AEAE100C9756D /* OrdersRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrdersRequest.swift; sourceTree = "<group>"; };
B1DC375E203AEB4800C9756D /* OrderRequestTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderRequestTest.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; };
BB5D6A9D20232EE8000FC5AB /* CurrencyRate+Fee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CurrencyRate+Fee.swift"; sourceTree = "<group>"; };
BBF4F9B62029D0B2009E04C0 /* GasViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GasViewModel.swift; sourceTree = "<group>"; };
@ -1174,6 +1188,7 @@
442FC8B1D5B4317DE7D68325 /* GetStormBirdBalance.swift */,
442FCA1F19B6293FE5FAD494 /* GetIsStormBird.swift */,
442FC72F9D22CC36AC015274 /* GetERC20Decimals.swift */,
76F1D1417613174D447DEE56 /* ClaimStormBirdOrder.swift */,
);
path = Commands;
sourceTree = "<group>";
@ -1552,8 +1567,6 @@
29F1C8461FEB6664003780D8 /* EditTokenTableViewCell.xib */,
29F1C84B1FEC4F6F003780D8 /* TokensFooterView.swift */,
442FCFA81968EBB72349E849 /* StormBirdTokenSummaryTableViewCell.swift */,
AAEB8DA5204D5CDE00CB0B2C /* TicketView.xib */,
AAEB8DA7204D5CFA00CB0B2C /* TicketView.swift */,
5E7C796039C0F47CDCA236C0 /* TicketsViewControllerHeader.swift */,
5E7C7C077372C3F2A4349FA1 /* TokenViewCell.swift */,
5E7C7C58586099F082973073 /* WalletFilterView.swift */,
@ -1981,6 +1994,7 @@
442FC002CDEA792B8ECD3E13 /* GetStormBirdBalanceCoordinator.swift */,
442FCEBC98D0A367D258D2C3 /* GetIsStormBirdCoordinator.swift */,
442FC0F2E3BB3EE420BC2C16 /* GetDecimalsCoordinator.swift */,
76F1D419EE36261E50ABAFAE /* ClaimOrderCoordinator.swift */,
5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */,
);
path = Coordinators;
@ -2098,6 +2112,7 @@
732E0F512022754600B39C1F /* FakeLockProtocol.swift */,
73CBC760202139FB00374666 /* FakeGetBalanceCoordinator(.swift */,
771A84812032423800528D28 /* PreferencesController.swift */,
442FCE526E2089CAE88D5602 /* FakeClaimOrderCoordinator.swift */,
);
path = Factories;
sourceTree = "<group>";
@ -2118,6 +2133,7 @@
295996131FAB09A200DB66A8 /* DepositCoordinatorTests.swift */,
732E0F4F2022716700B39C1F /* LockEnterPasscodeCoordinatorTest.swift */,
732E0F532022786400B39C1F /* LockCreatePasscodeCoordinatorTest.swift */,
76F1DACA9404AD6740BEADBB /* ClaimOrderCoordinatorTests.swift */,
);
path = Coordinators;
sourceTree = "<group>";
@ -2126,6 +2142,8 @@
isa = PBXGroup;
children = (
442FCD4311FCAFE6FB288A5E /* RedeemTicketTableViewCell.swift */,
5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */,
5E7C7ACB94CEE493AC37487F /* TicketRowView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -2136,6 +2154,8 @@
442FCB2915417EB871E40D6C /* RedeemTicketsViewModel.swift */,
442FC54DA900FA2F9BB73A63 /* QuantitySelectionViewModel.swift */,
442FCBDB86579889BC773540 /* TicketRedemptionViewModel.swift */,
5E7C77061BEF269BCE358086 /* RedeemTicketTableViewCellViewModel.swift */,
5E7C7742709724B3BD0C2A0D /* TicketRowViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -2144,7 +2164,6 @@
isa = PBXGroup;
children = (
AAEB8DA1204BC7B700CB0B2C /* RedeemTicketsViewController.swift */,
442FCC6869F8D9EBCE80C1E0 /* RedeemTickets.storyboard */,
442FCC17EAB857C58732831E /* QuantitySelectionViewController.swift */,
442FCACD2BF4DD90BD9C6DBC /* TicketRedemptionViewController.swift */,
);
@ -2324,8 +2343,9 @@
76F1D1936604D6A022E9AE90 /* Market */ = {
isa = PBXGroup;
children = (
76F1DADFD07E2941897FD2E1 /* SignOrders.swift */,
B1DC375C203AEAE100C9756D /* OrdersRequest.swift */,
76F1DADFD07E2941897FD2E1 /* OrderHandler.swift */,
B1DC375C203AEAE100C9756D /* MarketQueueHandler.swift */,
76F1DCD54618349AC91C6DF8 /* UniversalLinkHandler.swift */,
);
path = Market;
sourceTree = "<group>";
@ -2347,7 +2367,8 @@
isa = PBXGroup;
children = (
76F1DE8ADA3176D0277EDF20 /* OrderSigningTests.swift */,
B1DC375E203AEB4800C9756D /* OrderRequestTest.swift */,
B1DC375E203AEB4800C9756D /* MarketQueueHandlerTests.swift */,
76F1D96298E216CBFC3DD78B /* UniversalLinkHandlerTests.swift */,
);
path = Market;
sourceTree = "<group>";
@ -2679,7 +2700,7 @@
enabled = 1;
};
com.apple.SafariKeychain = {
enabled = 0;
enabled = 1;
};
com.apple.iCloud = {
enabled = 0;
@ -2742,7 +2763,6 @@
613D04891FDE15F8008DE72E /* COMODO ECC Domain Validation Secure Server CA 2.cer in Resources */,
C887C53A2057B703005ACF81 /* termsOfService.html in Resources */,
C876FF7D204A79D300B7D0EA /* SourceSansPro-Bold.otf in Resources */,
AAEB8DA6204D5CDE00CB0B2C /* TicketView.xib in Resources */,
C880330D2054371500D73D6F /* non_asset_catalog_redemption_location@2x.png in Resources */,
C880332120551DF800D73D6F /* whyUseEthereumInfo.html in Resources */,
29FA00D2201CA79F002F7DC5 /* web3.min.js in Resources */,
@ -2761,7 +2781,6 @@
73CBC75F2020CBF800374666 /* AccountViewCell.xib in Resources */,
C880332220551DF800D73D6F /* whatIsEthereumInfo.html in Resources */,
29F1C8481FEB6664003780D8 /* EditTokenTableViewCell.xib in Resources */,
442FC11C94F17E8FDC0DA7B7 /* RedeemTickets.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3182,7 +3201,7 @@
29A13E331F6B1B7A00E432A2 /* AppStyle.swift in Sources */,
737EEDDA201BE3A8009D9D5D /* Lock.swift in Sources */,
29FF12F81F747D6C00AFD326 /* Error.swift in Sources */,
B1DC375D203AEAE200C9756D /* OrdersRequest.swift in Sources */,
B1DC375D203AEAE200C9756D /* MarketQueueHandler.swift in Sources */,
29AD8A061F93DC8C008E10E7 /* PushDevice.swift in Sources */,
294DFBA91FE6EBFB004CEB56 /* NewTokenViewController.swift in Sources */,
29B933F51F860074009FCABB /* SendTransactionCoordinator.swift in Sources */,
@ -3198,7 +3217,6 @@
771A847520322FD700528D28 /* PreferencesViewModel.swift in Sources */,
296AF9A51F736BA20058AF78 /* Config.swift in Sources */,
7721A6C8202EF81B004DB16C /* CustomRPC.swift in Sources */,
AAEB8DA8204D5CFA00CB0B2C /* TicketView.swift in Sources */,
29E9CFCD1FE7343C00017744 /* NewTokenViewModel.swift in Sources */,
AAEB8DA2204BC7B700CB0B2C /* RedeemTicketsViewController.swift in Sources */,
291F52A21F6B6DCF00B369AB /* EtherClient.swift in Sources */,
@ -3291,7 +3309,7 @@
442FC65BFA2D42879648EC06 /* Int.swift in Sources */,
442FC9C4FAC64192FF5DCC40 /* GetERC20Decimals.swift in Sources */,
442FC249C2A39F85BDBD7987 /* GetDecimalsCoordinator.swift in Sources */,
76F1D5AF727A83205BBCF0EC /* SignOrders.swift in Sources */,
76F1D5AF727A83205BBCF0EC /* OrderHandler.swift in Sources */,
442FCD76B5C25C9FB7FE7479 /* TicketsViewModel.swift in Sources */,
442FC2CC21FFFC4FAB33A300 /* Ticket.swift in Sources */,
442FCAD7292D93C261B97D09 /* TicketHolder.swift in Sources */,
@ -3299,6 +3317,8 @@
442FC126C8A73F70F2782025 /* TicketDecode.swift in Sources */,
442FC03669B2BB02B0876278 /* StormBirdTokenSummaryTableViewCell.swift in Sources */,
442FC258BAEFDE2D64E48D0D /* TicketsCoordinator.swift in Sources */,
76F1DB9E1443DCFC36228B08 /* ClaimOrderCoordinator.swift in Sources */,
76F1D76BF700FCC461B11CC0 /* ClaimStormBirdOrder.swift in Sources */,
76F1D91659771C9EEA7B48DC /* CreateRedeem.swift in Sources */,
442FCA2F6C6A21C27B628AA4 /* RedeemTicketTableViewCell.swift in Sources */,
442FC7777D495D3095836CA6 /* RedeemTicketsViewModel.swift in Sources */,
@ -3365,9 +3385,14 @@
5E7C74B99922D0CAB635970E /* PasscodeCharacterView.swift in Sources */,
5E7C76B917517C93D1E26B0A /* LockEnterPasscodeCoordinator.swift in Sources */,
5E7C76A0365D128B7F19A0C2 /* ProtectionCoordinator.swift in Sources */,
76F1DEFF94F9A1F67BDF2735 /* UniversalLinkHandler.swift in Sources */,
5E7C774B5332AC0DC19C5B1B /* EthTokenViewCellViewModel.swift in Sources */,
5E7C75D46140FACBD12333BF /* EthTokenViewCell.swift in Sources */,
5E7C728CDF33FBDBA47F71A6 /* MarketplaceViewController.swift in Sources */,
5E7C7CDB837DCD57E0594CBA /* TicketsViewControllerTitleHeader.swift in Sources */,
5E7C7700014B93A966BBA463 /* RedeemTicketTableViewCellViewModel.swift in Sources */,
5E7C798E5F5EE00D405B91AE /* TicketRowView.swift in Sources */,
5E7C76A65C14D0F11AF7848F /* TicketRowViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3379,7 +3404,7 @@
29F1C85620036887003780D8 /* AppTrackerTests.swift in Sources */,
29BDF19F1FEE51650023A45F /* GasLimitConfigurationTests.swift in Sources */,
290B2B6C1F92C35B0053C83E /* RPCServerTests.swift in Sources */,
B1DC375F203AEB4800C9756D /* OrderRequestTest.swift in Sources */,
B1DC375F203AEB4800C9756D /* MarketQueueHandlerTests.swift in Sources */,
29FF13031F75EB7500AFD326 /* Account.swift in Sources */,
290B2B6A1F92C0440053C83E /* ConfigTests.swift in Sources */,
29BDF1941FEE43AA0023A45F /* TransactionConfiguratorTests.swift in Sources */,
@ -3435,8 +3460,11 @@
29F114E91FA3EC9E00114A29 /* InCoordinatorTests.swift in Sources */,
2981F4731F8303E600CA6590 /* TransactionCoordinatorTests.swift in Sources */,
76F1D137B10D8309E513BBDD /* OrderSigningTests.swift in Sources */,
76F1D13FC8A41AD967C59947 /* ClaimOrderCoordinatorTests.swift in Sources */,
76F1DD10DF9A6C844E5F57D6 /* CreateRedeemTests.swift in Sources */,
442FC8E82231273273B95D85 /* FakeClaimOrderCoordinator.swift in Sources */,
442FCBA4A5E39A4C4B16426A /* EventsRestTest.swift in Sources */,
76F1DC92CDEB695115DBC47C /* UniversalLinkHandlerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -4,6 +4,7 @@ import UIKit
import Lokalise
import Branch
import RealmSwift
import Alamofire
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
@ -68,15 +69,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
)
if !branchHandled {
// If not handled by Branch, do other deep link routing for the Facebook SDK, Pinterest SDK, etc
}
}
// do other deep link routing for the Facebook SDK, Pinterest SDK, etc
return true
}
// Respond to Universal Links
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([Any]?) -> Void) -> Bool
{
Branch.getInstance().continue(userActivity)
let url = userActivity.webpageURL
if(url?.description.contains(UniversalLinkHandler().urlPrefix))!
{
let keystore = try! EtherKeystore()
let signedOrder = UniversalLinkHandler().parseURL(url: (url?.description)!)
let signature = signedOrder.signature.substring(from: 2)
// form the json string out of the order for the paymaster server
// James S. wrote
let indices = signedOrder.order.indices
var indicesStringEncoded = ""
for i in 0...indices.count - 1 {
indicesStringEncoded += String(indices[i]) + ","
}
//cut off last comma
indicesStringEncoded = indicesStringEncoded.substring(from: indicesStringEncoded.count - 1)
let parameters: Parameters = [
"address" : keystore.recentlyUsedWallet?.address.description,
"indices": indicesStringEncoded,
"v" : signature.substring(from: 128),
"r": "0x" + signature.substring(with: Range(uncheckedBounds: (0, 64))),
"s": "0x" + signature.substring(with: Range(uncheckedBounds: (64, 128)))
]
let query = UniversalLinkHandler.paymentServer
Alamofire.request(
query,
method: .post,
parameters: parameters
).responseJSON {
result in
// TODO handle http response
print(result)
}
}
return true
}
}

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ticket_bundle_checked.pdf",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ticket_bundle_unchecked.pdf",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

@ -55,7 +55,10 @@ extension DappAction {
gasLimit: gasLimit,
gasPrice: gasPrice,
nonce: nonce,
//TODO add indices in
v: .none,
r: .none,
s: .none,
expiry: .none,
indices: .none
)
}

@ -361,7 +361,7 @@ open class EtherKeystore: Keystore {
}
}
func signTransaction(_ transaction: SignTransaction) -> Result<Data, KeystoreError> {
func signTransaction(_ transaction: UnsignedTransaction) -> Result<Data, KeystoreError> {
guard let account = keyStore.account(for: transaction.account.address) else {
return .failure(.failedToSignTransaction)
}

@ -25,7 +25,7 @@ protocol Keystore {
func updateAccount(account: Account, password: String, newPassword: String) -> Result<Void, KeystoreError>
func signPersonalMessage(_ data: Data, for account: Account) -> Result<Data, KeystoreError>
func signMessage(_ data: Data, for account: Account) -> Result<Data, KeystoreError>
func signTransaction(_ signTransaction: SignTransaction) -> Result<Data, KeystoreError>
func signTransaction(_ signTransaction: UnsignedTransaction) -> Result<Data, KeystoreError>
func getPassword(for account: Account) -> String?
func convertPrivateKeyToKeystoreFile(privateKey: String, passphrase: String) -> Result<[String: Any], KeystoreError>
}

@ -4,8 +4,8 @@ import BigInt
import CryptoSwift
protocol Signer {
func hash(transaction: SignTransaction) -> Data
func values(transaction: SignTransaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt)
func hash(transaction: UnsignedTransaction) -> Data
func values(transaction: UnsignedTransaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt)
}
struct EIP155Signer: Signer {
@ -15,7 +15,7 @@ struct EIP155Signer: Signer {
self.chainId = chainId
}
func hash(transaction: SignTransaction) -> Data {
func hash(transaction: UnsignedTransaction) -> Data {
return rlpHash([
transaction.nonce,
transaction.gasPrice,
@ -27,7 +27,7 @@ struct EIP155Signer: Signer {
] as [Any])!
}
func values(transaction: SignTransaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt) {
func values(transaction: UnsignedTransaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt) {
let (r, s, v) = HomesteadSigner().values(transaction: transaction, signature: signature)
let newV: BigInt
if chainId != 0 {
@ -40,7 +40,7 @@ struct EIP155Signer: Signer {
}
struct HomesteadSigner: Signer {
func hash(transaction: SignTransaction) -> Data {
func hash(transaction: UnsignedTransaction) -> Data {
return rlpHash([
transaction.nonce,
transaction.gasPrice,
@ -51,7 +51,7 @@ struct HomesteadSigner: Signer {
])!
}
func values(transaction: SignTransaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt) {
func values(transaction: UnsignedTransaction, signature: Data) -> (r: BigInt, s: BigInt, v: BigInt) {
precondition(signature.count == 65, "Wrong size for signature")
let r = BigInt(sign: .plus, magnitude: BigUInt(signature[..<32]))
let s = BigInt(sign: .plus, magnitude: BigUInt(signature[32..<64]))

@ -103,6 +103,7 @@ extension String {
return String(self[startIndex..<endIndex])
}
func nextLetterInAlphabet(for index: Int) -> String? {
guard let uniCode = UnicodeScalar(self) else {
return nil

@ -4,6 +4,7 @@ import Foundation
import TrustKeystore
import UIKit
import RealmSwift
import BigInt
protocol InCoordinatorDelegate: class {
func didCancel(in coordinator: InCoordinator)
@ -94,7 +95,7 @@ class InCoordinator: Coordinator {
let tokensStorage = TokensDataStore(realm: realm, account: account, config: config, web3: web3)
let alphaWalletTokensStorage = TokensDataStore(realm: realm, account: account, config: config, web3: web3)
let balanceCoordinator = GetBalanceCoordinator(web3: web3)
let balance = BalanceCoordinator(account: account, config: config, storage: tokensStorage)
let balance = BalanceCoordinator(wallet: account, config: config, storage: tokensStorage)
let session = WalletSession(
account: account,
config: config,
@ -314,15 +315,15 @@ class InCoordinator: Coordinator {
private func showTransactions(for type: PaymentFlow) {
if nonTabTransactionCoordinator == nil {
if let account = keystore.recentlyUsedWallet {
let migration = MigrationInitializer(account: account, chainID: config.chainID)
if let wallet = keystore.recentlyUsedWallet {
let migration = MigrationInitializer(account: wallet, chainID: config.chainID)
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 balance = BalanceCoordinator(account: account, config: config, storage: tokensStorage)
let tokensStorage = TokensDataStore(realm: realm, account: wallet, config: config, web3: web3)
let balance = BalanceCoordinator(wallet: wallet, config: config, storage: tokensStorage)
let session = WalletSession(
account: account,
account: wallet,
config: config,
web3: web3,
balanceCoordinator: balance
@ -437,6 +438,93 @@ extension InCoordinator: TokensCoordinatorDelegate {
func didPressStormBird(for type: PaymentFlow, token: TokenObject, in coordinator: TokensCoordinator) {
showTicketList(for: type, token: token)
}
// When a user clicks a Universal Link, either the user pays to publish a
// transaction or, if the ticket price = 0 (new purchase or incoming
// transfer from a buddy), the user can send the data to a paymaster.
// This function deal with the special case that the ticket price = 0
// but not sent to the paymaster because the user has ether.
func importSignedOrder(signedOrder: SignedOrder, in coordinator: TokensCoordinator, tokenObject: TokenObject) {
let web3 = self.web3(for: config.server)
web3.start()
let signature = signedOrder.signature.substring(from: 2)
let v = UInt8(signature.substring(from: 128), radix: 16)!
let r = "0x" + signature.substring(with: Range(uncheckedBounds: (0, 64)))
let s = "0x" + signature.substring(with: Range(uncheckedBounds: (64, 128)))
ClaimOrderCoordinator.init(web3: web3).claimOrder(indices: signedOrder.order.indices, expiry: signedOrder.order.expiry, v: v, r: r, s: s) {
result in
switch result {
case .success(let payload):
let address: Address = self.initialWallet.address
let transaction = UnconfirmedTransaction(
transferType: .stormBirdOrder(tokenObject),
value: BigInt("0"),
to: address,
data: Data(bytes: payload.hexa2Bytes),
gasLimit: .none,
gasPrice: 14400,
nonce: .none,
v: v,
r: r,
s: s,
expiry: signedOrder.order.expiry,
indices: signedOrder.order.indices
)
let balanceCoordinator = GetStormBirdBalanceCoordinator(web3: web3)
let wallet = self.keystore.recentlyUsedWallet!
let migration = MigrationInitializer(account: wallet, chainID: self.config.chainID)
migration.perform()
let realm = self.realm(for: migration.config)
let tokensStorage = TokensDataStore(realm: realm, account: wallet, config: self.config, web3: web3)
let balance = BalanceCoordinator(wallet: wallet, config: self.config, storage: tokensStorage)
let session = WalletSession(
account: wallet,
config: self.config,
web3: web3,
balanceCoordinator: balance
)
let account = try! EtherKeystore().getAccount(for: wallet.address)!
let configurator = TransactionConfigurator(
session: session,
account: account,
transaction: transaction
)
let signTransaction = configurator.formUnsignedTransaction()
let signedTransaction = UnsignedTransaction(value: signTransaction.value,
account: account,
to: signTransaction.to,
nonce: signTransaction.nonce,
data: signTransaction.data,
gasPrice: signTransaction.gasPrice,
gasLimit: signTransaction.gasLimit,
chainID: 3)
let sendTransactionCoordinator = SendTransactionCoordinator(session: session,
keystore: self.keystore,
confirmType: .signThenSend)
sendTransactionCoordinator.send(transaction: signedTransaction) { result in
switch result {
case .success(let res):
// TODO: user still isn't unaware when this happens
print(res);
case .failure(let error):
// TODO: user still isn't unaware when this happens
print(error);
}
}
case .failure: break
}
}
}
}
extension InCoordinator: PaymentCoordinatorDelegate {

@ -28,7 +28,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>157</string>
<string>161</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>

@ -176,6 +176,11 @@
"a.wallet.navigation.title" = "Wallet";
"a.wallet.import.wallet.title" = "Import";
"a.wallet.ticketToken.redeem.button.title" = "Redeem";
"a.wallet.ticketToken.redeem.selectTickets.title" = "Select Tickets To Redeem:";
"a.wallet.ticketToken.redeem.selectTickets.atLeastOne.title" = "Please select a ticket to redeem";
"a.wallet.ticketToken.redeem.selectQuantity.title" = "Select Quantity of Tickets";
"a.wallet.ticketToken.redeem.quantity.title" = "QUANTITY OF TICKETS";
"a.wallet.ticketToken.redeem.showQRCode.title" = "Show QR Code to Redemption Booth";
"a.wallet.ticketToken.sell.button.title" = "Sell";
"a.wallet.ticketToken.transfer.button.title" = "Transfer";
"a.wallet.ticketTokenBundle.status.sold.title" = "Sold";
@ -191,6 +196,7 @@
"a.wallet.contents.filter.assetsOnly.title" = "ASSETS";
"a.wallet.contents.issuer.title" = "Issuer";
"a.wallet.tickets" = "%@ Tickets";
"a.wallet.next.button.title" = "Next";
"a.help.navigation.title" = "Help & FAQs";
"a.help.contact.footer.button.title" = "Still need help? Contact Us";
"a.help.contact.email.subject" = "Help/Feedback for AlphaWallet";

@ -0,0 +1,103 @@
//
// Created by James Sangalli on 15/2/18.
// Sends the sales orders to and from the market queue server
//
import Foundation
import Alamofire
import SwiftyJSON
import BigInt
//"orders": [
// {
// "message": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOG8m/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWojJJwB77oK92ehmsr0RR4CkfyJhxoTjAAIAAwAE)",
// "expiry": "1518913831",
// "start": "32800312",
// "count": "3",
// "price": "10000000000000000",
// "signature": "jrzcgpsnV7IPGE3nZQeHQk5vyZdy5c8rHk0R/iG7wpiK9NT730I//DN5Dg5fHs+s4ZFgOGQnk7cXLQROBs9NvgE="
// }
//]
public class MarketQueueHandler {
public let baseURL = "https://482kdh4npg.execute-api.ap-southeast-1.amazonaws.com/dev/"
public let contractAddress = "bC9a1026A4BC6F0BA8Bbe486d1D09dA5732B39e4".lowercased()
public func getOrders(callback: @escaping (_ result : Any) -> Void) {
Alamofire.request(baseURL + "contract/" + contractAddress, method: .get).responseJSON {
response in
var orders = [SignedOrder]()
if let json = response.result.value {
let parsedJSON = try! JSON(data: response.data!)
for i in 0...parsedJSON.count - 1 {
let orderObj: JSON = parsedJSON["orders"][i]
if(orderObj == nil)
{
callback("no orders")
return
}
orders.append(self.parseOrder(orderObj))
}
callback(orders)
}
}
}
func parseOrder(_ orderObj: JSON) -> SignedOrder {
let orderString = orderObj["message"].string!
let message = MarketQueueHandler.bytesToHexa(Array(Data(base64Encoded: orderString)!))
let price = message.substring(to: 64)
let expiry = message.substring(with: Range(uncheckedBounds: (64, 128)))
let contractAddress = "0x" + message.substring(with: Range(uncheckedBounds: (128, 168)))
let indices = message.substring(from: 168)
let order = Order(
price: BigUInt(price, radix: 16)!,
indices: indices.hexa2Bytes.map({ UInt16($0) }),
expiry: BigUInt(expiry, radix: 16)!,
contractAddress: contractAddress,
start: BigUInt(orderObj["start"].string!)!,
count: orderObj["count"].intValue
)
let signedOrder = SignedOrder(
order: order,
message: message.hexa2Bytes,
signature: "0x" + MarketQueueHandler.bytesToHexa(Array(Data(base64Encoded: orderObj["signature"].string!)!))
)
return signedOrder
}
//only have to give first order to server then pad the signatures
public func putOrderToServer(signedOrders: [SignedOrder],
publicKey: String,
callback: @escaping (_ result: Any) -> Void) {
//TODO get encoding for count and start
let query: String = baseURL + "public-key/" + publicKey + "?start=" +
signedOrders[0].order.start.description + ";count=" + signedOrders.count.description
var messageBytes: [UInt8] = signedOrders[0].message
print(signedOrders[0].signature.count)
for i in 0...signedOrders.count - 1 {
for j in 0...64 {
messageBytes.append(signedOrders[i].signature.hexa2Bytes[j])
}
}
let headers: HTTPHeaders = ["Content-Type": "application/vnd.awallet-signed-orders-v0"]
print(query)
Alamofire.upload(Data(bytes: messageBytes), to: query, method: .put, headers: headers).response { response in
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)") // original server data as UTF8 string
callback(data)
}
}
}
public static func bytesToHexa(_ bytes: [UInt8]) -> String {
return bytes.map {
String(format: "%02X", $0)
}.joined()
}
}

@ -39,31 +39,42 @@ extension Data {
}
}
public class SignOrders {
public class OrderHandler {
private let keyStore = try! EtherKeystore()
//takes a list of orders and returns a list of signature objects
//TODO sign message bulk
func signOrders(orders: [Order], account: Account) -> [SignedOrder] {
func signOrders(orders: [Order], account: Account) throws -> [SignedOrder] {
var signedOrders = [SignedOrder]()
var messages = [Data]()
for i in 0...orders.count - 1 {
let message: [UInt8] = encodeMessageForTrade(price: orders[i].price,
expiryBuffer: orders[i].expiry,
tickets: orders[i].indices,
contractAddress: orders[i].contractAddress)
let signature = keyStore.signMessageData(Data(bytes: message), for: account)
let signedOrder: SignedOrder = SignedOrder(order: orders[i],
message: message,
signature: signature.description)
let message: [UInt8] = encodeMessageForTrade(
price: orders[i].price,
expiryBuffer: orders[i].expiry,
tickets: orders[i].indices,
contractAddress: orders[i].contractAddress
)
messages.append(Data(bytes: message))
}
let signatures: [Data] = try! keyStore.signMessageBulk(messages, for: account).dematerialize()
for i in 0...signatures.count - 1 {
let signedOrder = SignedOrder(
order: orders[i],
message: messages[i].bytes,
signature: signatures[i].hexString
)
signedOrders.append(signedOrder)
}
return signedOrders
}
//buffer size is 84 + tickets
//first 32 bytes is allocated for price
//next 32 for expiry
//20 for contract address
//remaining for tickets
func encodeMessageForTrade(price: BigUInt,
expiryBuffer: BigUInt,
tickets: [UInt16],
@ -97,7 +108,7 @@ public class SignOrders {
buffer.append(contractAddr[i])
}
var ticketsUint8 = SignOrders.uInt16ArrayToUInt8(arrayOfUInt16: tickets)
var ticketsUint8 = OrderHandler.uInt16ArrayToUInt8(arrayOfUInt16: tickets)
for i in 0...ticketsUint8.count - 1 {
buffer.append(ticketsUint8[i])
@ -106,7 +117,7 @@ public class SignOrders {
return buffer
}
static func uInt16ArrayToUInt8(arrayOfUInt16: [UInt16]) -> [UInt8] {
public static func uInt16ArrayToUInt8(arrayOfUInt16: [UInt16]) -> [UInt8] {
var arrayOfUint8 = [UInt8]()
for i in 0...arrayOfUInt16.count - 1 {
var UInt8ArrayPair = arrayOfUInt16[i].bigEndian.data.array
@ -116,11 +127,4 @@ public class SignOrders {
return arrayOfUint8
}
func bufferToString(buffer: [UInt8]) -> String {
var bufferString: String = ""
for i in 0...buffer.count - 1 {
bufferString += String(buffer[i])
}
return bufferString
}
}

@ -1,77 +0,0 @@
//
// Created by James Sangalli on 15/2/18.
//
import Foundation
import Alamofire
import SwiftyJSON
//"orders": [
// {
// "message": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOG8m/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWojJJwB77oK92ehmsr0RR4CkfyJhxoTjAAIAAwAE)",
// "expiry": "1518913831",
// "start": "32800312",
// "count": "3",
// "price": "10000000000000000",
// "signature": "jrzcgpsnV7IPGE3nZQeHQk5vyZdy5c8rHk0R/iG7wpiK9NT730I//DN5Dg5fHs+s4ZFgOGQnk7cXLQROBs9NvgE="
// }
//]
public class OrdersRequest {
public let baseURL = "https://482kdh4npg.execute-api.ap-southeast-1.amazonaws.com/dev/"
public let contractAddress = "0x007bee82bdd9e866b2bd114780a47f2261c684e3" //this is wrong as it is the deployer address, will be corrected later
public func getOrders(callback: @escaping (_ result: Any) -> Void) {
Alamofire.request(baseURL + "/contract/" + contractAddress, method: .get).responseJSON { response in
callback(response)
}
}
//only have to give first order to server then pad the signatures
public func putOrderToServer(signedOrders: [SignedOrder],
publicKey: String,
callback: @escaping (_ result: Any) -> Void) {
//TODO get encoding for count and start
let query: String = baseURL + "public-key/" + publicKey + "?start=" +
signedOrders[0].order.start.description + ";count=" + signedOrders[0].order.count.description
var data: [UInt8] = signedOrders[0].message
for i in 0...signedOrders.count - 1 {
for j in 0...64 {
data.append(signedOrders[i].signature.hexa2Bytes[j])
}
}
let hexData: String = OrdersRequest.bytesToHexa(data)
let parameters: Parameters = ["data": hexData]
let headers: HTTPHeaders = ["Content-Type": "application/vnd.awallet-signed-orders-v0"]
Alamofire.request(query, method: .put,
parameters: parameters,
encoding: JSONEncoding.default,
headers: headers).responseJSON { response in
print("Request: \(String(describing: response.request))") // original url request
print("Response: \(String(describing: response.response))") // http url response
print("Result: \(response.result)") // response serialization result
if let json = response.result.value {
//print("JSON: \(json)") // serialized json response
let parsedJSON = JSON(parseJSON: json as! String)
callback(parsedJSON["orders"]["accepted"])
}
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)") // original server data as UTF8 string
}
}
}
public static func bytesToHexa(_ bytes: [UInt8]) -> String {
return bytes.map {
String(format: "%02X", $0)
}.joined()
}
}

@ -0,0 +1,160 @@
//
// Created by James Sangalli on 24/3/18.
//
/**
* Universal link format
*
* Android requires the link to be in the form:
*
* https://www.awallet.io/[base64]
*
* The format forbids using a prefix other than 'www'.
* There needs to be text in the specific link too, in this case 'import'.
$ echo -n https://www.awallet.io/; \
echo -n 000f42405AB5B400007bee82bdd9e866b2bd114780a47f2261c684e30102030405060708092F982B84C635967A9B6306ED5789A7C1919164171E37DCCDF4B59BE54754410530818B896B7D240F56C59EBDF209062EE54DA7A3590905739674DCFDCECF3E9B1b | xxd -r -p | base64;\
https://app.awallet.io/AA9CQFq1tAAAe+6CvdnoZrK9EUeApH8iYcaE4wECAwQFBgcICS+YK4TGNZZ6m2MG7VeJp8GRkWQXHjfczfS1m+VHVEEFMIGLiWt9JA9WxZ698gkGLuVNp6NZCQVzlnTc/c7PPpsb
* uint32: price in Szabo 000f4240
* uint32: expiry in Unix Time 5AB5B400
* bytes20: contract address 007bee82bdd9e866b2bd114780a47f2261c684e3
* Uint16[]: ticket indices 010203040506070809
* bytes32: 2F982B84C635967A9B6306ED5789A7C1919164171E37DCCDF4B59BE547544105
* bytes32: 30818B896B7D240F56C59EBDF209062EE54DA7A3590905739674DCFDCECF3E9B
* byte: 1b
* 1521857536, [0,1,2,3,4,5,6,7,8,9], 27, "0x2F982B84C635967A9B6306ED5789A7C1919164171E37DCCDF4B59BE547544105", "0x30818B896B7D240F56C59EBDF209062EE54DA7A3590905739674DCFDCECF3E9B" -> 0xd2bef24c7e90192426b54bf437a5eac4e220dde7
*/
import Foundation
import BigInt
public class UniversalLinkHandler {
public let urlPrefix = "https://app.awallet.io/"
public static let paymentServer = "http://stormbird.duckdns.org:8080/api/claimToken"
//TODO fix encoding of this link later as it is low priority
func createUniversalLink(signedOrder: SignedOrder) -> String
{
let message = MarketQueueHandler.bytesToHexa(signedOrder.message)
let signature = signedOrder.signature.substring(from: 2)
let link = (message + signature).hexa2Bytes
let binaryData = Data(bytes: link)
let base64String = binaryData.base64EncodedString()
return urlPrefix + base64String
}
func parseURL(url: String) -> SignedOrder {
let linkInfo = url.substring(from: urlPrefix.count)
let linkBytes = Data(base64Encoded: linkInfo)?.array
let price = getPriceFromLinkBytes(linkBytes: linkBytes)
let expiry = getExpiryFromLinkBytes(linkBytes: linkBytes)
let contractAddress = getContractAddressFromLinkBytes(linkBytes: linkBytes)
let ticketIndices = getTicketIndicesFromLinkBytes(linkBytes: linkBytes)
let (v, r, s) = getVRSFromLinkBytes(linkBytes: linkBytes)
let message = getMessageFromLinkBytes(linkBytes: linkBytes!)
let order = Order(
price: price,
indices: ticketIndices,
expiry: expiry,
contractAddress: contractAddress,
start: BigUInt("0")!,
count: ticketIndices.count
)
return SignedOrder(order: order, message: message, signature: "0x" + r + s + v)
}
func getPriceFromLinkBytes(linkBytes: [UInt8]?) -> BigUInt {
var priceBytes = [UInt8]()
for i in 0...3 {
//price in szabo
priceBytes.append(linkBytes![i])
}
return (BigUInt(MarketQueueHandler.bytesToHexa(priceBytes),
radix: 16)?.multiplied(by: BigUInt("1000000000000")!))!
}
func getExpiryFromLinkBytes(linkBytes: [UInt8]?) -> BigUInt {
var expiryBytes = [UInt8]()
for i in 4...7 {
expiryBytes.append(linkBytes![i])
}
let expiry = MarketQueueHandler.bytesToHexa(expiryBytes)
return BigUInt(expiry, radix: 16)!
}
func getContractAddressFromLinkBytes(linkBytes: [UInt8]?) -> String {
var contractAddrBytes = [UInt8]()
for i in 8...27 {
contractAddrBytes.append(linkBytes![i])
}
return MarketQueueHandler.bytesToHexa(contractAddrBytes)
}
func getTicketIndicesFromLinkBytes(linkBytes: [UInt8]?) -> [UInt16] {
let ticketLength = ((linkBytes?.count)! - (65 + 20 + 8)) - 1
var ticketIndices = [UInt16]()
var state: Int = 1
var currentIndex: UInt16 = 0
let ticketStart = 28
for i in stride(from: ticketStart, through: ticketStart + ticketLength, by: 1) {
let byte: UInt8 = linkBytes![i]
switch(state) {
case 1:
//8th bit is equal to 128, if not set then it is only one ticket and will change the state
if(byte & (128) == 128) { //should be done with masks
currentIndex = UInt16((byte & 127)) << 8
state = 2
}
else {
ticketIndices.append(UInt16(byte))
}
break;
case 2:
currentIndex += UInt16(byte)
ticketIndices.append(currentIndex)
state = 1
break;
default:
break
}
}
return ticketIndices
}
func getVRSFromLinkBytes(linkBytes: [UInt8]?) -> (String, String, String) {
var signatureStart = (linkBytes?.count)! - 65
var rBytes = [UInt8]()
for i in signatureStart...signatureStart + 31
{
rBytes.append(linkBytes![i])
}
let r = MarketQueueHandler.bytesToHexa(rBytes)
signatureStart += 32
var sBytes = [UInt8]()
for i in signatureStart...signatureStart + 31 {
sBytes.append(linkBytes![i])
}
let s = MarketQueueHandler.bytesToHexa(sBytes)
let v = String(format:"%2X", linkBytes![(linkBytes?.count)! - 1]).trimmed
return (v, r, s)
}
func getMessageFromLinkBytes(linkBytes: [UInt8]?) -> [UInt8] {
let ticketLength = (linkBytes?.count)! - (65 + 20 + 8) - 1
var message = [UInt8]()
for i in 0...ticketLength + 84 {
message.append(linkBytes![i])
}
return message
}
}

@ -4,7 +4,7 @@ import Foundation
import BigInt
import TrustKeystore
public struct SignTransaction {
public struct UnsignedTransaction {
let value: BigInt
let account: Account
let to: Address?

@ -14,27 +14,83 @@ protocol QuantitySelectionViewControllerDelegate: class {
class QuantitySelectionViewController: UIViewController {
@IBOutlet weak var ticketView: TicketView!
@IBOutlet weak var quantityStepper: NumberStepper!
//roundedBackground is used to achieve the top 2 rounded corners-only effect since maskedCorners to not round bottom corners is not available in iOS 10
let roundedBackground = UIView()
let header = TicketsViewControllerTitleHeader()
let subtitleLabel = UILabel()
var quantityStepper = NumberStepper()
let ticketView = TicketRowView()
let nextButton = UIButton(type: .system)
var viewModel: QuantitySelectionViewModel!
weak var delegate: QuantitySelectionViewControllerDelegate?
override
func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Next", value: "Next", comment: ""),
style: .done,
target: self,
action: #selector(nextButtonTapped)
)
configureUI()
init() {
super.init(nibName: nil, bundle: nil)
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.backgroundColor = Colors.appWhite
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
nextButton.setTitle(R.string.localizable.aWalletTicketTokenRedeemButtonTitle(), for: .normal)
nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
ticketView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(ticketView)
quantityStepper.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(quantityStepper)
let stackView = UIStackView(arrangedSubviews: [
header,
subtitleLabel,
.spacer(height: 4),
quantityStepper,
.spacer(height: 50),
ticketView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
roundedBackground.addSubview(stackView)
let buttonsStackView = UIStackView(arrangedSubviews: [nextButton])
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
buttonsStackView.axis = .horizontal
buttonsStackView.spacing = 0
buttonsStackView.distribution = .fillEqually
buttonsStackView.setContentHuggingPriority(.required, for: .horizontal)
roundedBackground.addSubview(buttonsStackView)
let marginToHideBottomRoundedCorners = CGFloat(30)
NSLayoutConstraint.activate([
header.heightAnchor.constraint(equalToConstant: 90),
quantityStepper.heightAnchor.constraint(equalToConstant: 50),
roundedBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor),
roundedBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor),
roundedBackground.topAnchor.constraint(equalTo: view.topAnchor),
roundedBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: marginToHideBottomRoundedCorners),
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor),
// stackView.bottomAnchor.constraint(lessThanOrEqualTo: buttonsStackView.topAnchor),
buttonsStackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
buttonsStackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
buttonsStackView.heightAnchor.constraint(equalToConstant: 60),
buttonsStackView.bottomAnchor.constraint(equalTo: roundedBackground.bottomAnchor, constant: -marginToHideBottomRoundedCorners),
])
}
override
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
title = viewModel.title
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc
@ -51,9 +107,42 @@ class QuantitySelectionViewController: UIViewController {
}
}
private func configureUI() {
ticketView.configure(ticketHolder: viewModel.ticketHolder)
func configure(viewModel: QuantitySelectionViewModel) {
self.viewModel = viewModel
view.backgroundColor = viewModel.backgroundColor
header.configure(title: viewModel.headerTitle)
subtitleLabel.textAlignment = .center
subtitleLabel.textColor = viewModel.subtitleColor
subtitleLabel.font = viewModel.subtitleFont
subtitleLabel.text = viewModel.subtitleText
ticketView.configure(viewModel: .init())
quantityStepper.borderWidth = 2
quantityStepper.clipsToBounds = true
quantityStepper.borderColor = viewModel.stepperBorderColor
quantityStepper.maximumValue = viewModel.maxValue
ticketView.stateLabel.isHidden = true
ticketView.ticketCountLabel.text = viewModel.ticketCount
ticketView.titleLabel.text = viewModel.title
ticketView.venueLabel.text = viewModel.venue
ticketView.dateLabel.text = viewModel.date
ticketView.seatRangeLabel.text = viewModel.seatRange
ticketView.zoneNameLabel.text = viewModel.zoneName
nextButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
nextButton.backgroundColor = viewModel.buttonBackgroundColor
nextButton.titleLabel?.font = viewModel.buttonFont
}
private func getTicketHolderFromQuantity() -> TicketHolder {
@ -70,6 +159,11 @@ class QuantitySelectionViewController: UIViewController {
)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
quantityStepper.layer.cornerRadius = quantityStepper.frame.size.height / 2
}
deinit {
print("deinit quantity view controller")
}

@ -1,231 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Redeem Tickets-->
<scene sceneID="FVp-jv-uXu">
<objects>
<viewController storyboardIdentifier="RedeemTicketsViewController" id="dt4-4a-Rec" userLabel="Redeem Tickets" customClass="RedeemTicketsViewController" customModule="Trust" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Lhy-Sv-9I6">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="V6W-o7-JQ1">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="RedeemTableViewCell" rowHeight="30" id="TFt-LR-2x5">
<rect key="frame" x="0.0" y="28" width="375" height="30"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="TFt-LR-2x5" id="kE7-Tf-gCn">
<rect key="frame" x="0.0" y="0.0" width="375" height="30"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="SELECT TICKETS TO REDEEM" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0F1-Dz-cM8">
<rect key="frame" x="15" y="7" width="207" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="RedeemTicketCell" rowHeight="90" id="x30-SZ-UO2" customClass="RedeemTicketTableViewCell" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="0.0" y="58" width="375" height="90"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="x30-SZ-UO2" id="0T6-c0-w1v">
<rect key="frame" x="0.0" y="0.0" width="375" height="90"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gtm-9O-Und" customClass="RadioButton" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="8" y="36" width="18" height="18"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="ZRs-66-jgP"/>
<constraint firstAttribute="width" constant="18" id="da0-OQ-uoy"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isOn" value="NO"/>
<userDefinedRuntimeAttribute type="color" keyPath="buttonColor">
<color key="value" red="0.12941176469999999" green="0.73725490199999999" blue="0.29411764709999999" alpha="1" colorSpace="calibratedRGB"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="space">
<real key="value" value="5"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Bkz-zm-vkj" customClass="TicketView" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="34" y="4" width="333" height="82"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<constraints>
<constraint firstItem="gtm-9O-Und" firstAttribute="centerY" secondItem="0T6-c0-w1v" secondAttribute="centerY" id="3NW-8X-Vtp"/>
<constraint firstItem="gtm-9O-Und" firstAttribute="leading" secondItem="0T6-c0-w1v" secondAttribute="leading" constant="8" id="3fy-DO-idO"/>
<constraint firstAttribute="trailing" secondItem="Bkz-zm-vkj" secondAttribute="trailing" constant="8" id="Vah-ht-N4R"/>
<constraint firstItem="Bkz-zm-vkj" firstAttribute="top" secondItem="0T6-c0-w1v" secondAttribute="top" constant="4" id="XkM-QH-ctA"/>
<constraint firstAttribute="bottom" secondItem="Bkz-zm-vkj" secondAttribute="bottom" constant="4" id="aRd-ju-Z5k"/>
<constraint firstItem="Bkz-zm-vkj" firstAttribute="leading" secondItem="gtm-9O-Und" secondAttribute="trailing" constant="8" id="f1L-SU-5PM"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="radioButton" destination="gtm-9O-Und" id="tKD-f3-KBV"/>
<outlet property="ticketView" destination="Bkz-zm-vkj" id="boc-Zz-qPt"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="dt4-4a-Rec" id="1zm-N8-TiD"/>
<outlet property="delegate" destination="dt4-4a-Rec" id="hlz-fa-mcS"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="V6W-o7-JQ1" firstAttribute="top" secondItem="TiC-xe-4Ea" secondAttribute="top" id="BQs-dt-bLR"/>
<constraint firstItem="TiC-xe-4Ea" firstAttribute="trailing" secondItem="V6W-o7-JQ1" secondAttribute="trailing" id="IYZ-d0-wXs"/>
<constraint firstItem="V6W-o7-JQ1" firstAttribute="leading" secondItem="TiC-xe-4Ea" secondAttribute="leading" id="Qq3-gt-Eje"/>
<constraint firstItem="TiC-xe-4Ea" firstAttribute="bottom" secondItem="V6W-o7-JQ1" secondAttribute="bottom" id="r8a-Xa-ozT"/>
</constraints>
<viewLayoutGuide key="safeArea" id="TiC-xe-4Ea"/>
</view>
<navigationItem key="navigationItem" title="Use Token" id="KPo-ua-2Ez"/>
<connections>
<outlet property="tableView" destination="V6W-o7-JQ1" id="PAH-CT-vni"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yVC-XP-XG2" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="228" y="170.46476761619192"/>
</scene>
<!--Quantity Selection View Controller-->
<scene sceneID="IWC-WP-rP7">
<objects>
<viewController storyboardIdentifier="QuantitySelectionViewController" id="rXu-Vx-puG" customClass="QuantitySelectionViewController" customModule="Trust" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="9Kh-8o-KAb">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Select Quantity of Tickets" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zSr-ce-F0B">
<rect key="frame" x="35" y="76" width="304" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="25"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oQ8-qj-Gnu" customClass="NumberStepper" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="63" y="149" width="227" height="47"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="tintColor" red="0.20000000000000001" green="0.70980392160000005" blue="0.8980392157" alpha="1" colorSpace="calibratedRGB"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="value">
<real key="value" value="0.0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="minimumValue">
<real key="value" value="0.0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="maximumValue">
<real key="value" value="1000"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="stepValue">
<real key="value" value="1"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="buttonsTextColor">
<color key="value" red="0.20000000000000001" green="0.70980392160000005" blue="0.8980392157" alpha="1" colorSpace="calibratedRGB"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="buttonsBackgroundColor">
<color key="value" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="labelTextColor">
<color key="value" red="0.20000000000000001" green="0.70980392160000005" blue="0.8980392157" alpha="1" colorSpace="calibratedRGB"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="labelBackgroundColor">
<color key="value" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="labelCornerRadius">
<real key="value" value="0.0"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="9"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" red="0.20000000000000001" green="0.70980392160000005" blue="0.8980392157" alpha="1" colorSpace="calibratedRGB"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="QUANTITY OF TICKETS" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I8D-gw-m7M">
<rect key="frame" x="95" y="119" width="163" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jBH-Fe-D1q" customClass="TicketView" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="16" y="269" width="343" height="90"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<viewLayoutGuide key="safeArea" id="8by-Gd-al4"/>
</view>
<connections>
<outlet property="quantityStepper" destination="oQ8-qj-Gnu" id="5Tr-yT-e6o"/>
<outlet property="ticketView" destination="jBH-Fe-D1q" id="nIa-hz-9tm"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="RA3-pW-I2b" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="896.79999999999995" y="169.56521739130437"/>
</scene>
<!--Ticket Redemption View Controller-->
<scene sceneID="iaD-Yd-G0B">
<objects>
<viewController storyboardIdentifier="TicketRedemptionViewController" id="Czc-tX-qsc" customClass="TicketRedemptionViewController" customModule="Trust" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="N7W-u3-8n7">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xvi-Wz-ipm">
<rect key="frame" x="67" y="103" width="240" height="233"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Show QR Code to Redemption Booth" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mGS-KR-f9p">
<rect key="frame" x="54" y="20" width="267" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="25"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gxE-62-C2c" customClass="TicketView" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="16" y="355" width="343" height="90"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<viewLayoutGuide key="safeArea" id="W9z-g1-VSi"/>
</view>
<connections>
<outlet property="imageView" destination="xvi-Wz-ipm" id="tfw-TC-adc"/>
<outlet property="ticketView" destination="gxE-62-C2c" id="D9X-zC-jGD"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="sTV-lv-Ngl" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1578.4000000000001" y="169.56521739130437"/>
</scene>
</scenes>
</document>

@ -14,25 +14,76 @@ protocol RedeemTicketsViewControllerDelegate: class {
class RedeemTicketsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
//roundedBackground is used to achieve the top 2 rounded corners-only effect since maskedCorners to not round bottom corners is not available in iOS 10
let roundedBackground = UIView()
let header = TicketsViewControllerTitleHeader()
let tableView = UITableView(frame: .zero, style: .plain)
let nextButton = UIButton(type: .system)
var viewModel: RedeemTicketsViewModel!
weak var delegate: RedeemTicketsViewControllerDelegate?
override
func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Next", value: "Next", comment: ""),
style: .done,
target: self,
action: #selector(nextButtonTapped)
)
init() {
super.init(nibName: nil, bundle: nil)
view.backgroundColor = Colors.appBackground
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.backgroundColor = Colors.appWhite
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
tableView.register(RedeemTicketTableViewCell.self, forCellReuseIdentifier: RedeemTicketTableViewCell.identifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.separatorStyle = .none
tableView.backgroundColor = Colors.appWhite
tableView.tableHeaderView = header
roundedBackground.addSubview(tableView)
nextButton.setTitle(R.string.localizable.aWalletNextButtonTitle(), for: .normal)
nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside)
let buttonsStackView = UIStackView(arrangedSubviews: [nextButton])
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
buttonsStackView.axis = .horizontal
buttonsStackView.spacing = 0
buttonsStackView.distribution = .fillEqually
buttonsStackView.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
roundedBackground.addSubview(buttonsStackView)
let marginToHideBottomRoundedCorners = CGFloat(30)
NSLayoutConstraint.activate([
roundedBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor),
roundedBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor),
roundedBackground.topAnchor.constraint(equalTo: view.topAnchor),
roundedBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: marginToHideBottomRoundedCorners),
tableView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
tableView.topAnchor.constraint(equalTo: roundedBackground.topAnchor),
tableView.bottomAnchor.constraint(equalTo: buttonsStackView.topAnchor),
buttonsStackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
buttonsStackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
buttonsStackView.heightAnchor.constraint(equalToConstant: 60),
buttonsStackView.bottomAnchor.constraint(equalTo: roundedBackground.bottomAnchor, constant: -marginToHideBottomRoundedCorners),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
title = viewModel.title
func configure(viewModel: RedeemTicketsViewModel) {
self.viewModel = viewModel
tableView.dataSource = self
header.configure(title: viewModel.title)
tableView.tableHeaderView = header
nextButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
nextButton.backgroundColor = viewModel.buttonBackgroundColor
nextButton.titleLabel?.font = viewModel.buttonFont
}
private func resetSelection(for ticketHolder: TicketHolder) {
@ -47,7 +98,7 @@ class RedeemTicketsViewController: UIViewController {
let selectedTicketHolders = viewModel.ticketHolders?.filter { $0.status == .redeemed }
if selectedTicketHolders!.isEmpty {
UIAlertController.alert(title: "",
message: "Please select a ticket to redeem",
message: R.string.localizable.aWalletTicketTokenRedeemSelectTicketsAtLeastOneTitle(),
alertButtonTitles: ["OK"],
alertButtonStyles: [.cancel],
viewController: self,
@ -60,7 +111,7 @@ class RedeemTicketsViewController: UIViewController {
extension RedeemTicketsViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return viewModel.numberOfSections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@ -68,17 +119,21 @@ extension RedeemTicketsViewController: UITableViewDelegate, UITableViewDataSourc
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return viewModel.cell(for: tableView, indexPath: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: RedeemTicketTableViewCell.identifier, for: indexPath) as! RedeemTicketTableViewCell
let ticketHolder = viewModel.item(for: indexPath)
cell.configure(viewModel: .init(ticketHolder: ticketHolder))
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return viewModel.height(for: indexPath.section)
let ticketHolder = viewModel.item(for: indexPath)
let cellViewModel = RedeemTicketTableViewCellViewModel(ticketHolder: ticketHolder)
return cellViewModel.cellHeight
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if viewModel.ticketCellPressed(for: indexPath) {
let ticketHolder = viewModel.item(for: indexPath)
resetSelection(for: ticketHolder)
}
let ticketHolder = viewModel.item(for: indexPath)
resetSelection(for: ticketHolder)
tableView.deselectRow(at: indexPath, animated: true)
}
}

@ -10,18 +10,71 @@ import UIKit
class TicketRedemptionViewController: UIViewController {
@IBOutlet weak var ticketView: TicketView!
@IBOutlet weak var imageView: UIImageView!
var viewModel: TicketRedemptionViewModel!
var titleLabel = UILabel()
let imageView = UIImageView()
let ticketView = TicketRowView()
let redeem = CreateRedeem()
var timer: Timer!
var session: WalletSession!
var session: WalletSession
let redeemListener = RedeemEventListener()
init(session: WalletSession) {
self.session = session
super.init(nibName: nil, bundle: nil)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
let imageHolder = UIView()
imageHolder.translatesAutoresizingMaskIntoConstraints = false
imageHolder.backgroundColor = Colors.appWhite
imageHolder.cornerRadius = 20
imageHolder.addSubview(imageView)
ticketView.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
.spacer(height: 10),
imageHolder,
.spacer(height: 4),
ticketView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
stackView.alignment = .center
view.addSubview(stackView)
let xMargin = CGFloat(16)
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
imageView.leadingAnchor.constraint(equalTo: imageHolder.leadingAnchor, constant: 70),
imageView.trailingAnchor.constraint(equalTo: imageHolder.trailingAnchor, constant: -70),
imageView.topAnchor.constraint(equalTo: imageHolder.topAnchor, constant: 70),
imageView.bottomAnchor.constraint(equalTo: imageHolder.bottomAnchor, constant: -70),
imageHolder.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: xMargin),
imageHolder.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -xMargin),
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stackView.topAnchor.constraint(equalTo: view.topAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override
func viewDidLoad() {
super.viewDidLoad()
configureUI()
timer = Timer.scheduledTimer(timeInterval: 30,
target: self,
selector: #selector(configureUI),
@ -35,12 +88,6 @@ class TicketRedemptionViewController: UIViewController {
})
}
override
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
title = viewModel.title
}
override
func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
@ -58,7 +105,6 @@ class TicketRedemptionViewController: UIViewController {
imageView.image = qrCodeInfo.toQRCode()
case .watch: break // TODO: What to do here?
}
ticketView.configure(ticketHolder: viewModel.ticketHolder)
}
private func showSuccessMessage() {
@ -85,4 +131,33 @@ class TicketRedemptionViewController: UIViewController {
print("deinit called")
}
func configure(viewModel: TicketRedemptionViewModel) {
self.viewModel = viewModel
view.backgroundColor = viewModel.backgroundColor
titleLabel.textAlignment = .center
titleLabel.textColor = viewModel.headerColor
titleLabel.font = viewModel.headerFont
titleLabel.numberOfLines = 0
titleLabel.text = viewModel.headerTitle
configureUI()
ticketView.configure(viewModel: .init())
ticketView.stateLabel.isHidden = true
ticketView.ticketCountLabel.text = viewModel.ticketCount
ticketView.titleLabel.text = viewModel.title
ticketView.venueLabel.text = viewModel.venue
ticketView.dateLabel.text = viewModel.date
ticketView.seatRangeLabel.text = viewModel.seatRange
ticketView.zoneNameLabel.text = viewModel.zoneName
}
}

@ -13,12 +13,68 @@ struct QuantitySelectionViewModel {
var ticketHolder: TicketHolder
var title: String {
return "Quantity"
var headerTitle: String {
return R.string.localizable.aWalletTicketTokenRedeemSelectQuantityTitle()
}
var maxValue: Int {
return ticketHolder.tickets.count
}
var backgroundColor: UIColor {
return Colors.appBackground
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
var subtitleColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var subtitleFont: UIFont {
return Fonts.regular(size: 10)!
}
var stepperBorderColor: UIColor {
return Colors.appBackground
}
var ticketCount: String {
return "x\(ticketHolder.tickets.count)"
}
var title: String {
return ticketHolder.name
}
var seatRange: String {
return ticketHolder.seatRange
}
var zoneName: String {
return ticketHolder.zone
}
var venue: String {
return ticketHolder.venue
}
var subtitleText: String {
return R.string.localizable.aWalletTicketTokenRedeemQuantityTitle()
}
var date: String {
//TODO Should format be localized?
return ticketHolder.date.format("dd MMM yyyy")
}
}

@ -0,0 +1,57 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct RedeemTicketTableViewCellViewModel {
private let ticketHolder: TicketHolder
init(
ticketHolder: TicketHolder
) {
self.ticketHolder = ticketHolder
}
var ticketCount: String {
return "x\(ticketHolder.tickets.count)"
}
var title: String {
return ticketHolder.name
}
var seatRange: String {
return ticketHolder.seatRange
}
var zoneName: String {
return ticketHolder.zone
}
var venue: String {
return ticketHolder.venue
}
var date: String {
//TODO Should format be localized?
return ticketHolder.date.format("dd MMM yyyy")
}
var backgroundColor: UIColor {
return Colors.appWhite
}
var status: String {
return ""
}
var cellHeight: CGFloat {
return 113
}
var checkboxImage: UIImage {
if ticketHolder.status == .redeemed {
return R.image.ticket_bundle_checked()!
} else {
return R.image.ticket_bundle_unchecked()!
}
}
}

@ -23,49 +23,27 @@ struct RedeemTicketsViewModel {
return ticketHolders![indexPath.row]
}
func cell(for tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
return summaryCell(for: tableView, indexPath: indexPath)
}
return ticketCell(for: tableView, indexPath: indexPath)
}
var numberOfSections: Int {
return 2
}
func numberOfItems(for section: Int) -> Int {
if section == 0 {
return 1
}
return ticketHolders!.count
}
func height(for section: Int) -> CGFloat {
if section == 0 {
return 30
}
return 90
}
var title: String {
return "Redeem Asset"
return R.string.localizable.aWalletTicketTokenRedeemSelectTicketsTitle ()
}
func ticketCellPressed(for indexPath: IndexPath) -> Bool {
return indexPath.section == 1
var buttonTitleColor: UIColor {
return Colors.appWhite
}
private func summaryCell(for tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.redeemTableViewCell, for: indexPath)
return cell!
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
private func ticketCell(for tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.redeemTicketCell, for: indexPath)!
let ticketHolder = item(for: indexPath)
cell.configure(ticketHolder: ticketHolder)
return cell
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
}

@ -11,7 +11,45 @@ import UIKit
struct TicketRedemptionViewModel {
var ticketHolder: TicketHolder
var headerTitle: String {
return R.string.localizable.aWalletTicketTokenRedeemShowQRCodeTitle()
}
var headerColor: UIColor {
return Colors.appWhite
}
var headerFont: UIFont {
return Fonts.light(size: 25)!
}
var backgroundColor: UIColor {
return Colors.appBackground
}
var ticketCount: String {
return "x\(ticketHolder.tickets.count)"
}
var title: String {
return "Redemption"
return ticketHolder.name
}
var seatRange: String {
return ticketHolder.seatRange
}
var zoneName: String {
return ticketHolder.zone
}
var venue: String {
return ticketHolder.venue
}
var date: String {
//TODO Should format be localized?
return ticketHolder.date.format("dd MMM yyyy")
}
}

@ -0,0 +1,53 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct TicketRowViewModel {
var backgroundColor: UIColor {
return Colors.appWhite
}
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var titleColor: UIColor {
return Colors.appText
}
var countColor: UIColor {
return Colors.appHighlightGreen
}
var subtitleColor: UIColor {
return UIColor(red: 112, green: 112, blue: 112)
}
var iconsColor: UIColor {
return Colors.appBackground
}
var ticketCountFont: UIFont {
return Fonts.bold(size: 21)!
}
var titleFont: UIFont {
return Fonts.light(size: 21)!
}
var venueFont: UIFont {
return Fonts.light(size: 18)!
}
var stateBackgroundColor: UIColor {
return UIColor(red: 151, green: 151, blue: 151)
}
var stateColor: UIColor {
return .white
}
var subtitleFont: UIFont {
return Fonts.semibold(size: 15)!
}
}

@ -9,12 +9,51 @@
import UIKit
class RedeemTicketTableViewCell: UITableViewCell {
static let identifier = "RedeemTicketTableViewCell"
@IBOutlet weak var ticketView: TicketView!
@IBOutlet weak var radioButton: RadioButton!
let rowView = TicketRowView(showCheckbox: true)
func configure(ticketHolder: TicketHolder) {
ticketView.configure(ticketHolder: ticketHolder)
radioButton.isOn = ticketHolder.status == .redeemed
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
rowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(rowView)
NSLayoutConstraint.activate([
rowView.leadingAnchor.constraint(equalTo: leadingAnchor),
rowView.trailingAnchor.constraint(equalTo: trailingAnchor),
rowView.topAnchor.constraint(equalTo: topAnchor),
rowView.bottomAnchor.constraint(equalTo:bottomAnchor),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: RedeemTicketTableViewCellViewModel) {
selectionStyle = .none
backgroundColor = viewModel.backgroundColor
contentView.backgroundColor = viewModel.backgroundColor
rowView.configure(viewModel: .init())
rowView.checkboxImageView.image = viewModel.checkboxImage
rowView.stateLabel.text = " \(viewModel.status) "
rowView.stateLabel.isHidden = viewModel.status.isEmpty
rowView.ticketCountLabel.text = viewModel.ticketCount
rowView.titleLabel.text = viewModel.title
rowView.venueLabel.text = viewModel.venue
rowView.dateLabel.text = viewModel.date
rowView.seatRangeLabel.text = viewModel.seatRange
rowView.zoneNameLabel.text = viewModel.zoneName
}
}

@ -0,0 +1,150 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
class TicketRowView: UIView {
let checkboxImageView = UIImageView(image: R.image.ticket_bundle_unchecked())
let background = UIView()
let stateLabel = UILabel()
let ticketCountLabel = UILabel()
let titleLabel = UILabel()
let venueLabel = UILabel()
let dateLabel = UILabel()
let seatRangeLabel = UILabel()
let zoneNameLabel = UILabel()
let dateImageView = UIImageView()
let seatRangeImageView = UIImageView()
let zoneNameImageView = UIImageView()
let showCheckbox: Bool
init(showCheckbox: Bool = false) {
self.showCheckbox = showCheckbox
super.init(frame: .zero)
checkboxImageView.translatesAutoresizingMaskIntoConstraints = false
if showCheckbox {
addSubview(checkboxImageView)
}
background.translatesAutoresizingMaskIntoConstraints = false
addSubview(background)
venueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
//A spacer view to take up empty horizontal space so venueLabel can be right aligned while the rest is left aligned in topRowStack
let topRowStack = UIStackView(arrangedSubviews: [
ticketCountLabel,
titleLabel,
.spacer(),
venueLabel])
topRowStack.axis = .horizontal
topRowStack.spacing = 15
topRowStack.distribution = .fill
topRowStack.alignment = .center
topRowStack.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
let bottomRowStack = UIStackView(arrangedSubviews: [
dateImageView,
dateLabel,
.spacerWidth(7),
seatRangeImageView,
seatRangeLabel,
.spacerWidth(7),
zoneNameImageView,
zoneNameLabel
])
bottomRowStack.axis = .horizontal
bottomRowStack.spacing = 7
bottomRowStack.distribution = .fill
bottomRowStack.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
let stackView = UIStackView(arrangedSubviews: [
stateLabel,
topRowStack,
bottomRowStack
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .leading
stackView.spacing = 10
stackView.distribution = .fill
stackView.setContentHuggingPriority(UILayoutPriority.required, for: .vertical)
background.addSubview(stackView)
// TODO extract constant. Maybe StyleLayout.sideMargin
let xMargin = CGFloat(7)
let yMargin = CGFloat(5)
var checkboxRelatedConstraints = [NSLayoutConstraint]()
if showCheckbox {
checkboxRelatedConstraints.append(checkboxImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: xMargin))
checkboxRelatedConstraints.append(checkboxImageView.centerYAnchor.constraint(equalTo: centerYAnchor))
checkboxRelatedConstraints.append(background.leadingAnchor.constraint(equalTo: checkboxImageView.trailingAnchor, constant: xMargin))
} else {
checkboxRelatedConstraints.append(background.leadingAnchor.constraint(equalTo: leadingAnchor, constant: xMargin))
}
NSLayoutConstraint.activate([
topRowStack.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 21),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -21),
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: background.bottomAnchor, constant: -16),
background.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -xMargin),
background.topAnchor.constraint(equalTo: topAnchor, constant: yMargin),
background.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -yMargin),
stateLabel.heightAnchor.constraint(equalToConstant: 22),
] + checkboxRelatedConstraints)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: TicketRowViewModel) {
background.backgroundColor = viewModel.contentsBackgroundColor
background.layer.cornerRadius = 20
background.layer.shadowRadius = 3
background.layer.shadowColor = UIColor.black.cgColor
background.layer.shadowOffset = CGSize(width: 0, height: 0)
background.layer.shadowOpacity = 0.14
background.layer.borderColor = UIColor.black.cgColor
stateLabel.backgroundColor = viewModel.stateBackgroundColor
stateLabel.layer.cornerRadius = 8
stateLabel.clipsToBounds = true
stateLabel.textColor = viewModel.stateColor
stateLabel.font = viewModel.subtitleFont
ticketCountLabel.textColor = viewModel.countColor
ticketCountLabel.font = viewModel.ticketCountFont
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
venueLabel.textColor = viewModel.titleColor
venueLabel.font = viewModel.venueFont
venueLabel.textAlignment = .right
dateLabel.textColor = viewModel.subtitleColor
dateLabel.font = viewModel.subtitleFont
seatRangeLabel.textColor = viewModel.subtitleColor
seatRangeLabel.font = viewModel.subtitleFont
zoneNameLabel.textColor = viewModel.subtitleColor
zoneNameLabel.font = viewModel.subtitleFont
dateImageView.image = R.image.calendar()?.withRenderingMode(.alwaysTemplate)
seatRangeImageView.image = R.image.ticket()?.withRenderingMode(.alwaysTemplate)
zoneNameImageView.image = R.image.category()?.withRenderingMode(.alwaysTemplate)
dateImageView.tintColor = viewModel.iconsColor
seatRangeImageView.tintColor = viewModel.iconsColor
zoneNameImageView.tintColor = viewModel.iconsColor
}
}

@ -0,0 +1,53 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
class TicketsViewControllerTitleHeader: UIView {
let background = UIView()
let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
background.translatesAutoresizingMaskIntoConstraints = false
addSubview(background)
titleLabel.textAlignment = .center
titleLabel.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView(arrangedSubviews: [titleLabel])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
background.addSubview(stackView)
let backgroundWidthConstraint = background.widthAnchor.constraint(equalTo: widthAnchor)
backgroundWidthConstraint.priority = .defaultHigh
// TODO extract constant. Maybe StyleLayout.sideMargin
NSLayoutConstraint.activate([
background.leadingAnchor.constraint(equalTo: leadingAnchor),
// background.topAnchor.constraint(equalTo: topAnchor),
background.centerYAnchor.constraint(equalTo: centerYAnchor),
backgroundWidthConstraint,
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 21),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -21),
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: background.bottomAnchor, constant: -16),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(title: String) {
frame = CGRect(x: 0, y: 0, width: 300, height: 90)
backgroundColor = Colors.appWhite
titleLabel.textColor = Colors.appText
titleLabel.font = Fonts.light(size: 25)!
titleLabel.text = title
}
}

@ -69,7 +69,7 @@ struct Config {
case .classic: return "https://mewapi.epool.io/"
case .callisto: return "https://callisto.network/" //TODO Add endpoint
case .kovan: return "https://kovan.infura.io/llyrtzQ3YhkdESt2Fzrk"
case .ropsten: return "https://ropsten.infura.io/llyrtzQ3YhkdESt2Fzrk"
case .ropsten: return "http://stormbird.duckdns.org:8545"//"https://ropsten.infura.io/llyrtzQ3YhkdESt2Fzrk"
case .rinkeby: return "https://rinkeby.infura.io/llyrtzQ3YhkdESt2Fzrk"
case .poa: return "https://core.poa.network"
case .sokol: return "https://sokol.poa.network"

@ -0,0 +1,50 @@
//
// Created by James Sangalli on 7/3/18.
// When someone takes an order and pays for it, we call it "claim an order"
//
import Foundation
import BigInt
import JSONRPCKit
import APIKit
import Result
import TrustKeystore
import JavaScriptKit
import Result
class ClaimOrderCoordinator {
private let web3: Web3Swift
init(
web3: Web3Swift
) {
self.web3 = web3
}
func claimOrder(indices: [UInt16],
expiry: BigUInt,
v: UInt8,
r: String,
s: String,
completion: @escaping (Result<String, AnyError>) -> Void
) {
let request = ClaimStormBirdOrder(expiry: expiry, indices: indices, v: v, r: r, s: s)
web3.request(request: request) { result in
switch result {
//TODO handle cases for UI
case .success(let res):
print(res)
completion(.success(res))
case .failure(let err):
print(err)
completion(.failure(AnyError(err)))
}
}
}
// TODO: Testing purposes only. Remove this when it is fully functional
func startWeb3() {
web3.start()
}
}

@ -6,6 +6,7 @@ import UIKit
protocol TokensCoordinatorDelegate: class {
func didPress(for type: PaymentFlow, in coordinator: TokensCoordinator)
func didPressStormBird(for type: PaymentFlow, token: TokenObject, in coordinator: TokensCoordinator)
func importSignedOrder(signedOrder: SignedOrder, in coordinator: TokensCoordinator, tokenObject: TokenObject)
}
class TokensCoordinator: Coordinator {
@ -66,6 +67,25 @@ class TokensCoordinator: Coordinator {
navigationController.present(nav, animated: true, completion: nil)
}
// relevent when user pays transaction fee to redeem from UniversalLink
// which contains a ticket priced 0. TODO: The case when ticket price > 0
// is not dealt yet.
@objc func useUniversalLink(url: String) {
let signedOrder = UniversalLinkHandler().parseURL(url: url)
//TODO get info around name, symbol etc
let tokenObject = TokenObject(
contract: signedOrder.order.contractAddress,
name: "ticket",
symbol: "TIX",
decimals: 0,
value: "0",
isCustom: true,
isDisabled: false,
isStormBird: true
)
delegate?.importSignedOrder(signedOrder: signedOrder, in: self, tokenObject: tokenObject)
}
@objc func dismiss() {
navigationController.dismiss(animated: true, completion: nil)
}
@ -97,6 +117,8 @@ extension TokensCoordinator: TokensViewControllerDelegate {
delegate?.didPress(for: .send(type: .token(token)), in: self)
case .stormBird:
delegate?.didPressStormBird(for: .send(type: .stormBird(token)), token: token, in: self)
case .stormBirdOrder:
break
}
}

@ -6,4 +6,5 @@ enum TokenType: Int {
case ether
case token
case stormBird
case stormBirdOrder
}

@ -22,6 +22,10 @@ class TokensDataStore {
return GetBalanceCoordinator(web3: self.web3)
}()
private lazy var claimOrderCoordinator: ClaimOrderCoordinator = {
return ClaimOrderCoordinator(web3: self.web3)
}()
private lazy var getNameCoordinator: GetNameCoordinator = {
return GetNameCoordinator(web3: self.web3)
}()
@ -161,6 +165,18 @@ class TokensDataStore {
}
}
//Result<Void, AnyError>
func claimOrder(ticketIndices: [UInt16],
expiry: BigUInt,
v: UInt8,
r: String,
s: String,
completion: @escaping(Any) -> Void) {
claimOrderCoordinator.claimOrder(indices: ticketIndices, expiry: expiry, v: v, r: r, s: s) { result in
completion(result)
}
}
func refreshBalance() {
guard !enabledObject.isEmpty else {
updateDelegate()

@ -202,7 +202,6 @@ class NewTokenViewController: FormViewController {
guard CryptoAddressValidator.isValidAddress(value) else {
return
}
updateContractValue(value: value)
}

@ -239,6 +239,12 @@ extension TokensViewController: UITableViewDelegate {
ticker: viewModel.ticker(for: token)
)
return cellViewModel.cellHeight
case .stormBirdOrder:
let cellViewModel = TicketTokenViewCellViewModel(
token: token,
ticker: viewModel.ticker(for: token)
)
return cellViewModel.cellHeight
}
}
}
@ -305,6 +311,16 @@ extension TokensViewController: UITableViewDataSource {
)
)
return cell
//TODO patch
case .stormBirdOrder:
let cell = tableView.dequeueReusableCell(withIdentifier: TicketTokenViewCell.identifier, for: indexPath) as! TicketTokenViewCell
cell.configure(
viewModel: .init(
token: token,
ticker: viewModel.ticker(for: token)
)
)
return cell
}
}

@ -36,46 +36,6 @@ struct TicketTableViewCellViewModel {
return Colors.appWhite
}
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var titleColor: UIColor {
return Colors.appText
}
var countColor: UIColor {
if ticketHolder.status == .available {
return Colors.appHighlightGreen
} else {
return UIColor(red: 155, green: 155, blue: 155)
}
}
var subtitleColor: UIColor {
return UIColor(red: 112, green: 112, blue: 112)
}
var iconsColor: UIColor {
if ticketHolder.status == .available {
return Colors.appBackground
} else {
return UIColor(red: 151, green: 151, blue: 151)
}
}
var ticketCountFont: UIFont {
return Fonts.bold(size: 21)!
}
var titleFont: UIFont {
return Fonts.light(size: 21)!
}
var subtitleFont: UIFont {
return Fonts.semibold(size: 15)!
}
var status: String {
switch ticketHolder.status {
case .available:
@ -91,14 +51,6 @@ struct TicketTableViewCellViewModel {
}
}
var stateBackgroundColor: UIColor {
return UIColor(red: 151, green: 151, blue: 151)
}
var stateColor: UIColor {
return .white
}
var cellHeight: CGFloat {
if status.isEmpty {
return 113

@ -11,72 +11,19 @@ import UIKit
class TicketTableViewCell: UITableViewCell {
static let identifier = "TicketTableViewCell"
let background = UIView()
let stateLabel = UILabel()
let ticketCountLabel = UILabel()
let titleLabel = UILabel()
let dateLabel = UILabel()
let seatRangeLabel = UILabel()
let zoneNameLabel = UILabel()
let dateImageView = UIImageView()
let seatRangeImageView = UIImageView()
let zoneNameImageView = UIImageView()
let rowView = TicketRowView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(background)
background.translatesAutoresizingMaskIntoConstraints = false
rowView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(rowView)
let topRowStack = UIStackView(arrangedSubviews: [ticketCountLabel, titleLabel])
topRowStack.axis = .horizontal
topRowStack.spacing = 15
topRowStack.distribution = .fill
topRowStack.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
let bottomRowStack = UIStackView(arrangedSubviews: [
dateImageView,
dateLabel,
.spacerWidth(7),
seatRangeImageView,
seatRangeLabel,
.spacerWidth(7),
zoneNameImageView,
zoneNameLabel
])
bottomRowStack.axis = .horizontal
bottomRowStack.spacing = 7
bottomRowStack.distribution = .fill
bottomRowStack.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
let stackView = UIStackView(arrangedSubviews: [
stateLabel,
topRowStack,
bottomRowStack
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .leading
stackView.spacing = 10
stackView.distribution = .fill
stackView.setContentHuggingPriority(UILayoutPriority.required, for: .vertical)
background.addSubview(stackView)
// TODO extract constant. Maybe StyleLayout.sideMargin
let xMargin = CGFloat(7)
let yMargin = CGFloat(5)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 21),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -21),
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: background.bottomAnchor, constant: -16),
background.leadingAnchor.constraint(equalTo: leadingAnchor, constant: xMargin),
background.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -xMargin),
background.topAnchor.constraint(equalTo: topAnchor, constant: yMargin),
background.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -yMargin),
stateLabel.heightAnchor.constraint(equalToConstant: 22),
rowView.leadingAnchor.constraint(equalTo: leadingAnchor),
rowView.trailingAnchor.constraint(equalTo: trailingAnchor),
rowView.topAnchor.constraint(equalTo: topAnchor),
rowView.bottomAnchor.constraint(equalTo:bottomAnchor),
])
}
@ -87,51 +34,21 @@ class TicketTableViewCell: UITableViewCell {
func configure(viewModel: TicketTableViewCellViewModel) {
selectionStyle = .none
backgroundColor = viewModel.backgroundColor
contentView.backgroundColor = viewModel.backgroundColor
background.backgroundColor = viewModel.contentsBackgroundColor
background.layer.cornerRadius = 20
background.layer.shadowRadius = 3
background.layer.shadowColor = UIColor.black.cgColor
background.layer.shadowOffset = CGSize(width: 0, height: 0)
background.layer.shadowOpacity = 0.14
background.layer.borderColor = UIColor.black.cgColor
contentView.backgroundColor = viewModel.backgroundColor
stateLabel.backgroundColor = viewModel.stateBackgroundColor
stateLabel.layer.cornerRadius = 8
stateLabel.clipsToBounds = true
stateLabel.textColor = viewModel.stateColor
stateLabel.font = viewModel.subtitleFont
stateLabel.text = " \(viewModel.status) "
stateLabel.isHidden = viewModel.status.isEmpty
ticketCountLabel.textColor = viewModel.countColor
ticketCountLabel.font = viewModel.ticketCountFont
ticketCountLabel.text = viewModel.ticketCount
rowView.configure(viewModel: .init())
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.text = viewModel.title
rowView.stateLabel.text = " \(viewModel.status) "
rowView.stateLabel.isHidden = viewModel.status.isEmpty
dateLabel.textColor = viewModel.subtitleColor
dateLabel.font = viewModel.subtitleFont
dateLabel.text = viewModel.date
rowView.ticketCountLabel.text = viewModel.ticketCount
seatRangeLabel.textColor = viewModel.subtitleColor
seatRangeLabel.font = viewModel.subtitleFont
seatRangeLabel.text = viewModel.seatRange
rowView.titleLabel.text = viewModel.title
zoneNameLabel.textColor = viewModel.subtitleColor
zoneNameLabel.font = viewModel.subtitleFont
zoneNameLabel.text = viewModel.zoneName
rowView.dateLabel.text = viewModel.date
dateImageView.image = R.image.calendar()?.withRenderingMode(.alwaysTemplate)
seatRangeImageView.image = R.image.ticket()?.withRenderingMode(.alwaysTemplate)
zoneNameImageView.image = R.image.category()?.withRenderingMode(.alwaysTemplate)
rowView.seatRangeLabel.text = viewModel.seatRange
dateImageView.tintColor = viewModel.iconsColor
seatRangeImageView.tintColor = viewModel.iconsColor
zoneNameImageView.tintColor = viewModel.iconsColor
rowView.zoneNameLabel.text = viewModel.zoneName
}
}

@ -1,63 +0,0 @@
//
// TicketView.swift
// Alpha-Wallet
//
// Created by Oguzhan Gungor on 3/5/18.
// Copyright © 2018 Alpha-Wallet. All rights reserved.
//
import UIKit
@IBDesignable
class TicketView: UIView {
let nibName = "TicketView"
var contentView: UIView?
@IBOutlet weak var ticketNumberLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var venueLabel: UILabel!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var seatLabel: UILabel!
@IBOutlet weak var zoneLabel: UILabel!
func configure(ticketHolder: TicketHolder) {
ticketNumberLabel.text = ticketHolder.ticketCount
nameLabel.text = ticketHolder.name
venueLabel.text = ticketHolder.venue
dateLabel.text = ticketHolder.date.format("dd MMM yyyy")
zoneLabel.text = ticketHolder.zone
seatLabel.text = ticketHolder.seatRange
}
}
extension TicketView {
override
func awakeFromNib() {
super.awakeFromNib()
nibSetup()
}
override
func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
nibSetup()
contentView?.prepareForInterfaceBuilder()
}
func nibSetup() {
guard let view = loadViewFromNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}

@ -1,100 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="TicketView" customModule="Trust" customModuleProvider="target">
<connections>
<outlet property="dateLabel" destination="edH-Mp-Aoq" id="SdC-7C-36B"/>
<outlet property="nameLabel" destination="Ohf-I4-SrC" id="qQj-i4-25Q"/>
<outlet property="seatLabel" destination="muF-oJ-WiE" id="pGO-iv-93P"/>
<outlet property="ticketNumberLabel" destination="k4Z-7C-cpP" id="QXS-N0-ygF"/>
<outlet property="venueLabel" destination="O4M-DA-Ryo" id="FNY-sW-Miv"/>
<outlet property="zoneLabel" destination="Etp-V9-ttP" id="r4T-s0-NtG"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="85"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GNV-WP-nNS" customClass="DesignableView" customModule="Trust" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="85"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="x10" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="k4Z-7C-cpP">
<rect key="frame" x="8" y="16" width="27" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.12941176469999999" green="0.73725490199999999" blue="0.29411764709999999" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="ARRANGING MORTGAGE" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Ohf-I4-SrC">
<rect key="frame" x="41" y="16" width="177" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="RBS Forbidden City" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="O4M-DA-Ryo">
<rect key="frame" x="224" y="16" width="135" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="05 Mar 2018" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="edH-Mp-Aoq">
<rect key="frame" x="8" y="53" width="102" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="1-11" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="muF-oJ-WiE">
<rect key="frame" x="118" y="53" width="102" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="ZONE A" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Etp-V9-ttP">
<rect key="frame" x="249" y="53" width="102" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="borderColor">
<color key="value" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
<real key="value" value="1"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
<real key="value" value="7"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="GNV-WP-nNS" secondAttribute="trailing" id="4xI-H5-uEd"/>
<constraint firstItem="GNV-WP-nNS" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="BJv-JU-JZs"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="GNV-WP-nNS" secondAttribute="bottom" id="XAz-dY-Mpn"/>
<constraint firstItem="GNV-WP-nNS" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="bQi-Kl-Hca"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="-96" y="91"/>
</view>
</objects>
</document>

@ -11,7 +11,7 @@ protocol BalanceCoordinatorDelegate: class {
}
class BalanceCoordinator {
let account: Wallet
let wallet: Wallet
let storage: TokensDataStore
var balance: Balance?
var currencyRate: CurrencyRate?
@ -23,11 +23,11 @@ class BalanceCoordinator {
)
}
init(
account: Wallet,
config: Config,
storage: TokensDataStore
wallet: Wallet,
config: Config,
storage: TokensDataStore
) {
self.account = account
self.wallet = wallet
self.storage = storage
self.storage.refreshBalance()

@ -89,24 +89,25 @@ class TicketsCoordinator: Coordinator {
}
private func makeRedeemTicketsViewController() -> RedeemTicketsViewController {
let controller = R.storyboard.redeemTickets.redeemTicketsViewController()!
let controller = RedeemTicketsViewController()
let viewModel = RedeemTicketsViewModel(token: token)
controller.viewModel = viewModel
controller.configure(viewModel: viewModel)
controller.delegate = self
return controller
}
private func makeQuantitySelectionViewController(for ticketHolder: TicketHolder) -> QuantitySelectionViewController {
let controller = R.storyboard.redeemTickets.quantitySelectionViewController()!
controller.viewModel = QuantitySelectionViewModel(ticketHolder: ticketHolder)
let controller = QuantitySelectionViewController()
let viewModel = QuantitySelectionViewModel(ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
controller.delegate = self
return controller
}
private func makeTicketRedemptionViewController(for ticketHolder: TicketHolder) -> TicketRedemptionViewController {
let controller = R.storyboard.redeemTickets.ticketRedemptionViewController()!
controller.viewModel = TicketRedemptionViewModel(ticketHolder: ticketHolder)
controller.session = session
let controller = TicketRedemptionViewController(session: session)
let viewModel = TicketRedemptionViewModel(ticketHolder: ticketHolder)
controller.configure(viewModel: viewModel)
return controller
}

@ -67,6 +67,8 @@ class TransactionConfigurator {
return Address(string: token.contract)
case .stormBird(let token):
return Address(string: token.contract)
case .stormBirdOrder(let token):
return Address(string: token.contract)
}
}()
let request = EstimateGasRequest(
@ -98,48 +100,65 @@ class TransactionConfigurator {
func load(completion: @escaping (Result<Void, AnyError>) -> Void) {
switch transaction.transferType {
case .ether:
guard requestEstimateGas else {
return completion(.success(()))
}
estimateGasLimit()
self.configuration = TransactionConfiguration(
gasPrice: calculatedGasPrice,
gasLimit: GasLimitConfiguration.default,
data: transaction.data ?? self.configuration.data
)
completion(.success(()))
case .token:
session.web3.request(request: ContractERC20Transfer(amount: transaction.value, address: transaction.to!.description)) { [unowned self] result in
switch result {
case .success(let res):
let data = Data(hex: res.drop0x)
self.configuration = TransactionConfiguration(
gasPrice: self.calculatedGasPrice,
gasLimit: 144000,
data: data
)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
case .ether:
guard requestEstimateGas else {
return completion(.success(()))
}
}
//TODO clean up
case .stormBird:
session.web3.request(request: ContractStormBirdTransfer(address: transaction.to!.description, indices: (transaction.indices)!)) { [unowned self] result in
switch result {
case .success(let res):
let data = Data(hex: res.drop0x)
self.configuration = TransactionConfiguration(
gasPrice: self.calculatedGasPrice,
gasLimit: 144000,
data: data
)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
estimateGasLimit()
self.configuration = TransactionConfiguration(
gasPrice: calculatedGasPrice,
gasLimit: GasLimitConfiguration.default,
data: transaction.data ?? self.configuration.data
)
completion(.success(()))
case .token:
session.web3.request(request: ContractERC20Transfer(amount: transaction.value, address: transaction.to!.description)) { [unowned self] result in
switch result {
case .success(let res):
let data = Data(hex: res.drop0x)
self.configuration = TransactionConfiguration(
gasPrice: self.calculatedGasPrice,
gasLimit: 144000,
data: data
)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
//TODO clean up
case .stormBird:
session.web3.request(request: ContractStormBirdTransfer(address: transaction.to!.description, indices: (transaction.indices)!)) { [unowned self] result in
switch result {
case .success(let res):
let data = Data(hex: res.drop0x)
self.configuration = TransactionConfiguration(
gasPrice: self.calculatedGasPrice,
gasLimit: 144000,
data: data
)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
//TODO put order claim tx here somehow, or maybe the same one above
case .stormBirdOrder:
session.web3.request(request: ClaimStormBirdOrder(expiry: transaction.expiry!, indices: transaction.indices!,
v: transaction.v!, r: transaction.r!, s: transaction.s!)) { [unowned self] result in
switch result {
case .success(let res):
let data = Data(hex: res.drop0x)
self.configuration = TransactionConfiguration(
gasPrice: self.calculatedGasPrice,
gasLimit: 144000,
data: data
)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
}
}
@ -157,12 +176,13 @@ class TransactionConfigurator {
)
}
func signTransaction() -> SignTransaction {
func formUnsignedTransaction() -> UnsignedTransaction {
let value: BigInt = {
switch transaction.transferType {
case .ether: return transaction.value
case .token: return 0
case .stormBird: return 0
case .stormBirdOrder: return transaction.value
}
}()
let address: Address? = {
@ -170,9 +190,10 @@ class TransactionConfigurator {
case .ether: return transaction.to
case .token(let token): return token.address
case .stormBird(let token): return token.address
case .stormBirdOrder(let token): return token.address
}
}()
let signTransaction = SignTransaction(
let signTransaction = UnsignedTransaction(
value: value,
account: account,
to: address,

@ -77,6 +77,7 @@ class SendCoordinator: Coordinator {
controller.addressRow?.cell.row.updateCell()
case .token: break
case .stormBird: break
case .stormBirdOrder: break
}
controller.delegate = self
return controller

@ -25,7 +25,7 @@ class SendTransactionCoordinator {
}
func send(
transaction: SignTransaction,
transaction: UnsignedTransaction,
completion: @escaping (Result<ConfirmResult, AnyError>) -> Void
) {
if transaction.nonce >= 0 {
@ -35,8 +35,8 @@ class SendTransactionCoordinator {
address: session.account.address.description,
state: "pending"
)))
Session.send(request) { [weak self] result in
guard let `self` = self else { return }
Session.send(request) { result in
// guard let `self` = self else { return }
switch result {
case .success(let count):
let transaction = self.appendNonce(to: transaction, currentNonce: count)
@ -48,8 +48,8 @@ class SendTransactionCoordinator {
}
}
private func appendNonce(to: SignTransaction, currentNonce: Int) -> SignTransaction {
return SignTransaction(
private func appendNonce(to: UnsignedTransaction, currentNonce: Int) -> UnsignedTransaction {
return UnsignedTransaction(
value: to.value,
account: to.account,
to: to.to,
@ -62,7 +62,7 @@ class SendTransactionCoordinator {
}
func signAndSend(
transaction: SignTransaction,
transaction: UnsignedTransaction,
completion: @escaping (Result<ConfirmResult, AnyError>) -> Void
) {
let signedTransaction = keystore.signTransaction(transaction)

@ -5,7 +5,7 @@ import TrustKeystore
struct SentTransaction {
let id: String
let original: SignTransaction
let original: UnsignedTransaction
}
extension SentTransaction {

@ -7,6 +7,7 @@ enum TransferType {
case ether(destination: Address?)
case token(TokenObject)
case stormBird(TokenObject)
case stormBirdOrder(TokenObject)
}
extension TransferType {
@ -18,6 +19,8 @@ extension TransferType {
return token.symbol
case .stormBird(let token):
return token.symbol
case .stormBirdOrder(let token):
return token.symbol
}
}
@ -29,6 +32,8 @@ extension TransferType {
return Address(string: token.contract)!
case .stormBird(let token):
return Address(string: token.contract)!
case .stormBirdOrder(let token):
return Address(string: token.contract)!
}
}
}

@ -12,5 +12,12 @@ struct UnconfirmedTransaction {
let gasLimit: BigInt?
let gasPrice: BigInt?
let nonce: BigInt?
// these are not the v, r, s value of a signed transaction
// but are the v, r, s value of a signed ERC875 order
// TODO: encapsulate it in the data field
let v: UInt8?
let r: String?
let s: String?
let expiry: BigUInt?
let indices: [UInt16]?
}

@ -159,7 +159,7 @@ class ConfirmPaymentViewController: UIViewController {
@objc func send() {
self.displayLoading()
let transaction = configurator.signTransaction()
let transaction = configurator.formUnsignedTransaction()
self.sendTransactionCoordinator.send(transaction: transaction) { [weak self] result in
guard let `self` = self else { return }
self.didCompleted?(result)

@ -232,6 +232,8 @@ class SendViewController: FormViewController {
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .stormBird(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
case .stormBirdOrder(let token):
return EtherNumberFormatter.full.number(from: amountString, decimals: token.decimals)
}
}()
guard let value = parsedValue else {
@ -246,6 +248,10 @@ class SendViewController: FormViewController {
gasLimit: .none,
gasPrice: gasPrice,
nonce: .none,
v: .none,
r: .none,
s: .none,
expiry: .none,
indices: viewModel.isStormBird ? getIndiciesFromUI() : .none
)
self.delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self)

@ -46,6 +46,7 @@ struct ConfigureTransactionViewModel {
case .ether: return false
case .token: return true
case .stormBird: return true
case .stormBirdOrder: return true
}
}
}

@ -111,6 +111,10 @@ struct ConfirmPaymentDetailsViewModel {
return amountAttributedText(
string: fullFormatter.string(from: transaction.value, decimals: token.decimals)
)
case .stormBirdOrder(let token):
return amountAttributedText(
string: fullFormatter.string(from: transaction.value, decimals: token.decimals)
)
}
}

@ -54,6 +54,8 @@ struct SendViewModel {
return token
case .stormBird(let token):
return token
case .stormBirdOrder(let token):
return token
}
}

@ -4,6 +4,11 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:app.awallet.io</string>
<string>applinks:blockchainapis.herokuapp.com</string>
</array>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>
</dict>

@ -108,6 +108,7 @@ extension BalanceTitleView {
view.viewModel = BalanceTokenViewModel(token: token)
case .stormBird(let token):
view.viewModel = BalanceTokenViewModel(token: token)
case .stormBirdOrder: break
}
session.refresh(.ethBalance)
return view

@ -27,7 +27,7 @@ class NumberStepper: UIControl {
}
@IBInspectable
public var buttonsTextColor: UIColor = UIColor.white {
public var buttonsTextColor: UIColor = Colors.appBackground {
didSet {
for button in [leftButton, rightButton] {
button.setTitleColor(buttonsTextColor, for: .normal)
@ -36,7 +36,7 @@ class NumberStepper: UIControl {
}
@IBInspectable
public var buttonsBackgroundColor: UIColor = UIColor(red: 0.21, green: 0.5, blue: 0.74, alpha: 1) {
public var buttonsBackgroundColor: UIColor = .clear {
didSet {
for button in [leftButton, rightButton] {
button.backgroundColor = buttonsBackgroundColor
@ -45,7 +45,7 @@ class NumberStepper: UIControl {
}
}
public var buttonsFont = UIFont(name: "AvenirNext-Bold", size: 20.0)! {
public var buttonsFont = Fonts.bold(size: 21)! {
didSet {
for button in [leftButton, rightButton] {
button.titleLabel?.font = buttonsFont
@ -54,46 +54,29 @@ class NumberStepper: UIControl {
}
@IBInspectable
public var labelTextColor: UIColor = UIColor.white {
public var labelTextColor: UIColor = Colors.appBackground {
didSet {
label.textColor = labelTextColor
}
}
@IBInspectable
public var labelBackgroundColor: UIColor = UIColor(red: 0.26, green: 0.6, blue: 0.87, alpha: 1) {
public var labelBackgroundColor: UIColor = .clear {
didSet {
label.backgroundColor = labelBackgroundColor
}
}
public var labelFont = UIFont(name: "AvenirNext-Bold", size: 25.0)! {
public var labelFont = Fonts.semibold(size: 21)! {
didSet {
label.font = labelFont
}
}
@IBInspectable
public var labelCornerRadius: CGFloat = 0 {
didSet {
label.layer.cornerRadius = labelCornerRadius
}
}
@IBInspectable override
public var cornerRadius: CGFloat {
didSet {
layer.cornerRadius = cornerRadius
clipsToBounds = true
}
}
@IBInspectable override
public var borderWidth: CGFloat {
didSet {
layer.borderWidth = borderWidth
label.layer.borderWidth = borderWidth
}
}
@ -101,61 +84,48 @@ class NumberStepper: UIControl {
public var borderColor: UIColor! {
didSet {
layer.borderColor = borderColor.cgColor
label.layer.borderColor = borderColor.cgColor
}
}
@IBInspectable
public var labelWidthWeight: CGFloat = 0.5 {
didSet {
labelWidthWeight = min(1, max(0, labelWidthWeight))
setNeedsLayout()
}
}
lazy var leftButton: UIButton = {
lazy var rightButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("-", for: .normal)
button.setTitleColor(self.buttonsTextColor, for: .normal)
button.backgroundColor = self.buttonsBackgroundColor
button.titleLabel?.font = self.buttonsFont
button.addTarget(self, action: #selector(NumberStepper.leftButtonTouchDown), for: .touchDown)
button.addTarget(self, action: #selector(NumberStepper.buttonTouchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(NumberStepper.buttonTouchUp), for: .touchUpOutside)
button.addTarget(self, action: #selector(NumberStepper.buttonTouchUp), for: .touchCancel)
button.addTarget(self, action: #selector(rightButtonTouchDown), for: .touchDown)
button.addTarget(self, action: #selector(buttonTouchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(buttonTouchUp), for: .touchUpOutside)
button.addTarget(self, action: #selector(buttonTouchUp), for: .touchCancel)
return button
}()
lazy var rightButton: UIButton = {
lazy var leftButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("+", for: .normal)
button.setTitleColor(self.buttonsTextColor, for: .normal)
button.backgroundColor = self.buttonsBackgroundColor
button.titleLabel?.font = self.buttonsFont
button.addTarget(self, action: #selector(NumberStepper.rightButtonTouchDown), for: .touchDown)
button.addTarget(self, action: #selector(NumberStepper.buttonTouchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(NumberStepper.buttonTouchUp), for: .touchUpOutside)
button.addTarget(self, action: #selector(NumberStepper.buttonTouchUp), for: .touchCancel)
button.addTarget(self, action: #selector(leftButtonTouchDown), for: .touchDown)
button.addTarget(self, action: #selector(buttonTouchUp), for: .touchUpInside)
button.addTarget(self, action: #selector(buttonTouchUp), for: .touchUpOutside)
button.addTarget(self, action: #selector(buttonTouchUp), for: .touchCancel)
return button
}()
lazy var label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.text = String(self.value)
label.textColor = self.labelTextColor
label.backgroundColor = self.labelBackgroundColor
label.font = self.labelFont
label.layer.cornerRadius = self.labelCornerRadius
label.layer.masksToBounds = true
label.isUserInteractionEnabled = true
return label
}()
var labelOriginalCenter: CGPoint!
var labelMaximumCenterX: CGFloat!
var labelMinimumCenterX: CGFloat!
enum StepperState {
case Stable, ShouldIncrease, ShouldDecrease
}
@ -178,6 +148,7 @@ class NumberStepper: UIControl {
public init(frame: CGRect) {
super.init(frame: frame)
setup()
layout()
}
func setup() {
@ -188,19 +159,26 @@ class NumberStepper: UIControl {
backgroundColor = buttonsBackgroundColor
layer.cornerRadius = cornerRadius
clipsToBounds = true
labelOriginalCenter = label.center
}
override
public func layoutSubviews() {
let buttonWidth = bounds.size.width * ((1 - labelWidthWeight) / 2)
let labelWidth = bounds.size.width * labelWidthWeight
func layout() {
let xMargin = CGFloat(3)
NSLayoutConstraint.activate([
leftButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 7),
leftButton.trailingAnchor.constraint(equalTo: label.leadingAnchor, constant: -xMargin),
leftButton.topAnchor.constraint(equalTo: topAnchor),
leftButton.bottomAnchor.constraint(equalTo: bottomAnchor),
leftButton.frame = CGRect(x: 0, y: 0, width: buttonWidth, height: bounds.size.height)
label.frame = CGRect(x: buttonWidth, y: 0, width: labelWidth, height: bounds.size.height)
rightButton.frame = CGRect(x: labelWidth + buttonWidth, y: 0, width: buttonWidth, height: bounds.size.height)
label.trailingAnchor.constraint(equalTo: rightButton.leadingAnchor, constant: -xMargin),
//Just so the label doesn't change width when we change the value
label.widthAnchor.constraint(greaterThanOrEqualToConstant: 50),
label.topAnchor.constraint(equalTo: topAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
labelOriginalCenter = label.center
rightButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -7),
rightButton.topAnchor.constraint(equalTo: topAnchor),
rightButton.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
func updateValue() {
@ -218,26 +196,23 @@ extension NumberStepper {
stepperState = .Stable
leftButton.isEnabled = true
rightButton.isEnabled = true
label.isUserInteractionEnabled = true
}
}
extension NumberStepper {
@objc
func leftButtonTouchDown(button: UIButton) {
rightButton.isEnabled = false
label.isUserInteractionEnabled = false
if value != minimumValue {
stepperState = .ShouldDecrease
leftButton.isEnabled = false
if value != maximumValue {
stepperState = .ShouldIncrease
}
}
@objc
func rightButtonTouchDown(button: UIButton) {
leftButton.isEnabled = false
label.isUserInteractionEnabled = false
if value != maximumValue {
stepperState = .ShouldIncrease
rightButton.isEnabled = false
if value != minimumValue {
stepperState = .ShouldDecrease
}
}

@ -0,0 +1,33 @@
//
// Created by James Sangalli on 7/3/18.
// This is a struct with the capacity to convert an order to a new format:
// the data field of a transaction.
// There are 4 formats of orders:
// 1) the binary data the signature is corrisponding to.
// 2) the compressed format, which is Base64 encoded to UniversalLink
// 3) the JSON format, which is used to pass to feeMaster server.
// 4) this data format, to pass as part of an Ethereum transaction
// This class gets you the 4th format.
//
import Foundation
import Foundation
import TrustKeystore
import BigInt
struct ClaimStormBirdOrder: Web3Request {
typealias Response = String
let expiry: BigUInt
let indices: [UInt16]
let v: UInt8
let r: String
let s: String
var type: Web3RequestType {
let abi = "{\"constant\":false,\"inputs\":[{\"name\":\"expiry\",\"type\":\"uint256\"},{\"name\":\"ticketIndices\",\"type\":\"uint16[]\"},{\"name\":\"v\",\"type\":\"uint8\"},{\"name\":\"r\",\"type\":\"bytes32\"},{\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"trade\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"}, [\"\(expiry)\", \(indices), \(v), \"\(r)\", \"\(s)\"]"
let run = "web3.eth.abi.encodeFunctionCall(" + abi + ")"
return .script(command: run)
}
}

@ -10,7 +10,10 @@ struct ContractERC20Transfer: Web3Request {
let address: String
var type: Web3RequestType {
let run = "web3.eth.abi.encodeFunctionCall({\"constant\": false, \"inputs\": [ { \"name\": \"_to\", \"type\": \"address\" }, { \"name\": \"_value\", \"type\": \"uint256\" } ], \"name\": \"transfer\", \"outputs\": [ { \"name\": \"success\", \"type\": \"bool\" } ], \"type\": \"function\"} , [\"\(address)\", \"\(amount.description)\"])"
let run = "web3.eth.abi.encodeFunctionCall({\"constant\": false, \"inputs\": [ { \"name\": \"_to\", \"type\": " +
"\"address\" }, { \"name\": \"_value\", \"type\": \"uint256\" } ], \"name\": \"transfer\", \"outputs\": " +
"[ { \"name\": \"success\", \"type\": \"bool\" } ], \"type\": \"function\"} " +
", [\"\(address)\", \"\(amount.description)\"])"
return .script(command: run)
}
}

@ -7,7 +7,7 @@ struct ContractStormBirdTransfer: Web3Request {
let indices: [UInt16]
var type: Web3RequestType {
let abi = "{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"ticketIndices\",\"type\":\"uint16[]\"}],\"name\":\"transfer\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"} , [\"\(address)\", \(indices)]"
let abi = "{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"ticketIndices\",\"type\":\"uint16[]\"}],\"name\":\"transfer\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}, [\"\(address)\", \(indices)]"
let run = "web3.eth.abi.encodeFunctionCall(" + abi + ")"
return .script(command: run)
}

@ -16,9 +16,9 @@ struct GetIsStormBirdEncode: Web3Request {
struct GetIsStormBirdDecode: Web3Request {
typealias Response = String
let data: String
var type: Web3RequestType {
let run = "web3.eth.abi.decodeParameter('uint256', '\(data)')"
return .script(command: run)

@ -12,13 +12,13 @@ class Web3Swift: NSObject {
let url: URL
var isLoaded = false
init(url: URL = URL(string: "http://localhost:8545")!) {
let nodeURL = "http://stormbird.duckdns.org:8545"
init(url: URL = URL(string: "http://stormbird.duckdns.org:8545")!) {
self.url = url
}
func start() {
self.webView.navigationDelegate = self
loadWeb3()
}

@ -0,0 +1,98 @@
//
// Created by James Sangalli on 8/3/18.
//
import Foundation
import XCTest
@testable import Trust
import BigInt
import TrustKeystore
class ClaimOrderCoordinatorTests : XCTestCase {
var expectations = [XCTestExpectation]()
func testClaimOrder() {
let keystore = try! EtherKeystore()
let claimOrderCoordinator = FakeClaimOrderCoordinator()
let expectation = self.expectation(description: "wait til callback")
expectations.append(expectation)
var indices = [UInt16]()
indices.append(14)
let expiry = BigUInt("0")
let v = UInt8(27)
let r = "0x2d8e40406bf6175036ab1e1099b48590438bf48d429a8b209120fecd07894566"
let s = "0x59ccf58ca36f681976228309fdd9de7e30e860084d9d63014fa79d48a25bb93d"
let token = TokenObject(
contract: "0xacDe9017473D7dC82ACFd0da601E4de291a7d6b0",
name: "MJ Comeback",
symbol: "MJC",
decimals: 0,
value: "0",
isCustom: true,
isDisabled: false,
isStormBird: true
)
claimOrderCoordinator.claimOrder(indices: indices, expiry: expiry!, v: v, r: r, s: s) { result in
switch result {
case .success(let payload):
let address: Address = .makeStormBird()
let transaction = UnconfirmedTransaction(
transferType: .stormBirdOrder(token),
value: BigInt("0"),
to: address,
data: Data(bytes: payload.hexa2Bytes),
gasLimit: .none,
gasPrice: 14400,
nonce: .none,
v: v,
r: r,
s: s,
expiry: expiry,
indices: indices
)
let session: WalletSession = .makeStormBirdSession()
let configurator = TransactionConfigurator(
session: session,
account: .make(),
transaction: transaction
)
let unsignedTransaction = configurator.formUnsignedTransaction()
let account = keystore.getAccount(for: address)!
let signedTransaction = UnsignedTransaction(value: unsignedTransaction.value,
account: account,
to: unsignedTransaction.to,
nonce: unsignedTransaction.nonce,
data: unsignedTransaction.data,
gasPrice: unsignedTransaction.gasPrice,
gasLimit: unsignedTransaction.gasLimit,
chainID: 3)
let sendTransactionCoordinator = SendTransactionCoordinator(session: session,
keystore: keystore,
confirmType: .signThenSend)
sendTransactionCoordinator.send(transaction: signedTransaction) { result in
switch result {
case .success(let res):
print(res);
expectation.fulfill()
case .failure(let error):
print(error);
}
}
case .failure: break
}
}
wait(for: expectations, timeout: 10000)
}
}

@ -21,7 +21,7 @@ class SendCoordinatorTests: XCTestCase {
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is SendViewController)
}
func testDestanation() {
func testDestination() {
let address: Address = .make()
let coordinator = SendCoordinator(
transferType: .ether(destination: address),
@ -36,4 +36,5 @@ class SendCoordinatorTests: XCTestCase {
XCTAssertEqual(address.description, coordinator.sendViewController.addressRow?.value)
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is SendViewController)
}
}

@ -8,7 +8,7 @@ import XCTest
class TransactionSigningTests: XCTestCase {
func testEIP155SignHash() {
let address = Address(string: "0x3535353535353535353535353535353535353535")!
let transaction = SignTransaction(
let transaction = UnsignedTransaction(
value: BigInt("1000000000000000000"),
account: .make(address: address),
to: address,
@ -25,7 +25,7 @@ class TransactionSigningTests: XCTestCase {
func testHomesteadSignHash() {
let address = Address(string: "0x3535353535353535353535353535353535353535")!
let transaction = SignTransaction(
let transaction = UnsignedTransaction(
value: BigInt("1000000000000000000"),
account: .make(address: address),
to: address,
@ -42,7 +42,7 @@ class TransactionSigningTests: XCTestCase {
func testSignTransaction() {
let account: Account = .make(address: Address(data: Data(repeating: 0, count: 20)))
let transaction = SignTransaction(
let transaction = UnsignedTransaction(
value: BigInt("1000000000000000000"),
account: account,
to: Address(string: "0x3535353535353535353535353535353535353535")!,

@ -12,4 +12,12 @@ extension Address {
string: address
)!
}
static func makeStormBird(
address: String = "0x007bEe82BDd9e866b2bd114780a47f2261C684E3"
) -> Address {
return Address(
string: address
)!
}
}

@ -0,0 +1,11 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
@testable import Trust
class FakeClaimOrderCoordinator: ClaimOrderCoordinator {
convenience init() {
self.init(web3: Web3Swift())
startWeb3()
}
}

@ -86,7 +86,7 @@ struct FakeKeystore: Keystore {
return .failure(KeystoreError.failedToSignMessage)
}
func signTransaction(_ signTransaction: SignTransaction) -> Result<Data, KeystoreError> {
func signTransaction(_ signTransaction: UnsignedTransaction) -> Result<Data, KeystoreError> {
//TODO: Implement
return .failure(KeystoreError.failedToSignTransaction)
}

@ -23,6 +23,10 @@ extension UnconfirmedTransaction {
gasLimit: gasLimit,
gasPrice: gasPrice,
nonce: nonce,
v: .none,
r: .none,
s: .none,
expiry: .none,
indices: .none
)
}

@ -1,6 +1,7 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import TrustKeystore
@testable import Trust
extension Wallet {
@ -11,4 +12,13 @@ extension Wallet {
type: type
)
}
static func makeStormBird(
type: WalletType = .real(Account(address: Address(string: "0x007bEe82BDd9e866b2bd114780a47f2261C684E3")!,
url: URL(fileURLWithPath: "")))
) -> Wallet {
return Wallet(
type: type
)
}
}

@ -10,7 +10,21 @@ extension WalletSession {
config: Config = .make(),
web3: Web3Swift = Web3Swift()
) -> WalletSession {
let balance = BalanceCoordinator(account: account, config: config, storage: FakeTokensDataStore())
let balance = BalanceCoordinator(wallet: account, config: config, storage: FakeTokensDataStore())
return WalletSession(
account: account,
config: config,
web3: web3,
balanceCoordinator: balance
)
}
static func makeStormBirdSession(
account: Wallet = .makeStormBird(),
config: Config = .make(),
web3: Web3Swift = Web3Swift()
) -> WalletSession {
let balance = BalanceCoordinator(wallet: account, config: config, storage: FakeTokensDataStore())
return WalletSession(
account: account,
config: config,

@ -0,0 +1,59 @@
import Foundation
import XCTest
@testable import Trust
import TrustKeystore
import BigInt
class MarketQueueHandlerTests: XCTestCase {
var expectations = [XCTestExpectation]()
let keyStore = FakeEtherKeystore()
func testGetOrders() {
let expectation = self.expectation(description: "wait til callback")
expectations.append(expectation)
MarketQueueHandler().getOrders(callback: {
callback in
print(callback)
expectation.fulfill()
})
wait(for: expectations, timeout: 10)
}
//TODO reuse when test account works
func testPuttingOrderToQueue() {
// let expectation = self.expectation(description: "wait til callback")
// expectations.append(expectation)
// var testOrdersList : Array<Order> = Array<Order>()
// let account = keyStore.createAccount(password: "test")
// //set up test orders
// var indices = [UInt16]()
// indices.append(1)
// indices.append(2)
//
// var timestamp = NSDate().timeIntervalSince1970.description //1521562138
// timestamp = timestamp.substring(to: timestamp.count - 6);
// let ts = Int(timestamp)! + 300;
// let testOrder1 = Order(
// price: BigUInt("100000")!,
// indices: indices,
// expiry: BigUInt(String(ts))!,
// contractAddress: "bC9a1026A4BC6F0BA8Bbe486d1D09dA5732B39e4".lowercased(),
// start: BigUInt("500000210121213")!,
// count: 3
// )
// testOrdersList.append(testOrder1)
// let signedOrders = try! SignOrders().signOrders(orders: testOrdersList, account: account)
// let privateKey = keyStore.exportPrivateKey(account: account)
// let publicKey = try! Secp256k1.shared.pubKeyFromPrivateKey(from: privateKey.dematerialize())
//
// OrdersRequest.init().putOrderToServer(signedOrders: signedOrders, publicKey: publicKey.hexString, callback: {
// callback in
// print(callback)
// expectation.fulfill()
// })
//
// wait(for: expectations, timeout: 10)
}
}

@ -1,64 +0,0 @@
import Foundation
import XCTest
@testable import Trust
import TrustKeystore
import BigInt
class OrderRequestTest : XCTestCase {
var expectations = [XCTestExpectation]()
func testHttpCallToQueue() {
let expectation = self.expectation(description: "wait til callback")
expectations.append(expectation)
OrdersRequest.init().getOrders(callback: {
callback in
print(callback)
expectation.fulfill()
})
wait(for: expectations, timeout: 10)
}
func testPuttingOrderToQueue() {
let expectation = self.expectation(description: "wait til callback")
expectations.append(expectation)
var testOrdersList : Array<Order> = Array<Order>()
let keyStore = FakeEtherKeystore()
let account = keyStore.createAccount(password: "haha")
//set up test orders
var indices = [UInt16]()
indices.append(1)
indices.append(2)
let testOrder1 = Order(price: BigUInt("100000")!, indices: indices,
expiry: BigUInt("0")!, contractAddress: "007bee82bdd9e866b2bd114780a47f2261c684e3",
start: BigUInt("500000210121213")!, count: 3)
testOrdersList.append(testOrder1)
let signOrders = SignOrders()
//TODO fix signature issues
var signedOrders : Array<SignedOrder> = signOrders.signOrders(orders: testOrdersList, account: account)
signedOrders[0].signature = "0x1cae08113567db5303fb1ed1b157fbc8c7247aa" +
"9689ee76902d731c9806ab9853d8fcded6145fc7ebe5c32e41e247b315" +
"b2b23f41dcb3acd17d01a9f6140669f1c"
let privateKey = keyStore.exportPrivateKey(account: account)
let publicKey = try! Secp256k1.shared.pubKeyFromPrivateKey(from:
privateKey.dematerialize()).hexString
OrdersRequest.init().putOrderToServer(signedOrders: signedOrders, publicKey: publicKey, callback: {
callback in
print(callback)
expectation.fulfill()
})
wait(for: expectations, timeout: 10)
}
}

@ -6,47 +6,37 @@ import BigInt
class OrderSigningTests : XCTestCase {
var contractAddress = "d9864b424447B758CdE90f8655Ff7cA4673956bf"
var contractAddress = "0xacDe9017473D7dC82ACFd0da601E4de291a7d6b0"
let keystore = FakeEtherKeystore()
let address: Address = .makeStormBird()
func testSigningOrders() {
var testOrdersList : Array<Order> = Array<Order>()
var keyStore = FakeEtherKeystore()
//set up test orders
var indices = [UInt16]()
indices.append(1)
indices.append(2)
let testOrder1 = Order(price: BigUInt("0")!, indices: indices,
expiry: BigUInt("0")!, contractAddress: contractAddress,
start: BigUInt("91239231313")!, count: 3)
testOrdersList.append(testOrder1)
let signOrders = SignOrders()
var account = keyStore.createAccount(password: "deleteOnceWorking")
print(account.address)
var signedOrders : Array<SignedOrder> = signOrders.signOrders(orders: testOrdersList, account: account)
signedOrders[0].signature = "jrzcgpsnV7IPGE3nZQeHQk5vyZdy5c8rHk0R/iG7wpiK9NT730I//DN5Dg5fHs+s4ZFgOGQnk7cXLQROBs9NvgE="
var signature = try! keyStore.signMessageData(Data(bytes: signedOrders[0].message), for: account).dematerialize().hexString
print("v: " + Int(signature.substring(from: 128), radix: 16)!.description)
print("r: 0x" + signature.substring(to: 64))
print("s: 0x" + signature.substring(from: 64))
//test signing speed for bulk orders
var bulkMessages = [Data]()
for i in 0...2015 {
bulkMessages.append(Data(bytes: signedOrders[0].message))
indices.append(14)
let testOrder1 = Order(price: BigUInt("0")!,
indices: indices,
expiry: BigUInt("0")!,
contractAddress: contractAddress,
start: BigUInt("91239231313")!,
count: 3
)
for _ in 0...2015 {
testOrdersList.append(testOrder1)
}
print(account.address)
try! keyStore.signMessageBulk(bulkMessages, for: account)
print(signedOrders.description)
let signOrders = OrderHandler()
let account = keystore.getAccount(for: address)!
//let signedOrders = try! signOrders.signOrders(orders: testOrdersList, account: account)
//XCTAssertGreaterThanOrEqual(2016, signedOrders.count)
}
}

@ -0,0 +1,20 @@
//
// Created by James Sangalli on 24/3/18.
//
import Foundation
@testable import Trust
import XCTest
class UniversalLinkHandlerTests: XCTestCase {
func testUniversalLinkParser() {
let testUrl = "https://app.awallet.io/AAGGoFq8Ule8mhAmpLxvC6i75IbR0J2lcys55AECAwQFBgcICYvWi5I+Tl5m9XumBD5jLIm6i39kD7F40UW4BaJDEVOWLTYz3kek7wjT7Bn+2w0NCiyx7zWuvseTA8qfoIqCIxob"
let signedOrder = UniversalLinkHandler().parseURL(url: testUrl)
XCTAssertGreaterThanOrEqual(signedOrder.signature.count, 130)
let url = UniversalLinkHandler().createUniversalLink(signedOrder: signedOrder)
print(url)
XCTAssertEqual(testUrl, url)
}
}

@ -8,7 +8,7 @@ import BigInt
class CreateRedeemTests: XCTestCase {
var keyStore = FakeEtherKeystore()
let keyStore = FakeEtherKeystore()
//when loading qr only include signature in decimal and the indices
func testGenerateRedeem() {

Loading…
Cancel
Save