diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 38450f55d..58a42a333 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -9,6 +9,12 @@ /* Begin PBXBuildFile section */ 1DDD23FEDFC7A122D2EA5E39 /* Pods_NotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D3CE61AE0A5F67EEF5270A /* Pods_NotificationServiceExtension.framework */; }; 3C06931576393125C61FB8F6 /* Pods_Adamant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33975C0D891698AA7E74EBCC /* Pods_Adamant.framework */; }; + 6403F5DB2272389800D58779 /* (null) in Sources */ = {isa = PBXBuildFile; }; + 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5DD22723C6800D58779 /* DashMainnet.swift */; }; + 6403F5E022723F6400D58779 /* DashWalletRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5DF22723F6400D58779 /* DashWalletRouter.swift */; }; + 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5E122723F7500D58779 /* DashWallet.swift */; }; + 6403F5E422723F8C00D58779 /* DashWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5E322723F8C00D58779 /* DashWalletService.swift */; }; + 6403F5E622723FDA00D58779 /* DashWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5E522723FDA00D58779 /* DashWalletViewController.swift */; }; 6406D74A21C7F06000196713 /* SearchResultsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6406D74821C7F06000196713 /* SearchResultsViewController.xib */; }; 6406D74D21CE3AC100196713 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6406D74C21CE3AC100196713 /* KeyboardManager.swift */; }; 6414C18E217DF43100373FA6 /* String+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6414C18D217DF43100373FA6 /* String+adamant.swift */; }; @@ -37,6 +43,16 @@ 645FEB35213E72C100D6BA2D /* OnboardViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 645FEB33213E72C100D6BA2D /* OnboardViewController.xib */; }; 648BCA6B213D37A900875EB5 /* AdamantAvatarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BCA6A213D37A800875EB5 /* AdamantAvatarService.swift */; }; 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BCA6C213D384F00875EB5 /* AvatarService.swift */; }; + 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648C696E22915A12006645F5 /* DashTransaction.swift */; }; + 648C697122915CB8006645F5 /* BTCRPCServerResponce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648C697022915CB8006645F5 /* BTCRPCServerResponce.swift */; }; + 648C697322916192006645F5 /* DashTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648C697222916192006645F5 /* DashTransactionsViewController.swift */; }; + 648CE3A022999C890070A2CC /* BaseBtcTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE39F22999C890070A2CC /* BaseBtcTransaction.swift */; }; + 648CE3A222999CE70070A2CC /* BTCRawTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE3A122999CE70070A2CC /* BTCRawTransaction.swift */; }; + 648CE3A42299A94D0070A2CC /* DashTransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE3A32299A94D0070A2CC /* DashTransactionDetailsViewController.swift */; }; + 648CE3A6229AD1CD0070A2CC /* DashWalletService+Send.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE3A5229AD1CD0070A2CC /* DashWalletService+Send.swift */; }; + 648CE3A8229AD1E20070A2CC /* DashWalletService+RichMessageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE3A7229AD1E20070A2CC /* DashWalletService+RichMessageProvider.swift */; }; + 648CE3AA229AD1F90070A2CC /* DashWalletService+RichMessageProviderWithStatusCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE3A9229AD1F90070A2CC /* DashWalletService+RichMessageProviderWithStatusCheck.swift */; }; + 648CE3AC229AD2190070A2CC /* DashTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648CE3AB229AD2190070A2CC /* DashTransferViewController.swift */; }; 648DD79E2236A0B500B811FD /* DogeTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD79D2236A0B500B811FD /* DogeTransactionsViewController.swift */; }; 648DD7A02236A59200B811FD /* DogeTransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD79F2236A59200B811FD /* DogeTransactionDetailsViewController.swift */; }; 648DD7A22237D9A000B811FD /* DogeTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648DD7A12237D9A000B811FD /* DogeTransaction.swift */; }; @@ -52,6 +68,8 @@ 64A223D620F760BB005157CB /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A223D520F760BB005157CB /* Localization.swift */; }; 64A223D820F7A08E005157CB /* LskApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A223D720F7A08E005157CB /* LskApiService.swift */; }; 64A223DA20F7A14B005157CB /* AdamantLskApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A223D920F7A14B005157CB /* AdamantLskApiService.swift */; }; + 64AE84612300012400F38FBD /* DashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AE845F2300012200F38FBD /* DashProvider.swift */; }; + 64AE84622300012400F38FBD /* DashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AE845F2300012200F38FBD /* DashProvider.swift */; }; 64BD2B7520E2814B00E2CD36 /* EthTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BD2B7420E2814B00E2CD36 /* EthTransaction.swift */; }; 64BD2B7720E2820300E2CD36 /* TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */; }; 64D059FF20D3116B003AD655 /* NodesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D059FE20D3116A003AD655 /* NodesListViewController.swift */; }; @@ -517,6 +535,11 @@ 4A4D67BD3DC89C07D1351248 /* Pods-AdmCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AdmCore.release.xcconfig"; path = "Target Support Files/Pods-AdmCore/Pods-AdmCore.release.xcconfig"; sourceTree = ""; }; 53D3CE61AE0A5F67EEF5270A /* Pods_NotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 574F8302871CE64C213242AB /* Pods-NotificationServiceExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationServiceExtension.release.xcconfig"; path = "Target Support Files/Pods-NotificationServiceExtension/Pods-NotificationServiceExtension.release.xcconfig"; sourceTree = ""; }; + 6403F5DD22723C6800D58779 /* DashMainnet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashMainnet.swift; sourceTree = ""; }; + 6403F5DF22723F6400D58779 /* DashWalletRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletRouter.swift; sourceTree = ""; }; + 6403F5E122723F7500D58779 /* DashWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWallet.swift; sourceTree = ""; }; + 6403F5E322723F8C00D58779 /* DashWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletService.swift; sourceTree = ""; }; + 6403F5E522723FDA00D58779 /* DashWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletViewController.swift; sourceTree = ""; }; 6406D74821C7F06000196713 /* SearchResultsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchResultsViewController.xib; sourceTree = ""; }; 6406D74C21CE3AC100196713 /* KeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; 6414C18D217DF43100373FA6 /* String+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+adamant.swift"; sourceTree = ""; }; @@ -546,6 +569,16 @@ 645FEB33213E72C100D6BA2D /* OnboardViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardViewController.xib; sourceTree = ""; }; 648BCA6A213D37A800875EB5 /* AdamantAvatarService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdamantAvatarService.swift; sourceTree = ""; }; 648BCA6C213D384F00875EB5 /* AvatarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarService.swift; sourceTree = ""; }; + 648C696E22915A12006645F5 /* DashTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashTransaction.swift; sourceTree = ""; }; + 648C697022915CB8006645F5 /* BTCRPCServerResponce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTCRPCServerResponce.swift; sourceTree = ""; }; + 648C697222916192006645F5 /* DashTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashTransactionsViewController.swift; sourceTree = ""; }; + 648CE39F22999C890070A2CC /* BaseBtcTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseBtcTransaction.swift; sourceTree = ""; }; + 648CE3A122999CE70070A2CC /* BTCRawTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTCRawTransaction.swift; sourceTree = ""; }; + 648CE3A32299A94D0070A2CC /* DashTransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashTransactionDetailsViewController.swift; sourceTree = ""; }; + 648CE3A5229AD1CD0070A2CC /* DashWalletService+Send.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashWalletService+Send.swift"; sourceTree = ""; }; + 648CE3A7229AD1E20070A2CC /* DashWalletService+RichMessageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashWalletService+RichMessageProvider.swift"; sourceTree = ""; }; + 648CE3A9229AD1F90070A2CC /* DashWalletService+RichMessageProviderWithStatusCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashWalletService+RichMessageProviderWithStatusCheck.swift"; sourceTree = ""; }; + 648CE3AB229AD2190070A2CC /* DashTransferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashTransferViewController.swift; sourceTree = ""; }; 648DD79D2236A0B500B811FD /* DogeTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeTransactionsViewController.swift; sourceTree = ""; }; 648DD79F2236A59200B811FD /* DogeTransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeTransactionDetailsViewController.swift; sourceTree = ""; }; 648DD7A12237D9A000B811FD /* DogeTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeTransaction.swift; sourceTree = ""; }; @@ -562,6 +595,7 @@ 64A223D520F760BB005157CB /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; 64A223D720F7A08E005157CB /* LskApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskApiService.swift; sourceTree = ""; }; 64A223D920F7A14B005157CB /* AdamantLskApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantLskApiService.swift; sourceTree = ""; }; + 64AE845F2300012200F38FBD /* DashProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashProvider.swift; sourceTree = ""; }; 64BD2B7420E2814B00E2CD36 /* EthTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthTransaction.swift; sourceTree = ""; }; 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetails.swift; sourceTree = ""; }; 64D059FE20D3116A003AD655 /* NodesListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodesListViewController.swift; sourceTree = ""; }; @@ -960,6 +994,24 @@ path = Pods; sourceTree = ""; }; + 6403F5DC22723C2800D58779 /* Dash */ = { + isa = PBXGroup; + children = ( + 6403F5DD22723C6800D58779 /* DashMainnet.swift */, + 6403F5DF22723F6400D58779 /* DashWalletRouter.swift */, + 6403F5E122723F7500D58779 /* DashWallet.swift */, + 6403F5E322723F8C00D58779 /* DashWalletService.swift */, + 648CE3A5229AD1CD0070A2CC /* DashWalletService+Send.swift */, + 648CE3A7229AD1E20070A2CC /* DashWalletService+RichMessageProvider.swift */, + 648CE3A9229AD1F90070A2CC /* DashWalletService+RichMessageProviderWithStatusCheck.swift */, + 6403F5E522723FDA00D58779 /* DashWalletViewController.swift */, + 648CE3AB229AD2190070A2CC /* DashTransferViewController.swift */, + 648C697222916192006645F5 /* DashTransactionsViewController.swift */, + 648CE3A32299A94D0070A2CC /* DashTransactionDetailsViewController.swift */, + ); + path = Dash; + sourceTree = ""; + }; 644EC35020EFA96700F40C73 /* Delegates */ = { isa = PBXGroup; children = ( @@ -1130,6 +1182,8 @@ E91947B320002809001362F8 /* AdamantAccount.swift */, E9393FA92055D03300EE6F30 /* AdamantMessage.swift */, 644EC34E20EFA77A00F40C73 /* Delegate.swift */, + 648CE39F22999C890070A2CC /* BaseBtcTransaction.swift */, + 648CE3A122999CE70070A2CC /* BTCRawTransaction.swift */, 648DD7A12237D9A000B811FD /* DogeTransaction.swift */, E923222521135F9000A7E5AF /* EthAccount.swift */, 64BD2B7420E2814B00E2CD36 /* EthTransaction.swift */, @@ -1139,6 +1193,7 @@ E95F85682006AB9D0070534A /* NormalizedTransaction.swift */, E9FCA1E5218334C00005E83D /* SimpleTransactionDetails.swift */, E971591921681D6900A5F904 /* TransactionStatus.swift */, + 648C696E22915A12006645F5 /* DashTransaction.swift */, ); path = Models; sourceTree = ""; @@ -1214,6 +1269,7 @@ E95F856E2007B61D0070534A /* GetPublicKeyResponse.swift */, E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */, E9771DA622997F310099AAC7 /* ServerResponseWithTimestamp.swift */, + 648C697022915CB8006645F5 /* BTCRPCServerResponce.swift */, ); path = ServerResponses; sourceTree = ""; @@ -1257,6 +1313,7 @@ E94008792114ECF100CD2D67 /* Ethereum */, E94008812114EE3900CD2D67 /* Lisk */, 64E1C82B222E958C006C4DA7 /* Doge */, + 6403F5DC22723C2800D58779 /* Dash */, E94008712114EACF00CD2D67 /* WalletAccount.swift */, E940086D2114AA2E00CD2D67 /* WalletService.swift */, E9B1AA582122D59600080A2A /* WalletsRoutes.swift */, @@ -1379,6 +1436,7 @@ children = ( E957E0FA229AB9310019732A /* AdamantProvider.swift */, E957E0F422999A5D0019732A /* DogeProvider.swift */, + 64AE845F2300012200F38FBD /* DashProvider.swift */, E957E100229AC2050019732A /* EthProvider.swift */, E957E0FE229AC1C80019732A /* LskProvider.swift */, E957E0F622999BD50019732A /* TransferBaseProvider.swift */, @@ -2077,8 +2135,6 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Adamant/Pods-Adamant-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", @@ -2120,8 +2176,6 @@ "${BUILT_PRODUCTS_DIR}/web3swift/web3swift.framework", ); name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - ); outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BigInt.framework", @@ -2193,15 +2247,11 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-AdamantTests/Pods-AdamantTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/GRDB.swift/GRDB.framework", ); name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - ); outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRDB.framework", ); @@ -2311,9 +2361,12 @@ E94E7B08205D4CB80042B639 /* SharedRoutes.swift in Sources */, E9960B3621F5154300C840A8 /* DummyAccount+CoreDataProperties.swift in Sources */, E9CAE8D22018AA7700345E76 /* AdamantApi+Accounts.swift in Sources */, + 648CE3A6229AD1CD0070A2CC /* DashWalletService+Send.swift in Sources */, E987024920C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift in Sources */, + 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */, 648DD7A42237DB9E00B811FD /* DogeWalletService+Send.swift in Sources */, E9942B84203CBFCE00C163AF /* AdamantQRTools.swift in Sources */, + 6403F5E422723F8C00D58779 /* DashWalletService.swift in Sources */, E908472F2196FEA80095825D /* BaseTransaction+CoreDataProperties.swift in Sources */, E96D64B92295BED700CA5587 /* StateType.swift in Sources */, E94008872114F05B00CD2D67 /* AddressValidationResult.swift in Sources */, @@ -2324,6 +2377,7 @@ E96D64B12295A88400CA5587 /* String+utilites.swift in Sources */, E993301E212EF39700CD5200 /* EthTransferViewController.swift in Sources */, E9CAE8DA2018ACD300345E76 /* AdamantApi+Chats.swift in Sources */, + 648CE3A42299A94D0070A2CC /* DashTransactionDetailsViewController.swift in Sources */, E90847362196FEA80095825D /* Chatroom+CoreDataClass.swift in Sources */, E96D64C52295C3ED00CA5587 /* Crypto.swift in Sources */, E91947B020002393001362F8 /* AdamantApiService.swift in Sources */, @@ -2351,6 +2405,7 @@ E908472B2196FEA80095825D /* RichMessageTransaction+CoreDataProperties.swift in Sources */, E96D64BC2295BED700CA5587 /* Transaction.swift in Sources */, E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */, + 648CE3AC229AD2190070A2CC /* DashTransferViewController.swift in Sources */, 64F085D920E2D7600006DE68 /* AdmTransactionsViewController.swift in Sources */, 648DD7AA2239150E00B811FD /* DogeWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, E96D64CB2295C51900CA5587 /* Decimal+adamant.swift in Sources */, @@ -2377,6 +2432,7 @@ E96BBE3121F70F5E009AA738 /* ReadonlyTextView.swift in Sources */, E926E032213EC43B005E536B /* FullscreenAlertView.swift in Sources */, 644EC35B20EFB8E900F40C73 /* AdamantDelegateCell.swift in Sources */, + 6403F5DB2272389800D58779 /* (null) in Sources */, 6416B1A521AEE157006089AC /* LskWalletService+Transfers.swift in Sources */, 648DD7A62237DC4000B811FD /* DogeTransferViewController.swift in Sources */, E9960B3421F5154300C840A8 /* BaseAccount+CoreDataProperties.swift in Sources */, @@ -2388,6 +2444,7 @@ E9B1AA592122D59600080A2A /* WalletsRoutes.swift in Sources */, E971591A21681D6900A5F904 /* TransactionStatus.swift in Sources */, E96D64B62295BED700CA5587 /* NormalizedTransaction.swift in Sources */, + 648CE3A022999C890070A2CC /* BaseBtcTransaction.swift in Sources */, E9E7CDB52002BA6900DFC4DB /* SwinjectedRouter.swift in Sources */, E908472C2196FEA80095825D /* CoreDataAccount+CoreDataClass.swift in Sources */, E935C84D22AC06E500A8CA2F /* RichMessageTools.swift in Sources */, @@ -2403,6 +2460,7 @@ E9E7CDAF2002B8A100DFC4DB /* Router.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, + 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */, 649D6BEA21B9627B009E727B /* LskWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, E940088B2114F63000CD2D67 /* NSRegularExpression+adamant.swift in Sources */, E9484B7F2285C016008E10F0 /* PKGeneratorViewController.swift in Sources */, @@ -2433,6 +2491,7 @@ E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */, E9D1BE1A211DA25300E86B72 /* UIView+constraints.swift in Sources */, E9DFB71C21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift in Sources */, + 6403F5E022723F6400D58779 /* DashWalletRouter.swift in Sources */, E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, @@ -2440,6 +2499,7 @@ E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */, + 648CE3A222999CE70070A2CC /* BTCRawTransaction.swift in Sources */, 648DD79E2236A0B500B811FD /* DogeTransactionsViewController.swift in Sources */, E905D39B2048A9BD00DDB504 /* KeychainStore.swift in Sources */, E9AA8BFC212C169200F9249F /* EthWalletService+Transfers.swift in Sources */, @@ -2460,6 +2520,7 @@ E9CAE8D82018ACA700345E76 /* AdamantApi+Transfers.swift in Sources */, E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */, E9FEECA62143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift in Sources */, + 6403F5E622723FDA00D58779 /* DashWalletViewController.swift in Sources */, E91947AC20001A9A001362F8 /* ApiService.swift in Sources */, E96D64B72295BED700CA5587 /* DelegateVote.swift in Sources */, E993302221354BC300CD5200 /* EthWalletRoutes.swift in Sources */, @@ -2473,6 +2534,7 @@ E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */, + 648C697322916192006645F5 /* DashTransactionsViewController.swift in Sources */, E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */, 6416B19D21AD7B92006089AC /* LskWalletRoutes.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, @@ -2483,6 +2545,7 @@ E9B1AA572121ACC000080A2A /* AdmWalletViewController.swift in Sources */, E95F857A2007F0260070534A /* ServerResponse.swift in Sources */, E9240BF5215D686500187B09 /* AdmWalletService+RichMessageProvider.swift in Sources */, + 648C697122915CB8006645F5 /* BTCRPCServerResponce.swift in Sources */, E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */, 649D6BE821B95DB7009E727B /* LskWalletService+RichMessageProvider.swift in Sources */, E9E7CDBE2003AEFB00DFC4DB /* CellFactory.swift in Sources */, @@ -2495,6 +2558,7 @@ E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */, E9FEECA421413659007DD7C8 /* RichMessageProvider.swift in Sources */, 6406D74D21CE3AC100196713 /* KeyboardManager.swift in Sources */, + 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */, 6416B1A321AD7EA1006089AC /* LskTransactionDetailsViewController.swift in Sources */, E9A03FD620DBC8E2007653A1 /* AdamantApi+Peers.swift in Sources */, 644EC34F20EFA77A00F40C73 /* Delegate.swift in Sources */, @@ -2529,6 +2593,7 @@ E90847352196FEA80095825D /* MessageTransaction+CoreDataProperties.swift in Sources */, E9771DA722997F310099AAC7 /* ServerResponseWithTimestamp.swift in Sources */, E9A03FD420DBC824007653A1 /* NodeVersion.swift in Sources */, + 648CE3AA229AD1F90070A2CC /* DashWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, E90847342196FEA80095825D /* MessageTransaction+CoreDataClass.swift in Sources */, E9960B3521F5154300C840A8 /* DummyAccount+CoreDataClass.swift in Sources */, E94008892114F0F700CD2D67 /* AdmWalletService.swift in Sources */, @@ -2548,6 +2613,7 @@ 648DD7A22237D9A000B811FD /* DogeTransaction.swift in Sources */, E90847372196FEA80095825D /* Chatroom+CoreDataProperties.swift in Sources */, E905D39D204C13B900DDB504 /* SecuredStore.swift in Sources */, + 648CE3A8229AD1E20070A2CC /* DashWalletService+RichMessageProvider.swift in Sources */, E90055FB20ECE78A00D0CB2D /* SecurityViewController+notifications.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2596,6 +2662,7 @@ E957E14F229C3E530019732A /* StateType.swift in Sources */, E957E14B229C3E530019732A /* ChatType.swift in Sources */, E957E15A229C406F0019732A /* AdamantSecret.swift in Sources */, + 64AE84622300012400F38FBD /* DashProvider.swift in Sources */, E957E16C229C53980019732A /* LskProvider.swift in Sources */, E9D664D022A009AB00733F8A /* AdamantLocalized.swift in Sources */, E957E14D229C3E530019732A /* Node.swift in Sources */, @@ -2630,6 +2697,7 @@ E9771D9822996FEB0099AAC7 /* VotesAsset.swift in Sources */, E9771D9A229974EA0099AAC7 /* NativeAdamantCore.swift in Sources */, E9771D9022996FEB0099AAC7 /* Node.swift in Sources */, + 64AE84612300012400F38FBD /* DashProvider.swift in Sources */, E9EF7A8022A02DBC00F2E1C2 /* AdamantLocalized.swift in Sources */, E9771D9722996FEB0099AAC7 /* TransactionType.swift in Sources */, E957E128229B0CF10019732A /* NotificationContent.swift in Sources */, diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 544c7acd5..8e7f753b4 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -68,11 +68,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if !firstRun { UserDefaults.standard.set(true, forKey: StoreKey.application.firstRun) - /* For future updates if let securedStore = container.resolve(SecuredStore.self) { securedStore.purgeStore() } - */ } // MARK: 2. Init UI diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/Contents.json b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/Contents.json new file mode 100644 index 000000000..6b82225a8 --- /dev/null +++ b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "wallet_dash.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "wallet_dash@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "wallet_dash@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash.png b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash.png new file mode 100644 index 000000000..1f01cb920 Binary files /dev/null and b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash.png differ diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash@2x.png b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash@2x.png new file mode 100644 index 000000000..339a16b3d Binary files /dev/null and b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash@2x.png differ diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash@3x.png b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash@3x.png new file mode 100644 index 000000000..cf5c21ab2 Binary files /dev/null and b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash.imageset/wallet_dash@3x.png differ diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/Contents.json b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/Contents.json new file mode 100644 index 000000000..a548d583f --- /dev/null +++ b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "wallet_dash_row.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "wallet_dash_row@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "wallet_dash_row@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row.png b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row.png new file mode 100644 index 000000000..84ce6fadb Binary files /dev/null and b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row.png differ diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row@2x.png b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row@2x.png new file mode 100644 index 000000000..961f3bbda Binary files /dev/null and b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row@2x.png differ diff --git a/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row@3x.png b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row@3x.png new file mode 100644 index 000000000..49991807b Binary files /dev/null and b/Adamant/Assets/Assets.xcassets/Wallets/wallet_dash_row.imageset/wallet_dash_row@3x.png differ diff --git a/Adamant/Assets/l18n/de.lproj/Localizable.strings b/Adamant/Assets/l18n/de.lproj/Localizable.strings index 231d9de78..13c1809c6 100755 --- a/Adamant/Assets/l18n/de.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/de.lproj/Localizable.strings @@ -136,6 +136,9 @@ /* Account tab: 'Send DOGE tokens' button */ "AccountTab.Row.SendDoge" = "DOGE senden"; +/* Account tab: 'Send DASH tokens' button */ +"AccountTab.Row.SendDash" = "DASH senden"; + /* Account tab: Anonymously buy ADM tokens */ "AccountTab.Row.AnonymouslyBuyADM" = "Kaufen Sie ADM anonym"; @@ -181,6 +184,9 @@ /* Account tab: Doge wallet */ "AccountTab.Wallets.doge_wallet" = "Doge Wallet"; +/* Account tab: Dash wallet */ +"AccountTab.Wallets.dash_wallet" = "Dash Wallet"; + /* Account page: scene title */ "AccountTab.Title" = "Konto"; @@ -724,6 +730,9 @@ /* Transfer: Amount is zero, or even negative notification */ "TransferScene.Error.TooLittleMoney" = "Sie müssen mehr Tokens senden"; +/* Transfer: Minimal transaction amount is 0.00001 */ +"TransferScene.Error.MinAmount" = "Der Mindesttransaktionsbetrag beträgt 0,00001"; + /* Transfer: Alert title: Account not found or not initiated. Alert user that he still can send money, but need to double ckeck address */ "TransferScene.unsafeTransferAlert.title" = "Adresse %@ konnte nicht überprüft werden"; @@ -801,6 +810,13 @@ /* Wallet Services: Shared error, transaction not found */ "WalletServices.SharedErrors.TransactionNotFound" = "Transaktion nicht gefunden"; + +/* Wallet Services: Wait until other transactions approved */ +"WalletServices.SharedErrors.walletFrezzed" = "Warten Sie, bis andere Transaktionen genehmigt wurden"; + +/* Wallet Services: Transaction unavailable */ +"WalletServices.SharedErrors.transactionUnavailable" = "Transaktion nicht verfügbar"; + /* Welcome: Skip button */ "WelcomeScene.Skip" = "überspringen"; diff --git a/Adamant/Assets/l18n/en.lproj/Localizable.strings b/Adamant/Assets/l18n/en.lproj/Localizable.strings index 7565382aa..0a65eb135 100755 --- a/Adamant/Assets/l18n/en.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/en.lproj/Localizable.strings @@ -136,6 +136,9 @@ /* Account tab: 'Send DOGE tokens' button */ "AccountTab.Row.SendDoge" = "Send DOGE"; +/* Account tab: 'Send DASH tokens' button */ +"AccountTab.Row.SendDash" = "Send DASH"; + /* Account tab: Anonymously buy ADM tokens */ "AccountTab.Row.AnonymouslyBuyADM" = "Buy ADM anonymously"; @@ -178,6 +181,9 @@ /* Account tab: Doge wallet */ "AccountTab.Wallets.doge_wallet" = "Doge Wallet"; +/* Account tab: Dash wallet */ +"AccountTab.Wallets.dash_wallet" = "Dash Wallet"; + /* Account page: scene title */ "AccountTab.Title" = "Account"; @@ -760,6 +766,9 @@ /* TransfersProvider: Transaction not found error. %@ for transaction's ID */ "TransfersProvider.Error.TransactionNotFoundFormat" = "Transaction with ID %@ not found"; +/* Transfer: Minimal transaction amount is 0.00001 */ +"TransferScene.Error.MinAmount" = "Minimum transaction amount is 0.00001"; + /* Transfer: transfer amount placeholder */ "TransferScene.Amount.Placeholder" = "to send"; @@ -853,6 +862,12 @@ /* Wallet Services: Shared error, transaction not found */ "WalletServices.SharedErrors.TransactionNotFound" = "Transaction not found"; +/* Wallet Services: Wait until other transactions approved */ +"WalletServices.SharedErrors.walletFrezzed" = "Wait until other transactions will be approved"; + +/* Wallet Services: Transaction unavailable */ +"WalletServices.SharedErrors.transactionUnavailable" = "Transaction unavailable"; + /* Welcome: Skip button */ "WelcomeScene.Skip" = "skip"; diff --git a/Adamant/Assets/l18n/ru.lproj/Localizable.strings b/Adamant/Assets/l18n/ru.lproj/Localizable.strings index 09f4b1695..739932062 100644 --- a/Adamant/Assets/l18n/ru.lproj/Localizable.strings +++ b/Adamant/Assets/l18n/ru.lproj/Localizable.strings @@ -133,6 +133,9 @@ /* Account tab: 'Send DOGE tokens' button */ "AccountTab.Row.SendDoge" = "Отправить DOGE"; +/* Account tab: 'Send DASH tokens' button */ +"AccountTab.Row.SendDash" = "Отправить DASH"; + /* Account tab: Anonymously buy ADM tokens */ "AccountTab.Row.AnonymouslyBuyADM" = "Купить ADM анонимно"; @@ -178,6 +181,9 @@ /* Account tab: Doge wallet */ "AccountTab.Wallets.doge_wallet" = "Кошелек Doge"; +/* Account tab: Dash wallet */ +"AccountTab.Wallets.dash_wallet" = "Кошелек Dash"; + /* Account tab: Delegates section title */ "AccountTab.Section.Delegates" = "Делегаты"; @@ -775,6 +781,9 @@ /* Transfer: Amount is zero, or even negative notification */ "TransferScene.Error.TooLittleMoney" = "Вам следует отправить больше токенов"; +/* Transfer: Minimal transaction amount is 0.00001 */ +"TransferScene.Error.MinAmount" = "Минимальная сумма перевода 0.00001"; + /* Transfer: Alert title: Account not found or not initiated. Alert user that he still can send money, but need to double ckeck address */ "TransferScene.unsafeTransferAlert.title" = "Не удалось проверить адрес %@"; @@ -853,6 +862,12 @@ /* Wallet Services: Shared error, transaction not found */ "WalletServices.SharedErrors.TransactionNotFound" = "Транзакция не найдена"; +/* Wallet Services: Wait until other transactions approved */ +"WalletServices.SharedErrors.walletFrezzed" = "Подождите, пока не будут одобрены другие транзакции"; + +/* Wallet Services: Transaction unavailable */ +"WalletServices.SharedErrors.transactionUnavailable" = "Транзакция недоступна"; + /* Welcome: Skip button */ "WelcomeScene.Skip" = "пропустить"; diff --git a/Adamant/Helpers/String+localized.swift b/Adamant/Helpers/String+localized.swift index 8f795a5c8..52fc8ee2c 100644 --- a/Adamant/Helpers/String+localized.swift +++ b/Adamant/Helpers/String+localized.swift @@ -55,6 +55,10 @@ extension String.adamantLocalized { static let notEnoughMoney = NSLocalizedString("WalletServices.SharedErrors.notEnoughMoney", comment: "Wallet Services: Shared error, user do not have enought money.") + static let transactionUnavailable = NSLocalizedString("WalletServices.SharedErrors.transactionUnavailable", comment: "Wallet Services: Transaction unavailable") + + static let walletFrezzed = NSLocalizedString("WalletServices.SharedErrors.walletFrezzed", comment: "Wallet Services: Wait until other transactions approved") + static func internalError(message: String) -> String { return String.localizedStringWithFormat(NSLocalizedString("Error.InternalErrorFormat", comment: "Shared error: Internal error format, %@ for message"), message) } diff --git a/Adamant/Info.plist b/Adamant/Info.plist index 0f328969d..23609e363 100644 --- a/Adamant/Info.plist +++ b/Adamant/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.7.2 + 1.8.0 CFBundleVersion - 87 + 92 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/Adamant/Models/BTCRawTransaction.swift b/Adamant/Models/BTCRawTransaction.swift new file mode 100644 index 000000000..68932858d --- /dev/null +++ b/Adamant/Models/BTCRawTransaction.swift @@ -0,0 +1,267 @@ +// +// BTCRawTransaction.swift +// Adamant +// +// Created by Anton Boyarkin on 25/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation + +// MARK: - Raw BTC Transaction, for easy parsing. Support BTC Style transaction like BTC, Doge and Dash +struct BTCRawTransaction { + let txId: String + let date: Date? + + let valueIn: Decimal + let valueOut: Decimal + let fee: Decimal + + let confirmations: Int? + let blockHash: String? + + let inputs: [BTCInput] + let outputs: [BTCOutput] + + func asBtcTransaction(_ as:T.Type, for address: String, blockId: String? = nil) -> T { + // MARK: Known values + let confirmationsValue: String? + let transactionStatus: TransactionStatus + + if let confirmations = confirmations { + confirmationsValue = String(confirmations) + transactionStatus = confirmations > 0 ? .success : .pending + } else { + confirmationsValue = nil + transactionStatus = .pending + } + + // Transfers + var myInputs = inputs.filter { $0.sender == address } + var myOutputs = outputs.filter { $0.addresses.contains(address) } + + var totalInputsValue = myInputs.map { $0.value }.reduce(0, +) - fee + var totalOutputsValue = myOutputs.map { $0.value }.reduce(0, +) + + if totalInputsValue == totalOutputsValue { + totalInputsValue = 0 + totalOutputsValue = 0 + } + + if totalInputsValue > totalOutputsValue { + while let out = myOutputs.first { + totalInputsValue -= out.value + totalOutputsValue -= out.value + + myOutputs.removeFirst() + } + } + + if totalInputsValue < totalOutputsValue { + while let i = myInputs.first { + totalInputsValue -= i.value + totalOutputsValue -= i.value + + myInputs.removeFirst() + } + } + + let senders = Set(inputs.map { $0.sender } ) + let recipients = Set(outputs.compactMap { $0.addresses.first } ) + + let sender: String + let recipient: String + + if senders.count == 1 { + sender = senders.first! + } else { + let filtered = senders.filter { $0 != address } + + if filtered.count == 1 { + sender = filtered.first! + } else { + sender = String.adamantLocalized.dogeTransaction.senders(senders.count) + } + } + + if recipients.count == 1 { + recipient = recipients.first! + } else { + let filtered = recipients.filter { $0 != address } + + if filtered.count == 1 { + recipient = filtered.first! + } else { + recipient = String.adamantLocalized.dogeTransaction.recipients(recipients.count) + } + } + + + // MARK: Inputs + if myInputs.count > 0 { + let inputTransaction = T(txId: txId, + dateValue: date, + blockValue: blockId, + senderAddress: address, + recipientAddress: recipient, + amountValue: totalInputsValue, + feeValue: fee, + confirmationsValue: confirmationsValue, + isOutgoing: true, + transactionStatus: transactionStatus) + + return inputTransaction + } + + // MARK: Outputs + let outputTransaction = T(txId: txId, + dateValue: date, + blockValue: blockId, + senderAddress: sender, + recipientAddress: address, + amountValue: totalOutputsValue, + feeValue: fee, + confirmationsValue: confirmationsValue, + isOutgoing: false, + transactionStatus: transactionStatus) + + return outputTransaction + } +} + +extension BTCRawTransaction: Decodable { + enum CodingKeys: String, CodingKey { + case txId = "txid" + case date = "time" + case valueIn + case valueOut + case fee = "fees" + case confirmations + case blockHash = "blockhash" + case inputs = "vin" + case outputs = "vout" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // MARK: Required + self.txId = try container.decode(String.self, forKey: .txId) + + // MARK: Optionals for new transactions + if let timeInterval = try? container.decode(TimeInterval.self, forKey: .date) { + self.date = Date(timeIntervalSince1970: timeInterval) + } else { + self.date = nil + } + + self.confirmations = try? container.decode(Int.self, forKey: .confirmations) + self.blockHash = try? container.decode(String.self, forKey: .blockHash) + + // MARK: Inputs & Outputs + let inputs = try container.decode([BTCInput].self, forKey: .inputs) + self.inputs = inputs.filter { !$0.sender.isEmpty } // Filter incomplete transactions without sender + self.outputs = try container.decode([BTCOutput].self, forKey: .outputs) + + // Total In & Out. Can be null sometimes... + if let raw = try? container.decode(Decimal.self, forKey: .valueIn) { + self.valueIn = raw + } else { + self.valueIn = self.inputs.map { $0.value }.reduce(0, +) + } + + if let raw = try? container.decode(Decimal.self, forKey: .valueOut) { + self.valueOut = raw + } else { + self.valueOut = self.outputs.map { $0.value }.reduce(0, +) + } + + if let raw = try? container.decode(Decimal.self, forKey: .fee) { + self.fee = raw + } else { + self.fee = self.valueIn - self.valueOut + } + } +} + +// MARK: BTC internal +struct BTCInput: Decodable { + enum CodingKeys: String, CodingKey { + case sender = "addr" + case senderDash = "address" + case value = "valueSat" + case txId = "txid" + case vOut = "vout" + } + + let sender: String + let value: Decimal + let txId: String + let vOut: Int + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // Incomplete inputs doesn't contains address. We will filter them out + if let raw = try? container.decode(String.self, forKey: .sender) { + self.sender = raw + } else if let raw = try? container.decode(String.self, forKey: .senderDash) { + // Completable with DASH + self.sender = raw + } else { + self.sender = "" + } + + self.txId = try container.decode(String.self, forKey: .txId) + self.vOut = try container.decode(Int.self, forKey: .vOut) + + if let raw = try? container.decode(Decimal.self, forKey: .value) { + self.value = Decimal(sign: .plus, exponent: DogeWalletService.currencyExponent, significand: raw) + } else { + self.value = 0 + } + } +} + +struct BTCOutput: Decodable { + enum CodingKeys: String, CodingKey { + case signature = "scriptPubKey" + case value + case valueSat + case spentTxId + case spentIndex + } + + enum SignatureCodingKeys: String, CodingKey { + case addresses + } + + let addresses: [String] + var value: Decimal + let spentTxId: String? + let spentIndex: Int? + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let signatureContainer = try container.nestedContainer(keyedBy: SignatureCodingKeys.self, forKey: .signature) + self.addresses = try signatureContainer.decode([String].self, forKey: .addresses) + + if let raw = try? container.decode(String.self, forKey: .value), let value = Decimal(string: raw) { + self.value = value + } else if let raw = try? container.decode(Decimal.self, forKey: .value) { + self.value = raw + } else { + self.value = 0 + } + + if let raw = try? container.decode(String.self, forKey: .valueSat), let value = Decimal(string: raw) { + self.value = Decimal(sign: .plus, exponent: DogeWalletService.currencyExponent, significand: value) + } else if let raw = try? container.decode(Decimal.self, forKey: .valueSat) { + self.value = Decimal(sign: .plus, exponent: DogeWalletService.currencyExponent, significand: raw) + } + + self.spentTxId = try? container.decode(String.self, forKey: .spentTxId) + self.spentIndex = try? container.decode(Int.self, forKey: .spentIndex) + } +} diff --git a/Adamant/Models/BaseBtcTransaction.swift b/Adamant/Models/BaseBtcTransaction.swift new file mode 100644 index 000000000..873888a0f --- /dev/null +++ b/Adamant/Models/BaseBtcTransaction.swift @@ -0,0 +1,40 @@ +// +// BaseBtcTransaction.swift +// Adamant +// +// Created by Anton Boyarkin on 25/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation + +class BaseBtcTransaction: TransactionDetails { + class var defaultCurrencySymbol: String? { return "" } + + let txId: String + let dateValue: Date? + let blockValue: String? + + let senderAddress: String + let recipientAddress: String + + let amountValue: Decimal + let feeValue: Decimal? + let confirmationsValue: String? + + let isOutgoing: Bool + let transactionStatus: TransactionStatus? + + required init(txId: String, dateValue: Date?, blockValue: String?, senderAddress: String, recipientAddress: String, amountValue: Decimal, feeValue: Decimal?, confirmationsValue: String?, isOutgoing: Bool, transactionStatus: TransactionStatus?) { + self.txId = txId + self.dateValue = dateValue + self.blockValue = blockValue + self.senderAddress = senderAddress + self.recipientAddress = recipientAddress + self.amountValue = amountValue + self.feeValue = feeValue + self.confirmationsValue = confirmationsValue + self.isOutgoing = isOutgoing + self.transactionStatus = transactionStatus + } +} diff --git a/Adamant/Models/DashTransaction.swift b/Adamant/Models/DashTransaction.swift new file mode 100644 index 000000000..38d613954 --- /dev/null +++ b/Adamant/Models/DashTransaction.swift @@ -0,0 +1,71 @@ +// +// DashRawTransaction.swift +// Adamant +// +// Created by Anton Boyarkin on 19/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import BitcoinKit + +class DashTransaction: BaseBtcTransaction { + override class var defaultCurrencySymbol: String? { return DashWalletService.currencySymbol } +} + +struct BtcBlock: Decodable { + let hash: String + let height: Int64 + let time: Int64 + + enum CodingKeys: String, CodingKey { + case hash + case height + case time + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.hash = try container.decode(String.self, forKey: .hash) + self.height = try container.decode(Int64.self, forKey: .height) + self.time = try container.decode(Int64.self, forKey: .time) + } +} + +struct DashUnspentTransaction: Decodable { + let address: String + let txid: String + let outputIndex: UInt32 + let script: String + let amount: UInt64 + let height: UInt64 + + enum CodingKeys: String, CodingKey { + case address + case txid + case outputIndex + case script + case amount = "satoshis" + case height + } + + func asUnspentTransaction(with publicKeyHash: Data) -> UnspentTransaction { + let lockScript = Script.buildPublicKeyHashOut(pubKeyHash: publicKeyHash) + let txHash = Data(hex: txid).map { Data($0.reversed()) } ?? Data() + + let unspentOutput = TransactionOutput(value: amount, lockingScript: lockScript) + let unspentOutpoint = TransactionOutPoint(hash: txHash, index: outputIndex) + let utxo = UnspentTransaction(output: unspentOutput, outpoint: unspentOutpoint) + return utxo + } +} + +//{ +// "address": "Xp6kFbogHMD4QRBDLQdqRp5zUgzmfj1KPn", +// "txid": "4270bdbdcf89c0a39fd3e81f8b8bd991507d66c643703a007f8f6b466504de83", +// "outputIndex": 0, +// "script": "76a914931ef5cbdad28723ba9596de5da1145ae969a71888ac", +// "satoshis": 3000000, +// "height": 1009632 +//} diff --git a/Adamant/Models/DogeTransaction.swift b/Adamant/Models/DogeTransaction.swift index cabe2a89f..0d8f73bfc 100644 --- a/Adamant/Models/DogeTransaction.swift +++ b/Adamant/Models/DogeTransaction.swift @@ -22,269 +22,8 @@ extension String.adamantLocalized { } } -struct DogeTransaction: TransactionDetails { - static var defaultCurrencySymbol: String? { return DogeWalletService.currencySymbol } - - let txId: String - let dateValue: Date? - let blockValue: String? - - let senderAddress: String - let recipientAddress: String - - let amountValue: Decimal - let feeValue: Decimal? - let confirmationsValue: String? - - let isOutgoing: Bool - let transactionStatus: TransactionStatus? -} - - -// MARK: - Raw Doge Transaction, for easy parsing -struct DogeRawTransaction { - let txId: String - let date: Date? - - let valueIn: Decimal - let valueOut: Decimal - let fee: Decimal - - let confirmations: Int? - let blockHash: String? - - let inputs: [DogeInput] - let outputs: [DogeOutput] - - func asDogeTransaction(for address: String, blockId: String? = nil) -> DogeTransaction { - // MARK: Known values - let confirmationsValue: String? - let transactionStatus: TransactionStatus - - if let confirmations = confirmations { - confirmationsValue = String(confirmations) - transactionStatus = confirmations > 0 ? .success : .pending - } else { - confirmationsValue = nil - transactionStatus = .pending - } - - // Transfers - var myInputs = inputs.filter { $0.sender == address } - var myOutputs = outputs.filter { $0.addresses.contains(address) } - - var totalInputsValue = myInputs.map { $0.value }.reduce(0, +) - fee - var totalOutputsValue = myOutputs.map { $0.value }.reduce(0, +) - - if totalInputsValue == totalOutputsValue { - totalInputsValue = 0 - totalOutputsValue = 0 - } - - if totalInputsValue > totalOutputsValue { - while let out = myOutputs.first { - totalInputsValue -= out.value - totalOutputsValue -= out.value - - myOutputs.removeFirst() - } - } - - if totalInputsValue < totalOutputsValue { - while let i = myInputs.first { - totalInputsValue -= i.value - totalOutputsValue -= i.value - - myInputs.removeFirst() - } - } - - let senders = Set(inputs.map { $0.sender } ) - let recipients = Set(outputs.compactMap { $0.addresses.first } ) - - let sender: String - let recipient: String - - if senders.count == 1 { - sender = senders.first! - } else { - let filtered = senders.filter { $0 != address } - - if filtered.count == 1 { - sender = filtered.first! - } else { - sender = String.adamantLocalized.dogeTransaction.senders(senders.count) - } - } - - if recipients.count == 1 { - recipient = recipients.first! - } else { - let filtered = recipients.filter { $0 != address } - - if filtered.count == 1 { - recipient = filtered.first! - } else { - recipient = String.adamantLocalized.dogeTransaction.recipients(recipients.count) - } - } - - - // MARK: Inputs - if myInputs.count > 0 { - let inputTransaction = DogeTransaction(txId: txId, - dateValue: date, - blockValue: blockId, - senderAddress: address, - recipientAddress: recipient, - amountValue: totalInputsValue, - feeValue: fee, - confirmationsValue: confirmationsValue, - isOutgoing: true, - transactionStatus: transactionStatus) - - return inputTransaction - } - - // MARK: Outputs - let outputTransaction = DogeTransaction(txId: txId, - dateValue: date, - blockValue: blockId, - senderAddress: sender, - recipientAddress: address, - amountValue: totalOutputsValue, - feeValue: fee, - confirmationsValue: confirmationsValue, - isOutgoing: false, - transactionStatus: transactionStatus) - - return outputTransaction - } -} - -extension DogeRawTransaction: Decodable { - enum CodingKeys: String, CodingKey { - case txId = "txid" - case date = "time" - case valueIn - case valueOut - case fee = "fees" - case confirmations - case blockHash = "blockhash" - case inputs = "vin" - case outputs = "vout" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - // MARK: Required - self.txId = try container.decode(String.self, forKey: .txId) - - // MARK: Optionals for new transactions - if let timeInterval = try? container.decode(TimeInterval.self, forKey: .date) { - self.date = Date(timeIntervalSince1970: timeInterval) - } else { - self.date = nil - } - - self.confirmations = try? container.decode(Int.self, forKey: .confirmations) - self.blockHash = try? container.decode(String.self, forKey: .blockHash) - - // MARK: Inputs & Outputs - let inputs = try container.decode([DogeInput].self, forKey: .inputs) - self.inputs = inputs.filter { !$0.sender.isEmpty } // Filter incomplete transactions without sender - self.outputs = try container.decode([DogeOutput].self, forKey: .outputs) - - // Total In & Out. Can be null sometimes... - if let raw = try? container.decode(Decimal.self, forKey: .valueIn) { - self.valueIn = raw - } else { - self.valueIn = self.inputs.map { $0.value }.reduce(0, +) - } - - if let raw = try? container.decode(Decimal.self, forKey: .valueOut) { - self.valueOut = raw - } else { - self.valueOut = self.outputs.map { $0.value }.reduce(0, +) - } - - if let raw = try? container.decode(Decimal.self, forKey: .fee) { - self.fee = raw - } else { - self.fee = self.valueIn - self.valueOut - } - } -} - - -// MARK: Doge internal -struct DogeInput: Decodable { - enum CodingKeys: String, CodingKey { - case sender = "addr" - case value = "valueSat" - case txId = "txid" - case vOut = "vout" - } - - let sender: String - let value: Decimal - let txId: String - let vOut: Int - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - // Incomplete inputs doesn't contains address. We will filter them out - if let raw = try? container.decode(String.self, forKey: .sender) { - self.sender = raw - } else { - self.sender = "" - } - - self.txId = try container.decode(String.self, forKey: .txId) - self.vOut = try container.decode(Int.self, forKey: .vOut) - - if let raw = try? container.decode(Decimal.self, forKey: .value) { - self.value = Decimal(sign: .plus, exponent: DogeWalletService.currencyExponent, significand: raw) - } else { - self.value = 0 - } - } -} - -struct DogeOutput: Decodable { - enum CodingKeys: String, CodingKey { - case signature = "scriptPubKey" - case value - case spentTxId - case spentIndex - } - - enum SignatureCodingKeys: String, CodingKey { - case addresses - } - - let addresses: [String] - let value: Decimal - let spentTxId: String? - let spentIndex: Int? - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let signatureContainer = try container.nestedContainer(keyedBy: SignatureCodingKeys.self, forKey: .signature) - self.addresses = try signatureContainer.decode([String].self, forKey: .addresses) - - if let raw = try? container.decode(String.self, forKey: .value), let value = Decimal(string: raw) { - self.value = value - } else { - self.value = 0 - } - - self.spentTxId = try? container.decode(String.self, forKey: .spentTxId) - self.spentIndex = try? container.decode(Int.self, forKey: .spentIndex) - } +class DogeTransaction: BaseBtcTransaction { + override class var defaultCurrencySymbol: String? { return DogeWalletService.currencySymbol } } // MARK: - Sample Json diff --git a/Adamant/ServerResponses/BTCRPCServerResponce.swift b/Adamant/ServerResponses/BTCRPCServerResponce.swift new file mode 100644 index 000000000..c4d3d2d6f --- /dev/null +++ b/Adamant/ServerResponses/BTCRPCServerResponce.swift @@ -0,0 +1,20 @@ +// +// BTCRPCServerResponce.swift +// Adamant +// +// Created by Anton Boyarkin on 19/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation + +class BTCRPCServerResponce: Decodable { + let result: T? + let error: BTCRPCError? + let id: String? +} + +class BTCRPCError: Decodable { + let code: Int + let message: String +} diff --git a/Adamant/ServerResponses/DogeGetTransactionsResponse.swift b/Adamant/ServerResponses/DogeGetTransactionsResponse.swift index e3eae9c94..6ed3a6109 100644 --- a/Adamant/ServerResponses/DogeGetTransactionsResponse.swift +++ b/Adamant/ServerResponses/DogeGetTransactionsResponse.swift @@ -13,7 +13,7 @@ class DogeGetTransactionsResponse: Decodable { let from: Int let to: Int - let items: [DogeRawTransaction] + let items: [BTCRawTransaction] } /* Json diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 9e8981f5d..e0cccf0a4 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -108,7 +108,8 @@ class AdamantAccountService: AccountService { AdmWalletService(), EthWalletService(), LskWalletService(mainnet: true, origins: AdamantResources.lskServers), - DogeWalletService() + DogeWalletService(), + DashWalletService() // Testnet // LskWalletService(mainnet: false) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 8e9d55ffb..0f84b5702 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -795,7 +795,8 @@ extension AdamantChatsProvider { for (account, transactions) in partners { // We can't save whole context while we are mass creating MessageTransactions. - let privateChatroom = privateContext.object(with: account.chatroom!.objectID) as! Chatroom + guard let chatroom = account.chatroom else { continue } + let privateChatroom = privateContext.object(with: chatroom.objectID) as! Chatroom // MARK: Transactions var messages = Set() diff --git a/Adamant/Utilities/ByteBackpacker.swift b/Adamant/Utilities/ByteBackpacker.swift new file mode 100644 index 000000000..d17e2ff77 --- /dev/null +++ b/Adamant/Utilities/ByteBackpacker.swift @@ -0,0 +1,86 @@ +/* +This file is part of ByteBackpacker Project. It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at https://github.com/michaeldorner/ByteBackpacker/blob/master/LICENSE. No part of ByteBackpacker Project, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the LICENSE file. +*/ + +import Foundation + + +public typealias Byte = UInt8 + + +/// ByteOrder +/// +/// Byte order can be either big or little endian. +public enum ByteOrder { + case bigEndian + case littleEndian + + /// Machine specific byte order + public static let nativeByteOrder: ByteOrder = (Int(CFByteOrderGetCurrent()) == Int(CFByteOrderLittleEndian.rawValue)) ? .littleEndian : .bigEndian +} + + +open class ByteBackpacker { + + private static let referenceTypeErrorString = "TypeError: Reference Types are not supported." + + /// Unpack a byte array into type `T` + /// + /// - Parameters: + /// - valueByteArray: Byte array to unpack + /// - byteOrder: Byte order (wither little or big endian) + /// - Returns: Value type of type `T` + open class func unpack(_ valueByteArray: [Byte], byteOrder: ByteOrder = .nativeByteOrder) -> T { + return ByteBackpacker.unpack(valueByteArray, toType: T.self, byteOrder: byteOrder) + } + + + /// Unpack a byte array into type `T` for type inference + /// + /// - Parameters: + /// - valueByteArray: Byte array to unpack + /// - type: Origin type + /// - byteOrder: Byte order (wither little or big endian) + /// - Returns: Value type of type `T` + open class func unpack(_ valueByteArray: [Byte], toType type: T.Type, byteOrder: ByteOrder = .nativeByteOrder) -> T { + assert(!(T.self is AnyClass), ByteBackpacker.referenceTypeErrorString) + let bytes = (byteOrder == ByteOrder.nativeByteOrder) ? valueByteArray : valueByteArray.reversed() + return bytes.withUnsafeBufferPointer { + return $0.baseAddress!.withMemoryRebound(to: T.self, capacity: 1) { + $0.pointee + } + } + } + + + /// Pack method convinience method + /// + /// - Parameters: + /// - value: value to pack of type `T` + /// - byteOrder: Byte order (wither little or big endian) + /// - Returns: Byte array + open class func pack( _ value: T, byteOrder: ByteOrder = .nativeByteOrder) -> [Byte] { + assert(!(T.self is AnyClass), ByteBackpacker.referenceTypeErrorString) + var value = value // inout works only for var not let types + let valueByteArray = withUnsafePointer(to: &value) { + Array(UnsafeBufferPointer(start: $0.withMemoryRebound(to: Byte.self, capacity: 1){$0}, count: MemoryLayout.size)) + } + return (byteOrder == ByteOrder.nativeByteOrder) ? valueByteArray : valueByteArray.reversed() + } +} + + +public extension Data { + + /// Extension for exporting Data (NSData) to byte array directly + /// + /// - Returns: Byte array + func toByteArray() -> [Byte] { + let count = self.count / MemoryLayout.size + var array = [Byte](repeating: 0, count: count) + copyBytes(to: &array, count:count * MemoryLayout.size) + return array + } +} + + diff --git a/Adamant/Wallets/Dash/DashMainnet.swift b/Adamant/Wallets/Dash/DashMainnet.swift new file mode 100644 index 000000000..e05a1dc0f --- /dev/null +++ b/Adamant/Wallets/Dash/DashMainnet.swift @@ -0,0 +1,58 @@ +// +// DashMainnet.swift +// Adamant +// +// Created by Anton Boyarkin on 25/04/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import BitcoinKit + +class DashMainnet: Network { + override var name: String { + return "livenet" + } + + override var alias: String { + return "mainnet" + } + + override var scheme: String { + return "dash" + } + + override var magic: UInt32 { + return 0xbd6b0cbf + } + + override var pubkeyhash: UInt8 { + return 0x4c + } + + override var privatekey: UInt8 { + return 0xcc + } + + override var scripthash: UInt8 { + return 0x10 + } + + override var xpubkey: UInt32 { + return 0x0488b21e + } + + override var xprivkey: UInt32 { + return 0x0488ade4 + } + + override var port: UInt32 { + return 9999 + } + + override var dnsSeeds: [String] { + return [ + "dashnode1.adamant.im" + ] + } +} diff --git a/Adamant/Wallets/Dash/DashTransactionDetailsViewController.swift b/Adamant/Wallets/Dash/DashTransactionDetailsViewController.swift new file mode 100644 index 000000000..4ef75bab0 --- /dev/null +++ b/Adamant/Wallets/Dash/DashTransactionDetailsViewController.swift @@ -0,0 +1,137 @@ +// +// DashTransactionDetailsViewController.swift +// Adamant +// +// Created by Anton Boyarkin on 25/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import UIKit +import Eureka + +class DashTransactionDetailsViewController: TransactionDetailsViewControllerBase { + // MARK: - Dependencies + + weak var service: DashWalletService? + + // MARK: - Properties + + private var cachedBlockInfo: (hash: String, height: String)? = nil + + private let autoupdateInterval: TimeInterval = 5.0 + weak var timer: Timer? + + private lazy var refreshControl: UIRefreshControl = { + let control = UIRefreshControl() + control.addTarget(self, action: #selector(refresh), for: UIControl.Event.valueChanged) + return control + }() + + // MARK: - Lifecycle + + override func viewDidLoad() { + currencySymbol = DashWalletService.currencySymbol + + super.viewDidLoad() + if service != nil { tableView.refreshControl = refreshControl } + + updateTransaction() + + // MARK: Start update + if transaction != nil { + startUpdate() + } + } + + deinit { + stopUpdate() + } + + // MARK: - Overrides + + override func explorerUrl(for transaction: TransactionDetails) -> URL? { + let id = transaction.txId + + return URL(string: "\(AdamantResources.dashExplorerAddress)\(id)") + } + + @objc func refresh() { + updateTransaction { [weak self] error in + DispatchQueue.main.async { + self?.refreshControl.endRefreshing() + + if let error = error { + self?.dialogService.showRichError(error: error) + } + } + } + } + + // MARK: Autoupdate + + func startUpdate() { + timer?.invalidate() + timer = Timer.scheduledTimer(withTimeInterval: autoupdateInterval, repeats: true) { [weak self] _ in + self?.updateTransaction() // Silent, without errors + } + } + + func stopUpdate() { + timer?.invalidate() + } + + // MARK: Updating methods + + func updateTransaction(completion: ((RichError?) -> Void)? = nil) { + guard let service = service, let address = service.wallet?.address, let id = transaction?.txId else { + completion?(nil) + return + } + + service.getTransaction(by: id) { [weak self] result in + switch result { + case .success(let trs): + if let blockInfo = self?.cachedBlockInfo, blockInfo.hash == trs.blockHash { + self?.transaction = trs.asBtcTransaction(DashTransaction.self, for: address, blockId: blockInfo.height) + + DispatchQueue.main.async { [weak self] in + self?.tableView.reloadData() + } + + completion?(nil) + } else if let blockHash = trs.blockHash { + service.getBlockId(by: blockHash) { result in + let blockInfo: (hash: String, height: String)? + switch result { + case .success(let height): + blockInfo = (hash: blockHash, height: height) + + case .failure: + blockInfo = nil + } + + self?.transaction = trs.asBtcTransaction(DashTransaction.self, for: address, blockId: blockInfo?.height) + self?.cachedBlockInfo = blockInfo + + DispatchQueue.main.async { [weak self] in + self?.tableView.reloadData() + } + + completion?(nil) + } + } else { + self?.transaction = trs.asBtcTransaction(DashTransaction.self, for: address) + + DispatchQueue.main.async { [weak self] in + self?.tableView.reloadData() + } + + completion?(nil) + } + + case .failure(let error): + completion?(error) + } + } + } +} diff --git a/Adamant/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Wallets/Dash/DashTransactionsViewController.swift new file mode 100644 index 000000000..d9c92ccef --- /dev/null +++ b/Adamant/Wallets/Dash/DashTransactionsViewController.swift @@ -0,0 +1,256 @@ +// +// DashTransactionsViewController.swift +// Adamant +// +// Created by Anton Boyarkin on 19/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import UIKit +import ProcedureKit + +class DashTransactionsViewController: TransactionsListViewControllerBase { + + // MARK: - Dependencies + var walletService: DashWalletService! + var dialogService: DialogService! + var router: Router! + + // MARK: - Properties + var transactions: [DashTransaction] = [] + + private let limit = 200 // Limit autoload, as some wallets can have thousands of transactions. + private(set) var loadedTo: Int = 0 + private let procedureQueue = ProcedureQueue() + + override func viewDidLoad() { + super.viewDidLoad() + currencySymbol = DashWalletService.currencySymbol + + refreshControl.beginRefreshing() + handleRefresh(refreshControl) + } + + deinit { + procedureQueue.cancelAllOperations() + } + + override func handleRefresh(_ refreshControl: UIRefreshControl) { + procedureQueue.cancelAllOperations() + + loadedTo = 0 + walletService.getTransactions(from: loadedTo) { [weak self] result in + guard let vc = self else { + refreshControl.endRefreshing() + return + } + + switch result { + case .success(let tuple): + vc.transactions = tuple.transactions + vc.loadedTo = tuple.transactions.count + + DispatchQueue.main.async { + vc.tableView.reloadData() + refreshControl.endRefreshing() + + // Update tableView, then call loadMore() + if tuple.hasMore { + vc.loadMoreTransactions(from: tuple.transactions.count) + } + } + + case .failure(let error): + vc.transactions.removeAll() + vc.dialogService.showRichError(error: error) + + DispatchQueue.main.async { + vc.tableView.reloadData() + refreshControl.endRefreshing() + } + } + } + } + + // MARK: - UITableView + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return transactions.count + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let controller = router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { + fatalError("Failed to getDashTransactionDetailsViewController") + } + + // Hold reference + guard let sender = walletService.wallet?.address else { + return + } + + controller.service = self.walletService + dialogService.showProgress(withMessage: nil, userInteractionEnable: false) + let txId = transactions[indexPath.row].txId + + walletService.getTransaction(by: txId) { [weak self] result in + guard let vc = self else { + return + } + + switch result { + case .success(let dashTransaction): + let transaction = dashTransaction.asBtcTransaction(DashTransaction.self, for: sender) + + // Sender name + if transaction.senderAddress == sender { + controller.senderName = String.adamantLocalized.transactionDetails.yourAddress + } + + if transaction.recipientAddress == sender { + controller.recipientName = String.adamantLocalized.transactionDetails.yourAddress + } + + // Block Id + guard let blockHash = dashTransaction.blockHash else { + controller.transaction = transaction + DispatchQueue.main.async { + vc.navigationController?.pushViewController(controller, animated: true) + vc.tableView.deselectRow(at: indexPath, animated: true) + vc.dialogService.dismissProgress() + } + break + } + + vc.walletService.getBlockId(by: blockHash) { result in + switch result { + case .success(let id): + controller.transaction = dashTransaction.asBtcTransaction(DashTransaction.self, for: sender, blockId: id) + + case .failure: + controller.transaction = transaction + } + + DispatchQueue.main.async { + vc.tableView.deselectRow(at: indexPath, animated: true) + vc.dialogService.dismissProgress() + vc.navigationController?.pushViewController(controller, animated: true) + } + } + + case .failure(let error): + DispatchQueue.main.async { + vc.tableView.deselectRow(at: indexPath, animated: true) + vc.dialogService.dismissProgress() + vc.dialogService.showRichError(error: error) + } + } + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { + // TODO: Display & Log error + return UITableViewCell(style: .default, reuseIdentifier: "cell") + } + + let transaction = transactions[indexPath.row] + + cell.accessoryType = .disclosureIndicator + + configureCell(cell, for: transaction) + return cell + } + + func configureCell(_ cell: TransactionTableViewCell, for transaction: BaseBtcTransaction) { + let outgoing = transaction.isOutgoing + let partnerId = outgoing ? transaction.recipientAddress : transaction.senderAddress + + let partnerName: String? + if let address = walletService.wallet?.address, partnerId == address { + partnerName = String.adamantLocalized.transactionDetails.yourAddress + } else { + partnerName = nil + } + + configureCell(cell, + isOutgoing: outgoing, + partnerId: partnerId, + partnerName: partnerName, + amount: transaction.amountValue, + date: transaction.dateValue) + } + + // MARK: - Load more + private func loadMoreTransactions(from: Int) { + let procedure = LoadMoreDashTransactionsProcedure(service: walletService, from: from) + + procedure.addDidFinishBlockObserver { [weak self] (procedure, error) in + guard let vc = self, let result = procedure.result else { + return + } + + let total = vc.loadedTo + result.transactions.count + + guard from < total else { return } + + var indexPaths = [IndexPath]() + for index in from.. Bool in + return t1.dateValue ?? Date() > t2.dateValue ?? Date() + }) + + for transaction in result.transactions { + if let index = vc.transactions.firstIndex(where: { (tr) -> Bool in + return tr.txId == transaction.txId + }) { + let indexPath = IndexPath(row: index, section: 0) + vc.tableView.insertRows(at: [indexPath], with: .fade) + } + } + + // Update everything, and then call loadMore() + if result.hasMore && total < vc.limit { + vc.loadMoreTransactions(from: total) + } + } + } + + procedureQueue.addOperation(procedure) + } +} + + +private class LoadMoreDashTransactionsProcedure: Procedure { + let from: Int + let service: DashWalletService + + private(set) var result: (transactions: [DashTransaction], hasMore: Bool)? = nil + + init(service: DashWalletService, from: Int) { + self.from = from + self.service = service + + super.init() + log.severity = .warning + } + + override func execute() { + service.getTransactions(from: from) { result in + switch result { + case .success(let result): + self.result = result + self.finish() + + case .failure(let error): + self.result = nil + self.finish(with: error) + } + } + } +} diff --git a/Adamant/Wallets/Dash/DashTransferViewController.swift b/Adamant/Wallets/Dash/DashTransferViewController.swift new file mode 100644 index 000000000..b0846c8ae --- /dev/null +++ b/Adamant/Wallets/Dash/DashTransferViewController.swift @@ -0,0 +1,251 @@ +// +// DashTransferViewController.swift +// Adamant +// +// Created by Anton Boyarkin on 26/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import UIKit +import Eureka + +extension String.adamantLocalized.transfer { + static let minAmountError = NSLocalizedString("TransferScene.Error.MinAmount", comment: "Transfer: Minimal transaction amount is 0.00001") +} + +class DashTransferViewController: TransferViewControllerBase { + + // MARK: Dependencies + + var chatsProvider: ChatsProvider! + + + // MARK: Properties + + private var skipValueChange: Bool = false + + static let invalidCharacters: CharacterSet = CharacterSet.decimalDigits.inverted + + + // MARK: Send + + override func sendFunds() { + let comments: String + if let row: TextAreaRow = form.rowBy(tag: BaseRows.comments.tag), let text = row.value { + comments = text + } else { + comments = "" + } + + guard let service = service as? DashWalletService, let recipient = recipientAddress, let amount = amount, let dialogService = dialogService else { + return + } + + guard amount >= 0.00001 else { + dialogService.showAlert(title: nil, message: String.adamantLocalized.transfer.minAmountError, style: AdamantAlertStyle.alert, actions: nil, from: nil) + return + } + + guard let sender = service.wallet?.address else { + return + } + + dialogService.showProgress(withMessage: String.adamantLocalized.transfer.transferProcessingMessage, userInteractionEnable: false) + + service.create(recipient: recipient, amount: amount) { [weak self] result in + guard let vc = self else { + dialogService.dismissProgress() + dialogService.showError(withMessage: String.adamantLocalized.sharedErrors.unknownError, error: nil) + return + } + + switch result { + case .success(let transaction): + // MARK: 1. Send adm report + if let reportRecipient = vc.admReportRecipient, let hash = transaction.txHash { + self?.reportTransferTo(admAddress: reportRecipient, amount: amount, comments: comments, hash: hash) + } + + // MARK: 2. Send transaction + service.sendTransaction(transaction) { result in + switch result { + case .success(let hash): + service.update() + + service.getTransaction(by: hash) { result in + switch result { + case .success(let dashRawTransaction): + vc.dialogService.showSuccess(withMessage: String.adamantLocalized.transfer.transferSuccess) + + let transaction = dashRawTransaction.asBtcTransaction(DashTransaction.self, for: sender) + + guard let detailsVc = vc.router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { + vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: nil) + break + } + + detailsVc.transaction = transaction + detailsVc.service = service + + detailsVc.senderName = String.adamantLocalized.transactionDetails.yourAddress + + if let recipientName = self?.recipientName { + detailsVc.recipientName = recipientName + } else if transaction.recipientAddress == sender { + detailsVc.recipientName = String.adamantLocalized.transactionDetails.yourAddress + } + + if comments.count > 0 { + detailsVc.comment = comments + } + + vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: detailsVc) + + case .failure(let error): + guard case let .internalError(message, _) = error, message == "No transaction" else { + vc.dialogService.showRichError(error: error) + vc.delegate?.transferViewController(vc, didFinishWithTransfer: nil, detailsViewController: nil) + break + } + + vc.dialogService.showSuccess(withMessage: String.adamantLocalized.transfer.transferSuccess) + + guard let detailsVc = vc.router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { + vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: nil) + break + } + + detailsVc.transaction = transaction + detailsVc.service = service + detailsVc.senderName = String.adamantLocalized.transactionDetails.yourAddress + detailsVc.recipientName = self?.recipientName + + if comments.count > 0 { + detailsVc.comment = comments + } + + vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: detailsVc) + } + } + + case .failure(let error): + vc.dialogService.showRichError(error: error) + } + } + + case .failure(let error): + if case let .internalError(message, _) = error, message == "WAIT_FOR_COMPLETION" { + dialogService.dismissProgress() + dialogService.showAlert(title: nil, message: String.adamantLocalized.sharedErrors.walletFrezzed, style: AdamantAlertStyle.alert, actions: nil, from: nil) + } else { + dialogService.showRichError(error: error) + } + } + } + } + + + // MARK: Overrides + + private var _recipient: String? + + override var recipientAddress: String? { + set { + _recipient = newValue + + if let row: RowOf = form.rowBy(tag: BaseRows.address.tag) { + row.value = _recipient + row.updateCell() + } + } + get { + return _recipient + } + } + + override func validateRecipient(_ address: String) -> Bool { + guard let service = service else { + return false + } + + switch service.validate(address: address) { + case .valid: + return true + + case .invalid, .system: + return false + } + } + + override func recipientRow() -> BaseRow { + let row = TextRow() { + $0.tag = BaseRows.address.tag + $0.cell.textField.placeholder = String.adamantLocalized.newChat.addressPlaceholder + + if let recipient = recipientAddress { + $0.value = recipient + } + + if recipientIsReadonly { + $0.disabled = true + $0.cell.textField.isEnabled = false + } + }.onChange { [weak self] row in + if let text = row.value { + self?._recipient = text + } + + if let skip = self?.skipValueChange, skip { + self?.skipValueChange = false + return + } + + self?.validateForm() + } + + return row + } + + override func handleRawAddress(_ address: String) -> Bool { + guard let service = service else { + return false + } + + let parsedAddress: String + if address.hasPrefix("dash:"), let firstIndex = address.firstIndex(of: ":") { + let index = address.index(firstIndex, offsetBy: 1) + parsedAddress = String(address[index...]) + } else { + parsedAddress = address + } + + switch service.validate(address: parsedAddress) { + case .valid: + if let row: RowOf = form.rowBy(tag: BaseRows.address.tag) { + row.value = parsedAddress + row.updateCell() + } + + return true + + default: + return false + } + } + + func reportTransferTo(admAddress: String, amount: Decimal, comments: String, hash: String) { + let payload = RichMessageTransfer(type: DashWalletService.richMessageType, amount: amount, hash: hash, comments: comments) + + let message = AdamantMessage.richMessage(payload: payload) + + chatsProvider.sendMessage(message, recipientId: admAddress) { [weak self] result in + if case .failure(let error) = result { + self?.dialogService.showRichError(error: error) + } + } + } + + override func defaultSceneTitle() -> String? { + return String.adamantLocalized.sendDash + } +} diff --git a/Adamant/Wallets/Dash/DashWallet.swift b/Adamant/Wallets/Dash/DashWallet.swift new file mode 100644 index 000000000..632b61730 --- /dev/null +++ b/Adamant/Wallets/Dash/DashWallet.swift @@ -0,0 +1,24 @@ +// +// DashWallet.swift +// Adamant +// +// Created by Anton Boyarkin on 25/04/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import BitcoinKit + +class DashWallet: WalletAccount { + let address: String + let privateKey: PrivateKey + let publicKey: PublicKey + var balance: Decimal = 0.0 + var notifications: Int = 0 + + init(privateKey: PrivateKey) { + self.privateKey = privateKey + self.publicKey = privateKey.publicKey() + self.address = publicKey.toCashaddr().base58 + } +} diff --git a/Adamant/Wallets/Dash/DashWalletRouter.swift b/Adamant/Wallets/Dash/DashWalletRouter.swift new file mode 100644 index 000000000..045724b3f --- /dev/null +++ b/Adamant/Wallets/Dash/DashWalletRouter.swift @@ -0,0 +1,49 @@ +// +// DashWalletRouter.swift +// Adamant +// +// Created by Anton Boyarkin on 25/04/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation + +extension AdamantScene.Wallets { + struct Dash { + /// Wallet preview + static let wallet = AdamantScene(identifier: "DashWalletViewController") { r in + let c = DashWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = r.resolve(DialogService.self) + c.currencyInfoService = r.resolve(CurrencyInfoService.self) + return c + } + + /// Send tokens + static let transfer = AdamantScene(identifier: "DashTransferViewController") { r in + let c = DashTransferViewController() + c.dialogService = r.resolve(DialogService.self) + c.chatsProvider = r.resolve(ChatsProvider.self) + c.accountService = r.resolve(AccountService.self) + c.accountsProvider = r.resolve(AccountsProvider.self) + c.router = r.resolve(Router.self) + c.currencyInfoService = r.resolve(CurrencyInfoService.self) + return c + } + + /// List of transactions + static let transactionsList = AdamantScene(identifier: "DashTransactionsViewController") { r in + let c = DashTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + c.dialogService = r.resolve(DialogService.self) + c.router = r.resolve(Router.self) + return c + } + + /// Transaction details + static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in + let c = DashTransactionDetailsViewController() + c.dialogService = r.resolve(DialogService.self) + c.currencyInfo = r.resolve(CurrencyInfoService.self) + return c + } + } +} diff --git a/Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift b/Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift new file mode 100644 index 000000000..33537a774 --- /dev/null +++ b/Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift @@ -0,0 +1,209 @@ +// +// DashWalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 26/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import MessageKit + +extension DashWalletService: RichMessageProvider { + + // MARK: Events + + func richMessageTapped(for transaction: RichMessageTransaction, at indexPath: IndexPath, in chat: ChatViewController) { + // MARK: 0. Prepare + guard let richContent = transaction.richContent, + let hash = richContent[RichContentKeys.transfer.hash], + let dialogService = dialogService, + let address = wallet?.address else { + return + } + + dialogService.showProgress(withMessage: nil, userInteractionEnable: false) + + let comment: String? + if let raw = transaction.richContent?[RichContentKeys.transfer.comments], raw.count > 0 { + comment = raw + } else { + comment = nil + } + + // MARK: Get transaction + getTransaction(by: hash) { [weak self] result in + guard let vc = self?.router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController, let service = self else { + return + } + + // MARK: 1. Prepare details view controller + vc.service = service + vc.comment = comment + + switch result { + case .success(let rawTransaction): + let dashTransaction = rawTransaction.asBtcTransaction(DashTransaction.self, for: address) + + // MARK: 2. Self name + if dashTransaction.senderAddress == address { + vc.senderName = String.adamantLocalized.transactionDetails.yourAddress + } + if dashTransaction.recipientAddress == address { + vc.recipientName = String.adamantLocalized.transactionDetails.yourAddress + } + + vc.transaction = dashTransaction + + let group = DispatchGroup() + + // MARK: 3. Get partner name async + if let partner = transaction.partner, let partnerAddress = partner.address, let partnerName = partner.name { + group.enter() // Enter 1 + service.getDashAddress(byAdamandAddress: partnerAddress) { result in + switch result { + case .success(let address): + if dashTransaction.senderAddress == address { + vc.senderName = partnerName + } + if dashTransaction.recipientAddress == address { + vc.recipientName = partnerName + } + + case .failure: + break + } + + group.leave() // Leave 1 + } + } + + // MARK: 4. Get block id async + if let blockHash = rawTransaction.blockHash { + group.enter() // Enter 2 + service.getBlockId(by: blockHash) { result in + switch result { + case .success(let id): + vc.transaction = rawTransaction.asBtcTransaction(DashTransaction.self, for: address, blockId: id) + + case .failure: + break + } + + group.leave() // Leave 2 + } + } + + // MARK: 5. Wait async operations + group.wait() + + // MARK: 6. Display details view controller + DispatchQueue.main.async { + dialogService.dismissProgress() + chat.navigationController?.pushViewController(vc, animated: true) + } + + case .failure(let error): + switch error { + case .internalError(let message, _) where message == "Unaviable transaction": + dialogService.dismissProgress() + dialogService.showAlert(title: nil, message: String.adamantLocalized.sharedErrors.transactionUnavailable, style: AdamantAlertStyle.alert, actions: nil, from: nil) + break + case .internalError(let message, _) where message == "No transaction": + let amount: Decimal + if let amountRaw = transaction.richContent?[RichContentKeys.transfer.amount], let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + let failedTransaction = SimpleTransactionDetails(txId: hash, + senderAddress: transaction.senderAddress, + recipientAddress: transaction.recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: transaction.isOutgoing, + transactionStatus: TransactionStatus.failed) + + vc.transaction = failedTransaction + + DispatchQueue.main.async { + dialogService.dismissProgress() + chat.navigationController?.pushViewController(vc, animated: true) + } + + default: + dialogService.dismissProgress() + dialogService.showRichError(error: error) + } + } + + } + } + + // MARK: Cells + + func cellSizeCalculator(for messagesCollectionViewFlowLayout: MessagesCollectionViewFlowLayout) -> CellSizeCalculator { + let calculator = TransferMessageSizeCalculator(layout: messagesCollectionViewFlowLayout) + calculator.font = UIFont.systemFont(ofSize: 24) + return calculator + } + + func cell(for message: MessageType, isFromCurrentSender: Bool, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell { + guard case .custom(let raw) = message.kind, let transfer = raw as? RichMessageTransfer else { + fatalError("DASH service tried to render wrong message kind: \(message.kind)") + } + + let cellIdentifier = isFromCurrentSender ? cellIdentifierSent : cellIdentifierReceived + guard let cell = messagesCollectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? TransferCollectionViewCell else { + fatalError("Can't dequeue \(cellIdentifier) cell") + } + + cell.currencyLogoImageView.image = DashWalletService.currencyLogo + cell.currencySymbolLabel.text = DashWalletService.currencySymbol + + cell.amountLabel.text = AdamantBalanceFormat.full.format(transfer.amount) + cell.dateLabel.text = message.sentDate.humanizedDateTime(withWeekday: false) + cell.transactionStatus = (message as? RichMessageTransaction)?.transactionStatus + + cell.commentsLabel.text = transfer.comments + + if cell.isAlignedRight != isFromCurrentSender { + cell.isAlignedRight = isFromCurrentSender + } + + return cell + } + + // MARK: Short description + + private static var formatter: NumberFormatter = { + return AdamantBalanceFormat.currencyFormatter(for: .full, currencySymbol: currencySymbol) + }() + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.richContent?[RichContentKeys.transfer.amount] else { + return NSAttributedString(string: "⬅️ \(DashWalletService.currencySymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(DashWalletService.currencySymbol)" + } else { + string = "➡️ \(amount) \(DashWalletService.currencySymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift new file mode 100644 index 000000000..05d1aeaf9 --- /dev/null +++ b/Adamant/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift @@ -0,0 +1,115 @@ +// +// DashWalletService+RichMessageProviderWithStatusCheck.swift +// Adamant +// +// Created by Anton Boyarkin on 26/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation + +extension DashWalletService: RichMessageProviderWithStatusCheck { + func statusFor(transaction: RichMessageTransaction, completion: @escaping (WalletServiceResult) -> Void) { + guard let hash = transaction.richContent?[RichContentKeys.transfer.hash], let date = transaction.date as Date? else { + completion(.failure(error: WalletServiceError.internalError(message: "Failed to get transaction hash", error: nil))) + return + } + + guard let walletAddress = dashWallet?.address else { + completion(.failure(error: .notLogged)) + return + } + + getTransaction(by: hash) { result in + switch result { + case .success(let dashTransaction): + // MARK: Check confirmations + guard let confirmations = dashTransaction.confirmations, let dashDate = dashTransaction.date, (confirmations > 0 || dashDate.timeIntervalSinceNow > -60 * 15) else { + completion(.success(result: .pending)) + return + } + + // MARK: Check date + guard let sentDate = dashTransaction.date else { + let timeAgo = -1 * date.timeIntervalSinceNow + + let result: TransactionStatus + if timeAgo > 60 * 10 { + // 10m waiting for pending status + result = .failed + } else { + // Note: No info about processing transactions + result = .pending + } + completion(.success(result: result)) + return + } + + // 1 day + let dayInterval = TimeInterval(60 * 60 * 24) + let start = date.addingTimeInterval(-dayInterval) + let end = date.addingTimeInterval(dayInterval) + let range = start...end + + guard range.contains(sentDate) else { + completion(.success(result: .warning)) + return + } + + // MARK: Check amount & address + guard let raw = transaction.richContent?[RichContentKeys.transfer.amount], let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) else { + completion(.success(result: .warning)) + return + } + + var result: TransactionStatus = .warning + if transaction.isOutgoing { + var totalIncome: Decimal = 0 + for input in dashTransaction.inputs { + guard input.sender == walletAddress else { + continue + } + + totalIncome += input.value + } + + if totalIncome >= reportedValue { + result = .success + } + } else { + var totalOutcome: Decimal = 0 + for output in dashTransaction.outputs { + guard output.addresses.contains(walletAddress) else { + continue + } + + totalOutcome += output.value + } + + if totalOutcome >= reportedValue { + result = .success + } + } + + completion(.success(result: result)) + + case .failure(let error): + if case let .internalError(message, _) = error, message == "No transaction" { + let timeAgo = -1 * date.timeIntervalSinceNow + + let result: TransactionStatus + if timeAgo > 60 * 10 { + // 10m waiting for pending status + result = .failed + } else { + // Note: No info about processing transactions + result = .pending + } + completion(.success(result: result)) + } else { + completion(.failure(error: error.asWalletServiceError())) + } + } + } + } +} diff --git a/Adamant/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Wallets/Dash/DashWalletService+Send.swift new file mode 100644 index 000000000..6de97bc33 --- /dev/null +++ b/Adamant/Wallets/Dash/DashWalletService+Send.swift @@ -0,0 +1,134 @@ +// +// DashWalletService+Send.swift +// Adamant +// +// Created by Anton Boyarkin on 26/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import BitcoinKit +import BitcoinKit.Private +import Alamofire + +extension DashWalletService: WalletServiceTwoStepSend { + typealias T = BitcoinKit.Transaction + + func transferViewController() -> UIViewController { + guard let vc = router.get(scene: AdamantScene.Wallets.Dash.transfer) as? DashTransferViewController else { + fatalError("Can't get DashTransferViewController") + } + + vc.service = self + return vc + } + + + // MARK: Create & Send + func create(recipient: String, amount: Decimal, completion: @escaping (WalletServiceResult) -> Void) { + if let lastTransaction = self.lastTransactionId { + self.getTransaction(by: lastTransaction) { result in + switch result { + case .success(let transaction): + if let confirmations = transaction.confirmations, confirmations >= 1 { + self.createTransaction(recipient: recipient, amount: amount, completion: completion) + } else { + completion(.failure(error: WalletServiceError.internalError(message: "WAIT_FOR_COMPLETION", error: nil))) + } + case .failure: + completion(.failure(error: WalletServiceError.internalError(message: "WAIT_FOR_COMPLETION", error: nil))) + } + } + } else { + self.createTransaction(recipient: recipient, amount: amount, completion: completion) + } + } + + func createTransaction(recipient: String, amount: Decimal, completion: @escaping (WalletServiceResult) -> Void) { + // MARK: 1. Prepare + guard let wallet = self.dashWallet else { + completion(.failure(error: .notLogged)) + return + } + + let changeAddress = wallet.publicKey.toCashaddr() + let key = wallet.privateKey + + guard let toAddress = try? LegacyAddress(recipient, for: self.network) else { + completion(.failure(error: .accountNotFound)) + return + } + + let rawAmount = NSDecimalNumber(decimal: amount * DashWalletService.multiplier).uint64Value + let fee = NSDecimalNumber(decimal: self.transactionFee * DashWalletService.multiplier).uint64Value + + // MARK: 2. Search for unspent transactions + getUnspentTransactions { result in + switch result { + case .success(let utxos): + // MARK: 3. Check if we have enought money + let totalAmount: UInt64 = UInt64(utxos.reduce(0) { $0 + $1.output.value }) + guard totalAmount >= rawAmount + fee else { // This shit can crash BitcoinKit + completion(.failure(error: .notEnoughMoney)) + break + } + + // MARK: 4. Create local transaction + let transaction = BitcoinKit.Transaction.createNewTransaction(toAddress: toAddress, amount: rawAmount, fee: fee, changeAddress: changeAddress, utxos: utxos, keys: [key]) + completion(.success(result: transaction)) + + case .failure: + completion(.failure(error: .notEnoughMoney)) + } + } + } + + func sendTransaction(_ transaction: BitcoinKit.Transaction, completion: @escaping (WalletServiceResult) -> Void) { + guard let endpoint = AdamantResources.dashServers.randomElement() else { + fatalError("Failed to get DASH endpoint URL") + } + + let txHex = transaction.serialized().hex + + // Headers + let headers = [ + "Content-Type": "application/json" + ] + + let parameters: Parameters = [ + "method": "sendrawtransaction", + "params": [ + txHex + ] + ] + + // MARK: Sending request + Alamofire.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseData(queue: defaultDispatchQueue) { response in + + switch response.result { + case .success(let data): + do { + let response = try JSONDecoder().decode(BTCRPCServerResponce.self, from: data) + + if let result = response.result { + self.lastTransactionId = transaction.txID + completion(.success(result: result)) + } else if let error = response.error?.message { + if error.lowercased().contains("16: tx-txlock-conflict") { + completion(.failure(error: .internalError(message: String.adamantLocalized.sharedErrors.walletFrezzed, error: nil))) + } else { + completion(.failure(error: .internalError(message: error, error: nil))) + } + } else { + completion(.failure(error: .internalError(message: "DASH Wallet: not valid response", error: nil))) + } + } catch { + completion(.failure(error: .internalError(message: "DASH Wallet: not valid response", error: nil))) + } + + case .failure(let error): + completion(.failure(error: .remoteServiceError(message: error.localizedDescription))) + } + } + } +} diff --git a/Adamant/Wallets/Dash/DashWalletService.swift b/Adamant/Wallets/Dash/DashWalletService.swift new file mode 100644 index 000000000..036c9f523 --- /dev/null +++ b/Adamant/Wallets/Dash/DashWalletService.swift @@ -0,0 +1,672 @@ +// +// DashWalletService.swift +// Adamant +// +// Created by Anton Boyarkin on 25/04/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import Swinject +import Alamofire +import BitcoinKit +import BitcoinKit.Private + +class DashWalletService: WalletService { + var wallet: WalletAccount? { return dashWallet } + + var walletViewController: WalletViewController { + guard let vc = router.get(scene: AdamantScene.Wallets.Dash.wallet) as? DashWalletViewController else { + fatalError("Can't get DashWalletViewController") + } + + vc.service = self + return vc + } + + // MARK: RichMessageProvider properties + static let richMessageType = "dash_transaction" + let cellIdentifierSent = "dashTransferSent" + let cellIdentifierReceived = "dashTransferReceived" + let cellSource: CellSource? = CellSource.nib(nib: UINib(nibName: "TransferCollectionViewCell", bundle: nil)) + + // MARK: - Dependencies + var apiService: ApiService! + var accountService: AccountService! + var securedStore: SecuredStore! + var dialogService: DialogService! + var router: Router! + + // MARK: - Constants + static var currencySymbol = "DASH" + static var currencyLogo = #imageLiteral(resourceName: "wallet_dash") + + static let multiplier = Decimal(sign: .plus, exponent: 8, significand: 1) + static let chunkSize = 20 + + private (set) var transactionFee: Decimal = 0.0001 // 0.0001 DASH per transaction + + static let kvsAddress = "dash:address" + + private var transatrionsIds = [String]() + + internal var lastTransactionId: String? { + get { + guard let hash = self.securedStore.get("lastDashTransactionId"), let timestampString = self.securedStore.get("lastDashTransactionTime"), let timestamp = Double(string: timestampString) else { return nil } + + let date = Date(timeIntervalSince1970: TimeInterval(timestamp)) + let timeAgo = -1 * date.timeIntervalSinceNow + + if timeAgo > 10 * 60 { // 10m waiting for transaction complete + self.securedStore.remove("lastDashTransactionTime") + self.securedStore.remove("lastDashTransactionId") + return nil + } else { + return hash + } + } + set { + if let value = newValue { + let timestamp = Date().timeIntervalSince1970 + self.securedStore.set("\(timestamp)", for: "lastDashTransactionTime") + self.securedStore.set(value, for: "lastDashTransactionId") + } else { + self.securedStore.remove("lastDashTransactionTime") + self.securedStore.remove("lastDashTransactionId") + } + } + } + + // MARK: - Notifications + let walletUpdatedNotification = Notification.Name("adamant.dashWallet.walletUpdated") + let serviceEnabledChanged = Notification.Name("adamant.dashWallet.enabledChanged") + let serviceStateChanged = Notification.Name("adamant.dashWallet.stateChanged") + let transactionFeeUpdated = Notification.Name("adamant.dashWallet.feeUpdated") + + // MARK: - Delayed KVS save + private var balanceObserver: NSObjectProtocol? = nil + + // MARK: - Properties + private (set) var dashWallet: DashWallet? = nil + + private (set) var enabled = true + + public var network: Network + + private var initialBalanceCheck = false + + let defaultDispatchQueue = DispatchQueue(label: "im.adamant.dashWalletService", qos: .utility, attributes: [.concurrent]) + let stateSemaphore = DispatchSemaphore(value: 1) + + private static let jsonDecoder = JSONDecoder() + + // MARK: - State + private (set) var state: WalletServiceState = .notInitiated + + private func setState(_ newState: WalletServiceState, silent: Bool = false) { + guard newState != state else { + return + } + + state = newState + + if !silent { + NotificationCenter.default.post(name: serviceStateChanged, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.walletState: state]) + } + } + + init() { + self.network = DashMainnet() + + self.setState(.notInitiated) + } + + func update() { + guard let wallet = dashWallet else { + return + } + + defer { stateSemaphore.signal() } + stateSemaphore.wait() + + switch state { + case .notInitiated, .updating, .initiationFailed(_): + return + + case .upToDate: + break + } + + setState(.updating) + + getBalance { [weak self] result in + if let stateSemaphore = self?.stateSemaphore { + defer { + stateSemaphore.signal() + } + stateSemaphore.wait() + } + + switch result { + case .success(let balance): + let notification: Notification.Name? + + if wallet.balance != balance { + wallet.balance = balance + notification = self?.walletUpdatedNotification + self?.initialBalanceCheck = false + } else if let initialBalanceCheck = self?.initialBalanceCheck, initialBalanceCheck { + self?.initialBalanceCheck = false + notification = self?.walletUpdatedNotification + } else { + notification = nil + } + + if let notification = notification { + NotificationCenter.default.post(name: notification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) + } + + case .failure(let error): + switch error { + case .networkError: + break + + case .remoteServiceError(let message) where message.contains("Server not yet ready"): + break + + default: + self?.dialogService.showRichError(error: error) + } + } + + self?.setState(.upToDate) + } + } + + func validate(address: String) -> AddressValidationResult { + return AddressFactory.isValid(bitcoinAddress: address) ? .valid : .invalid + } +} + +// MARK: - WalletInitiatedWithPassphrase +extension DashWalletService: InitiatedWithPassphraseService { + func setInitiationFailed(reason: String) { + stateSemaphore.wait() + setState(.initiationFailed(reason: reason)) + dashWallet = nil + stateSemaphore.signal() + } + + func initWallet(withPassphrase passphrase: String, completion: @escaping (WalletServiceResult) -> Void) { + guard let adamant = accountService.account else { + completion(.failure(error: .notLogged)) + return + } + + stateSemaphore.wait() + + setState(.notInitiated) + + if enabled { + enabled = false + NotificationCenter.default.post(name: serviceEnabledChanged, object: self) + } + + defaultDispatchQueue.async { [unowned self] in + let privateKeyData = passphrase.data(using: .utf8)!.sha256() + let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true) + + let eWallet = DashWallet(privateKey: privateKey) + self.dashWallet = eWallet + + if !self.enabled { + self.enabled = true + NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) + } + + self.stateSemaphore.signal() + + // MARK: 4. Save address into KVS + self.getWalletAddress(byAdamantAddress: adamant.address) { [weak self] result in + guard let service = self else { + return + } + + switch result { + case .success(let address): + // Dash already saved + if address != eWallet.address { + service.save(dashAddress: eWallet.address) { result in + service.kvsSaveCompletionRecursion(dashAddress: eWallet.address, result: result) + } + } + + service.initialBalanceCheck = true + service.setState(.upToDate, silent: true) + service.update() + + completion(.success(result: eWallet)) + + case .failure(let error): + switch error { + case .walletNotInitiated: + // Show '0' without waiting for balance update + if let wallet = service.dashWallet { + NotificationCenter.default.post(name: service.walletUpdatedNotification, object: service, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) + } + + service.save(dashAddress: eWallet.address) { result in + service.kvsSaveCompletionRecursion(dashAddress: eWallet.address, result: result) + } + service.setState(.upToDate) + completion(.success(result: eWallet)) + + default: + service.setState(.upToDate) + completion(.failure(error: error)) + } + } + } + } + } +} + +// MARK: - Dependencies +extension DashWalletService: SwinjectDependentService { + func injectDependencies(from container: Container) { + accountService = container.resolve(AccountService.self) + apiService = container.resolve(ApiService.self) + securedStore = container.resolve(SecuredStore.self) + dialogService = container.resolve(DialogService.self) + router = container.resolve(Router.self) + } +} + +// MARK: - Balances & addresses +extension DashWalletService { + func getBalance(_ completion: @escaping (WalletServiceResult) -> Void) { + guard let endpoint = AdamantResources.dashServers.randomElement() else { + fatalError("Failed to get DASH endpoint URL") + } + + guard let address = self.dashWallet?.address else { + completion(.failure(error: .walletNotInitiated)) + return + } + + // Headers + let headers = [ + "Content-Type": "application/json" + ] + + // Parameters + let parameters: Parameters = [ + "method": "getaddressbalance", + "params": [ + address + ] + ] + + // MARK: Sending request + Alamofire.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON(queue: defaultDispatchQueue) { response in + + switch response.result { + case .success(let data): + if let object = data as? [String: Any] { + let result = object["result"] as? [String: Any] + let error = object["error"] + + if error is NSNull, let result = result, let raw = result["balance"] as? Int64 { + let balance = Decimal(raw) / DashWalletService.multiplier + completion(.success(result: balance)) + } else { + completion(.failure(error: .remoteServiceError(message: "DASH Wallet: \(data)"))) + } + } else { + completion(.failure(error: .remoteServiceError(message: "DASH Wallet: \(data)"))) + } + + case .failure: + completion(.failure(error: .networkError)) + } + } + } + + func getDashAddress(byAdamandAddress address: String, completion: @escaping (ApiServiceResult) -> Void) { + apiService.get(key: DashWalletService.kvsAddress, sender: address, completion: completion) + } + + func getWalletAddress(byAdamantAddress address: String, completion: @escaping (WalletServiceResult) -> Void) { + apiService.get(key: DashWalletService.kvsAddress, sender: address) { (result) in + switch result { + case .success(let value): + if let address = value { + completion(.success(result: address)) + } else { + completion(.failure(error: .walletNotInitiated)) + } + + case .failure(let error): + completion(.failure(error: .internalError(message: "DASH Wallet: fail to get address from KVS", error: error))) + } + } + } +} + +// MARK: - KVS +extension DashWalletService { + /// - Parameters: + /// - dashAddress: DASH address to save into KVS + /// - adamantAddress: Owner of Dash address + /// - completion: success + private func save(dashAddress: String, completion: @escaping (WalletServiceSimpleResult) -> Void) { + guard let adamant = accountService.account, let keypair = accountService.keypair else { + completion(.failure(error: .notLogged)) + return + } + + guard adamant.balance >= AdamantApiService.KvsFee else { + completion(.failure(error: .notEnoughMoney)) + return + } + + apiService.store(key: DashWalletService.kvsAddress, value: dashAddress, type: .keyValue, sender: adamant.address, keypair: keypair) { result in + switch result { + case .success: + completion(.success) + + case .failure(let error): + completion(.failure(error: .apiError(error))) + } + } + } + + /// New accounts doesn't have enought money to save KVS. We need to wait for balance update, and then - retry save + private func kvsSaveCompletionRecursion(dashAddress: String, result: WalletServiceSimpleResult) { + if let observer = balanceObserver { + NotificationCenter.default.removeObserver(observer) + balanceObserver = nil + } + + switch result { + case .success: + break + + case .failure(let error): + switch error { + case .notEnoughMoney: // Possibly new account, we need to wait for dropship + // Register observer + let observer = NotificationCenter.default.addObserver(forName: NSNotification.Name.AdamantAccountService.accountDataUpdated, object: nil, queue: nil) { [weak self] _ in + guard let balance = self?.accountService.account?.balance, balance > AdamantApiService.KvsFee else { + return + } + + self?.save(dashAddress: dashAddress) { result in + self?.kvsSaveCompletionRecursion(dashAddress: dashAddress, result: result) + } + } + + // Save referense to unregister it later + balanceObserver = observer + + default: + dialogService.showRichError(error: error) + } + } + } +} + +// MARK: - Transactions +extension DashWalletService { + func getTransactions(from: Int, completion: @escaping (ApiServiceResult<(transactions: [DashTransaction], hasMore: Bool)>) -> Void) { + guard let address = self.wallet?.address else { + completion(.failure(.notLogged)) + return + } + + if from == 0 { + self.transatrionsIds.removeAll() + } + + if self.transatrionsIds.count > 0, let id = self.transatrionsIds.first { + self.getTransaction(by: id, completion: { response in + switch response { + case .success(let transaction): + if let idx = self.transatrionsIds.index(of: id) { + self.transatrionsIds.remove(at: idx) + } + completion(.success((transactions: [transaction.asBtcTransaction(DashTransaction.self, for: address)], hasMore: self.transatrionsIds.count > 0))) + case .failure(let error): + completion(.failure(error)) + } + }) + } else { + getTransactionsIds(for: address) { response in + switch response { + case .success(let ids): + + self.transatrionsIds = ids + if let id = ids.last { + self.getTransaction(by: id, completion: { r in + switch r { + case .success(let transaction): + if let idx = self.transatrionsIds.index(of: id) { + self.transatrionsIds.remove(at: idx) + } + completion(.success((transactions: [transaction.asBtcTransaction(DashTransaction.self, for: address)], hasMore: self.transatrionsIds.count > 0))) + case .failure(let error): + completion(.failure(error)) + } + }) + } + + case .failure(let error): + completion(.failure(error)) + } + } + } + } + + private func getTransactionsIds(for address: String, completion: @escaping (ApiServiceResult<[String]>) -> Void) { + guard let endpoint = AdamantResources.dashServers.randomElement() else { + fatalError("Failed to get DASH endpoint URL") + } + + guard let address = self.dashWallet?.address else { + completion(.failure(.internalError(message: "DASH Wallet not found", error: nil))) + return + } + + // Headers + let headers = [ + "Content-Type": "application/json" + ] + + let parameters: Parameters = [ + "method": "getaddresstxids", + "params": [ + address + ] + ] + + // MARK: Sending request + Alamofire.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseData(queue: defaultDispatchQueue) { response in + + switch response.result { + case .success(let data): + do { + let response = try DashWalletService.jsonDecoder.decode(BTCRPCServerResponce<[String]>.self, from: data) + + if let result = response.result { + completion(.success(result)) + } else if let error = response.error?.message { + completion(.failure(.internalError(message: error, error: nil))) + } + } catch { + completion(.failure(.internalError(message: "DASH Wallet: not a valid response", error: error))) + } + + case .failure(let error): + completion(.failure(.internalError(message: "DASH Wallet: server not responding", error: error))) + } + } + } + + func getUnspentTransactions(_ completion: @escaping (ApiServiceResult<[UnspentTransaction]>) -> Void) { + guard let endpoint = AdamantResources.dashServers.randomElement() else { + fatalError("Failed to get DASH endpoint URL") + } + + guard let wallet = self.dashWallet else { + completion(.failure(.internalError(message: "DASH Wallet not found", error: nil))) + return + } + + // Headers + let headers = [ + "Content-Type": "application/json" + ] + + let parameters: Parameters = [ + "method": "getaddressutxos", + "params": [ + wallet.address + ] + ] + + // MARK: Sending request + Alamofire.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseData(queue: defaultDispatchQueue) { response in + + switch response.result { + case .success(let data): + do { + let response = try DashWalletService.jsonDecoder.decode(BTCRPCServerResponce<[DashUnspentTransaction]>.self, from: data) + + if let result = response.result { + let transactions = result.map { $0.asUnspentTransaction(with: wallet.publicKey.toCashaddr().data) } + completion(.success(transactions)) + } else if let error = response.error?.message { + completion(.failure(.internalError(message: error, error: nil))) + } + } catch { + completion(.failure(.internalError(message: "DASH Wallet: not a valid response", error: error))) + } + + case .failure(let error): + completion(.failure(.internalError(message: "DASH Wallet: server not responding", error: error))) + } + } + } + + func getTransaction(by hash: String, completion: @escaping (ApiServiceResult) -> Void) { + guard let endpoint = AdamantResources.dashServers.randomElement() else { + fatalError("Failed to get DASH endpoint URL") + } + + // Headers + let headers = [ + "Content-Type": "application/json" + ] + + let parameters: Parameters = [ + "method": "getrawtransaction", + "params": [ + hash, true + ] + ] + + // MARK: Sending request + Alamofire.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseData(queue: defaultDispatchQueue) { response in + switch response.result { + case .success(let data): + do { + let result = try DashWalletService.jsonDecoder.decode(BTCRPCServerResponce.self, from: data) + if let transaction = result.result { + completion(.success(transaction)) + } else { + completion(.failure(.internalError(message: "Unaviable transaction", error: nil))) + } + } catch { + completion(.failure(.internalError(message: "Unaviable transaction", error: error))) + } + + case .failure(let error): + completion(.failure(.internalError(message: "No transaction", error: error))) + } + } + } + + func getBlockId(by hash: String, completion: @escaping (ApiServiceResult) -> Void) { + guard let endpoint = AdamantResources.dashServers.randomElement() else { + fatalError("Failed to get DASH endpoint URL") + } + + // Headers + let headers = [ + "Content-Type": "application/json" + ] + + let parameters: Parameters = [ + "method": "getblock", + "params": [ + hash + ] + ] + + // MARK: Sending request + Alamofire.request(endpoint, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseData(queue: defaultDispatchQueue) { response in + switch response.result { + case .success(let data): + do { + let result = try DashWalletService.jsonDecoder.decode(BTCRPCServerResponce.self, from: data) + if let block = result.result { + completion(.success(String(block.height))) + } else { + completion(.failure(.internalError(message: "DASH: Parsing block error", error: nil))) + } + } catch { + completion(.failure(.internalError(message: "DASH: Parsing bloc error", error: error))) + } + + case .failure(let error): + completion(.failure(.internalError(message: "No block", error: error))) + } + + } + } +} + +// MARK: - WalletServiceWithTransfers +extension DashWalletService: WalletServiceWithTransfers { + func transferListViewController() -> UIViewController { + guard let vc = router.get(scene: AdamantScene.Wallets.Dash.transactionsList) as? DashTransactionsViewController else { + fatalError("Can't get DashTransactionsViewController") + } + + vc.walletService = self + return vc + } +} + +// MARK: - PrivateKey generator +extension DashWalletService: PrivateKeyGenerator { + var rowTitle: String { + return "Dash" + } + + var rowImage: UIImage? { + return #imageLiteral(resourceName: "wallet_dash_row") + } + + func generatePrivateKeyFor(passphrase: String) -> String? { + guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase), let privateKeyData = passphrase.data(using: .utf8)?.sha256() else { + return nil + } + + let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true) + + return privateKey.toWIF() + } +} diff --git a/Adamant/Wallets/Dash/DashWalletViewController.swift b/Adamant/Wallets/Dash/DashWalletViewController.swift new file mode 100644 index 000000000..12f7cc052 --- /dev/null +++ b/Adamant/Wallets/Dash/DashWalletViewController.swift @@ -0,0 +1,34 @@ +// +// DashWalletViewController.swift +// Adamant +// +// Created by Anton Boyarkin on 25/04/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import UIKit + +extension String.adamantLocalized { + static let dash = NSLocalizedString("AccountTab.Wallets.dash_wallet", comment: "Account tab: Dash wallet") + + static let sendDash = NSLocalizedString("AccountTab.Row.SendDash", comment: "Account tab: 'Send Dash tokens' button") +} + +class DashWalletViewController: WalletViewControllerBase { + // MARK: Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + walletTitleLabel.text = String.adamantLocalized.dash + } + + override func sendRowLocalizedLabel() -> String { + return String.adamantLocalized.sendDash + } + + override func encodeForQr(address: String) -> String? { + return "dash:\(address)" + } +} diff --git a/Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift b/Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift index 94bf774af..d5de5862a 100644 --- a/Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift +++ b/Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift @@ -92,7 +92,7 @@ class DogeTransactionDetailsViewController: TransactionDetailsViewControllerBase switch result { case .success(let trs): if let blockInfo = self?.cachedBlockInfo, blockInfo.hash == trs.blockHash { - self?.transaction = trs.asDogeTransaction(for: address, blockId: blockInfo.height) + self?.transaction = trs.asBtcTransaction(DogeTransaction.self, for: address, blockId: blockInfo.height) DispatchQueue.main.async { [weak self] in self?.tableView.reloadData() @@ -110,7 +110,7 @@ class DogeTransactionDetailsViewController: TransactionDetailsViewControllerBase blockInfo = nil } - self?.transaction = trs.asDogeTransaction(for: address, blockId: blockInfo?.height) + self?.transaction = trs.asBtcTransaction(DogeTransaction.self, for: address, blockId: blockInfo?.height) self?.cachedBlockInfo = blockInfo DispatchQueue.main.async { [weak self] in @@ -120,7 +120,7 @@ class DogeTransactionDetailsViewController: TransactionDetailsViewControllerBase completion?(nil) } } else { - self?.transaction = trs.asDogeTransaction(for: address) + self?.transaction = trs.asBtcTransaction(DogeTransaction.self, for: address) DispatchQueue.main.async { [weak self] in self?.tableView.reloadData() diff --git a/Adamant/Wallets/Doge/DogeTransactionsViewController.swift b/Adamant/Wallets/Doge/DogeTransactionsViewController.swift index 110785b0e..c2499a5b8 100644 --- a/Adamant/Wallets/Doge/DogeTransactionsViewController.swift +++ b/Adamant/Wallets/Doge/DogeTransactionsViewController.swift @@ -100,7 +100,7 @@ class DogeTransactionsViewController: TransactionsListViewControllerBase { switch result { case .success(let dogeTransaction): - let transaction = dogeTransaction.asDogeTransaction(for: sender) + let transaction = dogeTransaction.asBtcTransaction(DogeTransaction.self, for: sender) // Sender name if transaction.senderAddress == sender { @@ -125,7 +125,7 @@ class DogeTransactionsViewController: TransactionsListViewControllerBase { vc.walletService.getBlockId(by: blockHash) { result in switch result { case .success(let id): - controller.transaction = dogeTransaction.asDogeTransaction(for: sender, blockId: id) + controller.transaction = dogeTransaction.asBtcTransaction(DogeTransaction.self, for: sender, blockId: id) case .failure: controller.transaction = transaction diff --git a/Adamant/Wallets/Doge/DogeTransferViewController.swift b/Adamant/Wallets/Doge/DogeTransferViewController.swift index 5829cf94f..2458c9ff3 100644 --- a/Adamant/Wallets/Doge/DogeTransferViewController.swift +++ b/Adamant/Wallets/Doge/DogeTransferViewController.swift @@ -68,7 +68,7 @@ class DogeTransferViewController: TransferViewControllerBase { case .success(let dogeRawTransaction): vc.dialogService.showSuccess(withMessage: String.adamantLocalized.transfer.transferSuccess) - let transaction = dogeRawTransaction.asDogeTransaction(for: sender) + let transaction = dogeRawTransaction.asBtcTransaction(DogeTransaction.self, for: sender) guard let detailsVc = vc.router.get(scene: AdamantScene.Wallets.Doge.transactionDetails) as? DogeTransactionDetailsViewController else { vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: nil) diff --git a/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift b/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift index 388ce7675..e85a0e8bc 100644 --- a/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift +++ b/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift @@ -43,7 +43,7 @@ extension DogeWalletService: RichMessageProvider { switch result { case .success(let dogeRawTransaction): - let dogeTransaction = dogeRawTransaction.asDogeTransaction(for: address) + let dogeTransaction = dogeRawTransaction.asBtcTransaction(DogeTransaction.self, for: address) // MARK: 2. Self name if dogeTransaction.senderAddress == address { @@ -84,7 +84,7 @@ extension DogeWalletService: RichMessageProvider { service.getBlockId(by: blockHash) { result in switch result { case .success(let id): - vc.transaction = dogeRawTransaction.asDogeTransaction(for: address, blockId: id) + vc.transaction = dogeRawTransaction.asBtcTransaction(DogeTransaction.self, for: address, blockId: id) case .failure: break @@ -105,6 +105,10 @@ extension DogeWalletService: RichMessageProvider { case .failure(let error): switch error { + case .internalError(let message, _) where message == "Unaviable transaction": + dialogService.dismissProgress() + dialogService.showAlert(title: nil, message: String.adamantLocalized.sharedErrors.transactionUnavailable, style: AdamantAlertStyle.alert, actions: nil, from: nil) + break case .internalError(let message, _) where message == "No transaction": let amount: Decimal if let amountRaw = transaction.richContent?[RichContentKeys.transfer.amount], let decimal = Decimal(string: amountRaw) { diff --git a/Adamant/Wallets/Doge/DogeWalletService.swift b/Adamant/Wallets/Doge/DogeWalletService.swift index 4d8de61ef..892686ece 100644 --- a/Adamant/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Wallets/Doge/DogeWalletService.swift @@ -432,7 +432,7 @@ extension DogeWalletService { case .success(let doge): let hasMore = doge.to < doge.totalItems - let transactions = doge.items.map { $0.asDogeTransaction(for: address) } + let transactions = doge.items.map { $0.asBtcTransaction(DogeTransaction.self, for: address) } completion(.success((transactions: transactions, hasMore: hasMore))) @@ -542,7 +542,7 @@ extension DogeWalletService { } } - func getTransaction(by hash: String, completion: @escaping (ApiServiceResult) -> Void) { + func getTransaction(by hash: String, completion: @escaping (ApiServiceResult) -> Void) { guard let url = AdamantResources.dogeServers.randomElement() else { fatalError("Failed to get DOGE endpoint URL") } @@ -560,10 +560,10 @@ extension DogeWalletService { switch response.result { case .success(let data): do { - let transfers = try DogeWalletService.jsonDecoder.decode(DogeRawTransaction.self, from: data) + let transfers = try DogeWalletService.jsonDecoder.decode(BTCRawTransaction.self, from: data) completion(.success(transfers)) } catch { - completion(.failure(.internalError(message: "DOGE: Parsing transaction error", error: error))) + completion(.failure(.internalError(message: "Unaviable transaction", error: error))) } case .failure(let error): diff --git a/Adamant/Wallets/Ethereum/EthWalletService.swift b/Adamant/Wallets/Ethereum/EthWalletService.swift index 4909c3e62..97d666ff3 100644 --- a/Adamant/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Wallets/Ethereum/EthWalletService.swift @@ -316,6 +316,8 @@ extension EthWalletService: InitiatedWithPassphraseService { stateSemaphore.signal() return } + + guard let web3 = web3 else { return } web3.addKeystoreManager(KeystoreManager([keystore])) diff --git a/Adamant/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Wallets/TransactionDetailsViewControllerBase.swift index 68b7472b9..6de1cf194 100644 --- a/Adamant/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Wallets/TransactionDetailsViewControllerBase.swift @@ -118,7 +118,13 @@ class TransactionDetailsViewControllerBase: FormViewController { // MARK: - Properties - var transaction: TransactionDetails? = nil + var transaction: TransactionDetails? = nil { + didSet { + if !isFiatSet { + self.updateFiat() + } + } + } private lazy var dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium @@ -136,6 +142,8 @@ class TransactionDetailsViewControllerBase: FormViewController { return AdamantBalanceFormat.fiatFormatter(for: currencyInfo.currentCurrency) }() + private var isFiatSet = false + // MARK: - Lifecycle override func viewDidLoad() { @@ -547,11 +555,17 @@ class TransactionDetailsViewControllerBase: FormViewController { form.append(actionsSection) // Get fiat value + self.updateFiat() + } + + func updateFiat() { if let date = transaction?.dateValue, let currencySymbol = currencySymbol, let amount = transaction?.amountValue { + self.isFiatSet = true let currentFiat = currencyInfo.currentCurrency.rawValue currencyInfo.getHistory(for: currencySymbol, timestamp: date) { [weak self] (result) in switch result { case .success(let tickers): + self?.isFiatSet = true guard let tickers = tickers, let ticker = tickers["\(currencySymbol)/\(currentFiat)"] else { break } @@ -567,6 +581,7 @@ class TransactionDetailsViewControllerBase: FormViewController { } case .failure: + self?.isFiatSet = false break } } diff --git a/Adamant/Wallets/TransferViewControllerBase.swift b/Adamant/Wallets/TransferViewControllerBase.swift index 3d7abfbbd..b7f4e19e5 100644 --- a/Adamant/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Wallets/TransferViewControllerBase.swift @@ -673,15 +673,24 @@ extension TransferViewControllerBase { return row case .amount: - return DecimalRow { [weak self] in - $0.title = BaseRows.amount.localized - $0.placeholder = String.adamantLocalized.transfer.amountPlaceholder - $0.tag = BaseRows.amount.tag - $0.formatter = self?.balanceFormatter - $0.useFormatterDuringInput = false + return DecimalRow { [weak self] row in + row.title = BaseRows.amount.localized + row.placeholder = String.adamantLocalized.transfer.amountPlaceholder + row.tag = BaseRows.amount.tag + row.formatter = self?.balanceFormatter + row.useFormatterDuringInput = false + row.useFormatterOnDidBeginEditing = true + row.displayValueFor = { value in + guard let v = value else { return nil } + let formatter = AdamantBalanceFormat.currencyFormatter(for: .full, currencySymbol: nil) + + let d = Decimal(v) + let s = formatter.string(from: d) + return s + } if let amount = self?.amount { - $0.value = amount.doubleValue + row.value = amount.doubleValue } }.onChange { [weak self] (row) in if let rate = self?.rate, let fiatRow: DecimalRow = self?.form.rowBy(tag: BaseRows.fiat.tag) { diff --git a/AdamantShared/AdamantResources.swift b/AdamantShared/AdamantResources.swift index ba2bbba9e..cb082d5b8 100644 --- a/AdamantShared/AdamantResources.swift +++ b/AdamantShared/AdamantResources.swift @@ -34,6 +34,10 @@ struct AdamantResources { URL(string: "https://dogenode1.adamant.im/api")! ] + static let dashServers: [URL] = [ + URL(string: "https://dashnode1.adamant.im/")! + ] + static let coinsInfoSrvice = "https://info.adamant.im" // MARK: ADAMANT Addresses @@ -65,6 +69,7 @@ struct AdamantResources { // static let liskExplorerAddress = "https://testnet-explorer.lisk.io/tx/" // LISK Testnet static let dogeExplorerAddress = "https://dogechain.info/tx/" + static let dashExplorerAddress = "https://explorer.dash.org/tx/" private init() {} } diff --git a/AdamantShared/Core/Crypto.swift b/AdamantShared/Core/Crypto.swift index 9d7113c84..f3e5012ce 100644 --- a/AdamantShared/Core/Crypto.swift +++ b/AdamantShared/Core/Crypto.swift @@ -9,7 +9,6 @@ import Foundation import libsodium import CryptoSwift -import ByteBackpacker public typealias Bytes = Array diff --git a/AdamantShared/Core/NativeAdamantCore.swift b/AdamantShared/Core/NativeAdamantCore.swift index d1e175b8e..db7a5ca39 100644 --- a/AdamantShared/Core/NativeAdamantCore.swift +++ b/AdamantShared/Core/NativeAdamantCore.swift @@ -8,7 +8,6 @@ import Foundation import CryptoSwift -import ByteBackpacker /* * Native Adamanat Core diff --git a/AdamantShared/Helpers/AdamantBalanceFormat.swift b/AdamantShared/Helpers/AdamantBalanceFormat.swift index 2a6dae9e4..05dbcb01f 100644 --- a/AdamantShared/Helpers/AdamantBalanceFormat.swift +++ b/AdamantShared/Helpers/AdamantBalanceFormat.swift @@ -133,6 +133,6 @@ enum AdamantBalanceFormat { // MARK: - Helper extension NumberFormatter { func string(from decimal: Decimal) -> String? { - return string(from: decimal as NSNumber) + return string(from: NSNumber(value: decimal.doubleValue)) } } diff --git a/AdamantShared/Helpers/Decimal+adamant.swift b/AdamantShared/Helpers/Decimal+adamant.swift index eb24d02e6..02ebe1a0f 100644 --- a/AdamantShared/Helpers/Decimal+adamant.swift +++ b/AdamantShared/Helpers/Decimal+adamant.swift @@ -18,6 +18,7 @@ extension Decimal { } var doubleValue: Double { - return (self as NSNumber).doubleValue +// return (self as NSNumber).doubleValue + return Double(self.description) ?? 0 // Fix issue with floation point number, that adds 0.00000000000002 value } } diff --git a/AdamantShared/RichMessageProviders/DashProvider.swift b/AdamantShared/RichMessageProviders/DashProvider.swift new file mode 100644 index 000000000..44e6e6681 --- /dev/null +++ b/AdamantShared/RichMessageProviders/DashProvider.swift @@ -0,0 +1,27 @@ +// +// DashProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 11/08/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import UIKit + +class DashProvider: TransferBaseProvider { + override class var richMessageType: String { + return "dash_transaction" + } + + override var currencyLogoUrl: URL? { + return Bundle.main.url(forResource: "dash_notificationContent", withExtension: "png") + } + + override var currencySymbol: String { + return "DASH" + } + + override var currencyLogoLarge: UIImage { + return #imageLiteral(resourceName: "dash_notification") + } +} diff --git a/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/Contents.json b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/Contents.json new file mode 100644 index 000000000..772dd18ef --- /dev/null +++ b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "dash_notification.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "dash_notification@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "dash_notification@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification.png b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification.png new file mode 100644 index 000000000..1612c2d13 Binary files /dev/null and b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification.png differ diff --git a/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification@2x.png b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification@2x.png new file mode 100644 index 000000000..9f3f34456 Binary files /dev/null and b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification@2x.png differ diff --git a/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification@3x.png b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification@3x.png new file mode 100644 index 000000000..dcbdfe8cd Binary files /dev/null and b/AdamantShared/Shared.xcassets/Wallets/dash_notification.imageset/dash_notification@3x.png differ diff --git a/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/Contents.json b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/Contents.json new file mode 100644 index 000000000..6b82225a8 --- /dev/null +++ b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "wallet_dash.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "wallet_dash@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "wallet_dash@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash.png b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash.png new file mode 100644 index 000000000..f65dfaf2f Binary files /dev/null and b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash.png differ diff --git a/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash@2x.png b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash@2x.png new file mode 100644 index 000000000..a49265aa7 Binary files /dev/null and b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash@2x.png differ diff --git a/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash@3x.png b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash@3x.png new file mode 100644 index 000000000..39e94978c Binary files /dev/null and b/AdamantShared/Shared.xcassets/Wallets/wallet_dash.imageset/wallet_dash@3x.png differ diff --git a/MessageNotificationContentExtension/Info.plist b/MessageNotificationContentExtension/Info.plist index 7561c7a77..6e6b82181 100644 --- a/MessageNotificationContentExtension/Info.plist +++ b/MessageNotificationContentExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.7.2 + 1.8.0 CFBundleVersion - 87 + 92 NSExtension NSExtensionAttributes diff --git a/NotificationServiceExtension/Info.plist b/NotificationServiceExtension/Info.plist index 7107671d0..a72dd6df2 100644 --- a/NotificationServiceExtension/Info.plist +++ b/NotificationServiceExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.7.2 + 1.8.0 CFBundleVersion - 87 + 92 NSExtension NSExtensionPointIdentifier diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 1346ac717..c535f2321 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -21,7 +21,8 @@ class NotificationService: UNNotificationServiceExtension { private lazy var richMessageProviders: [String: () -> RichMessageNotificationProvider] = { return [EthProvider.richMessageType: { EthProvider() }, LskProvider.richMessageType: { LskProvider() }, - DogeProvider.richMessageType: { DogeProvider() }] + DogeProvider.richMessageType: { DogeProvider() }, + DashProvider.richMessageType: { DashProvider() }] }() // MARK: - Hanlder diff --git a/Podfile b/Podfile index 6b91f68ff..2ff111012 100644 --- a/Podfile +++ b/Podfile @@ -12,6 +12,7 @@ def core_pods pod 'CryptoSwift' # MD5 hash pod 'ByteBackpacker' # Utility to pack value types into a Byte array pod 'libsodium' # Sodium crypto library + pod 'GRKOpenSSLFramework', '1.0.2.15' end # Markdown parser, forked with fixed whitespaces. '5 * 5 * 6' @@ -32,7 +33,7 @@ target 'Adamant' do # UI pod 'FreakingSimpleRoundImageView' # Round avatars pod 'FTIndicator' # Notifications and activity indicator - pod 'Eureka' # Forms + pod 'Eureka', :git => 'https://github.com/boyarkin-anton/Eureka.git', :branch => 'develop' # Forms pod 'MessageKit' # Chat UI pod 'MyLittlePinpad' # Pinpad pod 'PMAlertController' # Custom alert controller diff --git a/Podfile.lock b/Podfile.lock index 0a8a5c912..f0f43e126 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -12,7 +12,7 @@ PODS: - DateToolsSwift (4.0.0) - EFQRCode (4.4.2): - swift_qrcodejs (~> 1.0.1) - - Eureka (4.3.1) + - Eureka (5.0.0) - FreakingSimpleRoundImageView (1.3) - FTIndicator (1.2.9): - FTIndicator/FTNotificationIndicator (= 1.2.9) @@ -83,10 +83,11 @@ DEPENDENCIES: - CryptoSwift - DateToolsSwift - EFQRCode - - Eureka + - Eureka (from `https://github.com/boyarkin-anton/Eureka.git`, branch `develop`) - FreakingSimpleRoundImageView - FTIndicator - GRDB.swift + - GRKOpenSSLFramework (= 1.0.2.15) - KeychainAccess - libsodium - Lisk (from `https://github.com/adamant-im/lisk-swift.git`) @@ -111,7 +112,6 @@ SPEC REPOS: - CryptoSwift - DateToolsSwift - EFQRCode - - Eureka - FreakingSimpleRoundImageView - FTIndicator - GRDB.swift @@ -143,6 +143,9 @@ EXTERNAL SOURCES: BitcoinKit: :branch: dev :git: https://github.com/boyarkin-anton/BitcoinKit.git + Eureka: + :branch: develop + :git: https://github.com/boyarkin-anton/Eureka.git Lisk: :git: https://github.com/adamant-im/lisk-swift.git MarkdownKit: @@ -153,8 +156,11 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: BitcoinKit: - :commit: b78dd8df08bee99bdd75eef5cb5e8de0e27638e8 + :commit: 48589d409715da8be93df83d7866132a2ec50d58 :git: https://github.com/boyarkin-anton/BitcoinKit.git + Eureka: + :commit: 796b430ada97851a17cac0ae4acf4abf42da8186 + :git: https://github.com/boyarkin-anton/Eureka.git Lisk: :commit: 154a3af05772136b776ff14ae05f92c8d844ea20 :git: https://github.com/adamant-im/lisk-swift.git @@ -173,7 +179,7 @@ SPEC CHECKSUMS: CryptoSwift: 1c07ca50843dd48bc54e6ea53d7a4dba3b645716 DateToolsSwift: 875d97ff9e3a5d54abdd67a269b3f51c757b71ab EFQRCode: e65a9eb6c7137ed6db4cfce37f0cee47938e44f0 - Eureka: 28ea296f06710f6745266b71f17862048941a32d + Eureka: e7b9a83e08867f8336176247bdc4c199b1f68e5e FreakingSimpleRoundImageView: 0d687cb05da8684e85c4c2ae9945bafcbe89d2a2 FTIndicator: f7f071fd159e5befa1d040a9ef2e3ab53fa9322c GRDB.swift: e10787d857b6b8135354b9e08fb23b4bb8e2fab7 @@ -204,6 +210,6 @@ SPEC CHECKSUMS: Swinject: 82cdb851f63f91bba974e3eca1d69780f2f7677e web3swift: d80d1b9a3feca16e614acec9eea47ec26310d37d -PODFILE CHECKSUM: 0bf602086adfe27d6043b83dcfd57b5bcc8a03ab +PODFILE CHECKSUM: 283e1a68ad9840052cad586d08b05308ac88d41e COCOAPODS: 1.7.1 diff --git a/TransferNotificationContentExtension/Info.plist b/TransferNotificationContentExtension/Info.plist index 1662debba..0ac46fb97 100644 --- a/TransferNotificationContentExtension/Info.plist +++ b/TransferNotificationContentExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.7.2 + 1.8.0 CFBundleVersion - 87 + 92 NSExtension NSExtensionAttributes diff --git a/TransferNotificationContentExtension/NotificationViewController.swift b/TransferNotificationContentExtension/NotificationViewController.swift index 7abb09bde..fe13f845c 100644 --- a/TransferNotificationContentExtension/NotificationViewController.swift +++ b/TransferNotificationContentExtension/NotificationViewController.swift @@ -25,7 +25,8 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi private lazy var richMessageProviders: [String: () -> TransferNotificationContentProvider] = { return [EthProvider.richMessageType: { EthProvider() }, LskProvider.richMessageType: { LskProvider() }, - DogeProvider.richMessageType: { DogeProvider() }] + DogeProvider.richMessageType: { DogeProvider() }, + DashProvider.richMessageType: { DashProvider() }] }() // MARK: - IBOutlets